[
  {
    "path": ".aiexclude",
    "content": "# Docs\nLICENSE\nCHANGELOG.md\n\n# Legacy\nStabilityMatrix/\n\n# Tests\n*.verified.*\n\n# Misc projects\nStabilityMatrix.Native/\nStabilityMatrix.Native.*/\nStabilityMatrix.Avalonia.Diagnostics/\nStabilityMatrix.Avalonia.Diagnostics/\nStabilityMatrix.UITests/\n\n# Vendored\nAvalonia.Gif/\n\n# Configs\n*.editorconfig\n*.DotSettings\n\n# Assets\n*.svg\nStabilityMatrix.Avalonia/Assets/Fonts/\nStabilityMatrix.Avalonia/Assets/linux-x64/\nStabilityMatrix.Avalonia/Assets/macos-arm64/\nStabilityMatrix.Avalonia/Assets/win-x64/\n"
  },
  {
    "path": ".backportrc.json",
    "content": "{\n  \"sourceBranch\": \"dev\",\n  \"targetBranch\": \"main\",\n  \"mainline\": 1,\n  \"fork\": false,\n  \"targetPRLabels\": [\"backport\"],\n  \"prTitle\": \"[{{sourceBranch}} to {{targetBranch}}] backport: {{sourcePullRequest.title}} ({{sourcePullRequest.number}})\"\n}\n"
  },
  {
    "path": ".config/.csharpierrc.json",
    "content": "{\n    \"printWidth\": 110,\n    \"preprocessorSymbolSets\": [\"\", \"DEBUG\", \"DEBUG,CODE_STYLE\"]\n}\n"
  },
  {
    "path": ".config/dotnet-tools.json",
    "content": "{\n  \"version\": 1,\n  \"isRoot\": true,\n  \"tools\": {\n    \"husky\": {\n      \"version\": \"0.7.2\",\n      \"commands\": [\n        \"husky\"\n      ],\n      \"rollForward\": false\n    },\n    \"xamlstyler.console\": {\n      \"version\": \"3.2404.2\",\n      \"commands\": [\n        \"xstyler\"\n      ],\n      \"rollForward\": false\n    },\n    \"csharpier\": {\n      \"version\": \"1.0.1\",\n      \"commands\": [\n        \"csharpier\"\n      ],\n      \"rollForward\": false\n    },\n    \"refitter\": {\n      \"version\": \"1.4.1\",\n      \"commands\": [\n        \"refitter\"\n      ],\n      \"rollForward\": false\n    },\n    \"dotnet-script\": {\n      \"version\": \"1.6.0\",\n      \"commands\": [\n        \"dotnet-script\"\n      ],\n      \"rollForward\": false\n    }\n  }\n}"
  },
  {
    "path": ".csharpierrc.yaml",
    "content": "printWidth: 110\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*.cs]\nmax_line_length = 120\ncsharp_style_var_for_built_in_types = true:suggestion\ndotnet_sort_system_directives_first = true\n\n# ReSharper properties\nresharper_csharp_max_line_length = 120\nresharper_place_field_attribute_on_same_line = false\n\n# dotnet code quality\n# noinspection EditorConfigKeyCorrectness\ndotnet_code_quality.ca1826.exclude_ordefault_methods = true\n\n# Microsoft .NET properties\ncsharp_new_line_before_members_in_object_initializers = false\ncsharp_preferred_modifier_order = public, private, protected, internal, file, static, new, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion\ncsharp_style_prefer_utf8_string_literals = true:suggestion\ncsharp_style_var_elsewhere = true:suggestion\ncsharp_style_var_when_type_is_apparent = true:suggestion\ndotnet_naming_rule.private_constants_rule.import_to_resharper = True\ndotnet_naming_rule.private_constants_rule.resharper_description = Constant fields (private)\ndotnet_naming_rule.private_constants_rule.resharper_guid = 236f7aa5-7b06-43ca-bf2a-9b31bfcff09a\ndotnet_naming_rule.private_constants_rule.severity = warning\ndotnet_naming_rule.private_constants_rule.style = upper_camel_case_style\ndotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols\ndotnet_naming_rule.private_instance_fields_rule.import_to_resharper = True\ndotnet_naming_rule.private_instance_fields_rule.resharper_description = Instance fields (private)\ndotnet_naming_rule.private_instance_fields_rule.resharper_guid = 4a98fdf6-7d98-4f5a-afeb-ea44ad98c70c\ndotnet_naming_rule.private_instance_fields_rule.resharper_style = aaBb, _ + aaBb\ndotnet_naming_rule.private_instance_fields_rule.severity = warning\ndotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style_1\ndotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols\ndotnet_naming_rule.private_static_fields_rule.import_to_resharper = True\ndotnet_naming_rule.private_static_fields_rule.resharper_description = Static fields (private)\ndotnet_naming_rule.private_static_fields_rule.resharper_guid = f9fce829-e6f4-4cb2-80f1-5497c44f51df\ndotnet_naming_rule.private_static_fields_rule.severity = warning\ndotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style\ndotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols\ndotnet_naming_rule.private_static_readonly_rule.import_to_resharper = True\ndotnet_naming_rule.private_static_readonly_rule.resharper_description = Static readonly fields (private)\ndotnet_naming_rule.private_static_readonly_rule.resharper_guid = 15b5b1f1-457c-4ca6-b278-5615aedc07d3\ndotnet_naming_rule.private_static_readonly_rule.severity = warning\ndotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style\ndotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols\ndotnet_naming_style.lower_camel_case_style.capitalization = camel_case\ndotnet_naming_style.lower_camel_case_style.required_prefix = _\ndotnet_naming_style.lower_camel_case_style_1.capitalization = camel_case\ndotnet_naming_style.upper_camel_case_style.capitalization = pascal_case\ndotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private\ndotnet_naming_symbols.private_constants_symbols.applicable_kinds = field\ndotnet_naming_symbols.private_constants_symbols.required_modifiers = const\ndotnet_naming_symbols.private_constants_symbols.resharper_applicable_kinds = constant_field\ndotnet_naming_symbols.private_constants_symbols.resharper_required_modifiers = any\ndotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private\ndotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field\ndotnet_naming_symbols.private_instance_fields_symbols.resharper_applicable_kinds = field,readonly_field\ndotnet_naming_symbols.private_instance_fields_symbols.resharper_required_modifiers = instance\ndotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private\ndotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field\ndotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static\ndotnet_naming_symbols.private_static_fields_symbols.resharper_applicable_kinds = field\ndotnet_naming_symbols.private_static_fields_symbols.resharper_required_modifiers = static\ndotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private\ndotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field\ndotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = readonly,static\ndotnet_naming_symbols.private_static_readonly_symbols.resharper_applicable_kinds = readonly_field\ndotnet_naming_symbols.private_static_readonly_symbols.resharper_required_modifiers = static\ndotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none\ndotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none\ndotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none\ndotnet_style_predefined_type_for_locals_parameters_members = true:suggestion\ndotnet_style_predefined_type_for_member_access = true:suggestion\ndotnet_style_qualification_for_event = false:suggestion\ndotnet_style_qualification_for_field = false:suggestion\ndotnet_style_qualification_for_method = false:suggestion\ndotnet_style_qualification_for_property = false:suggestion\ndotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "patreon: StabilityMatrix\nko_fi: StabilityMatrix\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-bug.yml",
    "content": "﻿name: Bug report\ndescription: Submit a bug report\nlabels: [\"bug\", \"triage\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        **New to Stability Matrix?**\n        For help or advice on using Stability Matrix, try one of the following options instead of opening a GitHub issue:\n        - Asking on our [Discord server](https://link.lykos.ai/discord?ref=github-issue-template)\n        - Creating a post on [Discussions](https://github.com/LykosAI/StabilityMatrix/discussions)\n        \n        This template is for reporting bugs experienced within the Stability Matrix app. \n        If your issue is regarding Package behavior when running it, or when installing or updating a specific Package, please use the [Package issue template](https://github.com/LykosAI/StabilityMatrix/issues/new/choose) instead.\n        \n        Make sure to also search the [existing issues](https://github.com/LykosAI/StabilityMatrix/issues) to see if your issue has already been reported.\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: What happened?\n      description: Give a clear and concise description of what happened. Provide screenshots or videos of UI if necessary. Also tell us, what did you expect to happen?\n      placeholder: |\n        When dragging a model file into the ... page to import, I expected to see...\n        \n        Instead, I saw...\n    validations:\n      required: true\n  - type: textarea\n    id: how-to-reproduce\n    attributes:\n      label: Steps to reproduce\n      description: Include a minimal step-by-step guide to reproduce the issue if possible.\n      placeholder: |\n        1. Open Stability Matrix\n        2. Go to the ... page\n        3. Click on the ... button\n        4. Expected to see ... open, but instead ...\n  - type: textarea\n    id: app-logs\n    attributes:\n      label: Relevant logs\n      description: Please copy and paste any relevant log output. (This will be automatically formatted, so no need for backticks.)\n      render: shell\n  - type: input\n    id: version\n    attributes:\n      label: Version\n      description: What version of Stability Matrix are you running? (Can be found at the bottom of the settings page)\n      placeholder: ex. v2.11.0\n    validations:\n      required: true\n  - type: dropdown\n    id: os-platform\n    attributes:\n      label: What Operating System are you using?\n      options:\n        - Windows\n        - macOS\n        - Linux\n        - Other\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2-bug-crash.yml",
    "content": "﻿name: Crash report\ndescription: A crash of Stability Matrix, likely with the \"An unexpected error occurred\" dialog\nlabels: [\"bug\", \"crash\", \"triage\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        This template is for reporting crashes of Stability Matrix, likely with the \"An unexpected error occurred\" dialog. \n        If you are experiencing a different issue, please use the [Bug Report or Package Issue templates](https://github.com/LykosAI/StabilityMatrix/issues/new/choose).\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: What happened?\n      description: Give a clear and concise description of what happened. Include some minimal steps to reproducible the issue if possible.\n      placeholder: |\n        1. Open Stability Matrix\n        2. Go to the \"...\" page\n        3. Click on the \"...\" button\n        4. See the crash\n    validations:\n      required: true\n  - type: textarea\n    id: exception-details\n    attributes:\n      label: Exception Details\n      description: Please click the \"Copy Details\" button on the crash dialog and paste the details exactly as formatted here.\n      placeholder: |\n        ## Exception\n        OperationCanceledException: Example Message\n        ### Sentry ID\n        ```\n        bc7da9b2fcc3e3568ceb81a72f3a128d\n        ```\n        ### Stack Trace\n        ```\n           at StabilityMatrix.Avalonia.ViewModels.Settings.MainSettingsViewModel.DebugThrowException() in MainSettingsViewModel.cs:line 716\n           at CommunityToolkit.Mvvm.Input.RelayCommand.Execute(Object parameter)\n           ...\n        ```\n  - type: input\n    id: version\n    attributes:\n      label: Version\n      description: What version of Stability Matrix are you running? (Can be found at the bottom of the settings page)\n      placeholder: ex. v2.11.0\n    validations:\n      required: true\n  - type: dropdown\n    id: os-platform\n    attributes:\n      label: What Operating System are you using?\n      options:\n        - Windows\n        - macOS\n        - Linux\n        - Other\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/3-bug-package.yml",
    "content": "﻿name: Package issue\ndescription: Report an issue with installing, updating, or running a Package\nlabels: [\"bug\", \"area: package\", \"triage\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        **Experiencing an issue while running a Package?**\n        \n        Make sure to also search the GitHub issues of the Package, to see if your issue has already been reported and being worked on by upstream authors.\n  - type: textarea\n    id: package-details\n    attributes:\n      label: Package\n      description: Provide the name of the Package you are experiencing issues with\n      placeholder: ex. `ComfyUI`\n    validations:\n      required: true\n  - type: dropdown\n    id: package-issue-phase\n    attributes:\n      label: When did the issue occur?\n      options:\n        - Installing the Package\n        - Updating the Package\n        - Running the Package\n        - Other\n    validations:\n      required: true\n  - type: input\n    id: hardware\n    attributes:\n      label: What GPU / hardware type are you using?\n      description: Installed dependencies and Package features often depend on the GPU or hardware type you are using.\n      placeholder: ex. Nvidia 2080 Super with CUDA, AMD Radeon VII, etc.\n    validations:\n      required: true\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: What happened?\n      description: Give a clear and concise description of what happened. Provide screenshots if necessary. Also tell us, what did you expect to happen?\n    validations:\n      required: true\n  - type: textarea\n    id: console-output\n    attributes:\n      label: Console output\n      description: Please copy and paste any console output or error messages. For failed install or updates, locate the progress on the bottom left, open the dialog, and click on \"More Details\" to copy the full console output.\n      placeholder: |\n        ```\n        Unpacking...\n        Successfully built lycoris_lora\n        Installing collected packages: library, tomlkit, onnx, ml-dtypes, onnxruntime-gpu\n          Running setup.py develop for library\n          Attempting uninstall: onnx\n            Found existing installation: onnx 1.14.1\n            ...\n        ```\n  - type: input\n    id: version\n    attributes:\n      label: Version\n      description: What version of Stability Matrix are you running? (Can be found at the bottom of the settings page)\n      placeholder: ex. v2.11.0\n    validations:\n      required: true\n  - type: dropdown\n    id: os-platform\n    attributes:\n      label: What Operating System are you using?\n      options:\n        - Windows\n        - macOS\n        - Linux\n        - Other\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/4-feature-request.yml",
    "content": "﻿name: Feature or enhancement\ndescription: Submit a proposal for a new Stability Matrix feature or enhancement\nlabels: [\"enhancement\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Consider first discussing your idea on our [Discord server](https://link.lykos.ai/discord?ref=github-issue-template) to get feedback from the developers and the community.\n  - type: textarea\n    id: proposal\n    attributes:\n      label: Proposal\n      description: Explain your idea for a new feature or enhancement. Include any relevant details or links to resources like Package documentation.\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "﻿blank_issues_enabled: false\ncontact_links:\n  - name: Getting help\n    url: https://link.lykos.ai/discord?ref=github-issue-template\n    about: Ask questions about using Stability Matrix and get tips on using Packages on our Discord server\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"nuget\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/backport.yml",
    "content": "name: Automatic Backport\n\non:\n  pull_request:\n    types: [\"closed\", \"labeled\"]\n\njobs:\n  backport:\n    if: ${{ (github.event.pull_request.merged == true) && (contains(github.event.pull_request.labels.*.name, 'backport-to-main') == true) }}\n    name: Backport PR\n    runs-on: ubuntu-latest\n    steps:\n      # Get the merge target branch to decide mainline number\n      # git cherry-pick mainline is 1 for merge to 'dev', else 2\n      - name: Get target branch\n        run: echo \"CP_MAINLINE=$(if [ '${{ github.event.pull_request.base.ref }}' == 'dev' ]; then echo 1; else echo 2; fi)\" >> $GITHUB_ENV\n\n      - name: Write json\n        id: create-json\n        uses: jsdaniell/create-json@v1.2.3\n        with:\n          name: \".backportrc.json\"\n          json: |\n            {\n              \"targetPRLabels\": [\"backport\"],\n              \"mainline\": ${{ env.CP_MAINLINE }},\n              \"commitConflicts\": \"true\",\n              \"prTitle\": \"[{{sourceBranch}} to {{targetBranch}}] backport: {{sourcePullRequest.title}} ({{sourcePullRequest.number}})\"\n            }\n\n      - name: Backport Action\n        uses: sorenlouv/backport-github-action@v9.5.1\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          auto_backport_label_prefix: backport-to-\n\n      - name: Info log\n        if: ${{ success() }}\n        run: cat ~/.backport/backport.info.log\n\n      - name: Debug log\n        if: ${{ failure() }}\n        run: cat ~/.backport/backport.debug.log        \n          \n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\nconcurrency:\n  group: build-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    if: github.repository == 'LykosAI/StabilityMatrix'\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Set up .NET\n        uses: actions/setup-dotnet@v3\n        with:\n          dotnet-version: '9.0.x'\n            \n      - name: Install dependencies\n        run: dotnet restore\n        \n      - name: Test\n        run: dotnet test StabilityMatrix.Tests\n\n      - name: Build\n        run: >\n          dotnet publish ./StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj \n          -o out -c Release -r linux-x64 \n          --self-contained\n"
  },
  {
    "path": ".github/workflows/cla.yml",
    "content": "name: \"CLA Assistant\"\n\non:\n  issue_comment:\n    types: [created]\n  pull_request_target:\n    types: [opened,closed,synchronize]\n\n# explicitly configure permissions, in case your GITHUB_TOKEN workflow permissions are set to read-only in repository settings\npermissions:\n  actions: write\n  contents: write\n  pull-requests: write\n  statuses: write\n\njobs:\n  CLAAssistant:\n    if: github.repository == 'LykosAI/StabilityMatrix'\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"CLA Assistant\"\n        if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'\n        uses: contributor-assistant/github-action@v2.3.0\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          # the below token should have repo scope and must be manually added by you in the repository's secret\n          # This token is required only if you have configured to store the signatures in a remote repository/organization\n          PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_CONFIG_ACCESS_TOKEN }}\n        with:\n          path-to-signatures: 'signatures/version1/cla.json'\n          path-to-document: 'https://lykos.ai/cla'\n          # branch should not be protected\n          branch: 'main'\n          allowlist: ionite34,mohnjiles,bot*\n          # the followings are the optional inputs - If the optional inputs are not given, then default values will be taken\n          remote-organization-name: LykosAI\n          remote-repository-name: clabot-config\n          custom-allsigned-prcomment: '**CLA Assistant bot** All Contributors have signed the CLA.'\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\npermissions:\n  contents: write\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        type: string\n        required: true\n        description: Version (Semver without leading v)\n      sentry-release:\n        type: boolean\n        description: Make Sentry Release?\n        default: true\n      github-release:\n        type: boolean\n        description: Make GitHub Release?\n        default: true\n      github-release-draft:\n        type: boolean\n        description: Mark GitHub Release as Draft?\n        default: false\n      github-release-prerelease:\n        type: boolean\n        description: Mark GitHub Release as Prerelease?\n        default: false\n      auto-update-release:\n        type: boolean\n        description: Release auto-update?\n        default: false\n      auto-update-release-mode:\n        type: choice\n        description: Release auto-update mode\n        options:\n          - github url\n          - upload to b2\n      auto-update-release-channel:\n        type: choice\n        description: Release auto-update channel\n        options:\n          - stable\n          - preview\n          - development\n      test-release-artifacts:\n        type: boolean\n        description: \"[Debug] Test release artifacts?\"\n        default: false\n\njobs:\n  release-linux:\n    name: Release (linux-x64)\n    env:\n      platform-id: linux-x64\n      out-name: StabilityMatrix.AppImage\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - uses: olegtarasov/get-tag@v2.1.2\n        if: github.event_name == 'release'\n        id: tag_name\n        with:\n          tagRegex: \"v(.*)\"\n          \n      - name: Set Version from Tag\n        if: github.event_name == 'release'\n        run: |\n          echo \"Using tag ${{ env.GIT_TAG_NAME }}\"\n          echo \"RELEASE_VERSION=${{ env.GIT_TAG_NAME }}\" >> $GITHUB_ENV\n          \n      - name: Set Version from manual input\n        if: github.event_name == 'workflow_dispatch'\n        run: |\n          echo \"Using version ${{ github.event.inputs.version }}\"\n          echo \"RELEASE_VERSION=${{ github.event.inputs.version }}\" >> $GITHUB_ENV\n\n      - name: Set up .NET 9\n        uses: actions/setup-dotnet@v3\n        with:\n          # Net 8 needed for PupNet\n          dotnet-version: | \n            8.0.x\n            9.0.x\n\n      - name: Install PupNet\n        run: |\n          sudo apt-get -y install libfuse2\n          dotnet tool install -g KuiperZone.PupNet --version 1.8.0\n      \n      - name: PupNet Build\n        env:\n          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}\n        run: pupnet -r linux-x64 -c Release --kind appimage --app-version $RELEASE_VERSION --clean -y\n\n      - name: Post Build\n        run: mv ./Release/linux-x64/StabilityMatrix.x86_64.AppImage ${{ env.out-name }}\n      \n      - name: Upload Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: StabilityMatrix-${{ env.platform-id }}\n          path: ${{ env.out-name }}\n          retention-days: 1\n          if-no-files-found: error\n         \n      - name: Create Sentry release\n        if: ${{ github.event.inputs.sentry-release == 'true' }}\n        uses: getsentry/action-release@v1\n        env:\n          MAKE_SENTRY_RELEASE: ${{ secrets.SENTRY_PROJECT != '' }}\n          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}\n          SENTRY_ORG: ${{ secrets.SENTRY_ORG }}\n          SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}\n        with:\n          environment: production\n          ignore_missing: true\n          ignore_empty: true\n          version: StabilityMatrix.Avalonia@${{ github.event.inputs.version }}\n\n          \n  release-windows:\n    name: Release (win-x64)\n    env:\n      platform-id: win-x64\n      out-name: StabilityMatrix.exe\n    runs-on: windows-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - uses: olegtarasov/get-tag@v2.1.2\n        if: github.event_name == 'release'\n        id: tag_name\n        with:\n          tagRegex: \"v(.*)\"\n\n      - name: Set Version from Tag\n        if: github.event_name == 'release'\n        run: |\n          echo \"Using tag ${{ env.GIT_TAG_NAME }}\"\n          echo \"RELEASE_VERSION=${{ env.GIT_TAG_NAME }}\" >> $env:GITHUB_ENV\n\n      - name: Set Version from manual input\n        if: github.event_name == 'workflow_dispatch'\n        run: |\n          echo \"Using version ${{ github.event.inputs.version }}\"\n          echo \"RELEASE_VERSION=${{ github.event.inputs.version }}\" >> $env:GITHUB_ENV\n\n      - name: Set up .NET 9\n        uses: actions/setup-dotnet@v3\n        with:\n          dotnet-version: '9.0.x'\n\n      - name: Install dependencies\n        run: dotnet restore\n\n      - name: .NET Publish\n        env:\n          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}\n        run: >\n          dotnet publish ./StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj\n          -o out -c Release -r ${{ env.platform-id }}\n          -p:Version=$env:RELEASE_VERSION\n          -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true\n          -p:PublishReadyToRun=true\n          -p:SentryOrg=${{ secrets.SENTRY_ORG }} -p:SentryProject=${{ secrets.SENTRY_PROJECT }}\n          -p:SentryUploadSymbols=true -p:SentryUploadSources=true\n\n      - name: Post Build\n        run: mv ./out/StabilityMatrix.Avalonia.exe ./out/${{ env.out-name }}\n\n      - name: Upload Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: StabilityMatrix-${{ env.platform-id }}\n          path: ./out/${{ env.out-name }}\n          retention-days: 1\n          if-no-files-found: error\n  \n  release-macos:\n    name: Release (macos-arm64)\n    env:\n      platform-id: osx-arm64\n      app-name: \"Stability Matrix.app\"\n      out-name: \"StabilityMatrix-macos-arm64.dmg\"\n    runs-on: macos-14\n    steps:\n      - uses: actions/checkout@v3\n\n      - uses: olegtarasov/get-tag@v2.1.2\n        if: github.event_name == 'release'\n        id: tag_name\n        with:\n          tagRegex: \"v(.*)\"\n\n      - name: Set Version from Tag\n        if: github.event_name == 'release'\n        run: |\n          echo \"Using tag ${{ env.GIT_TAG_NAME }}\"\n          echo \"RELEASE_VERSION=${{ env.GIT_TAG_NAME }}\" >> $GITHUB_ENV\n\n      - name: Set Version from manual input\n        if: github.event_name == 'workflow_dispatch'\n        run: |\n          echo \"Using version ${{ github.event.inputs.version }}\"\n          echo \"RELEASE_VERSION=${{ github.event.inputs.version }}\" >> $GITHUB_ENV\n\n      - name: Set up .NET 9\n        uses: actions/setup-dotnet@v3\n        with:\n          dotnet-version: '9.0.x'\n\n      - name: Install dependencies\n        run: dotnet restore -p:PublishReadyToRun=true\n        \n      - name: Check Version\n        run: echo $RELEASE_VERSION\n\n      - name: .NET Msbuild (App)\n        env:\n          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}\n        run: >\n          dotnet msbuild ./StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj\n          -t:BundleApp -p:UseAppHost=true -p:SelfContained=true\n          -p:Configuration=Release -p:RuntimeIdentifier=${{ env.platform-id }}\n          -p:Version=$RELEASE_VERSION\n          -p:PublishDir=out\n          -p:PublishReadyToRun=true\n          -p:CFBundleShortVersionString=$RELEASE_VERSION\n          -p:CFBundleName=\"Stability Matrix\"\n          -p:CFBundleDisplayName=\"Stability Matrix\"\n          -p:CFBundleVersion=$RELEASE_VERSION\n          -p:SentryOrg=${{ secrets.SENTRY_ORG }} -p:SentryProject=${{ secrets.SENTRY_PROJECT }}\n          -p:SentryUploadSymbols=true -p:SentryUploadSources=true\n\n      - name: Post Build (App)\n        run: mkdir -p signing && mv \"./StabilityMatrix.Avalonia/out/Stability Matrix.app\" \"./signing/${{ env.app-name }}\"\n\n      - name: Codesign app bundle\n        env:\n          MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}\n          MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}\n          MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}\n          MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}\n        run: ./Build/codesign_macos.sh \"./signing/${{ env.app-name }}\"\n\n      - name: Notarize app bundle\n        env:\n          MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}\n          MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}\n          MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}\n        run: ./Build/notarize_macos.sh \"./signing/${{ env.app-name }}\"\n\n      - name: Zip Artifact (App)\n        working-directory: signing\n        run: zip -r -y \"../StabilityMatrix-${{ env.platform-id }}-app.zip\" \"${{ env.app-name }}\"\n\n      - name: Upload Artifact (App)\n        uses: actions/upload-artifact@v4\n        with:\n          name: StabilityMatrix-${{ env.platform-id }}-app\n          path: StabilityMatrix-${{ env.platform-id }}-app.zip\n          retention-days: 1\n          if-no-files-found: error\n          \n      - uses: actions/setup-node@v4\n        with:\n          node-version: '20.11.x'\n      \n      - name: Install dependencies for dmg creation\n        run: brew install graphicsmagick imagemagick && npm install --global create-dmg\n      \n      - name: Create dmg\n        working-directory: signing\n        run: >\n          create-dmg \"${{ env.app-name }}\" --overwrite --identity \"${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}\"\n          \n      - name: Rename dmg\n        working-directory: signing\n        run: mv \"$(find . -type f -name \"*.dmg\")\" \"${{ env.out-name }}\"\n          \n      - name: Zip Artifact (dmg)\n        working-directory: signing\n        run: zip -r -y \"../StabilityMatrix-${{ env.platform-id }}-dmg.zip\" \"${{ env.out-name }}\"\n        \n      - name: Upload Artifact (dmg)\n        uses: actions/upload-artifact@v4\n        with:\n          name: StabilityMatrix-${{ env.platform-id }}-dmg\n          path: StabilityMatrix-${{ env.platform-id }}-dmg.zip\n          retention-days: 1\n          if-no-files-found: error\n  \n  publish-release:\n    name: Publish GitHub Release\n    needs: [ release-linux, release-windows, release-macos ]\n    if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.github-release == 'true' }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Extract Release Notes\n        id: release_notes\n        run: |\n          RELEASE_NOTES=\"$(awk -v version=\"${{ github.event.inputs.version }}\" '/## v/{if(p) exit; if($0 ~ version) p=1}; p' CHANGELOG.md)\"\n          RELEASE_NOTES=\"${RELEASE_NOTES//'%'/'%25'}\"\n          RELEASE_NOTES=\"${RELEASE_NOTES//$'\\n'/'%0A'}\"\n          RELEASE_NOTES=\"${RELEASE_NOTES//$'\\r'/'%0D'}\"\n          echo \"::set-output name=release_notes::$RELEASE_NOTES\"\n\n      # Downloads all previous artifacts to the current working directory\n      - name: Download Artifacts\n        uses: actions/download-artifact@v4\n        \n      # Zip each build (except macos which is already dmg)\n      - name: Zip Artifacts\n        run: |\n          cd StabilityMatrix-win-x64 && zip -r ../StabilityMatrix-win-x64.zip ./. && cd $OLDPWD\n          cd StabilityMatrix-linux-x64 && zip -r ../StabilityMatrix-linux-x64.zip ./. && cd $OLDPWD\n          unzip \"StabilityMatrix-osx-arm64-dmg/StabilityMatrix-osx-arm64-dmg.zip\"\n\n      - name: Create Github Release\n        id: create_release\n        uses: softprops/action-gh-release@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          files: |\n            StabilityMatrix-win-x64.zip\n            StabilityMatrix-linux-x64.zip\n            StabilityMatrix-macos-arm64.dmg\n          fail_on_unmatched_files: true\n          tag_name: v${{ github.event.inputs.version }}\n          body: ${{ steps.release_notes.outputs.release_notes }}\n          draft: ${{ github.event.inputs.github-release-draft == 'true' }}\n          prerelease: ${{ github.event.inputs.github-release-prerelease == 'true' }} \n          \n  test-artifacts:\n    name: Test Release Artifacts\n    needs: [ release-linux, release-windows, release-macos ]\n    if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.test-release-artifacts == 'true' }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Extract Release Notes\n        id: release_notes\n        run: |\n          RELEASE_NOTES=\"$(awk -v version=\"${{ github.event.inputs.version }}\" '/## v/{if(p) exit; if($0 ~ version) p=1}; p' CHANGELOG.md)\"\n          RELEASE_NOTES=\"${RELEASE_NOTES//'%'/'%25'}\"\n          RELEASE_NOTES=\"${RELEASE_NOTES//$'\\n'/'%0A'}\"\n          RELEASE_NOTES=\"${RELEASE_NOTES//$'\\r'/'%0D'}\"\n          echo \"::set-output name=release_notes::$RELEASE_NOTES\"\n          echo \"Release Notes:\"\n          echo \"$RELEASE_NOTES\"\n\n      # Downloads all previous artifacts to the current working directory\n      - name: Download Artifacts\n        uses: actions/download-artifact@v4\n\n      # Zip each build (except macos which is already dmg)\n      - name: Zip Artifacts\n        run: |\n          cd StabilityMatrix-win-x64 && zip -r ../StabilityMatrix-win-x64.zip ./. && cd $OLDPWD\n          cd StabilityMatrix-linux-x64 && zip -r ../StabilityMatrix-linux-x64.zip ./. && cd $OLDPWD\n          unzip \"StabilityMatrix-osx-arm64-dmg/StabilityMatrix-osx-arm64-dmg.zip\"\n\n      # Check that the zips and CHANGELOG.md are in the current working directory\n      - name: Check files\n        run: |\n          if [ ! -f StabilityMatrix-win-x64.zip ]; then\n            echo \"StabilityMatrix-win-x64.zip not found\"\n            exit 1\n          else\n            echo \"StabilityMatrix-win-x64.zip found\"\n            sha256sum StabilityMatrix-win-x64.zip\n          fi\n          if [ ! -f StabilityMatrix-linux-x64.zip ]; then\n            echo \"StabilityMatrix-linux-x64.zip not found\"\n            exit 1\n          else\n            echo \"StabilityMatrix-linux-x64.zip found\"\n            sha256sum StabilityMatrix-linux-x64.zip\n          fi\n          if [ ! -f StabilityMatrix-macos-arm64.dmg ]; then\n            echo \"StabilityMatrix-macos-arm64.dmg not found\"\n            exit 1\n          else\n            echo \"StabilityMatrix-macos-arm64.dmg found\"\n            sha256sum StabilityMatrix-macos-arm64.dmg\n          fi\n          if [ ! -f CHANGELOG.md ]; then\n            echo \"CHANGELOG.md not found\"\n            exit 1\n          fi\n  \n  publish-auto-update-github:\n    name: Publish Auto-Update Release (GitHub)\n    needs: [ release-linux, release-windows, release-macos, publish-release ]\n    if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.auto-update-release == 'true' && github.event.inputs.auto-update-release-mode == 'github url' }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        \n      - name: Set Version from manual input\n        run: |\n          echo \"Using version ${{ github.event.inputs.version }}\"\n          echo \"RELEASE_VERSION=${{ github.event.inputs.version }}\" >> $env:GITHUB_ENV\n        \n      - uses: actions/setup-python@v4\n        with:\n          python-version: '3.11'\n          \n      - name: Install Python Dependencies\n        run: pip install stability-matrix-tools>=0.3.0 --upgrade\n        \n      - name: Publish Auto-Update Release\n        env:\n          SM_B2_API_ID: ${{ secrets.SM_B2_API_ID }}\n          SM_B2_API_KEY: ${{ secrets.SM_B2_API_KEY }}\n          SM_CF_CACHE_PURGE_TOKEN: ${{ secrets.SM_CF_CACHE_PURGE_TOKEN }}\n          SM_CF_ZONE_ID: ${{ secrets.SM_CF_ZONE_ID }}\n          SM_SIGNING_PRIVATE_KEY: ${{ secrets.SM_SIGNING_PRIVATE_KEY }}\n        run: sm-tools updates publish-matrix-v3 -v ${{ github.event.inputs.version }} -y\n\n  publish-auto-update-b2:\n    name: Publish Auto-Update Release (B2)\n    needs: [ release-linux, release-windows, release-macos ]\n    if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.auto-update-release == 'true' && github.event.inputs.auto-update-release-mode == 'upload to b2' }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set Version from manual input\n        run: |\n          echo \"Using version ${{ github.event.inputs.version }}\"\n          echo \"RELEASE_VERSION=${{ github.event.inputs.version }}\" >> $env:GITHUB_ENV\n\n      # Downloads all previous artifacts to the current working directory\n      - name: Download Artifacts\n        uses: actions/download-artifact@v4\n      \n      # Zip each build (except macos which is already dmg)\n      - name: Zip Artifacts\n        run: |\n          cd StabilityMatrix-win-x64 && zip -r ../StabilityMatrix-win-x64.zip ./. && cd $OLDPWD\n          cd StabilityMatrix-linux-x64 && zip -r ../StabilityMatrix-linux-x64.zip ./. && cd $OLDPWD\n          unzip \"StabilityMatrix-osx-arm64-dmg/StabilityMatrix-osx-arm64-dmg.zip\"\n\n      - uses: actions/setup-python@v4\n        with:\n          python-version: '3.11'\n\n      - name: Install Python Dependencies\n        run: pip install stability-matrix-tools>=0.3.0 --upgrade\n      \n      # Check that the zips and CHANGELOG.md are in the current working directory\n      - name: Check files\n        run: |\n          if [ ! -f StabilityMatrix-win-x64.zip ]; then\n            echo \"StabilityMatrix-win-x64.zip not found\"\n            exit 1\n          fi\n          if [ ! -f StabilityMatrix-linux-x64.zip ]; then\n            echo \"StabilityMatrix-linux-x64.zip not found\"\n            exit 1\n          fi\n          if [ ! -f StabilityMatrix-macos-arm64.dmg ]; then\n            echo \"StabilityMatrix-macos-arm64.dmg not found\"\n            exit 1\n          fi\n          if [ ! -f CHANGELOG.md ]; then\n            echo \"CHANGELOG.md not found\"\n            exit 1\n          fi\n\n      - name: Publish Auto-Update Release\n        env:\n          SM_B2_API_ID: ${{ secrets.SM_B2_API_ID }}\n          SM_B2_API_KEY: ${{ secrets.SM_B2_API_KEY }}\n          SM_CF_CACHE_PURGE_TOKEN: ${{ secrets.SM_CF_CACHE_PURGE_TOKEN }}\n          SM_CF_ZONE_ID: ${{ secrets.SM_CF_ZONE_ID }}\n          SM_SIGNING_PRIVATE_KEY: ${{ secrets.SM_SIGNING_PRIVATE_KEY }}\n        run: >\n          sm-tools updates publish-files-v3 -v ${{ github.event.inputs.version }}\n          --channel ${{ github.event.inputs.auto-update-release-channel }}\n          --changelog CHANGELOG.md\n          --win-x64 StabilityMatrix-win-x64.zip\n          --linux-x64 StabilityMatrix-linux-x64.zip\n          --macos-arm64 StabilityMatrix-macos-arm64.dmg\n          -y\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: 'Close stale issues'\n\npermissions:\n  issues: write\n  pull-requests: write\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '30 1 * * *'\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v9\n        with:\n          stale-issue-message: 'This issue is pending because it has been awaiting a response for 30 days with no activity. Remove the pending label or comment, else this will be closed in 7 days.'\n          close-issue-message: 'This issue was closed because it has been pending for 7 days with no activity.'\n          only-labels: 'awaiting-feedback'\n          stale-issue-label: 'pending'\n          exempt-issue-labels: 'planned,milestone,work-in-progress,enhancement,crash'\n          days-before-issue-stale: 30\n          days-before-issue-close: 7\n          days-before-pr-close: -1\n          days-before-pr-stale: -1\n          operations-per-run: 45\n        \n      - uses: actions/stale@v9\n        with:\n          stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove the stale label or comment, else this will be closed in 7 days.'\n          close-issue-message: 'This issue was closed because it has been stale for 7 days with no activity.'\n          stale-issue-label: 'stale'\n          exempt-issue-labels: 'planned,milestone,work-in-progress,enhancement,crash'\n          days-before-issue-stale: 60\n          days-before-issue-close: 7\n          days-before-pr-close: -1\n          days-before-pr-stale: -1\n          operations-per-run: 45\n"
  },
  {
    "path": ".github/workflows/test-ui.yml",
    "content": "﻿name: UI Tests\n\non:\n  workflow_dispatch:\n\nconcurrency:\n  group: build-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    if: github.repository == 'LykosAI/StabilityMatrix' || github.event_name == 'workflow_dispatch'\n    runs-on: windows-latest\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Set up .NET\n        uses: actions/setup-dotnet@v3\n        with:\n          dotnet-version: '9.0.x'\n\n      - name: Install dependencies\n        run: dotnet restore\n\n      - name: Test\n        run: dotnet test StabilityMatrix.UITests\n"
  },
  {
    "path": ".github/workflows/version-bump.yml",
    "content": "﻿name: Version Bump\n\non:\n  workflow_dispatch:\n    inputs:\n      version_mask:\n        type: string\n        description: Version Bump Mask\n        default: \"0.0.1.0\"\n        required: false\n      \n      version_overwrite:\n        type: string\n        description: Version Overwrite Mask\n        default: \"*.*.*.*\"\n        required: false\n\njobs:\n  version-bump:\n    name: Version Bump\n    runs-on: windows-latest\n    steps:\n      - uses: actions/checkout@v3\n      \n      - name: Setup .NET Core\n        uses: actions/setup-dotnet@v2\n        with:\n          dotnet-version: '8.0.x'\n\n      - name: Bump versions\n        uses: SiqiLu/dotnet-bump-version@2.0.0\n        with:\n          version_files: \"**/*.csproj\"\n          version_mask: ${{ github.event.inputs.version_mask }}\n          version_overwrite: ${{ github.event.inputs.version_overwrite }}\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Rider\n.idea/\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Ww][Ii][Nn]32/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ll]ogs/\n[Oo]ut/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# ASP.NET Scaffolding\nScaffoldingReadMe.txt\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.tlog\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Coverlet is a free, cross platform Code Coverage Tool\ncoverage*.json\ncoverage*.xml\ncoverage*.info\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n!**/Models/Packages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio 6 auto-generated project file (contains which files were open etc.)\n*.vbp\n\n# Visual Studio 6 workspace and project file (working project files containing files to include in project)\n*.dsw\n*.dsp\n\n# Visual Studio 6 technical files\n*.ncb\n*.aps\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# Visual Studio History (VSHistory) files\n.vshistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n\n# Fody - auto-generated XML schema\nFodyWeavers.xsd\n\n# VS Code files for those working on multiple tools\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n*.code-workspace\n\n# Local History for Visual Studio Code\n.history/\n\n# Windows Installer files from build outputs\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# JetBrains Rider\n*.sln.iml\n.husky/pre-commit\n"
  },
  {
    "path": ".husky/task-runner.json",
    "content": "{\n   \"tasks\": [\n      {\n         \"name\": \"Run csharpier\",\n         \"group\": \"pre-commit\",\n         \"command\": \"dotnet\",\n         \"args\": [ \"csharpier\", \"format\", \"${staged}\" ],\n         \"include\": [ \"**/*.cs\" ]\n      }, \n      {\n         \"name\": \"Run xamlstyler\",\n         \"group\": \"pre-commit\",\n         \"command\": \"dotnet\",\n         \"args\": [ \"xstyler\", \"-f\", \"${staged}\" ],\n         \"include\": [ \"**/*.axaml\" ]\n      },\n      {\n         \"name\": \"Run refitter for LykosAuthApi\",\n         \"group\": \"generate-openapi\",\n         \"command\": \"dotnet\",\n         \"args\": [\"refitter\", \"--settings-file\", \"./StabilityMatrix.Core/Api/LykosAuthApi/.refitter\"]\n      },\n      {\n         \"name\": \"Run refitter for PromptGenApi\",\n         \"group\": \"generate-promptgen-openapi\",\n         \"command\": \"dotnet\",\n         \"args\": [\"refitter\", \"--settings-file\", \"./StabilityMatrix.Core/Api/PromptGen/.refitter\"]\n      }\n   ]\n}\n"
  },
  {
    "path": "Avalonia.Gif/Avalonia.Gif.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\t<Import Project=\"..\\Runtimes.Default.props\" />\n\t\n\t<PropertyGroup>\n\t\t<LangVersion>latest</LangVersion> \n\t\t<AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n\t\t<BuiltInComInteropSupport>true</BuiltInComInteropSupport>\n\t\t<EnableWindowsTargeting>true</EnableWindowsTargeting>\n  </PropertyGroup>\n\t\n\t<PropertyGroup>\n\t\t<!-- Ignore nullable / unused warnings since this was vendored -->\n\t\t<NoWarn>$(NoWarn);CS8765;CS8618;CS8625;CS0169</NoWarn>\n\t</PropertyGroup>\n\t\n  <ItemGroup>\n    <PackageReference Include=\"Avalonia\" />\n    <PackageReference Include=\"SkiaSharp\" />\n\t<PackageReference Include=\"DotNet.Bundle\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Avalonia.Gif/BgWorkerCommand.cs",
    "content": "﻿namespace Avalonia.Gif\n{\n    internal enum BgWorkerCommand\n    {\n        Null,\n        Play,\n        Pause,\n        Dispose\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/BgWorkerState.cs",
    "content": "﻿namespace Avalonia.Gif\n{\n    internal enum BgWorkerState\n    {\n        Null,\n        Start,\n        Running,\n        Paused,\n        Complete,\n        Dispose\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/Decoding/BlockTypes.cs",
    "content": "namespace Avalonia.Gif.Decoding\n{\n    internal enum BlockTypes\n    {\n        Empty = 0,\n        Extension = 0x21,\n        ImageDescriptor = 0x2C,\n        Trailer = 0x3B,\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/Decoding/ExtensionType.cs",
    "content": "namespace Avalonia.Gif.Decoding\n{\n    internal enum ExtensionType\n    {\n        GraphicsControl = 0xF9,\n        Application = 0xFF\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/Decoding/FrameDisposal.cs",
    "content": "namespace Avalonia.Gif.Decoding\n{\n    public enum FrameDisposal\n    {\n        Unknown = 0,\n        Leave = 1,\n        Background = 2,\n        Restore = 3\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/Decoding/GifColor.cs",
    "content": "using System.Runtime.InteropServices;\n\nnamespace Avalonia.Gif\n{\n    [StructLayout(LayoutKind.Explicit)]\n    public readonly struct GifColor\n    {\n        [FieldOffset(3)]\n        public readonly byte A;\n\n        [FieldOffset(2)]\n        public readonly byte R;\n\n        [FieldOffset(1)]\n        public readonly byte G;\n\n        [FieldOffset(0)]\n        public readonly byte B;\n\n        /// <summary>\n        /// A struct that represents a ARGB color and is aligned as\n        /// a BGRA bytefield in memory.\n        /// </summary>\n        /// <param name=\"r\">Red</param>\n        /// <param name=\"g\">Green</param>\n        /// <param name=\"b\">Blue</param>\n        /// <param name=\"a\">Alpha</param>\n        public GifColor(byte r, byte g, byte b, byte a = byte.MaxValue)\n        {\n            A = a;\n            R = r;\n            G = g;\n            B = b;\n        }\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/Decoding/GifDecoder.cs",
    "content": "// This source file's Lempel-Ziv-Welch algorithm is derived from Chromium's Android GifPlayer\n// as seen here (https://github.com/chromium/chromium/blob/master/third_party/gif_player/src/jp/tomorrowkey/android/gifplayer)\n// Licensed under the Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0)\n// Copyright (C) 2015 The Gifplayer Authors. All Rights Reserved.\n\n// The rest of the source file is licensed under MIT License.\n// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved.\n\nusing System;\nusing System.Buffers;\nusing System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing System.IO;\nusing System.Linq;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\nusing System.Text;\nusing System.Threading;\nusing Avalonia;\nusing Avalonia.Media.Imaging;\nusing static Avalonia.Gif.Extensions.StreamExtensions;\n\nnamespace Avalonia.Gif.Decoding\n{\n    public sealed class GifDecoder : IDisposable\n    {\n        private static readonly ReadOnlyMemory<byte> G87AMagic = \"GIF87a\"u8.ToArray().AsMemory();\n\n        private static readonly ReadOnlyMemory<byte> G89AMagic = \"GIF89a\"u8.ToArray().AsMemory();\n\n        private static readonly ReadOnlyMemory<byte> NetscapeMagic = \"NETSCAPE2.0\"u8.ToArray().AsMemory();\n\n        private static readonly TimeSpan FrameDelayThreshold = TimeSpan.FromMilliseconds(10);\n        private static readonly TimeSpan FrameDelayDefault = TimeSpan.FromMilliseconds(100);\n        private static readonly GifColor TransparentColor = new(0, 0, 0, 0);\n        private static readonly int MaxTempBuf = 768;\n        private static readonly int MaxStackSize = 4096;\n        private static readonly int MaxBits = 4097;\n\n        private readonly Stream _fileStream;\n        private readonly CancellationToken _currentCtsToken;\n        private readonly bool _hasFrameBackups;\n\n        private int _gctSize,\n            _bgIndex,\n            _prevFrame = -1,\n            _backupFrame = -1;\n        private bool _gctUsed;\n\n        private GifRect _gifDimensions;\n\n        // private ulong _globalColorTable;\n        private readonly int _backBufferBytes;\n        private GifColor[] _bitmapBackBuffer;\n\n        private short[] _prefixBuf;\n        private byte[] _suffixBuf;\n        private byte[] _pixelStack;\n        private byte[] _indexBuf;\n        private byte[] _backupFrameIndexBuf;\n        private volatile bool _hasNewFrame;\n\n        public GifHeader Header { get; private set; }\n\n        public readonly List<GifFrame> Frames = new();\n\n        public PixelSize Size => new PixelSize(Header.Dimensions.Width, Header.Dimensions.Height);\n\n        public GifDecoder(Stream fileStream, CancellationToken currentCtsToken)\n        {\n            _fileStream = fileStream;\n            _currentCtsToken = currentCtsToken;\n\n            ProcessHeaderData();\n            ProcessFrameData();\n\n            Header.IterationCount = Header.Iterations switch\n            {\n                -1 => new GifRepeatBehavior { Count = 1 },\n                0 => new GifRepeatBehavior { LoopForever = true },\n                > 0 => new GifRepeatBehavior { Count = Header.Iterations },\n                _ => Header.IterationCount\n            };\n\n            var pixelCount = _gifDimensions.TotalPixels;\n\n            _hasFrameBackups = Frames.Any(f => f.FrameDisposalMethod == FrameDisposal.Restore);\n\n            _bitmapBackBuffer = new GifColor[pixelCount];\n            _indexBuf = new byte[pixelCount];\n\n            if (_hasFrameBackups)\n                _backupFrameIndexBuf = new byte[pixelCount];\n\n            _prefixBuf = new short[MaxStackSize];\n            _suffixBuf = new byte[MaxStackSize];\n            _pixelStack = new byte[MaxStackSize + 1];\n\n            _backBufferBytes = pixelCount * Marshal.SizeOf(typeof(GifColor));\n        }\n\n        public void Dispose()\n        {\n            Frames.Clear();\n\n            _bitmapBackBuffer = null;\n            _prefixBuf = null;\n            _suffixBuf = null;\n            _pixelStack = null;\n            _indexBuf = null;\n            _backupFrameIndexBuf = null;\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private int PixCoord(int x, int y) => x + y * _gifDimensions.Width;\n\n        static readonly (int Start, int Step)[] Pass = { (0, 8), (4, 8), (2, 4), (1, 2) };\n\n        private void ClearImage()\n        {\n            Array.Fill(_bitmapBackBuffer, TransparentColor);\n            //ClearArea(_gifDimensions);\n\n            _prevFrame = -1;\n            _backupFrame = -1;\n        }\n\n        public void RenderFrame(int fIndex, WriteableBitmap writeableBitmap, bool forceClear = false)\n        {\n            if (_currentCtsToken.IsCancellationRequested)\n                return;\n\n            if (fIndex < 0 | fIndex >= Frames.Count)\n                return;\n\n            if (_prevFrame == fIndex)\n                return;\n\n            if (fIndex == 0 || forceClear || fIndex < _prevFrame)\n                ClearImage();\n\n            DisposePreviousFrame();\n\n            _prevFrame++;\n\n            // render intermediate frame\n            for (int idx = _prevFrame; idx < fIndex; ++idx)\n            {\n                var prevFrame = Frames[idx];\n\n                if (prevFrame.FrameDisposalMethod == FrameDisposal.Restore)\n                    continue;\n\n                if (prevFrame.FrameDisposalMethod == FrameDisposal.Background)\n                {\n                    ClearArea(prevFrame.Dimensions);\n                    continue;\n                }\n\n                RenderFrameAt(idx, writeableBitmap);\n            }\n\n            RenderFrameAt(fIndex, writeableBitmap);\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private void RenderFrameAt(int idx, WriteableBitmap writeableBitmap)\n        {\n            var tmpB = ArrayPool<byte>.Shared.Rent(MaxTempBuf);\n\n            var curFrame = Frames[idx];\n            DecompressFrameToIndexBuffer(curFrame, _indexBuf, tmpB);\n\n            if (_hasFrameBackups & curFrame.ShouldBackup)\n            {\n                Buffer.BlockCopy(_indexBuf, 0, _backupFrameIndexBuf, 0, curFrame.Dimensions.TotalPixels);\n                _backupFrame = idx;\n            }\n\n            DrawFrame(curFrame, _indexBuf);\n\n            _prevFrame = idx;\n            _hasNewFrame = true;\n\n            using var lockedBitmap = writeableBitmap.Lock();\n            WriteBackBufToFb(lockedBitmap.Address);\n\n            ArrayPool<byte>.Shared.Return(tmpB);\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private void DrawFrame(GifFrame curFrame, Memory<byte> frameIndexSpan)\n        {\n            var activeColorTable = curFrame.IsLocalColorTableUsed\n                ? curFrame.LocalColorTable\n                : Header.GlobarColorTable;\n\n            var cX = curFrame.Dimensions.X;\n            var cY = curFrame.Dimensions.Y;\n            var cH = curFrame.Dimensions.Height;\n            var cW = curFrame.Dimensions.Width;\n            var tC = curFrame.TransparentColorIndex;\n            var hT = curFrame.HasTransparency;\n\n            if (curFrame.IsInterlaced)\n            {\n                for (var i = 0; i < 4; i++)\n                {\n                    var curPass = Pass[i];\n                    var y = curPass.Start;\n                    while (y < cH)\n                    {\n                        DrawRow(y);\n                        y += curPass.Step;\n                    }\n                }\n            }\n            else\n            {\n                for (var i = 0; i < cH; i++)\n                    DrawRow(i);\n            }\n\n            //for (var row = 0; row < cH; row++)\n            void DrawRow(int row)\n            {\n                // Get the starting point of the current row on frame's index stream.\n                var indexOffset = row * cW;\n\n                // Get the target backbuffer offset from the frames coords.\n                var targetOffset = PixCoord(cX, row + cY);\n                var len = _bitmapBackBuffer.Length;\n\n                for (var i = 0; i < cW; i++)\n                {\n                    var indexColor = frameIndexSpan.Span[indexOffset + i];\n\n                    if (\n                        activeColorTable == null\n                        || targetOffset >= len\n                        || indexColor > activeColorTable.Length\n                    )\n                        return;\n\n                    if (!(hT & indexColor == tC))\n                        _bitmapBackBuffer[targetOffset] = activeColorTable[indexColor];\n\n                    targetOffset++;\n                }\n            }\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private void DisposePreviousFrame()\n        {\n            if (_prevFrame == -1)\n                return;\n\n            var prevFrame = Frames[_prevFrame];\n\n            switch (prevFrame.FrameDisposalMethod)\n            {\n                case FrameDisposal.Background:\n                    ClearArea(prevFrame.Dimensions);\n                    break;\n                case FrameDisposal.Restore:\n                    if (_hasFrameBackups && _backupFrame != -1)\n                        DrawFrame(Frames[_backupFrame], _backupFrameIndexBuf);\n                    else\n                        ClearArea(prevFrame.Dimensions);\n                    break;\n            }\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private void ClearArea(GifRect area)\n        {\n            for (var y = 0; y < area.Height; y++)\n            {\n                var targetOffset = PixCoord(area.X, y + area.Y);\n                for (var x = 0; x < area.Width; x++)\n                    _bitmapBackBuffer[targetOffset + x] = TransparentColor;\n            }\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private void DecompressFrameToIndexBuffer(GifFrame curFrame, Span<byte> indexSpan, byte[] tempBuf)\n        {\n            _fileStream.Position = curFrame.LzwStreamPosition;\n            var totalPixels = curFrame.Dimensions.TotalPixels;\n\n            // Initialize GIF data stream decoder.\n            var dataSize = curFrame.LzwMinCodeSize;\n            var clear = 1 << dataSize;\n            var endOfInformation = clear + 1;\n            var available = clear + 2;\n            var oldCode = -1;\n            var codeSize = dataSize + 1;\n            var codeMask = (1 << codeSize) - 1;\n\n            for (var code = 0; code < clear; code++)\n            {\n                _prefixBuf[code] = 0;\n                _suffixBuf[code] = (byte)code;\n            }\n\n            // Decode GIF pixel stream.\n            int bits,\n                first,\n                top,\n                pixelIndex;\n            var datum = bits = first = top = pixelIndex = 0;\n\n            while (pixelIndex < totalPixels)\n            {\n                var blockSize = _fileStream.ReadBlock(tempBuf);\n\n                if (blockSize == 0)\n                    break;\n\n                var blockPos = 0;\n\n                while (blockPos < blockSize)\n                {\n                    datum += tempBuf[blockPos] << bits;\n                    blockPos++;\n\n                    bits += 8;\n\n                    while (bits >= codeSize)\n                    {\n                        // Get the next code.\n                        var code = datum & codeMask;\n                        datum >>= codeSize;\n                        bits -= codeSize;\n\n                        // Interpret the code\n                        if (code == clear)\n                        {\n                            // Reset decoder.\n                            codeSize = dataSize + 1;\n                            codeMask = (1 << codeSize) - 1;\n                            available = clear + 2;\n                            oldCode = -1;\n                            continue;\n                        }\n\n                        // Check for explicit end-of-stream\n                        if (code == endOfInformation)\n                            return;\n\n                        if (oldCode == -1)\n                        {\n                            indexSpan[pixelIndex++] = _suffixBuf[code];\n                            oldCode = code;\n                            first = code;\n                            continue;\n                        }\n\n                        var inCode = code;\n                        if (code >= available)\n                        {\n                            _pixelStack[top++] = (byte)first;\n                            code = oldCode;\n\n                            if (top == MaxBits)\n                                ThrowException();\n                        }\n\n                        while (code >= clear)\n                        {\n                            if (code >= MaxBits || code == _prefixBuf[code])\n                                ThrowException();\n\n                            _pixelStack[top++] = _suffixBuf[code];\n                            code = _prefixBuf[code];\n\n                            if (top == MaxBits)\n                                ThrowException();\n                        }\n\n                        first = _suffixBuf[code];\n                        _pixelStack[top++] = (byte)first;\n\n                        // Add new code to the dictionary\n                        if (available < MaxStackSize)\n                        {\n                            _prefixBuf[available] = (short)oldCode;\n                            _suffixBuf[available] = (byte)first;\n                            available++;\n\n                            if ((available & codeMask) == 0 && available < MaxStackSize)\n                            {\n                                codeSize++;\n                                codeMask += available;\n                            }\n                        }\n\n                        oldCode = inCode;\n\n                        // Drain the pixel stack.\n                        do\n                        {\n                            indexSpan[pixelIndex++] = _pixelStack[--top];\n                        } while (top > 0);\n                    }\n                }\n            }\n\n            while (pixelIndex < totalPixels)\n                indexSpan[pixelIndex++] = 0; // clear missing pixels\n\n            void ThrowException() => throw new LzwDecompressionException();\n        }\n\n        /// <summary>\n        /// Directly copies the <see cref=\"GifColor\"/> struct array to a bitmap IntPtr.\n        /// </summary>\n        private void WriteBackBufToFb(IntPtr targetPointer)\n        {\n            if (_currentCtsToken.IsCancellationRequested)\n                return;\n\n            if (!(_hasNewFrame & _bitmapBackBuffer != null))\n                return;\n\n            unsafe\n            {\n                fixed (void* src = &_bitmapBackBuffer[0])\n                    Buffer.MemoryCopy(\n                        src,\n                        targetPointer.ToPointer(),\n                        (uint)_backBufferBytes,\n                        (uint)_backBufferBytes\n                    );\n                _hasNewFrame = false;\n            }\n        }\n\n        /// <summary>\n        /// Processes GIF Header.\n        /// </summary>\n        [MemberNotNull(nameof(Header))]\n        private void ProcessHeaderData()\n        {\n            var str = _fileStream;\n            var tmpB = ArrayPool<byte>.Shared.Rent(MaxTempBuf);\n            var tempBuf = tmpB.AsSpan();\n\n            var _ = str.Read(tmpB, 0, 6);\n\n            if (!tempBuf[..3].SequenceEqual(G87AMagic[..3].Span))\n                throw new InvalidGifStreamException(\"Not a GIF stream.\");\n\n            if (!(tempBuf[..6].SequenceEqual(G87AMagic.Span) | tempBuf[..6].SequenceEqual(G89AMagic.Span)))\n                throw new InvalidGifStreamException(\n                    \"Unsupported GIF Version: \" + Encoding.ASCII.GetString(tempBuf[..6].ToArray())\n                );\n\n            ProcessScreenDescriptor(tmpB);\n\n            Header = new GifHeader\n            {\n                Dimensions = _gifDimensions,\n                HasGlobalColorTable = _gctUsed,\n                // GlobalColorTableCacheID = _globalColorTable,\n                GlobarColorTable = ProcessColorTable(ref str, tmpB, _gctSize),\n                GlobalColorTableSize = _gctSize,\n                BackgroundColorIndex = _bgIndex,\n                HeaderSize = _fileStream.Position\n            };\n\n            ArrayPool<byte>.Shared.Return(tmpB);\n        }\n\n        /// <summary>\n        /// Parses colors from file stream to target color table.\n        /// </summary>\n        private static GifColor[] ProcessColorTable(ref Stream stream, byte[] rawBufSpan, int nColors)\n        {\n            var nBytes = 3 * nColors;\n            var target = new GifColor[nColors];\n\n            var n = stream.Read(rawBufSpan, 0, nBytes);\n\n            if (n < nBytes)\n                throw new InvalidOperationException(\"Wrong color table bytes.\");\n\n            int i = 0,\n                j = 0;\n\n            while (i < nColors)\n            {\n                var r = rawBufSpan[j++];\n                var g = rawBufSpan[j++];\n                var b = rawBufSpan[j++];\n                target[i++] = new GifColor(r, g, b);\n            }\n\n            return target;\n        }\n\n        /// <summary>\n        /// Parses screen and other GIF descriptors.\n        /// </summary>\n        private void ProcessScreenDescriptor(byte[] tempBuf)\n        {\n            var width = _fileStream.ReadUShortS(tempBuf);\n            var height = _fileStream.ReadUShortS(tempBuf);\n\n            var packed = _fileStream.ReadByteS(tempBuf);\n\n            _gctUsed = (packed & 0x80) != 0;\n            _gctSize = 2 << (packed & 7);\n            _bgIndex = _fileStream.ReadByteS(tempBuf);\n\n            _gifDimensions = new GifRect(0, 0, width, height);\n            _fileStream.Skip(1);\n        }\n\n        /// <summary>\n        /// Parses all frame data.\n        /// </summary>\n        private void ProcessFrameData()\n        {\n            _fileStream.Position = Header.HeaderSize;\n\n            var tempBuf = ArrayPool<byte>.Shared.Rent(MaxTempBuf);\n\n            var terminate = false;\n            var curFrame = 0;\n\n            Frames.Add(new GifFrame());\n\n            do\n            {\n                var blockType = (BlockTypes)_fileStream.ReadByteS(tempBuf);\n\n                switch (blockType)\n                {\n                    case BlockTypes.Empty:\n                        break;\n\n                    case BlockTypes.Extension:\n                        ProcessExtensions(ref curFrame, tempBuf);\n                        break;\n\n                    case BlockTypes.ImageDescriptor:\n                        ProcessImageDescriptor(ref curFrame, tempBuf);\n                        _fileStream.SkipBlocks(tempBuf);\n                        break;\n\n                    case BlockTypes.Trailer:\n                        Frames.RemoveAt(Frames.Count - 1);\n                        terminate = true;\n                        break;\n\n                    default:\n                        _fileStream.SkipBlocks(tempBuf);\n                        break;\n                }\n\n                // Break the loop when the stream is not valid anymore.\n                if (_fileStream.Position >= _fileStream.Length & terminate == false)\n                    throw new InvalidProgramException(\n                        \"Reach the end of the filestream without trailer block.\"\n                    );\n            } while (!terminate);\n\n            ArrayPool<byte>.Shared.Return(tempBuf);\n        }\n\n        /// <summary>\n        /// Parses GIF Image Descriptor Block.\n        /// </summary>\n        private void ProcessImageDescriptor(ref int curFrame, byte[] tempBuf)\n        {\n            var str = _fileStream;\n            var currentFrame = Frames[curFrame];\n\n            // Parse frame dimensions.\n            var frameX = str.ReadUShortS(tempBuf);\n            var frameY = str.ReadUShortS(tempBuf);\n            var frameW = str.ReadUShortS(tempBuf);\n            var frameH = str.ReadUShortS(tempBuf);\n\n            frameW = (ushort)Math.Min(frameW, _gifDimensions.Width - frameX);\n            frameH = (ushort)Math.Min(frameH, _gifDimensions.Height - frameY);\n\n            currentFrame.Dimensions = new GifRect(frameX, frameY, frameW, frameH);\n\n            // Unpack interlace and lct info.\n            var packed = str.ReadByteS(tempBuf);\n            currentFrame.IsInterlaced = (packed & 0x40) != 0;\n            currentFrame.IsLocalColorTableUsed = (packed & 0x80) != 0;\n            currentFrame.LocalColorTableSize = (int)Math.Pow(2, (packed & 0x07) + 1);\n\n            if (currentFrame.IsLocalColorTableUsed)\n                currentFrame.LocalColorTable = ProcessColorTable(\n                    ref str,\n                    tempBuf,\n                    currentFrame.LocalColorTableSize\n                );\n\n            currentFrame.LzwMinCodeSize = str.ReadByteS(tempBuf);\n            currentFrame.LzwStreamPosition = str.Position;\n\n            curFrame += 1;\n            Frames.Add(new GifFrame());\n        }\n\n        /// <summary>\n        /// Parses GIF Extension Blocks.\n        /// </summary>\n        private void ProcessExtensions(ref int curFrame, byte[] tempBuf)\n        {\n            var extType = (ExtensionType)_fileStream.ReadByteS(tempBuf);\n\n            switch (extType)\n            {\n                case ExtensionType.GraphicsControl:\n\n                    _fileStream.ReadBlock(tempBuf);\n                    var currentFrame = Frames[curFrame];\n                    var packed = tempBuf[0];\n\n                    currentFrame.FrameDisposalMethod = (FrameDisposal)((packed & 0x1c) >> 2);\n\n                    if (\n                        currentFrame.FrameDisposalMethod != FrameDisposal.Restore\n                        && currentFrame.FrameDisposalMethod != FrameDisposal.Background\n                    )\n                        currentFrame.ShouldBackup = true;\n\n                    currentFrame.HasTransparency = (packed & 1) != 0;\n\n                    currentFrame.FrameDelay = TimeSpan.FromMilliseconds(SpanToShort(tempBuf.AsSpan(1)) * 10);\n\n                    if (currentFrame.FrameDelay <= FrameDelayThreshold)\n                        currentFrame.FrameDelay = FrameDelayDefault;\n\n                    currentFrame.TransparentColorIndex = tempBuf[3];\n                    break;\n\n                case ExtensionType.Application:\n                    var blockLen = _fileStream.ReadBlock(tempBuf);\n                    var _ = tempBuf.AsSpan(0, blockLen);\n                    var blockHeader = tempBuf.AsSpan(0, NetscapeMagic.Length);\n\n                    if (blockHeader.SequenceEqual(NetscapeMagic.Span))\n                    {\n                        var count = 1;\n\n                        while (count > 0)\n                            count = _fileStream.ReadBlock(tempBuf);\n\n                        var iterationCount = SpanToShort(tempBuf.AsSpan(1));\n\n                        Header.Iterations = iterationCount;\n                    }\n                    else\n                        _fileStream.SkipBlocks(tempBuf);\n\n                    break;\n\n                default:\n                    _fileStream.SkipBlocks(tempBuf);\n                    break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/Decoding/GifFrame.cs",
    "content": "using System;\n\nnamespace Avalonia.Gif.Decoding\n{\n    public class GifFrame\n    {\n        public bool HasTransparency,\n            IsInterlaced,\n            IsLocalColorTableUsed;\n        public byte TransparentColorIndex;\n        public int LzwMinCodeSize,\n            LocalColorTableSize;\n        public long LzwStreamPosition;\n        public TimeSpan FrameDelay;\n        public FrameDisposal FrameDisposalMethod;\n        public bool ShouldBackup;\n        public GifRect Dimensions;\n        public GifColor[] LocalColorTable;\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/Decoding/GifHeader.cs",
    "content": "// Licensed under the MIT License.\n// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved.\n\nnamespace Avalonia.Gif.Decoding\n{\n    public class GifHeader\n    {\n        public bool HasGlobalColorTable;\n        public int GlobalColorTableSize;\n        public ulong GlobalColorTableCacheId;\n        public int BackgroundColorIndex;\n        public long HeaderSize;\n        internal int Iterations = -1;\n        public GifRepeatBehavior IterationCount;\n        public GifRect Dimensions;\n        private GifColor[] _globarColorTable;\n        public GifColor[] GlobarColorTable;\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/Decoding/GifRect.cs",
    "content": "namespace Avalonia.Gif.Decoding\n{\n    public readonly struct GifRect\n    {\n        public int X { get; }\n        public int Y { get; }\n        public int Width { get; }\n        public int Height { get; }\n        public int TotalPixels { get; }\n\n        public GifRect(int x, int y, int width, int height)\n        {\n            X = x;\n            Y = y;\n            Width = width;\n            Height = height;\n            TotalPixels = width * height;\n        }\n\n        public static bool operator ==(GifRect a, GifRect b)\n        {\n            return a.X == b.X && a.Y == b.Y && a.Width == b.Width && a.Height == b.Height;\n        }\n\n        public static bool operator !=(GifRect a, GifRect b)\n        {\n            return !(a == b);\n        }\n\n        public override bool Equals(object obj)\n        {\n            if (obj == null || GetType() != obj.GetType())\n                return false;\n\n            return this == (GifRect)obj;\n        }\n\n        public override int GetHashCode()\n        {\n            return X.GetHashCode() ^ Y.GetHashCode() | Width.GetHashCode() ^ Height.GetHashCode();\n        }\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/Decoding/GifRepeatBehavior.cs",
    "content": "﻿namespace Avalonia.Gif.Decoding\n{\n    public class GifRepeatBehavior\n    {\n        public bool LoopForever { get; set; }\n        public int? Count { get; set; }\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/Decoding/InvalidGifStreamException.cs",
    "content": "// Licensed under the MIT License.\n// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved.\n\nnamespace Avalonia.Gif.Decoding\n{\n    [Serializable]\n    public class InvalidGifStreamException : Exception\n    {\n        public InvalidGifStreamException() { }\n\n        public InvalidGifStreamException(string message)\n            : base(message) { }\n\n        public InvalidGifStreamException(string message, Exception innerException)\n            : base(message, innerException) { }\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/Decoding/LzwDecompressionException.cs",
    "content": "// Licensed under the MIT License.\n// Copyright (C) 2018 Jumar A. Macato, All Rights Reserved.\n\nnamespace Avalonia.Gif.Decoding\n{\n    [Serializable]\n    public class LzwDecompressionException : Exception\n    {\n        public LzwDecompressionException() { }\n\n        public LzwDecompressionException(string message)\n            : base(message) { }\n\n        public LzwDecompressionException(string message, Exception innerException)\n            : base(message, innerException) { }\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/Extensions/StreamExtensions.cs",
    "content": "using System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Runtime.CompilerServices;\n\nnamespace Avalonia.Gif.Extensions\n{\n    [DebuggerStepThrough]\n    internal static class StreamExtensions\n    {\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        public static ushort SpanToShort(Span<byte> b) => (ushort)(b[0] | (b[1] << 8));\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        public static void Skip(this Stream stream, long count)\n        {\n            stream.Position += count;\n        }\n\n        /// <summary>\n        /// Read a Gif block from stream while advancing the position.\n        /// </summary>\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        public static int ReadBlock(this Stream stream, byte[] tempBuf)\n        {\n            stream.Read(tempBuf, 0, 1);\n\n            var blockLength = (int)tempBuf[0];\n\n            if (blockLength > 0)\n                stream.Read(tempBuf, 0, blockLength);\n\n            // Guard against infinite loop.\n            if (stream.Position >= stream.Length)\n                throw new InvalidGifStreamException(\"Reach the end of the filestream without trailer block.\");\n\n            return blockLength;\n        }\n\n        /// <summary>\n        /// Skips GIF blocks until it encounters an empty block.\n        /// </summary>\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        public static void SkipBlocks(this Stream stream, byte[] tempBuf)\n        {\n            int blockLength;\n            do\n            {\n                stream.Read(tempBuf, 0, 1);\n\n                blockLength = tempBuf[0];\n                stream.Position += blockLength;\n\n                // Guard against infinite loop.\n                if (stream.Position >= stream.Length)\n                    throw new InvalidGifStreamException(\"Reach the end of the filestream without trailer block.\");\n            } while (blockLength > 0);\n        }\n\n        /// <summary>\n        /// Read a <see cref=\"ushort\"/> from stream by providing a temporary buffer.\n        /// </summary>\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        public static ushort ReadUShortS(this Stream stream, byte[] tempBuf)\n        {\n            stream.Read(tempBuf, 0, 2);\n            return SpanToShort(tempBuf);\n        }\n\n        /// <summary>\n        /// Read a <see cref=\"ushort\"/> from stream by providing a temporary buffer.\n        /// </summary>\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        public static byte ReadByteS(this Stream stream, byte[] tempBuf)\n        {\n            stream.Read(tempBuf, 0, 1);\n            var finalVal = tempBuf[0];\n            return finalVal;\n        }\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/GifImage.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Numerics;\nusing Avalonia;\nusing Avalonia.Animation;\nusing Avalonia.Controls;\nusing Avalonia.Logging;\nusing Avalonia.Media;\nusing Avalonia.Rendering.Composition;\nusing Avalonia.VisualTree;\n\nnamespace Avalonia.Gif\n{\n    public class GifImage : Control\n    {\n        public static readonly StyledProperty<string> SourceUriRawProperty = AvaloniaProperty.Register<\n            GifImage,\n            string\n        >(\"SourceUriRaw\");\n\n        public static readonly StyledProperty<Uri> SourceUriProperty = AvaloniaProperty.Register<\n            GifImage,\n            Uri\n        >(\"SourceUri\");\n\n        public static readonly StyledProperty<Stream> SourceStreamProperty = AvaloniaProperty.Register<\n            GifImage,\n            Stream\n        >(\"SourceStream\");\n\n        public static readonly StyledProperty<IterationCount> IterationCountProperty =\n            AvaloniaProperty.Register<GifImage, IterationCount>(\"IterationCount\", IterationCount.Infinite);\n\n        private IGifInstance? _gifInstance;\n\n        public static readonly StyledProperty<StretchDirection> StretchDirectionProperty =\n            AvaloniaProperty.Register<GifImage, StretchDirection>(\"StretchDirection\");\n\n        public static readonly StyledProperty<Stretch> StretchProperty = AvaloniaProperty.Register<\n            GifImage,\n            Stretch\n        >(\"Stretch\");\n\n        private CompositionCustomVisual? _customVisual;\n\n        private object? _initialSource = null;\n\n        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n        {\n            switch (change.Property.Name)\n            {\n                case nameof(SourceUriRaw):\n                case nameof(SourceUri):\n                case nameof(SourceStream):\n                    SourceChanged(change);\n                    break;\n                case nameof(Stretch):\n                case nameof(StretchDirection):\n                    InvalidateArrange();\n                    InvalidateMeasure();\n                    Update();\n                    break;\n                case nameof(IterationCount):\n                    IterationCountChanged(change);\n                    break;\n                case nameof(Bounds):\n                    Update();\n                    break;\n            }\n\n            base.OnPropertyChanged(change);\n        }\n\n        public string SourceUriRaw\n        {\n            get => GetValue(SourceUriRawProperty);\n            set => SetValue(SourceUriRawProperty, value);\n        }\n\n        public Uri SourceUri\n        {\n            get => GetValue(SourceUriProperty);\n            set => SetValue(SourceUriProperty, value);\n        }\n\n        public Stream SourceStream\n        {\n            get => GetValue(SourceStreamProperty);\n            set => SetValue(SourceStreamProperty, value);\n        }\n\n        public IterationCount IterationCount\n        {\n            get => GetValue(IterationCountProperty);\n            set => SetValue(IterationCountProperty, value);\n        }\n\n        public StretchDirection StretchDirection\n        {\n            get => GetValue(StretchDirectionProperty);\n            set => SetValue(StretchDirectionProperty, value);\n        }\n\n        public Stretch Stretch\n        {\n            get => GetValue(StretchProperty);\n            set => SetValue(StretchProperty, value);\n        }\n\n        private static void IterationCountChanged(AvaloniaPropertyChangedEventArgs e)\n        {\n            var image = e.Sender as GifImage;\n            if (image is null || e.NewValue is not IterationCount iterationCount)\n                return;\n\n            image.IterationCount = iterationCount;\n        }\n\n        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)\n        {\n            var compositor = ElementComposition.GetElementVisual(this)?.Compositor;\n            if (compositor == null || _customVisual?.Compositor == compositor)\n                return;\n            _customVisual = compositor.CreateCustomVisual(new CustomVisualHandler());\n            ElementComposition.SetElementChildVisual(this, _customVisual);\n            _customVisual.SendHandlerMessage(CustomVisualHandler.StartMessage);\n\n            if (_initialSource is not null)\n            {\n                UpdateGifInstance(_initialSource);\n                _initialSource = null;\n            }\n\n            Update();\n            base.OnAttachedToVisualTree(e);\n        }\n\n        private void Update()\n        {\n            if (_customVisual is null || _gifInstance is null)\n                return;\n\n            var dpi = this.GetVisualRoot()?.RenderScaling ?? 1.0;\n            var sourceSize = _gifInstance.GifPixelSize.ToSize(dpi);\n            var viewPort = new Rect(Bounds.Size);\n\n            var scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);\n            var scaledSize = sourceSize * scale;\n            var destRect = viewPort.CenterRect(new Rect(scaledSize)).Intersect(viewPort);\n\n            if (Stretch == Stretch.None)\n            {\n                _customVisual.Size = new Vector2((float)sourceSize.Width, (float)sourceSize.Height);\n            }\n            else\n            {\n                _customVisual.Size = new Vector2((float)destRect.Size.Width, (float)destRect.Size.Height);\n            }\n\n            _customVisual.Offset = new Vector3((float)destRect.Position.X, (float)destRect.Position.Y, 0);\n        }\n\n        private class CustomVisualHandler : CompositionCustomVisualHandler\n        {\n            private TimeSpan _animationElapsed;\n            private TimeSpan? _lastServerTime;\n            private IGifInstance? _currentInstance;\n            private bool _running;\n\n            public static readonly object StopMessage = new(),\n                StartMessage = new();\n\n            public override void OnMessage(object message)\n            {\n                if (message == StartMessage)\n                {\n                    _running = true;\n                    _lastServerTime = null;\n                    RegisterForNextAnimationFrameUpdate();\n                }\n                else if (message == StopMessage)\n                {\n                    _running = false;\n                }\n                else if (message is IGifInstance instance)\n                {\n                    _currentInstance?.Dispose();\n                    _currentInstance = instance;\n                }\n            }\n\n            public override void OnAnimationFrameUpdate()\n            {\n                if (!_running)\n                    return;\n                Invalidate();\n                RegisterForNextAnimationFrameUpdate();\n            }\n\n            public override void OnRender(ImmediateDrawingContext drawingContext)\n            {\n                if (_running)\n                {\n                    if (_lastServerTime.HasValue)\n                        _animationElapsed += (CompositionNow - _lastServerTime.Value);\n                    _lastServerTime = CompositionNow;\n                }\n\n                try\n                {\n                    if (_currentInstance is null || _currentInstance.IsDisposed)\n                        return;\n\n                    var bitmap = _currentInstance.ProcessFrameTime(_animationElapsed);\n                    if (bitmap is not null)\n                    {\n                        drawingContext.DrawBitmap(\n                            bitmap,\n                            new Rect(_currentInstance.GifPixelSize.ToSize(1)),\n                            GetRenderBounds()\n                        );\n                    }\n                }\n                catch (Exception e)\n                {\n                    Logger.Sink?.Log(LogEventLevel.Error, \"GifImage Renderer \", this, e.ToString());\n                }\n            }\n        }\n\n        /// <summary>\n        /// Measures the control.\n        /// </summary>\n        /// <param name=\"availableSize\">The available size.</param>\n        /// <returns>The desired size of the control.</returns>\n        protected override Size MeasureOverride(Size availableSize)\n        {\n            var result = new Size();\n            var scaling = this.GetVisualRoot()?.RenderScaling ?? 1.0;\n            if (_gifInstance != null)\n            {\n                result = Stretch.CalculateSize(\n                    availableSize,\n                    _gifInstance.GifPixelSize.ToSize(scaling),\n                    StretchDirection\n                );\n            }\n\n            return result;\n        }\n\n        /// <inheritdoc/>\n        protected override Size ArrangeOverride(Size finalSize)\n        {\n            if (_gifInstance is null)\n                return new Size();\n            var scaling = this.GetVisualRoot()?.RenderScaling ?? 1.0;\n            var sourceSize = _gifInstance.GifPixelSize.ToSize(scaling);\n            var result = Stretch.CalculateSize(finalSize, sourceSize);\n            return result;\n        }\n\n        private void SourceChanged(AvaloniaPropertyChangedEventArgs e)\n        {\n            if (\n                e.NewValue is null\n                || (e.NewValue is string value && !Uri.IsWellFormedUriString(value, UriKind.Absolute))\n            )\n            {\n                return;\n            }\n\n            if (_customVisual is null)\n            {\n                _initialSource = e.NewValue;\n                return;\n            }\n\n            UpdateGifInstance(e.NewValue);\n\n            InvalidateArrange();\n            InvalidateMeasure();\n            Update();\n        }\n\n        private void UpdateGifInstance(object source)\n        {\n            _gifInstance?.Dispose();\n\n            try\n            {\n                _gifInstance = new WebpInstance(source);\n                // _gifInstance = new GifInstance(source);\n\n                _gifInstance.IterationCount = IterationCount;\n                _customVisual?.SendHandlerMessage(_gifInstance);\n            }\n            catch (Exception e)\n            {\n                Logger.Sink?.Log(LogEventLevel.Warning, \"GifImage Update Source \", this, e.ToString());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/GifInstance.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Threading;\nusing Avalonia;\nusing Avalonia.Animation;\nusing Avalonia.Gif.Decoding;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Platform;\nusing SkiaSharp;\n\nnamespace Avalonia.Gif\n{\n    public class GifInstance : IGifInstance\n    {\n        public IterationCount IterationCount { get; set; }\n        public bool AutoStart { get; private set; } = true;\n        private readonly GifDecoder _gifDecoder;\n        private readonly WriteableBitmap? _targetBitmap;\n        private TimeSpan _totalTime;\n        private readonly List<TimeSpan> _frameTimes;\n        private uint _iterationCount;\n        private int _currentFrameIndex;\n        private readonly List<ulong> _colorTableIdList;\n\n        public CancellationTokenSource CurrentCts { get; }\n\n        internal GifInstance(object newValue)\n            : this(\n                newValue switch\n                {\n                    Stream s => s,\n                    Uri u => GetStreamFromUri(u),\n                    string str => GetStreamFromString(str),\n                    _ => throw new InvalidDataException(\"Unsupported source object\")\n                }\n            ) { }\n\n        public GifInstance(string uri)\n            : this(GetStreamFromString(uri)) { }\n\n        public GifInstance(Uri uri)\n            : this(GetStreamFromUri(uri)) { }\n\n        public GifInstance(Stream currentStream)\n        {\n            if (!currentStream.CanSeek)\n                throw new InvalidDataException(\"The provided stream is not seekable.\");\n\n            if (!currentStream.CanRead)\n                throw new InvalidOperationException(\"Can't read the stream provided.\");\n\n            currentStream.Seek(0, SeekOrigin.Begin);\n\n            CurrentCts = new CancellationTokenSource();\n\n            _gifDecoder = new GifDecoder(currentStream, CurrentCts.Token);\n            var pixSize = new PixelSize(\n                _gifDecoder.Header.Dimensions.Width,\n                _gifDecoder.Header.Dimensions.Height\n            );\n\n            // Different on os: https://github.com/mono/SkiaSharp/issues/1492#issuecomment-689015409\n            // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault\n            var format = SKImageInfo.PlatformColorType switch\n            {\n                SKColorType.Bgra8888 => PixelFormat.Bgra8888,\n                SKColorType.Rgba8888 => PixelFormat.Rgba8888,\n                _\n                    => throw new NotSupportedException(\n                        $\"Unsupported color type: {SKImageInfo.PlatformColorType}\"\n                    )\n            };\n\n            _targetBitmap = new WriteableBitmap(pixSize, new Vector(96, 96), format, AlphaFormat.Opaque);\n\n            GifPixelSize = pixSize;\n\n            _totalTime = TimeSpan.Zero;\n\n            _frameTimes = _gifDecoder\n                .Frames.Select(frame =>\n                {\n                    _totalTime = _totalTime.Add(frame.FrameDelay);\n                    return _totalTime;\n                })\n                .ToList();\n\n            _gifDecoder.RenderFrame(0, _targetBitmap);\n        }\n\n        private static Stream GetStreamFromString(string str)\n        {\n            if (!Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out var res))\n            {\n                throw new InvalidCastException(\"The string provided can't be converted to URI.\");\n            }\n\n            return GetStreamFromUri(res);\n        }\n\n        private static Stream GetStreamFromUri(Uri uri)\n        {\n            var uriString = uri.OriginalString.Trim();\n\n            if (!uriString.StartsWith(\"resm\") && !uriString.StartsWith(\"avares\"))\n            {\n                return new FileStream(uriString, FileMode.Open, FileAccess.Read);\n            }\n\n            return AssetLoader.Open(uri);\n        }\n\n        public int GifFrameCount => _frameTimes.Count;\n\n        public PixelSize GifPixelSize { get; }\n\n        public void Dispose()\n        {\n            IsDisposed = true;\n            CurrentCts.Cancel();\n            _targetBitmap?.Dispose();\n        }\n\n        public bool IsDisposed { get; private set; }\n\n        public WriteableBitmap? ProcessFrameTime(TimeSpan stopwatchElapsed)\n        {\n            if (!IterationCount.IsInfinite && _iterationCount > IterationCount.Value)\n            {\n                return null;\n            }\n\n            if (CurrentCts.IsCancellationRequested || _targetBitmap is null)\n            {\n                return null;\n            }\n\n            var elapsedTicks = stopwatchElapsed.Ticks;\n            var timeModulus = TimeSpan.FromTicks(elapsedTicks % _totalTime.Ticks);\n            var targetFrame = _frameTimes.FirstOrDefault(x => timeModulus < x);\n            var currentFrame = _frameTimes.IndexOf(targetFrame);\n            if (currentFrame == -1)\n                currentFrame = 0;\n\n            if (_currentFrameIndex == currentFrame)\n                return _targetBitmap;\n\n            _iterationCount = (uint)(elapsedTicks / _totalTime.Ticks);\n\n            return ProcessFrameIndex(currentFrame);\n        }\n\n        internal WriteableBitmap ProcessFrameIndex(int frameIndex)\n        {\n            _gifDecoder.RenderFrame(frameIndex, _targetBitmap!);\n            _currentFrameIndex = frameIndex;\n\n            return _targetBitmap!;\n        }\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/IGifInstance.cs",
    "content": "﻿using Avalonia.Animation;\nusing Avalonia.Media.Imaging;\n\nnamespace Avalonia.Gif;\n\npublic interface IGifInstance : IDisposable\n{\n    IterationCount IterationCount { get; set; }\n    bool AutoStart { get; }\n    CancellationTokenSource CurrentCts { get; }\n    int GifFrameCount { get; }\n    PixelSize GifPixelSize { get; }\n    bool IsDisposed { get; }\n    WriteableBitmap? ProcessFrameTime(TimeSpan stopwatchElapsed);\n}\n"
  },
  {
    "path": "Avalonia.Gif/InvalidGifStreamException.cs",
    "content": "namespace Avalonia.Gif\n{\n    [Serializable]\n    internal class InvalidGifStreamException : Exception\n    {\n        public InvalidGifStreamException() { }\n\n        public InvalidGifStreamException(string message)\n            : base(message) { }\n\n        public InvalidGifStreamException(string message, Exception innerException)\n            : base(message, innerException) { }\n    }\n}\n"
  },
  {
    "path": "Avalonia.Gif/WebpInstance.cs",
    "content": "﻿using Avalonia.Animation;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Platform;\nusing SkiaSharp;\n\nnamespace Avalonia.Gif;\n\npublic class WebpInstance : IGifInstance\n{\n    public IterationCount IterationCount { get; set; }\n    public bool AutoStart { get; private set; } = true;\n\n    private readonly WriteableBitmap? _targetBitmap;\n    private TimeSpan _totalTime;\n    private readonly List<TimeSpan> _frameTimes;\n    private uint _iterationCount;\n    private int _currentFrameIndex;\n\n    private SKCodec? _codec;\n\n    public CancellationTokenSource CurrentCts { get; }\n\n    internal WebpInstance(object newValue)\n        : this(\n            newValue switch\n            {\n                Stream s => s,\n                Uri u => GetStreamFromUri(u),\n                string str => GetStreamFromString(str),\n                _ => throw new InvalidDataException(\"Unsupported source object\")\n            }\n        ) { }\n\n    public WebpInstance(string uri)\n        : this(GetStreamFromString(uri)) { }\n\n    public WebpInstance(Uri uri)\n        : this(GetStreamFromUri(uri)) { }\n\n    public WebpInstance(Stream currentStream)\n    {\n        if (!currentStream.CanSeek)\n            throw new InvalidDataException(\"The provided stream is not seekable.\");\n\n        if (!currentStream.CanRead)\n            throw new InvalidOperationException(\"Can't read the stream provided.\");\n\n        currentStream.Seek(0, SeekOrigin.Begin);\n\n        CurrentCts = new CancellationTokenSource();\n\n        var managedStream = new SKManagedStream(currentStream);\n        _codec = SKCodec.Create(managedStream);\n\n        var pixSize = new PixelSize(_codec.Info.Width, _codec.Info.Height);\n\n        // Different on os: https://github.com/mono/SkiaSharp/issues/1492#issuecomment-689015409\n        // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault\n        var format = SKImageInfo.PlatformColorType switch\n        {\n            SKColorType.Bgra8888 => PixelFormat.Bgra8888,\n            SKColorType.Rgba8888 => PixelFormat.Rgba8888,\n            _ => throw new NotSupportedException($\"Unsupported color type: {SKImageInfo.PlatformColorType}\")\n        };\n\n        _targetBitmap = new WriteableBitmap(pixSize, new Vector(96, 96), format, AlphaFormat.Opaque);\n\n        GifPixelSize = pixSize;\n\n        _totalTime = TimeSpan.Zero;\n\n        _frameTimes = _codec\n            .FrameInfo.Select(frame =>\n            {\n                _totalTime = _totalTime.Add(TimeSpan.FromMilliseconds(frame.Duration));\n                return _totalTime;\n            })\n            .ToList();\n\n        RenderFrame(_codec, _targetBitmap, 0);\n    }\n\n    private static void RenderFrame(SKCodec codec, WriteableBitmap targetBitmap, int index)\n    {\n        codec.GetFrameInfo(index, out var frameInfo);\n\n        var info = new SKImageInfo(codec.Info.Width, codec.Info.Height);\n        var decodeInfo = info.WithAlphaType(frameInfo.AlphaType);\n\n        using var frameBuffer = targetBitmap.Lock();\n\n        var result = codec.GetPixels(decodeInfo, frameBuffer.Address, new SKCodecOptions(index));\n\n        if (result != SKCodecResult.Success)\n            throw new InvalidDataException($\"Could not decode frame {index} of {codec.FrameCount}.\");\n    }\n\n    private static void RenderFrame(SKCodec codec, WriteableBitmap targetBitmap, int index, int priorIndex)\n    {\n        codec.GetFrameInfo(index, out var frameInfo);\n\n        var info = new SKImageInfo(codec.Info.Width, codec.Info.Height);\n        var decodeInfo = info.WithAlphaType(frameInfo.AlphaType);\n\n        using var frameBuffer = targetBitmap.Lock();\n\n        var result = codec.GetPixels(decodeInfo, frameBuffer.Address, new SKCodecOptions(index, priorIndex));\n\n        if (result != SKCodecResult.Success)\n            throw new InvalidDataException($\"Could not decode frame {index} of {codec.FrameCount}.\");\n    }\n\n    private static Stream GetStreamFromString(string str)\n    {\n        if (!Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out var res))\n        {\n            throw new InvalidCastException(\"The string provided can't be converted to URI.\");\n        }\n\n        return GetStreamFromUri(res);\n    }\n\n    private static Stream GetStreamFromUri(Uri uri)\n    {\n        var uriString = uri.OriginalString.Trim();\n\n        if (!uriString.StartsWith(\"resm\") && !uriString.StartsWith(\"avares\"))\n        {\n            // Local file\n            using var fs = new FileStream(uriString, FileMode.Open, FileAccess.Read);\n\n            // Copy to memory stream then return\n            var memoryStream = new MemoryStream();\n            fs.CopyTo(memoryStream);\n            memoryStream.Seek(0, SeekOrigin.Begin);\n\n            return memoryStream;\n        }\n\n        // Internal Avalonia resources\n        return AssetLoader.Open(uri);\n    }\n\n    public int GifFrameCount => _frameTimes.Count;\n\n    public PixelSize GifPixelSize { get; }\n\n    public void Dispose()\n    {\n        IsDisposed = true;\n        CurrentCts.Cancel();\n        _targetBitmap?.Dispose();\n        _codec?.Dispose();\n    }\n\n    public bool IsDisposed { get; private set; }\n\n    public WriteableBitmap? ProcessFrameTime(TimeSpan stopwatchElapsed)\n    {\n        if (!IterationCount.IsInfinite && _iterationCount > IterationCount.Value)\n        {\n            return null;\n        }\n\n        if (CurrentCts.IsCancellationRequested || _targetBitmap is null)\n        {\n            return null;\n        }\n\n        var elapsedTicks = stopwatchElapsed.Ticks;\n        var timeModulus = TimeSpan.FromTicks(elapsedTicks % _totalTime.Ticks);\n        var targetFrame = _frameTimes.FirstOrDefault(x => timeModulus < x);\n        var currentFrame = _frameTimes.IndexOf(targetFrame);\n        if (currentFrame == -1)\n            currentFrame = 0;\n\n        if (_currentFrameIndex == currentFrame)\n            return _targetBitmap;\n\n        _iterationCount = (uint)(elapsedTicks / _totalTime.Ticks);\n\n        return ProcessFrameIndex(currentFrame);\n    }\n\n    internal WriteableBitmap ProcessFrameIndex(int frameIndex)\n    {\n        if (_codec is null)\n            throw new InvalidOperationException(\"The codec is null.\");\n\n        if (_targetBitmap is null)\n            throw new InvalidOperationException(\"The target bitmap is null.\");\n\n        RenderFrame(_codec, _targetBitmap, frameIndex, _currentFrameIndex);\n        _currentFrameIndex = frameIndex;\n\n        return _targetBitmap;\n    }\n}\n"
  },
  {
    "path": "Build/AppEntitlements.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n    <dict>\n        <key>com.apple.security.cs.allow-jit</key>\n        <true/>\n    </dict>\n</plist>\n"
  },
  {
    "path": "Build/EmbeddedEntitlements.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n    <dict>\n        <key>com.apple.security.cs.allow-jit</key>\n        <true/>\n        <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n        <true/>\n        <key>com.apple.security.cs.disable-library-validation</key>\n        <true/>\n    </dict>\n</plist>\n"
  },
  {
    "path": "Build/_utils.sh",
    "content": "#!/bin/bash\n\nprint_hyperlink() {\n    local url=\"$1\"\n    local text=\"$2\"\n    \n    # macOS Terminal supports clickable links in the following format\n    printf \"\\033]8;;%s\\a%s\\033]8;;\\a\" \"$url\" \"$text\"\n}\n"
  },
  {
    "path": "Build/build_macos_app.sh",
    "content": "#!/bin/bash\n\noutput_dir=\"$(pwd)/out/osx-arm64/\"\napp_name=\"Stability Matrix.app\"\n\n. \"./_utils.sh\" > /dev/null 2>&1 || . \"${BASH_SOURCE%/*}/_utils.sh\"\n\n# Parse args\nwhile getopts v: flag\ndo\n    case \"${flag}\" in\n        v) \n          version=${OPTARG}\n          ;;\n        *) \n          echo \"Invalid option: -$OPTARG\" >&2\n          exit 2\n          ;;\n    esac\ndone\n\nshift $((OPTIND - 1))\necho $\"Passing extra args to msbuild: $@\"\n\nset -e\n\n# Build the app\ndotnet \\\nmsbuild \\\nStabilityMatrix.Avalonia \\\n-t:BundleApp \\\n-p:RuntimeIdentifier=osx-arm64 \\\n-p:UseAppHost=true \\\n-p:Configuration=Release \\\n-p:SelfContained=true \\\n-p:CFBundleName=\"Stability Matrix\" \\\n-p:CFBundleDisplayName=\"Stability Matrix\" \\\n-p:CFBundleVersion=\"$version\" \\\n-p:CFBundleShortVersionString=\"$version\" \\\n-p:PublishDir=\"${output_dir:?}/bin\" \\\n\"$@\"\n\ntarget_plist_path=\"${output_dir:?}/bin/${app_name:?}/Contents/Info.plist\"\n\necho \"> Checking Info.plist...\"\nfile \"${target_plist_path:?}\"\nplutil -lint \"${target_plist_path:?}\"\n\necho \"> Copying app to output...\"\n# Delete existing file\nrm -rf \"${output_dir:?}/${app_name:?}\"\n# Copy the app out of bin\ncp -r \"${output_dir:?}/bin/${app_name:?}\" \"${output_dir:?}/${app_name:?}\"\n\n# Print output location\necho \"[App Build Completed]\"\nprint_hyperlink \"file:///${output_dir:?}\" \"${output_dir:?}\"\nprint_hyperlink \"file:///${output_dir:?}/${app_name:?}\" \"${app_name:?}\"\necho \"\"\n"
  },
  {
    "path": "Build/codesign_embedded_macos.sh",
    "content": "#!/bin/sh\n\necho \"Signing file: $1\"\n\n# Setup keychain in CI\nif [ -n \"$CI\" ]; then\n    # Turn our base64-encoded certificate back to a regular .p12 file\n    \n    echo \"$MACOS_CERTIFICATE\" | base64 --decode -o certificate.p12\n    \n    # We need to create a new keychain, otherwise using the certificate will prompt\n    # with a UI dialog asking for the certificate password, which we can't\n    # use in a headless CI environment\n    \n    security create-keychain -p \"$MACOS_CI_KEYCHAIN_PWD\" build.keychain \n    security default-keychain -s build.keychain\n    security unlock-keychain -p \"$MACOS_CI_KEYCHAIN_PWD\" build.keychain\n    security import certificate.p12 -k build.keychain -P \"$MACOS_CERTIFICATE_PWD\" -T /usr/bin/codesign\n    security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k \"$MACOS_CI_KEYCHAIN_PWD\" build.keychain\nfi\n\n# Sign all files\nPARENT_PATH=$( cd \"$(dirname \"${BASH_SOURCE[0]}\")\" || return ; pwd -P )\nENTITLEMENTS=\"$PARENT_PATH/EmbeddedEntitlements.entitlements\"\n\necho \"Using entitlements file: $ENTITLEMENTS\"\n\n# App\nif [ \"$1\" == \"*.app\" ]; then\n  echo \"[INFO] Signing app contents\"\n  \n  find \"$1/Contents/MacOS/\"|while read fname; do\n      if [[ -f $fname ]]; then\n          echo \"[INFO] Signing $fname\"\n          codesign --force --timestamp -s \"$MACOS_CERTIFICATE_NAME\" --options=runtime --entitlements \"$ENTITLEMENTS\" \"$fname\"\n      fi\n  done\n  \n  echo \"[INFO] Signing app file\"\n  \n  codesign --force --timestamp -s \"$MACOS_CERTIFICATE_NAME\" --options=runtime --entitlements \"$ENTITLEMENTS\" \"$1\" -v\n# Directory\nelif [ -d \"$1\" ]; then\n  echo \"[INFO] Signing directory contents\"\n    \n  find \"$1\"|while read fname; do\n      if [[ -f $fname ]] && [[ ! $fname =~ /(*.(py|msg|enc))/ ]]; then\n          echo \"[INFO] Signing $fname\"\n          \n          codesign --force --timestamp -s \"$MACOS_CERTIFICATE_NAME\" --options=runtime --entitlements \"$ENTITLEMENTS\" \"$fname\"\n      fi\n  done\n# File\nelif [ -f \"$1\" ]; then\n  echo \"[INFO] Signing file\"\n  \n  codesign --force --timestamp -s \"$MACOS_CERTIFICATE_NAME\" --options=runtime --entitlements \"$ENTITLEMENTS\" \"$1\" -v\n# Not matched\nelse\n  echo \"[ERROR] Unknown file type\"\n  exit 1\nfi\n"
  },
  {
    "path": "Build/codesign_macos.sh",
    "content": "#!/bin/sh\n\necho \"Signing file: $1\"\n\n# Setup keychain in CI\nif [ -n \"$CI\" ]; then\n    # Turn our base64-encoded certificate back to a regular .p12 file\n    \n    echo \"$MACOS_CERTIFICATE\" | base64 --decode -o certificate.p12\n    \n    # We need to create a new keychain, otherwise using the certificate will prompt\n    # with a UI dialog asking for the certificate password, which we can't\n    # use in a headless CI environment\n    \n    security create-keychain -p \"$MACOS_CI_KEYCHAIN_PWD\" build.keychain \n    security default-keychain -s build.keychain\n    security unlock-keychain -p \"$MACOS_CI_KEYCHAIN_PWD\" build.keychain\n    security import certificate.p12 -k build.keychain -P \"$MACOS_CERTIFICATE_PWD\" -T /usr/bin/codesign\n    security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k \"$MACOS_CI_KEYCHAIN_PWD\" build.keychain\nfi\n\n# Sign all files\nPARENT_PATH=$( cd \"$(dirname \"${BASH_SOURCE[0]}\")\" || return ; pwd -P )\nENTITLEMENTS=\"$PARENT_PATH/AppEntitlements.entitlements\"\n\necho \"Using entitlements file: $ENTITLEMENTS\"\n\nfind \"$1/Contents/MacOS/\"|while read fname; do\n    if [[ -f $fname ]]; then\n        echo \"[INFO] Signing $fname\"\n        codesign --force --timestamp -s \"$MACOS_CERTIFICATE_NAME\" --options=runtime --entitlements \"$ENTITLEMENTS\" \"$fname\"\n    fi\ndone\n\necho \"[INFO] Signing app file\"\n\ncodesign --force --timestamp -s \"$MACOS_CERTIFICATE_NAME\" --options=runtime --entitlements \"$ENTITLEMENTS\" \"$1\" -v\n"
  },
  {
    "path": "Build/notarize_macos.sh",
    "content": "#!/bin/sh\n\necho \"Notarizing file: $1\"\n\n# Store the notarization credentials so that we can prevent a UI password dialog\n# from blocking the CI\n\necho \"Create keychain profile\"\nxcrun notarytool store-credentials \"notarytool-profile\" \\\n--apple-id \"$MACOS_NOTARIZATION_APPLE_ID\" \\\n--team-id \"$MACOS_NOTARIZATION_TEAM_ID\" \\\n--password \"$MACOS_NOTARIZATION_PWD\"\n\n# We can't notarize an app bundle directly, but we need to compress it as an archive.\n# Therefore, we create a zip file containing our app bundle, so that we can send it to the\n# notarization service\n\necho \"Creating temp notarization archive\"\nditto -c -k --keepParent \"$1\" \"notarization.zip\"\n\n# Here we send the notarization request to the Apple's Notarization service, waiting for the result.\n# This typically takes a few seconds inside a CI environment, but it might take more depending on the App\n# characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if\n# you're curious\n\necho \"Notarize app\"\nxcrun notarytool submit \"notarization.zip\" --keychain-profile \"notarytool-profile\" --wait\n\n# Finally, we need to \"attach the staple\" to our executable, which will allow our app to be\n# validated by macOS even when an internet connection is not available.\necho \"Attach staple\"\nxcrun stapler staple \"$1\"\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to Stability Matrix will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).\n\n## v2.15.6\n### Added\n- Added NVIDIA driver version warning when launching ComfyUI with CUDA 13.0 (cu130) and driver versions below 580.x\n- Added legacy Python warning when launching InvokeAI installations using Python 3.10.11\n- Added Tiled VAE Decode to the Inference video workflows - thanks to @NeuralFault!\n### Changed\n- Disabled update checking for legacy InvokeAI installations using Python 3.10.11\n- Hide rating stars in the Civitai browser page if no rating is available\n- Updated uv to v0.9.30\n- Updated PortableGit to v2.52.0.windows.1\n- Updated Sage/Triton/Nunchaku installers to use GitHub API to fetch latest releases\n- Updated ComfyUI installations and updates to automatically install ComfyUI Manager\n- Updated gfx110X Windows ROCm nightly index - thanks to @NeuralFault!\n- Updated ComfyUI-Zluda install to more closely match the author's intended installation method - thanks to @NeuralFault!\n- Updated Forge Classic installs/updates to use the upstream install script for better version compatibility with torch/sage/triton/nunchaku\n- Backslashes can now be escaped in Inference prompts via `\\\\`\n### Fixed\n- Fixed parsing of escape sequences in Inference such as `\\\\`\n- Fixed [#1546](https://github.com/LykosAI/StabilityMatrix/issues/1546), [#1541](https://github.com/LykosAI/StabilityMatrix/issues/1541) - \"No module named 'pkg_resources'\" error when installing Automatic1111/Forge/reForge packages\n- Fixed [#1545](https://github.com/LykosAI/StabilityMatrix/issues/1545), [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things\n- Fixed [#1529](https://github.com/LykosAI/StabilityMatrix/issues/1529) - \"Selected commit is null\" error when installing packages and rate limited by GitHub\n- Fixed [#1525](https://github.com/LykosAI/StabilityMatrix/issues/1525) - Crash after downloading a model\n- Fixed [#1523](https://github.com/LykosAI/StabilityMatrix/issues/1523), [#1499](https://github.com/LykosAI/StabilityMatrix/issues/1499), [#1494](https://github.com/LykosAI/StabilityMatrix/issues/1494) - Automatic1111 using old stable diffusion repo\n- Fixed [#1505](https://github.com/LykosAI/StabilityMatrix/issues/1505) - incorrect port argument for Wan2GP\n- Possibly fix [#1502](https://github.com/LykosAI/StabilityMatrix/issues/1502) - English fonts not displaying correctly on Linux in Chinese environments\n- Fixed [#1476](https://github.com/LykosAI/StabilityMatrix/issues/1476) - Incorrect shared output folder for Forge Classic/Neo\n- Fixed [#1466](https://github.com/LykosAI/StabilityMatrix/issues/1466) - crash after moving portable install\n- Fixed [#1445](https://github.com/LykosAI/StabilityMatrix/issues/1445) - Linux app updates not actually updating - thanks to @NeuralFault!\n### Supporters\n#### 🌟 Visionaries\nTo our stellar Visionaries: **Waterclouds**, **JungleDragon**, **bluepopsicle**, **Bob S**, and **whudunit**! Your generosity keeps this project thriving and gives us the confidence to tackle the big challenges. Thank you for being the foundation that makes it all possible!\n#### 🚀 Pioneers\nShoutout to our incredible Pioneer crew for keeping the momentum going! Thank you to: **Szir777**, **Noah M**, **[USA]TechDude**, **Thom**, **SeraphOfSalem**, **Desert Viber**, **Adam**, **Droolguy**, **ACTUALLY_the_Real_Willem_Dafoe**, **takyamtom**, **robek**, **Ghislain G**, **Phil R**, **Tundra Everquill**, and a warm welcome to our newest Pioneers: **Andrew B**, **snotty**, **Miguel A**, **SinthCore**, and **Ahmed S**!\n\n## v2.15.5\n### Added\n- Added new package - [Wan2GP](https://github.com/deepbeepmeep/Wan2GP)\n- Added [Stable Diffusion WebUI Forge - Neo](https://github.com/Haoming02/sd-webui-forge-classic/tree/neo) as a separate package for convenience\n- Added Tiled VAE decoding as an Inference addon thanks to @NeuralFault!\n- Added togglable `--uv` argument to the SD.Next launch options\n### Changed\n- Moved the original Stable Diffusion WebUI Forge to the \"Legacy\" packages tab due to inactivity\n- Updated to cu130 torch index for ComfyUI installs with Nvidia GPUs\n- Consolidated and fixed AMD GPU architecture detection\n- Updated SageAttention installer to latest v2.2.0-windows.post4 version\n- Updated torch index for Forge-based UIs to rocm6.4 / cu128 depending on GPU - thanks to @NeuralFault!\n### Fixed\n- Fixed [#1450](https://github.com/LykosAI/StabilityMatrix/issues/1450) - Older SD.Next not launching due to forced `--uv` argument\n- Fixed duplicate custom node installations when installing workflows from the Workflow Browser - thanks again to @NeuralFault!\n#### 🌟 Visionaries\nTo our incredible Visionaries: **Waterclouds**, **JungleDragon**, **bluepopsicle**, **Bob S**, and **whudunit**! Your generosity drives this project forward and helps us turn ideas into reality. Thank you for being such a vital part of Stability Matrix!\n#### 🚀 Pioneers\nA massive thank you to our Pioneer crew! Your support keeps the lights on and the updates flowing. Shoutout to: **Szir777**, **Noah M**, **[USA]TechDude**, **Thom**, **SeraphOfSalem**, **Desert Viber**, **Adam**, **Droolguy**, **ACTUALLY_the_Real_Willem_Dafoe**, **takyamtom**, **robek**, **Phil R**, **Tundra Everquill**, **TheTekknician**, and a warm welcome to our new Pioneers, **Benjamin M** and **Ghislain G**!\n\n## v2.15.4\n### Changed\n- Updated Early Access indicators in the Civitai Details page to be more visible\n- Updated error message when attempting to download a website-generation-only model from Civitai\n- Updated nunchaku installer to 1.0.2\n- Updated Package Import dialog to have Python version selector\n### Fixed\n- Fixed [#1435](https://github.com/LykosAI/StabilityMatrix/issues/1435) - SwarmUI not launching due to missing dotnet\n- Fixed various install and Inference issues with ComfyUI-Zluda - big thanks to @neural_fault for the PRs!\n- Fixed sageattention version getting out of sync after torch updates in ComfyUI\n- Potentially fixed issue where uv-managed Python versions would not appear in the version selector\n### Supporters\n#### 🌟 Visionaries\nOur heartfelt thanks to the driving force behind our progress, our Visionaries: **Waterclouds**, **JungleDragon**, **bluepopsicle**, **Bob S**, and **whudunit**! Your incredible support is the fuel that powers our development, allowing us to tackle bugs and push forward with confidence.\n#### 🚀 Pioneers\nA huge shoutout to our amazing Pioneers, who keep the momentum going strong! Thank you for being our trusted crew on this journey: **Szir777**, **Noah M**, **USATechDude**, **Thom**, **SeraphOfSalem**, **Desert Viber**, **Tundra Everquill**, **Adam**, **Droolguy**, **Philip R.**, **ACTUALLY_the_Real_Willem_Dafoe**, **takyamtom**, and **robek**!\n\n## v2.15.3\n### Changed\n- Updated fallback rocm index for InvokeAI to rocm6.3\n- Updated SwarmUI to launch via the launch script for better compatibility\n### Fixed\n- Fixed cuDNN frontend error on ComfyUI-Zluda startup (thanks @neural_fault!)\n- Maybe finally actually fixed threading issue with the Python Packages dialog search box for real this time? (may fix [#1392](https://github.com/LykosAI/StabilityMatrix/issues/1392))\n- Fixed potential install failures when moving duplicate files into shared model folders (may fix [#1393](https://github.com/LykosAI/StabilityMatrix/issues/1393))\n- Fixed potential threading issues with the Inference image gallery (may fix [#1408](https://github.com/LykosAI/StabilityMatrix/issues/1408))\n- Fixed [#1424](https://github.com/LykosAI/StabilityMatrix/issues/1424) - Civitai account 401 error when connecting accounts, updated for new API changes\n### Supporters\n#### 🌟 Visionaries\nOur deepest gratitude to our Visionaries for their foundational support: **Waterclouds**, **JungleDragon**, **bluepopsicle**, **Bob S**, and **whudunit**! Your commitment allows us to focus on the essential work of squashing bugs and improving stability, ensuring a rock-solid experience for everyone.\n#### 🚀 Pioneers\nA huge thank you to our incredible Pioneers for keeping the project on track! Your support is vital for these important refinement updates. Thank you to **Szir777**, **Noah M**, **USATechDude**, **Thom**, **SeraphOfSalem**, **Desert Viber**, **Tundra Everquill**, **Adam**, **Droolguy**, **Philip R.**, **ACTUALLY_the_Real_Willem_Dafoe**, **takyamtom**, and a warm welcome to our newest Pioneer, **robek**!\n\n## v2.15.2\n### Changed\n- Updated Avalonia to 11.3.7\n### Fixed\n- Fixed [#1409](https://github.com/LykosAI/StabilityMatrix/issues/1409) - incorrect triton version installed during FramePack install on non-Windows systems\n- Fixed [#1410](https://github.com/LykosAI/StabilityMatrix/issues/1410) - crash when resizing Civitai model details page\n- Fixed [#1417](https://github.com/LykosAI/StabilityMatrix/issues/1417), [#1419](https://github.com/LykosAI/StabilityMatrix/issues/1419) - unable to connect Inference to ComfyUI after updating to latest ComfyUI\n- Fixed missing dependencies for ComfyUI API nodes\n### Supporters\n#### 🌟 Visionaries\nHuge thanks to our incredible Visionaries: **Waterclouds**, **JungleDragon**, **bluepopsicle**, **Bob S**, **Ibixat**, and **whudunit**! You’re the reason Stability Matrix keeps pushing forward. Your support lights the path and keeps the dream alive.\n#### 🚀 Pioneers\nShoutout to our amazing Pioneer crew: **Szir777**, **Tigon**, **Noah M**, **USATechDude**, **Thom**, **SeraphOfSalem**, **Desert Viber**, **Tundra Everquill**, **Adam**, **Droolguy**, **Philip R.**, **ACTUALLY_the_Real_Willem_Dafoe**, and **takyamtom**! You help keep the gears turning and the magic flowing.\n\n## v2.15.1\n### Changed\n- Upgraded ComfyUI-Zluda and Stable Diffusion WebUI AMDGPU Forge packages to install and use HIP SDK 6.4.2\n- Changed ComfyUI torch index from `cu129` back to `cu128` for better compatibility with custom nodes\n- Updated SageAttention installer to install v2.2.0-windows.post3\n- Updated Nunchaku installer to install v1.0.1\n- Updated Windows ROCm ComfyUI installs to use recommended environment variables by default\n### Fixed\n- Fixed [#1372](https://github.com/LykosAI/StabilityMatrix/issues/1372), [#1399](https://github.com/LykosAI/StabilityMatrix/issues/1399) - LiteAsyncException upon starting Stability Matrix v2.15.0\n- Fixed [#1391](https://github.com/LykosAI/StabilityMatrix/issues/1391) - \"Failed to parse\" error when upgrading pip packages with extra index url\n- Fixed [#1401](https://github.com/LykosAI/StabilityMatrix/issues/1401) - \"Python <version> was not found and/or failed to install\" errors when path contains special characters\n- Fixed [#1403](https://github.com/LykosAI/StabilityMatrix/issues/1403) - Checkpoint Manager filters not being saved correctly\n- Fixed [#1411](https://github.com/LykosAI/StabilityMatrix/issues/1411) - SD.Next installs not using correct torch version\n- Fixed \"cannot access local variable 'job' where it is not associated with a value\" error when running jobs in AI Toolkit\n- Fixed Civitai browser not always returning at least 30 results when possible on initial search\n- Fixed model browser crashing when downloading a file with invalid characters in the name\n- Fixed model browser crashing when no author exists for a model\n### Supporters\n#### 🌟 Visionaries\nTo our guiding stars, the Visionaries! Thank you **Waterclouds**, **JungleDragon**, **bluepopsicle**, **Bob S**, **Ibixat**, and **whudunit**! While this release is focused on fixes and stability, your foundational support is what empowers us to build a reliable and robust platform for everyone.\n#### 🚀 Pioneers\nA huge round of applause for our fantastic Pioneers! Your steady support helps us smooth out the rough edges and deliver a better experience with every update. Our deepest thanks to: **Szir777**, **Tigon**, **Noah M**, **USATechDude**, **Thom**, **SeraphOfSalem**, **Desert Viber**, **Tundra Everquill**, **Adam**, and **Droolguy**. We're also thrilled to welcome our newest Pioneers to the crew: **Philip R.**, **ACTUALLY_the_Real_Willem_Dafoe**, and **takyamtom**!\n\n## v2.15.0\n### Added\n- Added new package - [AI Toolkit](https://github.com/ostris/ai-toolkit/)\n- Added new package - [FramePack](https://github.com/lllyasviel/FramePack)\n- Added new package - [FramePack Studio](https://github.com/colinurbs/FramePack-Studio)\n- Added Python Version selector for all new package installs\n- Added the ability to rename packages\n- Added support for authenticated model downloads in the HuggingFace model browser. Visit Settings → Accounts to add your HuggingFace token.\n- Added support for dragging-and-dropping Civitai-generated images into Inference to load metadata\n- Added the ability to search by pasting an entire Civitai model URL into the search bar in the Civitai model browser\n- Added \"Clear Pip Cache\" and \"Clear uv Cache\" commands to the Settings -> Embedded Python section\n- Added settings to disable base models from appearing in the Checkpoint Manager and Civitai Model Browser base model selectors\n- Added Inference \"Favorite Dimensions\" quick selector - editable in Settings → Inference, or click the 💾 button inside the dropdown\n- Added setting for Inference dimension step change - the value the dimensions increase or decrease by when using the step buttons or scroll wheel in Inference\n- Added \"Install Nunchaku\" option to the ComfyUI Package Commands menu\n- Added \"Select All\" button to the Installed Extensions page\n- Added experimental ROCm pytorch install for ComfyUI (non-Zluda) on Windows - requires a compatible AMD GPU\n- Added base model type labels (SD1.5, SDXL, Flux, etc.) to Inference model selection boxes\n- Added UNET shared folder link for SD.Next\n- Added Manual Install button for installing Package extensions that aren't in the indexes\n- Added Next and Previous buttons to the Civitai details page to navigate between results\n- Added Negative Rejection Steering (NRS) by @reithan to Inference\n- Added Wan 2.2 models to the HuggingFace tab of the model browser\n- Added Tiled Encode/Decode options to FaceDetailer in Inference\n- Added Ukrainian translation thanks to @r0ddty!\n- Added Czech translation thanks to @PEKArt!\n### Changed\n🌟 Civitai Model Details: A Grand Reimagining! 🌟\n- No more peering through a tiny window! Introducing a massive overhaul of the Civitai Model Details page, transforming it from a cramped dialog into a spacious, feature-rich hub for all your model exploration needs.\n- We've listened to your howls for more, and now you can dive deep into every aspect of your favorite models with unprecedented clarity and control:\n    - Expansive View: The new full-page layout means all essential information, descriptions, and previews are laid out beautifully, banishing the old, restrictive dialog forever.\n    - Rich Details at a Glance: Author, base model, last updated, SHA hashes, file name overrides/patterns – everything you need, perfectly organized and always accessible.\n    - Overhauled Image Viewer: Enjoy a sleek, modern image viewer that includes Civitai metadata and supports zooming, panning, and full-screen viewing. No more squinting at tiny thumbnails!\n    - Integrated Inference Options: For supported models, adjust sampler, scheduler, steps, CFG Scale, width, and height directly from the details page, streamlining your workflow like never before!\n----\n- Updated all Python version management, virtual environment creation, and pip installs to use `uv` for improved reliability, compatibility, and speed\n- You can now select release versions when installing ComfyUI\n- You can no longer select branches when installing InvokeAI\n- Updated InvokeAI install to use the intended install method (resolves [#1329](https://github.com/LykosAI/StabilityMatrix/issues/1329))\n- Updated ComfyUI installs for AMD users on Linux to use the latest rocm6.3 torch index\n- Updated ComfyUI-Zluda installs to use the newer install-n method (fixes [#1347](https://github.com/LykosAI/StabilityMatrix/issues/1347))\n- Removed disclaimer from reForge since the author is now active again\n- Updated git operations to better avoid conflicts\n- Updated Japanese translation\n- Civitai model browser image loading now uses dynamic resizing for better performance and a smoother scrolling experience\n- Undo ComfyUI process tracking changes for now due to causing more issues than it solved\n- Updated GPU parsing fallback on Linux systems to use the method provided by @irql-notlessorequal\n- New installs of ComfyUI, SD.Next, and InvokeAI will now use Python 3.12, unless otherwise specified in the Advanced Options during installation\n- New installs of all other packages will now use Python 3.10.18, unless otherwise specified in the Advanced Options during installation\n- Updated ComfyUI installs for AMD users on Linux to use the latest rocm6.4 torch index\n- Updated package delete confirmation dialog\n### Fixed\n- Fixed an error when packages and other processes exit before process tracking on windows can initialize\n- Fixed \"none\" appearing in wildcard field when using Face Detailer in Inference\n- Fixed [#1254](https://github.com/LykosAI/StabilityMatrix/issues/1254) - Unable to scroll samplers in Inference\n- Fixed [#1294](https://github.com/LykosAI/StabilityMatrix/issues/1294) - Improper sorting of output folders in Output Browser\n- Fixed [#1300](https://github.com/LykosAI/StabilityMatrix/issues/1300) - Git errors when installing Extension Packs\n- Fixed [#1317](https://github.com/LykosAI/StabilityMatrix/issues/1317) - Inference missing GGUF text encoders\n- Fixed [#1324](https://github.com/LykosAI/StabilityMatrix/issues/1324) - Window height slightly increasing every launch\n- Fixed [#1357](https://github.com/LykosAI/StabilityMatrix/issues/1357) - Case insensitivity causing duplicate key exceptions on non-Windows systems\n- Fixed [#1360](https://github.com/LykosAI/StabilityMatrix/issues/1360) - A1111 install not using correct torch for 5000-series GPUs\n- Fixed [#1361](https://github.com/LykosAI/StabilityMatrix/issues/1361) - numpy and other Forge startup\n- Fixed [#1365](https://github.com/LykosAI/StabilityMatrix/issues/1365) - Output folder list not updating when Refresh button clicked\n### Supporters\n#### 🌟 Visionaries\nTo our incredible Visionaries, the architects of our ambition: Your profound support is the powerhouse behind this massive v2.15.0 release. You don't just light the path; you fuel the entire journey, allowing us to build bigger, move faster, and turn bold ideas into reality. Our deepest gratitude to: **Waterclouds**, **Corey T**, **bluepopsicle**, **Bob S**, **Ibixat**, **whudunit**, and **TheTekknician**! We are immensely grateful for your trust and partnership in shaping the future of Stability Matrix. Thank you for everything!\n#### 🚀 Pioneers\nA heartfelt salute to our trailblazing Pioneers! Your consistent support helps us navigate the development landscape, ensuring we stay on the right track and can explore new frontiers. A huge thanks to: **tankfox**, **Mr. Unknown**, **Szir777**, **Tigon**, **Noah M**, **USATechDude**, **Thom**, **SeraphOfSalem**, and a special welcome to our newest Pioneers - **Desert Viber**, **Tundra Everquill**, **Adam**, and **Droolguy**! Thank you for being the vanguard of our community!\n \n## v2.15.0-pre.2\n### Added\n- Added new package - [AI Toolkit](https://github.com/ostris/ai-toolkit/)\n- Added Manual Install button for installing Package extensions that aren't in the indexes\n- Added Next and Previous buttons to the Civitai details page to navigate between results\n- Added Negative Rejection Steering (NRS) by @reithan to Inference\n- Added Czech translation thanks to @PEKArt!\n- Added Wan 2.2 models to the HuggingFace tab of the model browser\n- Added Tiled Encode/Decode options to FaceDetailer in Inference\n### Changed\n- Brought back the \"size remaining after download\" tooltip in the new Civitai details page\n- Updated ComfyUI installs for AMD users on Linux to use the latest rocm6.4 torch index\n- Updated package delete confirmation dialog\n### Fixed\n- Fixed Inference custom step (e.g. HiresFix) Samplers potentially sharing state with other card UIs like model browser.\n- Fixed extension manager failing to install extensions due to incorrect clone directory\n- Fixed duplicate Python versions appearing in the Advanced Options when installing a package\n- Fixed an error when packages and other processes exit before process tracking on windows can initialize\n- Fixed \"none\" appearing in wildcard field when using Face Detailer in Inference\n- Fixed [#1254](https://github.com/LykosAI/StabilityMatrix/issues/1254) - Unable to scroll samplers in Inference\n- Fixed [#1294](https://github.com/LykosAI/StabilityMatrix/issues/1294) - Improper sorting of output folders in Output Browser\n- Fixed [#1300](https://github.com/LykosAI/StabilityMatrix/issues/1300) - Git errors when installing Extension Packs\n- Fixed [#1317](https://github.com/LykosAI/StabilityMatrix/issues/1317) - Inference missing GGUF text encoders\n- Fixed [#1324](https://github.com/LykosAI/StabilityMatrix/issues/1324) - Window height slightly increasing every launch\n- Fixed [#1360](https://github.com/LykosAI/StabilityMatrix/issues/1360) - A1111 install not using correct torch for 5000-series GPUs\n- Fixed [#1361](https://github.com/LykosAI/StabilityMatrix/issues/1361) - numpy and other Forge startup\n### Supporters\n#### 🌟 Visionaries\nA huge thank-you to our incredible Visionary-tier supporters: **Waterclouds**, **Corey T**, **bluepopsicle**, **Bob S**, **Ibixat**, **whudunit**, and **Akiro_Senkai**! Your continued support lights the way for Stability Matrix and helps us keep building features like these. We couldn’t do it without you.\n\n## v2.15.0-pre.1\n### Added\n- Added settings to disable base models from appearing in the Checkpoint Manager and Civitai Model Browser base model selectors\n- Added Inference \"Favorite Dimensions\" quick selector - editable in Settings → Inference, or click the 💾 button inside the dropdown\n- Added setting for Inference dimension step change - the value the dimensions increase or decrease by when using the step buttons or scroll wheel in Inference\n- Added \"Install Nunchaku\" option to the ComfyUI Package Commands menu\n- Added \"Select All\" button to the Installed Extensions page\n- Added experimental ROCm pytorch install for ComfyUI (non-Zluda) on Windows - requires a compatible AMD GPU\n- Added base model type labels (SD1.5, SDXL, Flux, etc.) to Inference model selection boxes\n- Added UNET shared folder link for SD.Next\n- Added Ukrainian translation thanks to @r0ddty!\n### Changed\n🌟 Civitai Model Details: A Grand Reimagining! 🌟\n  - No more peering through a tiny window! Introducing a massive overhaul of the Civitai Model Details page, transforming it from a cramped dialog into a spacious, feature-rich hub for all your model exploration needs.\n  - We've listened to your howls for more, and now you can dive deep into every aspect of your favorite models with unprecedented clarity and control:\n    - Expansive View: The new full-page layout means all essential information, descriptions, and previews are laid out beautifully, banishing the old, restrictive dialog forever.\n    - Rich Details at a Glance: Author, base model, last updated, SHA hashes, file name overrides/patterns – everything you need, perfectly organized and always accessible.\n    - Overhauled Image Viewer: Enjoy a sleek, modern image viewer that includes Civitai metadata and supports zooming, panning, and full-screen viewing. No more squinting at tiny thumbnails!\n    - Integrated Inference Options: For supported models, adjust sampler, scheduler, steps, CFG Scale, width, and height directly from the details page, streamlining your workflow like never before!\n----\n- You can now select release versions when installing ComfyUI\n- You can no longer select branches when installing InvokeAI\n- Updated InvokeAI install to use pinned torch index from release tag\n- Updated ComfyUI installs for AMD users on Linux to use the latest rocm6.3 torch index\n- Updated ComfyUI-Zluda installs to use the newer install-n method (fixes [#1347](https://github.com/LykosAI/StabilityMatrix/issues/1347))\n- Updated uv to 0.8.4\n- Removed disclaimer from reForge since the author is now active again\n- Updated git operations to better avoid conflicts\n- Updated Japanese translation\n- Undo ComfyUI process tracking changes for now due to causing more issues than it solved\n- Updated GPU parsing fallback on Linux systems to use the method provided by @irql-notlessorequal\n### Fixed\n- Fixed Civitai-generated image parsing in Inference\n- Fixed some first-time setup crashes from missing prerequisites\n- Fixed one-click installer not using default preferred Python version\n- Fixed updating from old installs of InvokeAI using old frontend\n- Fixed [#1357](https://github.com/LykosAI/StabilityMatrix/issues/1357) - Case insensitivity causing duplicate key exceptions on non-Windows systems\n### Supporters\n#### 🌟 Visionaries\nTo our brilliant Visionary-tier Patrons: **Waterclouds**, **Corey T**, **bluepopsicle**, **Bob S**, **Ibixat**, and **whudunit** — your support is the spark that keeps Stability Matrix blazing forward. Thanks to you, we can explore bolder features, tackle complex challenges, and keep making the impossible feel effortless. Thank you all so very much! 🚀\n\n## v2.15.0-dev.2\n### Added\n- Added new package - [FramePack](https://github.com/lllyasviel/FramePack)\n- Added new package - [FramePack Studio](https://github.com/colinurbs/FramePack-Studio)\n- Added support for authenticated model downloads in the HuggingFace model browser. Visit Settings → Accounts to add your HuggingFace token.\n- Added support for dragging-and-dropping Civitai-generated images into Inference to load metadata\n- Added the ability to search by pasting an entire Civitai model URL into the search bar in the Civitai model browser (when the Civitai API gets fixed)\n- Added \"Clear Pip Cache\" and \"Clear uv Cache\" commands to the Settings -> Embedded Python section\n### Changed\n- Civitai model browser image loading now uses dynamic resizing for better performance and a smoother scrolling experience\n- Detailed notifications for Civitai model browser api errors\n- The main sidebar now remembers whether it was collapsed or expanded between restarts\n- Updated pre-selected download locations for certain model types in the Civitai model browser\n- Updated uv to 0.7.19\n- Changed InvokeAI update process to no longer clone the repo\n### Fixed\n- Fixed missing .NET 8 dependency for SwarmUI installs in certain cases\n- Fixed [#1291](https://github.com/LykosAI/StabilityMatrix/issues/1291) - Certain GPUs not being detected on Linux\n- Fixed [#1284](https://github.com/LykosAI/StabilityMatrix/issues/1284) - Output browser not ignoring InvokeAI thumbnails folders\n- Fixed [#1305](https://github.com/LykosAI/StabilityMatrix/issues/1305) - FluxGym installing incorrect packages for Blackwell GPUs\n- Fixed [#1316](https://github.com/LykosAI/StabilityMatrix/issues/1316) - Errors when installing Triton & SageAttention\n- Fixed \"directory is not empty\" error when updating packages with symlinks\n- Fixed missing base model types in the Checkpoint Manager & Civitai Model Browser\n### Supporters\n#### 🌟 Visionaries\nA huge thank you to our amazing Visionary-tier Patrons: **Waterclouds**, **Corey T**, **bluepopsicle**, **Bob S**, **Ibixat**, and our newest Visionary, **whudunit**! 🚀 Your generous support enables Stability Matrix to grow faster and tackle ambitious new ideas. You're truly making all the magic happen!\n\n## v2.15.0-dev.1\n### Added\n- Added Python Version selector for all new package installs\n- Added the ability to rename packages\n### Changed\n- Updated all Python version management, virtual environment creation, and pip installs to use `uv` for improved reliability, compatibility, and speed\n- The Civitai model browser Download Location selector will now remember the last location used based on the model type\n- New installs of ComfyUI, SD.Next, and InvokeAI will now use Python 3.12.10, unless otherwise specified in the Advanced Options during installation\n- New installs of all other packages will now use Python 3.10.17, unless otherwise specified in the Advanced Options during installation\n### Supporters\n#### 🌟 Visionaries\nA massive thank you to our esteemed Visionary-tier Patrons: **Waterclouds**, **bluepopsicle**, **Bob S**, **Ibixat**, and **Corey T**! Your exceptional commitment propels Stability Matrix to new heights and allows us to push the boundaries of innovation. We're incredibly grateful for your foundational support! 🚀\n\n## v2.14.3\n### Added\n- Added the ability to search by pasting an entire Civitai model URL into the search bar in the Civitai model browser\n### Changed\n- The main sidebar now remembers whether it was collapsed or expanded between restarts.\n- Inference is now able to load image metadata from Civitai generated images via drag & drop\n- Updated process tracking for ComfyUI to help mitigate restart issues when using Comfy Manager\n- Updated pre-selected download locations for certain model types in the Civitai model browser\n- Updated nodejs to v20.19.3 to support newer InvokeAI versions\n### Fixed\n- Fixed missing .NET 8 dependency for SwarmUI installs in certain cases\n- Fixed ComfyUI-Zluda not being recognized as a valid Comfy install for the workflow browser\n- Fixed [#1291](https://github.com/LykosAI/StabilityMatrix/issues/1291) - Certain GPUs not being detected on Linux\n- Fixed [#1284](https://github.com/LykosAI/StabilityMatrix/issues/1284) - Output browser not ignoring InvokeAI thumbnails folders\n- Fixed [#1301](https://github.com/LykosAI/StabilityMatrix/issues/1301) - Error when installing kohya_ss\n- Fixed [#1305](https://github.com/LykosAI/StabilityMatrix/issues/1305) - FluxGym installing incorrect packages for Blackwell GPUs\n- Fixed [#1316](https://github.com/LykosAI/StabilityMatrix/issues/1316) - Errors when installing Triton & SageAttention\n- Fixed \"directory is not empty\" error when updating packages with symlinks\n- Fixed missing base model types in the Checkpoint Manager & Civitai Model Browser\n### Supporters\n#### 🌟 Visionaries\nBig heartfelt thanks to our stellar Visionary-tier Patrons: **Waterclouds**, **Corey T**, **bluepopsicle**, **Bob S**, **Ibixat**, and **whudunit**! 🌟 Your extraordinary generosity continues to fuel Stability Matrix’s journey toward innovation and excellence. We appreciate you immensely!\n#### 🚀 Pioneers\nMassive thanks to our fantastic Pioneer-tier Patrons: **tankfox**, **Mr. Unknown**, **Szir777**, **Tigon**, **Noah M**, **USATechDude**, **Thom**, and **SeraphOfSalem**! Your unwavering support keeps our community thriving and inspires us to push even further. You’re all awesome!\n\n## v2.14.2\n### Changed\n- Changed Nvidia GPU detection to use compute capability level instead of the GPU name for certain feature gates / torch indexes\n### Fixed\n- Fixed [#1266](https://github.com/LykosAI/StabilityMatrix/issues/1266) - crash when moving or deleting Lora models in the Checkpoint Manager\n- Fixed [#1268](https://github.com/LykosAI/StabilityMatrix/issues/1268) - wrong torch index used for Nvidia 1000-series GPUs and older\n- Fixed [#1269](https://github.com/LykosAI/StabilityMatrix/issues/1269), [#1257](https://github.com/LykosAI/StabilityMatrix/issues/1257), [#1234](https://github.com/LykosAI/StabilityMatrix/issues/1234) - \"no such file or directory\" errors when updating certain packages after folder migration\n- Fixed [#1274](https://github.com/LykosAI/StabilityMatrix/issues/1274), [#1276](https://github.com/LykosAI/StabilityMatrix/issues/1276) - incorrect torch installed when updating to InvokeAI v5.12+\n- Fixed missing shared folder links for SwarmUI's diffusion_models and clip folders\n### Supporters\n#### 🌟 Visionaries\nOur deepest gratitude to the brilliant Visionary-tier Patrons: **Waterclouds**, **bluepopsicle**, **Bob S**, **Ibixat**, and **Corey T**! Your incredible backing is instrumental in shaping the future of Stability Matrix and empowering us to deliver cutting-edge features. Thank you for believing in our vision! 🙏\n#### 🚀 Pioneers\nA huge shout-out to our fantastic Pioneer-tier Patrons: **Mr. Unknown**, **tankfox**, **Szir777**, **Noah M**, **USATechDude**, **Thom**, **TheTekknician**, and **SeraphOfSalem**! Your consistent support and active engagement are vital to our community's growth and our ongoing development efforts. You truly make a difference! ✨\n\n## v2.14.1\n### Changed\n- Updated Inference Extra Networks (Lora / LyCORIS) base model filtering to consider SDXL variations (e.g., Noob AI / Illustrious) as compatible, unrecognized models or models with no base model will be considered compatible.\n- Changed hotkey for Inference prompt weight adjustment to (`⌘+Up`/`⌘+Down`) on macOS\n- Improved style consistency of Inference Prompt action buttons on top right\n- (Internal) Improved log console formatting & colorization for development\n### Fixed\n- Fixed Inference hotkey weight adjustment multi-line behavior, now works as expected like the first line.\n- Fixed updates to versions with commit hash version parts not being recognized when the current version has no commit hash part.\n- Fixed Inference Extra Networks card not updating with newly added model files.\n- Fixed incorrect ROCmLibs being installed for RX 6800/6800XT users of Comfy-Zluda or AMDGPU-Forge\n- Fixed missing text when missing localized versions for Italian and Chinese languages\n- Fixed Python Packages dialog errors and potentially other issues due to concurrent OnLoaded events\n### Supporters\n#### 🌟 Visionaries\nBig cheers to our incredible Visionary-tier Patrons: **bluepopsicle**, **Bob S**, **Ibixat**, **Waterclouds**, and **Corey T**! 🚀 Your amazing support lets us dream bigger and reach further every single month. Thanks for being the driving force behind Stability Matrix - we genuinely couldn't do it without you!\n#### 🚀 Pioneers\nHuge thanks to our fantastic Pioneer-tier Patrons: **tankfox**, **Mr. Unknown**, **Szir777**, **Tigon**, and **Noah M**! Special shoutout and welcome back to **TheTekknician**, and a warm welcome aboard to our newest Pioneers: **USATechDude**, **SeraphOfSalem**, and **Thom**! ✨ Your continued support keeps our community vibrant and pushes us to keep creating. You all rock!\n\n## v2.14.0\n### Added\n#### New Packages & Integrations\n- Added new package - [Stable Diffusion WebUI AMDGPU Forge](https://github.com/lshqqytiger/stable-diffusion-webui-amdgpu-forge)\n- Added new package - [Stable Diffusion WebUI Forge - Classic](https://github.com/Haoming02/sd-webui-forge-classic)\n- Added new Package Command (in the 3-dots menu) for installing Triton & SageAttention in ComfyUI\n#### Inference Features\n- Added Prompt Amplifier to Inference - click the magic wand 🪄 in the prompt editor to expand and enrich your ideas. Tailor the vibe with the ‘Feel’ selector and watch as your generations come to life with extra detail, coherence, and flair!\n- Added support for HiDream in Inference - see [ComfyUI Examples](https://comfyanonymous.github.io/ComfyUI_examples/hidream/) for more details\n- Added toggle for filtering Inference Extra Networks by base model\n- Added Rescale CFG addon to Inference\n- Added Swap Dimensions button between the width/height input in Inference\n- Added Ctrl+Tab/Ctrl+Shift+Tab shortcuts for navigating between Inference tabs\n- Added Align Your Steps scheduler to Inference\n- Added wildcards to Inference prompts, e.g. `{blue|green|red}` will randomly select one of the colors\n- Added Wan 2.1 Text to Video and Text to Image project types for Inference\n- Added new autocomplete tag source to Inference - [Danbooru/e621 merged tags](https://civitai.com/models/950325?modelVersionId=1419692)\n- Added Abstract Syntax Tree (AST) parsing for Inference prompts. This provides a more robust internal understanding of prompt structure, paving the way for future enhancements.\n- Added hotkey (`Ctrl+Up`/`Ctrl+Down`) in Inference prompt editors to adjust the weight emphasis of the token under the caret or the currently selected text.\n  - This automatically wraps the token/selection in parentheses `()` if it's not already weighted.\n  - It modifies existing weights within parentheses or adds weights if none exist (e.g. `(word:1.1)`).\n  - Handles selection spanning multiple tokens intelligently.\n- Added Plasma Noise addon to Inference for text to image workflows\n#### Model Management & Discovery\n- Added Accelerated Model Discovery (Beta) (⚡ icon in Civitai Browser) for Insider+ supporters. Utilizes an optimized connection for dramatically faster, more responsive browsing of online model repositories.\n- Added OpenModelDB tab to the Model Browser\n- Added Wan 2.1 files to the HuggingFace model browser\n#### User Interface & Experience (UI/UX)\n- Added Undo/Redo commands to text editor context menus\n#### Internal / Developer Changes\n- (Internal) Introduced unified strategy pattern (`IConfigSharingStrategy`) to for handling different config file formats (JSON, YAML, FDS).\n  - Added support for configuring nested paths in JSON and YAML files (e.g. `paths.models.vae`) via dot-notation in `SharedFolderLayoutRule.ConfigDocumentPaths`.\n  - Packages can now use the `SharedFolderLayout` property to define a `ConfigFileType` and `ConfigSharingOptions` (like `RootKey`), without needing to implement custom configuration logic.\n\n### Changed\n#### Inference Features\n- Improved the quality of Inference inpainting by upgrading the workflow behind the scenes. The workflow remains the same for you — just better results!\n- FaceDetailers in Inference will now inherit the primary sampler/scheduler/etc. by default. You can still manually set these by enabling the options via the ⚙️ button on the FaceDetailer card\n- Slightly rearranged the FaceDetailer card layout due to the above change\n- Inference \"Extra Networks\" selector now filters extra networks based on the selected base model\n- Merged Inference GGUF workflows into the UNet model loader option (no longer need to choose GGUF separately)\n#### Model Management & Discovery\n- Changed the names of some of the shared model folders to better reflect their contents\n- Improved Checkpoint Manager memory usage (thanks to @FireGeek for the profiling assistance!)\n- Performance optimizations for Checkpoint Manager (progress indicators now fully uses Compiled Bindings)\n#### Package Management & Compatibility\n- Upgraded HIP SDK installs to 6.2.4 for ComfyUI-Zluda and AMDGPU-Forge\n- Updated install for kohya_ss to support RTX 5000-series GPUs\n#### User Interface & Experience (UI/UX)\n- Improved window state handling\n- Updated some date strings to take into account the user's locale\n#### Localization\n- Updated Japanese, Brazilian Portuguese, Chinese, and Russian translations\n#### Internal / Developer Changes\n- (Internal) Upgraded FluentAvalonia to 2.3.0\n- (Internal) Refactored configuration-based shared folder logic: Centralized handling into `SharedFoldersConfigHelper` and format-specific strategies, removing custom file I/O logic from individual package classes for improved consistency and maintainability.\n  - Migrated packages ComfyUI (incl. Zluda), VladAutomatic (SD.Next), Sdfx, and StableSwarm to use the unified system for configuration and symlink based sharing.\n\n### Fixed\n#### Installation, Compatibility & Core Functionality\n- Fixed RTX 5000-series GPU detection in certain cases\n- Fixed Package Updates and Change Version not using stored PyTorch index and instead using the default recommended index.\n- Fixed ComfyUI-Zluda not being recognized as an option for Inference or SwarmUI (for real this time)\n- Fixed errors from invalid pip specifiers in requirements files\n#### User Interface & Experience (UI/UX)\n- Fixed Image Viewer animation loader keeping file handles open, which resolves 2 different issues (OSes are fun):\n  - (macOS) Fixed `FileNotFoundException` crash when navigating to Inference tab after deleting a Webp animation file previously opened in the Image Viewer Dialog.\n  - (Windows) Fixed Webp animation files unable to be deleted without closing the app first.\n- Fixed Image Viewer `FileNotFoundException` on fetching image size, if navigating to a deleted image file.\n- (macOS) Fixed Webp / Gif animations RGB colors flipped.\n- Fixed window disappearing on macOS when the saved window size is very small\n- Fixed large white boxes appearing when tooltips are visible on macOS/Linux\n- Fixed package images sometimes showing as blank due to concurrent image caching. Requests to same image resources are now de-duplicated\n- Reduced memory usage from `ShowDisabledTooltipExtension`\n#### Inference & Workflows\n- Fixed some cases of missing custom nodes in SwarmUI\n- Fixed Inference ControlNet Preprocessors using incorrect resolution and increased maximum of smallest dimension to 16384\n- Fixed Inference Extra Networks card not allowing for more than one model at a time\n#### Model Management & Discovery\n- Fixed missing base model options in the Metadata Editor\n- Fixed some crashes when using Accelerated Model Discovery\n### Supporters\n#### Visionaries\nOur heartfelt gratitude goes out to our amazing Visionary-tier Patrons: **Waterclouds**, **Corey T**, **bluepopsicle**, **Bob S**, **Akiro_Senkai**, and **Ibixat**! Your exceptional support is fundamental to the ongoing development and success of Stability Matrix. We are immensely grateful for your partnership and belief in the project! 🙏\n#### Pioneers\nWe also want to give a huge thank you to our dedicated Pioneer-tier Patrons: **tankfox**, **Mr. Unknown**, **Szir777**, **Tigon**, **NowFallenAngel**, **Al Gorithm**, and welcome to our newest Pioneer, **Noah M.**! Your consistent support and enthusiasm keep the momentum going. Thank you all for being such an important part of our community! ✨\n\n## v2.14.0-pre.2\n### Added\n- Added new package - [Stable Diffusion WebUI Forge - Classic](https://github.com/Haoming02/sd-webui-forge-classic)\n- Added Accelerated Model Discovery (Beta) (⚡ icon in Civitai Browser) for Insider+ supporters. Utilizes an optimized connection for dramatically faster, more responsive browsing of online model repositories.\n- Added Undo/Redo commands to text editor context menus\n- Added Prompt Amplifier to Inference - click the magic wand 🪄 in the prompt editor to expand and enrich your ideas. Tailor the vibe with the ‘Feel’ selector and watch as your generations come to life with extra detail, coherence, and flair!\n- (pre.2 re-release) Added support for HiDream in Inference - see [ComfyUI Examples](https://comfyanonymous.github.io/ComfyUI_examples/hidream/) for more details\n- (pre.2 re-release) Added toggle for filtering Inference Extra Networks by base model\n### Changed\n- Updated install for kohya_ss to support RTX 5000-series GPUs\n- (pre.2 re-release) Merged Inference GGUF workflows into the UNet model loader option (no longer need to choose GGUF separately)\n- (pre.2 re-release) Updated some date strings to take into account the user's locale\n- (pre.2 re-release) Fixed some crashes when using Accelerated Model Discovery\n- (pre.2 re-release) Performance optimizations for Checkpoint Manager (progress indicators now fully uses Compiled Bindings)\n### Fixed\n- Fixed Inference ControlNet Preprocessors using incorrect resolution and increased maximum of smallest dimension to 16384\n- Fixed Triton/Sage install option showing for incompatible GPUs\n- Fixed errors from invalid pip specifiers in requirements files\n- Fixed package images sometimes showing as blank due to concurrent image caching. Requests to same image resources are now de-duplicated\n- (pre.2 re-release) Fixed Inference Extra Networks card not allowing for more than one model at a time\n- (pre.2 re-release) Reduced memory usage from `ShowDisabledTooltipExtension`\n### Supporters\n#### Visionaries\n- Big shout-out to our Visionary-tier patrons: Waterclouds, Corey T, bluepopsicle, and Bob S! Your steadfast support keeps Stability Matrix moving forward, and we couldn’t do it without you. 🚀 Thank you!\n\n## v2.14.0-pre.1\n### Added\n- Added new Package Command (in the 3-dots menu) for installing Triton & SageAttention in ComfyUI\n- Added Abstract Syntax Tree (AST) parsing for Inference prompts. This provides a more robust internal understanding of prompt structure, paving the way for future enhancements.\n- Added hotkey (`Ctrl+Up`/`Ctrl+Down`) in Inference prompt editors to adjust the weight emphasis of the token under the caret or the currently selected text.\n  - This automatically wraps the token/selection in parentheses `()` if it's not already weighted.\n  - It modifies existing weights within parentheses or adds weights if none exist (e.g. `(word:1.1)`).\n  - Handles selection spanning multiple tokens intelligently.\n- Added Plasma Noise addon to Inference for text to image workflows\n- (Internal) Introduced unified strategy pattern (`IConfigSharingStrategy`) to for handling different config file formats (JSON, YAML, FDS).\n  - Added support for configuring nested paths in JSON and YAML files (e.g. `paths.models.vae`) via dot-notation in `SharedFolderLayoutRule.ConfigDocumentPaths`.\n  - Packages can now use the `SharedFolderLayout` property to define a `ConfigFileType` and `ConfigSharingOptions` (like `RootKey`), without needing to implement custom configuration logic.\n### Changed\n- Changed the names of some of the shared model folders to better reflect their contents\n- Improved window state handling\n- Improved Checkpoint Manager memory usage (thanks to @FireGeek for the profiling assistance!)\n- Upgraded HIP SDK installs to 6.2.4 for ComfyUI-Zluda and AMDGPU-Forge\n- (Internal) Upgraded FluentAvalonia to 2.3.0\n- (Internal) Refactored configuration-based shared folder logic: Centralized handling into `SharedFoldersConfigHelper` and format-specific strategies, removing custom file I/O logic from individual package classes for improved consistency and maintainability.\n  - Migrated packages ComfyUI (incl. Zluda), VladAutomatic (SD.Next), Sdfx, and StableSwarm to use the unified system for configuration and symlink based sharing.\n### Fixed\n- Fixed RTX 5000-series GPU detection in certain cases\n- Fixed Image Viewer animation loader keeping file handles open, which resolves 2 different issues (OSes are fun):\n  - (macOS) Fixed `FileNotFoundException` crash when navigating to Inference tab after deleting a Webp animation file previously opened in the Image Viewer Dialog.\n  - (Windows) Fixed Webp animation files unable to be deleted without closing the app first.\n- Fixed Image Viewer `FileNotFoundException` on fetching image size, if navigating to a deleted image file.\n- (macOS) Fixed Webp / Gif animations RGB colors flipped.\n- Fixed Package Updates and Change Version not using stored PyTorch index and instead using the default recommended index.\n- Fixed some cases of missing custom nodes in SwarmUI\n- Fixed window disappearing on macOS when the saved window size is very small\n- Fixed ComfyUI-Zluda not being recognized as an option for Inference or SwarmUI (for real this time)\n- Fixed missing base model options in the Metadata Editor\n- Fixed large white boxes appearing when tooltips are visible on macOS/Linux\n### Supporters\n#### Visionaries\n- A special shout-out to our fantastic Visionary-tier Patreon supporters: Waterclouds, Corey T, and our newest Visionaries, bluepopsicle and Bob S! Your continued generosity powers the future of Stability Matrix—thank you so much!\n\n## v2.14.0-dev.3\n### Added\n- Added Wan 2.1 Text to Video and Text to Image project types for Inference\n- Added Wan 2.1 files to the HuggingFace model browser\n- Added new package - [Stable Diffusion WebUI AMDGPU Forge](https://github.com/lshqqytiger/stable-diffusion-webui-amdgpu-forge)\n- Added support for RTX 5000-series GPUs in ComfyUI, Forge, and reForge\n- Added \"Rebuild .NET Project\" command to SwarmUI installs - available via the 3-dots menu -> Package Commands -> Rebuild .NET Project\n- Added new autocomplete tag source to Inference - [Danbooru/e621 merged tags](https://civitai.com/models/950325?modelVersionId=1419692)\n### Changed\n- Upgraded ComfyUI CUDA torch to 12.6\n- Upgraded Lykos account connection to use OAuth 2.0 device flow\n- (Internal) Updated Avalonia to 11.2.5\n### Fixed\n- Fixed [#1128](https://github.com/LykosAI/StabilityMatrix/issues/1128) - overwriting models when downloading multiple with the same name\n- Fixed ROCm torch indexes for ComfyUI & Forge\n- Fixed model browser sometimes downloading to `ModelsLora` or `ModelsStableDiffusion` folders instead of the correct folder\n- Fixed incorrect Unet folder path for ComfyUI users on Linux/macOS\n- Fixed [#1157](https://github.com/LykosAI/StabilityMatrix/issues/1157) - crash when broken symlinks exist in model directories\n- Fixed [#1154](https://github.com/LykosAI/StabilityMatrix/issues/1154) - increased width for package name on the package cards\n- Fixed ComfyUI-Zluda not being recognized as an option for Inference or SwarmUI\n- Fixed SwarmUI showing Python options in the 3-dots menu\n- Fixed SD.Next install failures in certain cases when using Zluda\n### Supporters\n#### Visionaries\n- Many thanks to our amazing Visionary-tier Patreon supporters, **Waterclouds**, **TheTekknician**, and **Corey T**! Your unwavering support is very much appreciated!\n\n## v2.14.0-dev.2\n### Added\n- Added Align Your Steps scheduler to Inference\n- Added wildcards to Inference prompts, e.g. `{blue|green|red}` will randomly select one of the colors\n- Added Safetensor Metadata viewer to the Checkpoint Manager context menu - thanks to @genteure!\n### Changed\n- Updated the Civitai Model Browser base model selector to match the new Checkpoint Manager filter UI \n- FaceDetailers in Inference will now inherit the primary sampler/scheduler/etc. by default. You can still manually set these by enabling the options via the ⚙️ button on the FaceDetailer card\n- Slightly rearranged the FaceDetailer card layout due to the above change\n- \"Remove symbolic links on shutdown\" option now also removes links from Output Sharing\n- Inference \"Extra Networks\" selector now filters extra networks based on the selected base model\n- Updated Japanese, Brazilian Portuguese, Chinese, and Russian translations\n### Fixed\n- Fixed crash when dragging & dropping images in Inference (hopefully)\n- Fixed HiresFix Inference addon not inheriting sampler/scheduler properly\n- Fixed some plus (+) buttons getting cut off in the Inference UI\n- Fixed CFG Rescale addon interfering with refiner model in Inference\n- Fixed [#1083](https://github.com/LykosAI/StabilityMatrix/issues/1083) - \"Show Nested Models\" incorrectly displaying models from some non-nested folders\n- Fixed issue with InvokeAI model sharing when the host address is set to 0.0.0.0\n- Fixed issue when parsing index URLs in Python Dependencies Override menu\n- Fixed ComfyUI-Zluda not respecting pip user overrides\n- Fixed issue with Checkpoint Manager not displaying any models\n- (dev.2 re-release) Fixed autocomplete not showing in certain cases when using wildcards\n- (dev.2 re-release) Fixed package restart button not working \n- (dev.2 re-release) Fixed [#1120](https://github.com/LykosAI/StabilityMatrix/issues/1120) - crash when right clicking in the console after restarting a package\n### Supporters\n#### Visionaries\n- A huge thank you to our incredible Visionary-tier Patreon supporters, **Waterclouds**, **TheTekknician**, and our newest Visionary, **Corey**! Your generous support is greatly appreciated!\n\n## v2.14.0-dev.1\n### Added\n- Added Rescale CFG addon to Inference\n- Added Swap Dimensions button between the width/height input in Inference\n- Added Ctrl+Tab/Ctrl+Shift+Tab shortcuts for navigating between Inference tabs\n- Added OpenModelDB tab to the Model Browser\n### Changed\n- Improved the quality of Inference inpainting by upgrading the workflow behind the scenes. The workflow remains the same for you — just better results!\n- Redesigned the Checkpoint Manager Filter flyout to include more options and improve the layout\n- \"Clear All\" button will now remain at the top of the Downloads list regardless of scroll position - thanks to @Genteure!\n- Improved image metadata parsing - thanks to @Genteure!\n### Fixed\n- Fixed Inference image selector card buttons taking up the whole height of the card\n- Fixed Inference mask editor failing to paint to the right-most edge on large images\n- Fixed Inference mask editor not showing the entire image in certain circumstances\n- Fixed an issue where certain sampler/scheduler combos would not get saved in image metadata - thanks to @yansigit!\n- Fixed [#1078](https://github.com/LykosAI/StabilityMatrix/issues/1078) - \"Call from invalid thread\" error after one-click install finishes\n- Fixed [#1080](https://github.com/LykosAI/StabilityMatrix/issues/1080) - Some models not displayed in Checkpoint Manager\n### Supporters\n#### Visionaries\n- Many thanks to our incredible Visionary-tier Patreon supporters, **Waterclouds** and **TheTekknician**! Your support helps us continue to improve Stability Matrix!\n\n## v2.13.4\n### Added\n- Added support for RTX 5000-series GPUs in ComfyUI, Forge, and reForge\n- Added \"Rebuild .NET Project\" command to SwarmUI installs - available via the 3-dots menu -> Package Commands -> Rebuild .NET Project\n### Changed\n- Upgraded ComfyUI CUDA torch to 12.6\n- Upgraded Lykos account connection to use OAuth 2.0 device flow\n- (Internal) Updated Avalonia to 11.2.5\n### Fixed\n- Fixed [#1128](https://github.com/LykosAI/StabilityMatrix/issues/1128) - overwriting models when downloading multiple with the same name\n- Fixed ROCm torch indexes for ComfyUI & Forge\n- Fixed model browser sometimes downloading to `ModelsLora` or `ModelsStableDiffusion` folders instead of the correct folder\n- Fixed incorrect Unet folder path for ComfyUI users on Linux/macOS\n- Fixed [#1157](https://github.com/LykosAI/StabilityMatrix/issues/1157) - crash when broken symlinks exist in model directories\n- Fixed [#1154](https://github.com/LykosAI/StabilityMatrix/issues/1154) - increased width for package name on the package cards\n- Fixed ComfyUI-Zluda not being recognized as an option for Inference or SwarmUI\n- Fixed SwarmUI showing Python options in the 3-dots menu\n- Fixed SD.Next install failures in certain cases when using Zluda\n### Supporters\n#### Visionaries\n- Huge thanks to our amazing Visionary-tier Patrons, **Waterclouds** and **Corey T**! We're truly grateful for your continued generosity and support!\n#### Pioneers\n- Special appreciation to our fantastic Pioneer-tier Patrons: **tankfox**, **Mr. Unknown**, **Szir777**, **Tigon**, **NowFallenAngel**, and our newest addition, **Al Gorithm**! Thank you all for your incredible commitment and ongoing encouragement!\n\n## v2.13.3\n### Changed\n- \"Remove symbolic links on shutdown\" option now also removes links from Output Sharing\n### Fixed\n- Fixed [#1083](https://github.com/LykosAI/StabilityMatrix/issues/1083) - \"Show Nested Models\" incorrectly displaying models from some non-nested folders\n- Fixed [#1120](https://github.com/LykosAI/StabilityMatrix/issues/1120) - crash when right clicking in the console after restarting a package\n- Fixed issue with InvokeAI model sharing when the host address is set to 0.0.0.0\n- Fixed issue when parsing index URLs in Python Dependencies Override menu\n- Fixed issue where models were filtered incorrectly in the Checkpoint Manager\n- Fixed ComfyUI-Zluda not using the user-defined pip overrides\n### Supporters\n#### Visionaries\n- A heartfelt thank you to our incredible Visionary-tier Patrons, **Waterclouds**, **TheTekknician**, and **Corey**! Your unwavering support means the world to us!\n#### Pioneers\n- A big shoutout to our outstanding Pioneer-tier Patrons, **tankfox**, **Mr. Unknown**, **Szir777**, and **NowFallenAngel**! We deeply appreciate your ongoing support and dedication!\n\n## v2.13.2\n### Changed\n- Removed SimpleSDXL due to security concerns - thanks to @iwr-redmond for the detailed report. For more information please visit https://github.com/LykosAI/StabilityMatrix/security/advisories.\n### Supporters\n#### Visionaries\n- Many thanks to our amazing Visionary-tier Patrons, **Waterclouds** and **TheTekknician**! Your support is greatly appreciated!\n#### Pioneers\n- Shoutout to our Pioneer-tier Patrons, **tankfox**, **Mr. Unknown**, **Szir777**, **Tigon**, and **NowFallenAngel**! Thank you for your continued support!\n\n## v2.13.1\n### Changed\n- Redesigned the Checkpoint Manager Filter flyout to include more options and improve the layout\n- \"Clear All\" button will now remain at the top of the Downloads list regardless of scroll position - thanks to @Genteure!\n- Improved image metadata parsing - thanks to @Genteure!\n### Fixed\n- Fixed [#1078](https://github.com/LykosAI/StabilityMatrix/issues/1078) - \"Call from invalid thread\" error after one-click install finishes\n- Fixed [#1080](https://github.com/LykosAI/StabilityMatrix/issues/1080) - Some models not displayed in Checkpoint Manager\n- Fixed Inference image selector card buttons taking up the whole height of the card\n- Fixed Inference mask editor failing to paint to the right-most edge on large images\n- Fixed Inference mask editor not showing the entire image in certain circumstances\n- Fixed crash when dragging & dropping images in Inference (hopefully)\n- Fixed an issue where certain sampler/scheduler combos would not get saved in image metadata - thanks to @yansigit!\n### Supporters\n#### Visionaries\n- A heartfelt thank you to our exceptional Visionary-tier Patreon backers, **Waterclouds** and **TheTekknician**! We truly appreciate your steadfast support!\n#### Pioneers\n- We are also very grateful to our wonderful Pioneer-tier Patreon supporters, **tankfox**, **Mr Unknown**, **Szir777**, **Tigon**, and **NowFallenAngel**! Your support means a lot to us!\n\n## v2.13.0\n### Added\n- Added new package - [ComfyUI-Zluda](https://github.com/patientx/ComfyUI-Zluda) - for AMD GPU users on Windows\n- Added file sizes to the Checkpoint Manager tab\n- Added the Discrete Model Sampling addon for Inference samplers, allows selecting different sampling methods, such as v_prediction, lcm, or x0, and optionally adjusts the model’s noise reduction strategy with the zero-shot noise ratio (ZSNR) toggle.\n- Added Default GPU override in Settings -> System Settings -> Default GPU\n- Added new \"Copy\" menu to the Inference gallery context menu, allowing you to copy generation parameters as well as the image\n- Added \"StableDiffusion\" folder as an option when downloading Flux models in the CivitAI model browser\n- Added support for SD3.5 in Inference\n- Added CLIP_G to HuggingFace model browser\n- Added search bar to the Installed Workflows tab\n- Added \"Search with Google\" and \"Search with ChatGPT\" to the package console output & install progress console output context menus\n- Added \"Date Created\" and \"Date Last Modified\" sorting options to the Checkpoints tab\n- Added a new \"Extension Packs\" section to the extension manager, allowing you to create packs for easier installation of multiple extensions at once\n- Added \"Search by Creator\" command to Civitai browser context menu\n- Added Beta scheduler to the scheduler selector in Inference\n- Added zipping of log files and \"Show Log in Explorer\" button on exceptions dialog for easier support\n- Added max concurrent downloads option & download queueing for most downloads\n- Added the ability to change the Models directory separately from the rest of the Data directory. This can be set in `Settings > Select new Models Folder`\n- Added InvokeAI model sharing option\n### Changed\n- Improved Packages Page grid layout to dynamically stretch to fill available space\n- Text Encoder / CLIP selection in Inference is now enabled via the cogwheel ⚙️ button next to the model selector \n- Updated Civitai model descriptions to properly render the interactive elements\n- Adjusted the Branch/Release toggle during package install flow to be a little more obvious\n- Updated the Dock library used for Inference - fixes some weirdness with resizing / rearranging panels\n- New file format and key derivation for protecting locally encrypted secrets (i.e. Civit / Lykos accounts) that is no longer dependent on the OS Version. This should prevent system updates from clearing account logins.\n- (Internal) Updated to .NET 9 Runtime and Avalonia 11.2.2 for performance improvements, lower memory usage, and bug fixes\n- Updated pytorch index to `rocm6.2` for AMD users of ComfyUI on Linux\n### Fixed\n- Fixed text alignment issues in the Downloads tab for certain long names / progress infos\n- Improved startup performance and resource usage with optimizations to hardware lookups. Moved reflection usages in dependency injection to source generation.\n- Fixed an issue with ComfyUI-Impact-Subpack not being installed when using FaceDetailer in Inference\n- Fixed GGUF models not showing in Inference without the GGUF extension installed (this means it will now properly prompt you to install the extension as well)\n### Supporters\n#### Visionaries\n- We're extremely grateful to our incredible Visionary-tier Patreon supporters, **Waterclouds** and **TheTekknician**! Thank you very much for your unwavering support!\n#### Pioneers\n- Many thanks to our amazing Pioneer-tier Patreon supporters, **tankfox**, **Mr Unknown**, **Szir777**, and our newest Pioneer, **NowFallenAngel**! Your generous support is very much appreciated!\n\n## v2.13.0-pre.2\n### Added\n- Added new package - [ComfyUI-Zluda](https://github.com/patientx/ComfyUI-Zluda) - for AMD GPU users on Windows\n- Added \"StableDiffusion\" folder as an option when downloading Flux models in the CivitAI model browser\n### Changed\n- Updated pytorch index to `rocm6.2` for AMD users of ComfyUI on Linux\n### Supporters\n#### Visionaries\n- Big shoutout to our incredible Visionary-tier Patreon supporter, **Waterclouds**! We're also delighted to introduce our newest Visionary-tier Patreon supporter, **TheTekknician**! Thank you both for your generous support!\n\n## v2.13.0-pre.1\n### Added\n- Added new package - [CogVideo](https://github.com/THUDM/CogVideo) - many thanks to @NullDev for the contribution!\n- Added file sizes to the Checkpoint Manager tab\n- Added more formatting options for Inference output filenames - thanks to @yansigit!\n- Added the Discrete Model Sampling addon for Inference samplers, allows selecting different sampling methods, such as v_prediction, lcm, or x0, and optionally adjusts the model’s noise reduction strategy with the zero-shot noise ratio (ZSNR) toggle.\n- Added Default GPU override in Settings -> System Settings -> Default GPU\n- Added the ability to copy more generation parameters from the Inference gallery context menu\n### Changed\n- Improved Packages Page grid layout to dynamically stretch to fill available space\n- New file format and key derivation for protecting locally encrypted secrets (i.e. Civit / Lykos accounts) that is no longer dependent on the OS Version. This should prevent system updates from clearing account logins.\n- (Internal) Updated to .NET 9 Runtime and Avalonia 11.2.2 for performance improvements, lower memory usage, and bug fixes\n### Fixed\n- Improved startup performance and resource usage with optimizations to hardware lookups. Moved reflection usages in dependency injection to source generation.\n- Fixed a typo in the Japanese translation - thanks to @mattyatea!\n- Fixed missing package thumbnails due to moved or inaccessible urls\n- Fixed an issue with ComfyUI-Impact-Subpack not being installed when using FaceDetailer in Inference\n- Fixed GGUF models not showing in Inference without the GGUF extension installed (this means it will now properly prompt you to install the extension as well)\n### Supporters\n#### Visionaries\n- Huge thank you to our incredible Visionary-tier Patreon supporter, **Waterclouds**! Your unwavering support is very much appreciated!\n\n## v2.13.0-dev.3\n### Added\n- Added support for SD3.5 in Inference\n- Added CLIP_G to HuggingFace model browser\n- Added search bar to the Installed Workflows tab\n- Added \"Search with Google\" and \"Search with ChatGPT\" to the package console output & install progress console output context menus\n- Added \"Date Created\" and \"Date Last Modified\" sorting options to the Checkpoints tab\n### Changed\n- Text Encoder / CLIP selection in Inference is now enabled via the cogwheel ⚙️ button next to the model selector \n- Added more base model types to the CivitAI Model Browser & Checkpoint Manager\n- Model browser base model types are now loaded dynamically from CivitAI, reducing the need for updates to add new types\n- Updated Civitai model descriptions to properly render the interactive elements\n- Updated Russian translations thanks to @vanja-san\n- Updated Simplified Chinese translations thanks to @QL-boy\n- (Internal) Updated to Avalonia 11.2.0\n### Fixed\n- Fixed some instances of Civitai model browser not loading new results\n- Fixed \"Unsupported Torch Version: Cuda\" errors when installing a1111\n- Fixed crash when clicking \"Remind me Later\" on the update dialog\n- Fixed some cases of crashing when GitHub API rate limits are exceeded\n- Fixed Git missing from env vars when running SwarmUI\n### Supporters\n#### Visionaries\n- Big shoutout to our amazing Visionary-tier Patreon supporter, **Waterclouds**! We are very grateful for your continued support!\n\n## v2.13.0-dev.2\n### Added\n- Added new package - [SimpleSDXL](https://github.com/metercai/SimpleSDXL) - many thanks to @NullDev for the contribution!\n- Added new package - [FluxGym](https://github.com/cocktailpeanut/fluxgym) - many thanks to @NullDev for the contribution!\n- Added a new \"Extension Packs\" section to the extension manager, allowing you to create packs for easier installation of multiple extensions at once\n- Added \"Search by Creator\" command to Civitai browser context menu\n- Added Beta scheduler to the scheduler selector in Inference\n- Added zipping of log files and \"Show Log in Explorer\" button on exceptions dialog for easier support\n- Added max concurrent downloads option & download queueing for most downloads\n### Changed\n- (Internal) Updated to Avalonia 11.1.4\n- Adjusted the Branch/Release toggle during package install flow to be a little more obvious\n- Updated the Dock library used for Inference - fixes some weirdness with resizing / rearranging panels\n### Fixed\n- Fixed ComfyUI NF4 extension not installing properly when prompted in Inference\n- Fixed [#932](https://github.com/LykosAI/StabilityMatrix/issues/932), [#935](https://github.com/LykosAI/StabilityMatrix/issues/935), [#939](https://github.com/LykosAI/StabilityMatrix/issues/939) - InvokeAI failing to update\n- Fixed repeated nested folders being created in `Models/StableDiffusion` when using Forge in Symlink mode in certain conditions. Existing folders will be repaired to their original structure on launch.\n- Fixed minimize button not working on macOS\n- Fixed InvokeAI model sharing spamming the console with \"This may take awhile\" in certain conditions\n- Fixed text alignment issues in the Downloads tab for certain long names / progress infos\n### Supporters\n#### Visionaries\n- A big thank you to our amazing Visionary-tier Patreon supporter, **Waterclouds**! Your continued support is invaluable!\n\n## v2.13.0-dev.1\n### Added\n- Added the ability to change the Models directory separately from the rest of the Data directory. This can be set in `Settings > Select new Models Folder`\n- Added \"Copy\" menu to the Inference gallery context menu, allowing you to copy the image or the seed (other params coming soon™️)\n- Added InvokeAI model sharing option\n### Supporters\n#### Visionaries\n- A heartfelt thank you to our incredible Visionary-tier Patreon supporter, **Waterclouds**! Your ongoing support means a lot to us, and we’re grateful to have you with us on this journey!\n\n## v2.12.4\n### Added\n- Added new package - [CogVideo](https://github.com/THUDM/CogVideo) - many thanks to @NullDev for the contribution!\n- Added more formatting options for Inference output filenames - thanks to @yansigit!\n### Changed\n- Model browser base model types are now loaded dynamically from CivitAI, reducing the need for updates to add new types\n### Fixed\n- Fixed crash when clicking \"Remind me Later\" on the update dialog\n- Fixed some cases of crashing when GitHub API rate limits are exceeded\n- Fixed Git missing from env vars when running SwarmUI\n- Fixed missing package thumbnails due to moved or inaccessible urls\n- Fixed an issue with updating FluxGym in certain cases - thanks to @NullDev!\n- Fixed a typo in the Japanese translation - thanks to @mattyatea!\n### Supporters\n#### Visionaries\n- A huge thank you to our dedicated Visionary-tier Patreon supporter, **Waterclouds**! We’re thrilled to have your ongoing support!\n#### Pioneers\n- Shoutout to our great Pioneer-tier patrons: **tankfox**, **tanangular**, **Mr. Unknown**, **Szir777**, and our newest Pioneer, **Tigon**!. Your continued support is greatly appreciated!\n\n## v2.12.3\n### Added\n- Added new package - [SimpleSDXL](https://github.com/metercai/SimpleSDXL) - many thanks to @NullDev for the contribution!\n- Added new package - [FluxGym](https://github.com/cocktailpeanut/fluxgym) - many thanks to @NullDev for the contribution!\n- Added more base model types to the CivitAI Model Browser & Checkpoint Manager\n### Changed\n- Updated Russian translations thanks to @vanja-san\n- Updated Simplified Chinese translations thanks to @QL-boy\n### Fixed\n- Fixed some cases of FileTransferExists error when running re/Forge or Automatic1111\n- Fixed update check not happening on startup for some users\n- Fixed error when installing Automatic1111 on macOS\n- Fixed some instances of Civitai model browser not loading new results\n- Fixed \"Unsupported Torch Version: Cuda\" errors when installing a1111\n### Supporters\n#### Visionaries\n- A huge shout-out to our dedicated Visionary-tier Patreon supporter, **Waterclouds**! Your unwavering support is greatly appreciated!\n#### Pioneers\n- We'd also like to express our gratitude to our amazing Pioneer-tier patrons: **tankfox**, **tanangular**, **Mr. Unknown**, and **Szir777**! Your ongoing support means a great deal!\n\n## v2.12.2\n### Added\n- Added Beta scheduler to the scheduler selector in Inference\n### Changed\n- (Internal) Updated to Avalonia 11.1.4\n### Fixed\n- Fixed ComfyUI NF4 extension not installing properly when prompted in Inference\n- Fixed [#932](https://github.com/LykosAI/StabilityMatrix/issues/932), [#935](https://github.com/LykosAI/StabilityMatrix/issues/935), [#939](https://github.com/LykosAI/StabilityMatrix/issues/939) - InvokeAI failing to update\n- Fixed repeated nested folders being created in `Models/StableDiffusion` when using Forge in Symlink mode in certain conditions. Existing folders will be repaired to their original structure on launch.\n- Fixed minimize button not working on macOS\n\n### Supporters\n#### Visionaries\n- We extend our heartfelt appreciation to our dedicated Visionary-tier Patreon supporter, **Waterclouds**. Your ongoing support is invaluable!\n#### Pioneers\n- We’d also like to thank our great Pioneer-tier patrons: **tankfox**, **tanangular**, **Mr. Unknown**, and **Szir777**. Your continuous support means a lot!\n\n## v2.12.1\n### Fixed\n- Fixed [#916](https://github.com/LykosAI/StabilityMatrix/issues/916) - InvokeAI failing to install/update on macOS\n- Fixed [#914](https://github.com/LykosAI/StabilityMatrix/issues/914) - Unable to use escaped colon `:` character in Inference prompts\n- Fixed [#908](https://github.com/LykosAI/StabilityMatrix/issues/908) - Forge unable to use models from \"unet\" shared folder\n- Fixed [#902](https://github.com/LykosAI/StabilityMatrix/issues/902) - Images from shared outputs folder not displaying properly in Stable Diffusion WebUI-UX\n- Fixed [#898](https://github.com/LykosAI/StabilityMatrix/issues/898) - Incorrect launch options for RuinedFooocus\n- Fixed index url parsing in Python Packages window causing some packages to not have versions available\n- Fixed a crash when switching between Model Sharing options for certain packages\n### Supporters\n#### Visionaries\n- A sincere thank you to our valued Visionary-tier Patreon supporter, **Waterclouds**. Your continued support is truly appreciated, and we’re grateful to have you with us on this journey.\n#### Pioneers\n- We’d also like to extend our gratitude to our Pioneer-tier patrons: **tankfox**, **tanangular**, **Mr. Unknown**, and **Szir777**. Your ongoing support means a great deal to us!\n\n## v2.12.0\n### Added\n#### New Packages\n- [Fooocus - mashb1t's 1-Up Edition](https://github.com/mashb1t/Fooocus) by mashb1t\n- [Stable Diffusion WebUI reForge](https://github.com/Panchovix/stable-diffusion-webui-reForge/) by Panchovix\n#### Inference\n- Added type-to-search for the Inference model selectors. Start typing while the dropdown is open to navigate the list.\n- Added \"Model Loader\" option to Inference, for loading UNet/GGUF/NF4 models (e.g. Flux)\n- Added support for the FP8 version of Flux in the default Model Loader via the \"Use Flux Guidance\" Sampler Addon\n- Added trigger words to the Inference Extra Networks (Lora/Lyco) selector for quick copy & paste\n- Image viewer context menus now have 2 options: `Copy (Ctrl+C)` which now always copies the image as a file, and `Copy as Bitmap (Shift+Ctrl+C)` (Available on Windows) which copies to the clipboard as native bitmap. This changes the previous single `Copy` button behavior that would first attempt a native bitmap copy on Windows when available, and fall back to a file copy if not.\n- Added Face Detailer module to Inference\n#### Package Manager\n- Added Python dependencies override table to package installation options, where the default pip packages may be overriden for a package's install and updates. This can be changed later or added to existing packages through `Package Menu > Python Dependencies Override`\n- Added \"Change Version\" option to the package card overflow menu, allowing you to downgrade or upgrade a package to a specific version or commit ([#701](https://github.com/LykosAI/StabilityMatrix/issues/701), [#857](https://github.com/LykosAI/StabilityMatrix/issues/857))\n- Added \"Disable Update Check\" option to the package card overflow menu, allowing you to disable update checks for a specific package\n- Added Custom commit option in the Advanced Options for package installs ([#670](https://github.com/LykosAI/StabilityMatrix/issues/670), [#839](https://github.com/LykosAI/StabilityMatrix/issues/839), [#842](https://github.com/LykosAI/StabilityMatrix/issues/842))\n- Added macOS support for Fooocus & related forks\n- Added Intel OneAPI XPU backend (IPEX) option for SD.Next\n#### Checkpoint Manager\n- Added new Metadata Editor (accessible via the right-click menu), allowing you to create or edit metadata for models\n- Added \"New Directory\" and \"Delete\" options to the context menu of the tree view.\n- Added new toggle for drag & drop - when enabled, all selected models will now move together with the dragged model\n- Added \"File Size\" sorting option\n- Added \"Hide Empty Categories\" toggle\n- Added \"Select All\" button to the InfoBar (shown when at least one model is selected)\n- Added \"Show NSFW Images\" toggle\n#### Model Browser\n- Added \"Hide Installed Models\" toggle to the CivitAI Model Browser\n- Added toggle to hide \"Early Access\" models in the CivitAI Model Browser\n- Added ultralytics models to HuggingFace model browser\n#### Other\n- Added \"Sign in with Google\" option for connecting your Lykos Account on the Account Settings page\n- Added zoom sliders for Outputs, Checkpoints, and Model Browser pages\n- Added Settings option \"Console: History Size\" to adjust the number of lines stored in the console history when running packages. Defaults to 9001 lines.\n- Added optional anonymous usage reporting for gauging popularity of package installs and features. You will be asked whether you want to enable this feature on launch, and can change your choice at any time in `Settings > System > Analytics`\n- Added \"Run Command\" option in Settings for running a command with the embedded Python or Git executables\n- Added \"Enable Long Paths\" option for Git to the Settings page\n- Added \"System Settings > Enable Long Paths\" option to enable NTFS long paths on Windows\n- Added Korean translations thanks to maakcode!\n- (Windows, Linux) Added Vulkan rendering support using launch argument `--vulkan`. (On Windows, the default WinUI composition renderer is likely still preferrable. Linux users are encouraged to try the new renderer to see if it improves performance and responsiveness.)\n### Changed\n- Optimized image loading across the app, with loading speed now up to 4x faster for local images, and up to 17x faster for remote images\n- Image loading in the Outputs page now uses native memory management for ~2x less peak memory usage, and will release memory more quickly when switching away from the Outputs page or scrolling images out of view\n- Improved animation fluidity of image rendering while scrolling quickly across large collections (e.g. Outputs, Model Browser)\n- ComfyUI will no longer be pinned to torch 2.1.2 for nvidia users on Windows ([#861](https://github.com/LykosAI/StabilityMatrix/issues/861))\n- Model browser download progress no longer covers the entire card for the entire duration of the download\n- Updated torch index to `rocm6.1` for AMD users of ComfyUI\n- Show better error message for early access model downloads\n- Updated torch version for a1111 on mac\n- Checkpoints tab now shows \"image hidden\" for images that are hidden by the NSFW filter\n- OAuth-type connection errors in Account Settings now show a more detailed error message\n- The \"Download Failed\" message for model downloads is now persistent until dismissed\n- Separated the Generate button from the prompt control in Inference so it can be moved like other controls\n- Updated translations for Turkish and Russian\n- (Internal) Updated Avalonia to 11.1.3 - Includes major rendering and performance optimizations, animation refinements, improved IME / text selection, and improvements for window sizing / z-order / multi-monitor DPI scaling. ([avaloniaui.net/blog/avalonia-11-1-a-quantum-leap-in-cross-platform-ui-development](https://avaloniaui.net/blog/avalonia-11-1-a-quantum-leap-in-cross-platform-ui-development))\n- (Internal) Updated SkiaSharp (Rendering Backend) to 3.0.0-preview.4.1, potentially fixes issues with window rendering artifacts on some machines.\n- (Internal) Updated other dependencies for security and bug fixes.\n### Fixed\n- Fixed [#888](https://github.com/LykosAI/StabilityMatrix/issues/888) - error updating kohya_ss due to long paths\n- Fixed some ScrollViewers changing scroll position when focus changes\n- Fixed CivitAI Model Browser sometimes incorrectly showing \"No models found\" before toggling \"Show NSFW\" or \"Hide Installed\" filters\n- Fixed SwarmUI settings being overwritten on launch\n- Fixed issue where some Inference-generated images would be saved with the bottom missing\n- Fixed [#851](https://github.com/LykosAI/StabilityMatrix/issues/851) - missing fbgemm.dll errors when using latest torch with certain packages\n- Fixed issue where ApproxVAE models would show up in the VAE folder\n- Fixed [#878](https://github.com/LykosAI/StabilityMatrix/issues/878) - Checkpoints tab will no longer try to browse directories it can't access\n- Fixed crash when opening Settings page when refreshing CivitAI account status results in an error\n- Fixed [#814](https://github.com/LykosAI/StabilityMatrix/issues/814), [#875](https://github.com/LykosAI/StabilityMatrix/issues/875) - Error when installing RuinedFooocus\n- LORAs are now sorted by model name properly in the Extra Networks dropdown\n- (macOS) Fixed OAuth connection prompts in Account Settings not automatically updating status after connection. Custom URL schemes are now also supported on macOS builds.\n### Supporters\n#### Visionaries\n- A heartfelt thank you to our Visionary-tier patron, **Waterclouds**! We greatly appreciate your continued support!\n#### Pioneers\n- A special shoutout to our Pioneer-tier patrons: **tankfox**, **tanangular**, **Mr. Unknown**, and **Szir777**! Your unwavering support means a great deal!\n\n## v2.12.0-pre.3\n### Added\n- Added Python dependencies override table to package installation options, where the default pip packages may be overriden for a package's install and updates. This can be changed later or added to existing packages through `Package Menu > Python Dependencies Override`\n- Added optional anonymous usage reporting for gauging popularity of package installs and features. You will be asked whether you want to enable this feature on launch, and can change your choice at any time in `Settings > System > Analytics`\n- Added Korean translations thanks to maakcode!\n### Changed\n- Show better error message for early access model downloads\n- Updated torch version for a1111 on mac\n- Checkpoints tab now shows \"image hidden\" for images that are hidden by the NSFW filter\n- Updated translations for Turkish and Russian\n### Fixed\n- Fixed issue where some Inference-generated images would be saved with the bottom missing\n- Fixed CivitAI Browser page scroll refresh not ordering models correctly\n- Fixed missing fbgemm.dll errors when using latest torch with certain packages\n- Fixed issue where ApproxVAE models would show up in the VAE folder\n- Fixed [#878](https://github.com/LykosAI/StabilityMatrix/issues/878) - Checkpoints tab will no longer try to browse directories it can't access\n- Fixed crash when opening Settings page when refreshing CivitAI account status results in an error\n### Supporters\n#### Visionaries\n- A huge thank you to our Visionary-tier Patreon supporter, **Waterclouds**! We appreciate your continued support, and are grateful to have you on this journey with us!\n\n## v2.12.0-pre.2\n### Added\n- Added \"Show NSFW Images\" toggle to the Checkpoints page\n- Added \"Model Loader\" option to Inference, for loading UNet/GGUF/NF4 models (e.g. Flux)\n- Added type-to-search for the Inference model selectors. Start typing while the dropdown is open to navigate the list.\n- Added \"Sign in with Google\" option for connecting your Lykos Account on the Account Settings page\n### Changed\n- Updated Brazilian Portuguese translations thanks to thiagojramos\n- Merged the \"Flux Text to Image\" workflow back into the main Text to Image workflow\n### Fixed\n- Fixed CivitAI Model Browser sometimes incorrectly showing \"No models found\" before toggling \"Show NSFW\" or \"Hide Installed\" filters\n- Fixed Automatic1111 & related packages not including the gradio-allowed-path argument for the shared output folder\n- Fixed SwarmUI settings being overwritten on launch\n- Fixed Forge output folder links pointing to the incorrect folder\n- LORAs are now sorted by model name properly in the Extra Networks dropdown\n- Fixed errors when downloading models with invalid characters in the file name\n\n## v2.12.0-pre.1\n### Added\n- Added \"Hide Installed Models\" toggle to the CivitAI Model Browser\n### Changed\n- ComfyUI will no longer be pinned to torch 2.1.2 for nvidia users on Windows\n- Model browser download progress no longer covers the entire card for the entire duration of the download\n- Updated torch index to `rocm6.0` for AMD users of ComfyUI\n- (Internal) Updated to Avalonia 11.1.2\n- OAuth-type connection errors in Account Settings now show a more detailed error message\n### Fixed\n- Fixed Inference not connecting with \"Could not connect to backend - JSON value could not be converted\" error with API changes from newer ComfyUI versions\n- (macOS) Fixed OAuth connection prompts in Account Settings not automatically updating status after connection. Custom URL schemes are now also supported on macOS builds.\n\n## v2.12.0-dev.3\n### Added\n- Added Settings option \"Console: History Size\" to adjust the number of lines stored in the console history when running packages. Defaults to 9001 lines.\n#### Inference\n- Added new project type, \"Flux Text to Image\", a Flux-native workflow for text-to-image projects\n- Added support for the FP8 version of Flux in the regular Text to Image and Image to Image workflows via the \"Use Flux Guidance\" Sampler Addon\n#### Model Browser\n- Added AuraFlow & Flux base model types to the CivitAI model browser\n- Added CLIP/Text Encoders section to HuggingFace model browser\n#### Checkpoint Manager\n- Added new Metadata Editor (accessible via the right-click menu), allowing you to create or edit metadata for models \n- Added \"New Directory\" and \"Delete\" options to the context menu of the tree view.\n- Added new toggle for drag & drop - when enabled, all selected models will now move together with the dragged model\n- Added \"File Size\" sorting option\n- Added \"Hide Empty Categories\" toggle\n- Added \"Select All\" button to the InfoBar (shown when at least one model is selected)\n- Added \"unet\" shared model folder for ComfyUI\n### Changed\n- Optimized image loading across the app, with loading speed now up to 4x faster for local images, and up to 17x faster for remote images\n- Image loading in the Outputs page now uses native memory management for ~2x less peak memory usage, and will release memory more quickly when switching away from the Outputs page or scrolling images out of view\n- Improved animation fluidity of image rendering while scrolling quickly across large collections (e.g. Outputs, Model Browser)\n- The \"Download Failed\" message for model downloads is now persistent until dismissed\n- Separated the Generate button from the prompt control in Inference so it can be moved like other controls\n### Fixed\n- Fixed \"The version of the native libSkiaSharp library (88.1) is incompatible with this version of SkiaSharp.\" error for Linux users\n- Fixed download links for IPAdapters in the HuggingFace model browser\n- Fixed potential memory leak of transient controls (Inference Prompt and Output Image Viewer) not being garbage collected due to event subscriptions\n- Fixed Batch Count seeds not being recorded properly in Inference projects and image metadata\n### Supporters\n#### Visionaries\n- A heartfelt thank you to our Visionary-tier Patreon supporter, **Scopp Mcdee**! We truly appreciate your continued support!\n\n## v2.12.0-dev.2\n### Added\n- Added Face Detailer module to Inference\n- Added ultralytics models to HuggingFace model browser\n- Added DoRA category to CivitAI model browser\n- Added macOS support for Fooocus & related forks\n- (Windows, Linux) Added Vulkan rendering support using launch argument `--vulkan`. (On Windows, the default WinUI composition renderer is likely still preferrable. Linux users are encouraged to try the new renderer to see if it improves performance and responsiveness.)\n### Changed\n- (Internal) Updated Avalonia to 11.1.1 - Includes major rendering and performance optimizations, animation refinements, improved IME / text selection, and improvements for window sizing / z-order / multi-monitor DPI scaling. ([avaloniaui.net/blog/avalonia-11-1-a-quantum-leap-in-cross-platform-ui-development](https://avaloniaui.net/blog/avalonia-11-1-a-quantum-leap-in-cross-platform-ui-development))\n- (Internal) Updated SkiaSharp (Rendering Backend) to 3.0.0-preview.4.1, potentially fixes issues with window rendering artifacts on some machines.\n- (Internal) Updated other dependencies for security and bug fixes.\n### Fixed\n- Fixed some ScrollViewers changing scroll position when focus changes\n- Fixed [#782](https://github.com/LykosAI/StabilityMatrix/issues/782) - conflict error when launching new versions of Forge\n- Fixed incorrect torch versions being installed for InvokeAI\n### Supporters\n#### Visionaries\n- A huge thank you goes out to our esteemed Visionary-tier Patreon backers: **Scopp Mcdee**, **Waterclouds**, and **Akiro_Senkai**. Your kind support means the world!\n\n## v2.12.0-dev.1\n### Added\n- Added new package: [Fooocus - mashb1t's 1-Up Edition](https://github.com/mashb1t/Fooocus) by mashb1t\n- Added new package: [Stable Diffusion WebUI reForge](https://github.com/Panchovix/stable-diffusion-webui-reForge/) by Panchovix\n- Image viewer context menus now have 2 options: `Copy (Ctrl+C)` which now always copies the image as a file, and `Copy as Bitmap (Shift+Ctrl+C)` (Available on Windows) which copies to the clipboard as native bitmap. This changes the previous single `Copy` button behavior that would first attempt a native bitmap copy on Windows when available, and fall back to a file copy if not.\n- Added \"Change Version\" option to the package card overflow menu, allowing you to downgrade or upgrade a package to a specific version or commit\n- Added \"Disable Update Check\" option to the package card overflow menu, allowing you to disable update checks for a specific package\n- Added \"Run Command\" option in Settings for running a command with the embedded Python or Git executables\n- Added Intel OneAPI XPU backend (IPEX) option for SD.Next\n### Supporters\n#### Visionaries\n- Shoutout to our Visionary-tier Patreon supporters, **Scopp Mcdee**, **Waterclouds**, and our newest Visionary, **Akiro_Senkai**! Many thanks for your generous support!\n\n## v2.11.8\n### Added\n- Added Flux & AuraFlow types to CivitAI Browser\n- Added unet folder links for ComfyUI thanks to jeremydk\n- Added CLIP folder links for Forge\n### Changed\n- Updated Brazilian Portuguese translations thanks to thiagojramos\n### Fixed\n- Fixed [#840](https://github.com/LykosAI/StabilityMatrix/issues/840) - CivitAI model browser not loading search results\n- Fixed SwarmUI settings being overwritten on launch\n- Fixed [#832](https://github.com/LykosAI/StabilityMatrix/issues/832) [#847](https://github.com/LykosAI/StabilityMatrix/issues/847) - Forge output folder links pointing to the incorrect folder\n- Fixed errors when downloading models with invalid characters in the file name\n- Fixed error when installing RuinedFooocus on nvidia GPUs\n### Supporters\n#### Pioneers\n- A big shoutout to our Pioneer-tier patrons: **tankfox**, **tanangular**, **Mr. Unknown**, and **Szir777**! We deeply appreciate your ongoing support!\n\n## v2.11.7\n### Changed\n- Forge will use the recommended pytorch version 2.3.1 the next time it is updated\n- InvokeAI users with AMD GPUs on Linux will be upgraded to the rocm5.6 version of pytorch the next time it is updated\n### Fixed\n- Fixed Inference not connecting with \"Could not connect to backend - JSON value could not be converted\" error with API changes from newer ComfyUI versions\n### Supporters\n#### Pioneers\n- Shoutout to our Pioneer-tier supporters on Patreon: **tankfox**, **tanangular**, **Mr. Unknown**, and **Szir777**! Thanks for all of your continued support!\n\n## v2.11.6\n### Fixed\n- Fixed incorrect IPAdapter download links in the HuggingFace model browser\n- Fixed potential memory leak of transient controls (Inference Prompt and Output Image Viewer) not being garbage collected due to event subscriptions\n- Fixed Batch Count seeds not being recorded properly in Inference projects and image metadata\n- Fixed [#795](https://github.com/LykosAI/StabilityMatrix/issues/795) - SwarmUI launch args not working properly\n- Fixed [#745](https://github.com/LykosAI/StabilityMatrix/issues/745) - not passing Environment Variables to SwarmUI\n### Supporters\n#### Visionaries\n- Shoutout to our Visionary-tier Patreon supporter, **Scopp Mcdee**! Huge thanks for your continued support!\n#### Pioneers\n- Many thanks to our Pioneer-tier supporters on Patreon: **tankfox**, **tanangular**, **Mr. Unknown**, and **Szir777**! Your continued support is greatly appreciated!\n\n## v2.11.5\n### Added\n- Added DoRA category to CivitAI model browser\n### Fixed\n- Fixed `TaskCanceledException` when adding CivitAI Api key or searching for models when the API takes too long to respond. Retry and timeout behavior has been improved.\n- Fixed [#782](https://github.com/LykosAI/StabilityMatrix/issues/782) - conflict error when launching new versions of Forge\n- Fixed incorrect torch versions being installed for InvokeAI\n- Fixed `ArgumentOutOfRangeException` with the Python Packages dialog ItemSourceView when interacting too quickly after loading.\n### Supporters\n#### Visionaries\n- Shoutout to our Visionary-tier Patreon supporters, **Scopp Mcdee**, **Waterclouds**, and our newest Visionary, **Akiro_Senkai**! Many thanks for your generous support!\n#### Pioneers\n- Many thanks to our Pioneer-tier supporters on Patreon, **tankfox**, **tanangular**, and our newest Pioneers, **Mr. Unknown** and **Szir777**! Your support is greatly appreciated!\n\n## v2.11.4\n### Changed\n- Base Python install will now use `setuptools==69.5.1` for compatibility with `torchsde`. Individual Packages can upgrade as required.\n- Improved formatting of \"Copy Details\" action on the Unexpected Error dialog\n- (Debug) Logging verbosity for classes can now be configured with environment variables (`Logging__LogLevel__<TypeFullName>`).  \n### Fixed\n- Fixed ComfyUI slower generation speed with new torch versions not including flash attention for windows, pinned `torch==2.1.2` for ComfyUI on Windows CUDA\n- Fixed [#719](https://github.com/LykosAI/StabilityMatrix/issues/719) - Fix comments in Inference prompt not being ignored\n- Fixed TaskCanceledException when Inference prompts finish before the delayed progress handler (250ms)\n### Supporters\n#### Visionaries\n- Huge thanks to our Visionary-tier supporters on Patreon, **Scopp Mcdee** and **Waterclouds**! Your support helps us continue to improve Stability Matrix!\n#### Pioneers\n- Thank you to our Pioneer-tier supporters on Patreon, **tankfox** and **tanangular**! Your support is greatly appreciated!\n\n## v2.11.3\n### Changed\n- Base Python install will now use `pip>=23.3.2,<24.1` for compatibility with `torchsde`.Individual Packages can upgrade as required.\n- Added default `PIP_DISABLE_PIP_VERSION_CHECK=1` environment variable to suppress notices about pip version checks.\n  - As with other default environment variables, this can be overridden by setting your own value in `Settings > Environment Variables [Edit]`.\n### Fixed\n- Fooocus Package - Added `pip>=23.3.2,<24.1` specifier before install, fixes potential install errors due to deprecated requirement spec used by `torchsde`.\n- Fixed error when launching SwarmUI when installed to a path with spaces\n- Fixed issue where model folders were being created too late in certain cases\n- Fixed [#683](https://github.com/LykosAI/StabilityMatrix/issues/683) - Model indexing causing LiteDB errors after upgrading from older versions due to updated enum values\n### Supporters\n#### Visionaries\n- Huge thanks to our Visionary-tier supporters on Patreon, **Scopp Mcdee** and **Waterclouds**! Your support helps us continue to improve Stability Matrix!\n#### Pioneers\n- Thank you to our Pioneer-tier supporters on Patreon, **tankfox** and **tanangular**! Your support is greatly appreciated!\n\n## v2.11.2\n### Changed\n- StableSwarmUI installs will be migrated to SwarmUI by mcmonkeyprojects the next time the package is updated\n  - Note: As of 2024/06/21 StableSwarmUI will no longer be maintained under Stability AI. The original developer will be maintaining an independent version of this project\n### Fixed\n- Fixed [#700](https://github.com/LykosAI/StabilityMatrix/issues/700) - `cannot import 'packaging'` error for Forge\n### Supporters\n#### Visionaries\n- Huge thanks to our Visionary-tier supporters on Patreon, **Scopp Mcdee** and **Waterclouds**! Your support helps us continue to improve Stability Matrix!\n#### Pioneers\n- Thank you to our Pioneer-tier supporters on Patreon, **tankfox** and **tanangular**! Your support is greatly appreciated!\n\n## v2.11.1\n### Added\n- Added Rename option back to the Checkpoints page\n### Changed\n- Unobserved Task Exceptions across the app will now show a toast notification to aid in debugging\n- Updated SD.Next Package details and thumbnail - [#697](https://github.com/LykosAI/StabilityMatrix/pull/697)\n### Fixed\n- Fixed [#689](https://github.com/LykosAI/StabilityMatrix/issues/689) - New ComfyUI installs encountering launch error due to torch 2.0.0 update, added pinned `numpy==1.26.4` to install and update.\n- Fixed Inference image mask editor's 'Load Mask' not able to load image files\n- Fixed Fooocus ControlNet default config shared folder mode not taking effect\n- Fixed tkinter python libraries not working on macOS with 'Can't find a usable init.tcl' error\n### Supporters\n#### Visionaries\n- Shoutout to our Visionary-tier supporters on Patreon, **Scopp Mcdee** and **Waterclouds**! Your generous support is appreciated and helps us continue to make Stability Matrix better for everyone!\n#### Pioneers\n- A big thank you to our Pioneer-tier supporters on Patreon, **tankfox** and **tanangular**! Your support helps us continue to improve Stability Matrix!\n\n## v2.11.0\n### Added\n#### Packages\n- Added new package: [SDFX](https://github.com/sdfxai/sdfx/) by sdfxai\n- Added ZLUDA option for SD.Next\n- Added more launch options for Forge - [#618](https://github.com/LykosAI/StabilityMatrix/issues/618)\n- Added search bar to the Python Packages dialog\n#### Inference\n- Added Inpainting support for Image To Image projects using the new image mask canvas editor\n- Added alternate Lora / LyCORIS drop-down model selection, can be toggled via the model settings button. Allows choosing both CLIP and Model Weights. The existing prompt-based `<lora:model:1.0>` method is still available.\n- Added optional Recycle Bin mode when deleting images in the Inference image browser, can be disabled in settings (Currently available on Windows and macOS)\n#### Model Browsers\n- Added PixArt, SDXL Hyper, and SD3 options to the CivitAI Model Browser\n- Added XL ControlNets section to HuggingFace model browser\n- Added download speed indicator to model downloads in the Downloads tab\n#### Output Browser\n- Added support for indexing and displaying jpg/jpeg & gif images (in additional to png and webp / animated webp), with metadata parsing and search for compatible formats\n#### Settings\n- Added setting for locale specific or invariant number formatting\n- Added setting for toggling model browser auto-search on load\n- Added option in Settings to choose whether to Copy or Move files when dragging and dropping files into the Checkpoint Manager\n- Added folder shortcuts in Settings for opening common app and system folders, such as Data Directory and Logs\n#### Translations\n- Added Brazilian Portuguese language option, thanks to jbostroski for the translation!\n### Changed\n- Maximized state is now stored on exit and restored on launch\n- Drag & drop imports now move files by default instead of copying\n- Clicking outside the Select Model Version dialog will now close it\n- Changed Package card buttons to better indicate that they are buttons\n- Log file storage has been moved from `%AppData%/StabilityMatrix` to a subfolder: `%AppData%/StabilityMatrix/Logs`\n- Archived log files now have an increased rolling limit of 9 files, from 2 files previously. Their file names will now be in the format `app.{yyyy-MM-dd HH_mm_ss}.log`. The current session log file remains named `app.log`.\n- Updated image controls on Recommended Models dialog to match the rest of the app\n- Improved app shutdown clean-up process reliability and speed\n- Improved ProcessTracker speed and clean-up safety for faster subprocess and package launching performance\n- Updated HuggingFace page so the command bar stays fixed at the top\n- Revamped Checkpoints page now shows available model updates and has better drag & drop functionality\n- Revamped file deletion confirmation dialog with affected file paths display and recycle bin / permanent delete options (Checkpoint and Output Browsers) (Currently available on Windows and macOS)\n### Fixed\n- Fixed crash when parsing invalid generated images in Output Browser and Inference image viewer, errors will be logged instead and the image will be skipped\n- Fixed missing progress text during package updates\n- (Windows) Fixed \"Open in Explorer\" buttons across the app not opening the correct path on ReFS partitions\n- (macOS, Linux) Fixed Subprocesses of packages sometimes not being closed when the app is closed\n- Fixed Inference tabs sometimes not being restored from previous sessions\n- Fixed multiple log files being archived in a single session, and losing some log entries\n- Fixed error when installing certain packages with comments in the requirements file\n- Fixed error when deleting Inference browser images in a nested project path with recycle bin mode\n- Fixed extra text in positive prompt when loading image parameters in Inference with empty negative prompt value\n- Fixed NullReferenceException that sometimes occurred when closing Inference tabs with images due to Avalonia.Bitmap.Size accessor issue\n- Fixed [#598](https://github.com/LykosAI/StabilityMatrix/issues/598) - program not exiting after printing help or version text\n- Fixed [#630](https://github.com/LykosAI/StabilityMatrix/issues/630) - InvokeAI update hangs forever waiting for input\n- Fixed issue where the \"installed\" state on HuggingFace model browser was not always correct\n- Fixed model folders not being created on startup\n\n### Supporters\n#### Visionaries\n- Shoutout to our Visionary-tier supporters on Patreon, **Scopp Mcdee** and **Waterclouds**! Your generous support is appreciated and helps us continue to make Stability Matrix better for everyone!\n#### Pioneers\n- A big thank you to our Pioneer-tier supporters on Patreon, **tankfox** and **tanangular**! Your support helps us continue to improve Stability Matrix!\n\n## v2.11.0-pre.2\n### Added\n- Added folder shortcuts in Settings for opening common app and system folders, such as Data Directory and Logs.\n### Changed\n- Log file storage have been moved from `%AppData%/StabilityMatrix` to a subfolder: `%AppData%/StabilityMatrix/Logs`\n- Archived log files now have an increased rolling limit of 9 files, from 2 files previously. Their file names will now be in the format `app.{yyyy-MM-dd HH_mm_ss}.log`. The current session log file remains named `app.log`.\n- Updated image controls on Recommended Models dialog to match the rest of the app\n- Improved app shutdown clean-up process reliability and speed\n- Improved ProcessTracker speed and clean-up safety for faster subprocess and package launching performance\n### Fixed\n- Fixed crash when parsing invalid generated images in Output Browser and Inference image viewer, errors will be logged instead and the image will be skipped\n- Fixed issue where blue and red color channels were swapped in the mask editor dialog\n- Fixed missing progress text during package updates\n- Fixed \"Git and Node.js are required\" error during SDFX install\n- (Windows) Fixed \"Open in Explorer\" buttons across the app not opening the correct path on ReFS partitions\n- (Windows) Fixed Sdfx electron window not closing when stopping the package\n- (macOS, Linux) Fixed Subprocesses of packages sometimes not being closed when the app is closed\n- Fixed Inference tabs sometimes not being restored from previous sessions\n- Fixed multiple log files being archived in a single session, and losing some log entries\n- Fixed error when installing certain packages with comments in the requirements file\n- Fixed some more missing progress texts during various activities\n### Supporters\n#### Visionaries\n- A heartfelt thank you to our Visionary-tier Patreon supporters, **Scopp Mcdee** and **Waterclouds**! Your generous contributions enable us to keep enhancing Stability Matrix!\n\n## v2.11.0-pre.1\n### Added\n- Added new package: [SDFX](https://github.com/sdfxai/sdfx/) by sdfxai\n- Added \"Show Nested Models\" toggle for new Checkpoints page, allowing users to show or hide models in subfolders of the selected folder\n- Added ZLUDA option for SD.Next\n- Added PixArt & SDXL Hyper options to the Civitai model browser\n- Added release date to model update notification card on the Checkpoints page\n- Added option in Settings to choose whether to Copy or Move files when dragging and dropping files into the Checkpoint Manager\n- Added more launch options for Forge - [#618](https://github.com/LykosAI/StabilityMatrix/issues/618)\n#### Inference\n- Added Inpainting support for Image To Image projects using the new image mask canvas editor\n### Changed\n- Maximized state is now stored on exit and restored on launch\n- Clicking outside the Select Model Version dialog will now close it\n- Changed Package card buttons to better indicate that they are buttons\n### Fixed\n- Fixed error when deleting Inference browser images in a nested project path with recycle bin mode\n- Fixed extra text in positive prompt when loading image parameters in Inference with empty negative prompt value\n- Fixed NullReferenceException that sometimes occured when closing Inference tabs with images due to Avalonia.Bitmap.Size accessor issue\n- Fixed package installs not showing any progress messages\n- Fixed crash when viewing model details for Unknown model types in the Checkpoint Manager\n- Fixed [#598](https://github.com/LykosAI/StabilityMatrix/issues/598) - program not exiting after printing help or version text\n- Fixed [#630](https://github.com/LykosAI/StabilityMatrix/issues/630) - InvokeAI update hangs forever waiting for input\n### Supporters\n#### Visionaries\n- Many thanks to our Visionary-tier supporters on Patreon, **Scopp Mcdee** and **Waterclouds**! Your generous support helps us continue to improve Stability Matrix!\n\n## v2.11.0-dev.3\n### Added\n- Added download speed indicator to model downloads in the Downloads tab\n- Added XL ControlNets section to HuggingFace model browser\n- Added toggle in Settings for model browser auto-search on load\n- Added optional Recycle Bin mode when deleting images in the Inference image browser, can be disabled in settings (Currently on Windows only)\n### Changed\n- Revamped Checkpoints page now shows available model updates and has better drag & drop functionality\n- Updated HuggingFace page so the command bar stays fixed at the top\n- Revamped file deletion confirmation dialog with affected file paths display and recycle bin / permanent delete options (Checkpoint and Output Browsers) (Currently on Windows only)\n### Fixed\n- Fixed issue where the \"installed\" state on HuggingFace model browser was not always correct\n### Supporters\n#### Visionaries\n- Special shoutout to our first two Visionaries on Patreon, **Scopp Mcdee** and **Waterclouds**! Thank you for your generous support!\n\n## v2.11.0-dev.2\n### Added\n- Added Brazilian Portuguese language option, thanks to jbostroski for the translation!\n- Added setting for locale specific or invariant number formatting\n- Added support for jpg/jpeg & gif images in the Output Browser\n### Changed\n- Centered OpenArt browser cards\n### Fixed\n- Fixed MPS install on macOS for ComfyUI, A1111, SDWebUI Forge, and SDWebUI UX causing torch to be upgraded to dev nightly versions and causing incompatibilities with dependencies.\n- Fixed \"Auto Scroll to End\" not working in some scenarios\n- Fixed \"Auto Scroll to End\" toggle button not scrolling to the end when toggled on\n- Fixed/reverted output folder name changes for Automatic1111\n- Fixed xformers being uninstalled with every ComfyUI update\n- Fixed Inference Lora menu strength resetting to default if out of slider range (0 to 1)\n- Fixed missing progress text during package installs\n\n## v2.11.0-dev.1\n### Added\n- Added search bar to the Python Packages dialog\n#### Inference\n- Alternate Lora / LyCORIS drop-down model selection, can be toggled via the model settings button. The existing prompt-based Lora / LyCORIS method is still available.\n### Fixed\n- Fixed crash when failing to parse Python package details\n\n## v2.10.3\n### Changed\n- Centered OpenArt browser cards\n### Fixed\n- Fixed MPS install on macOS for ComfyUI, A1111, SDWebUI Forge, and SDWebUI UX causing torch to be upgraded to dev nightly versions and causing incompatibilities with dependencies.\n- Fixed crash when failing to parse Python package details\n- Fixed \"Auto Scroll to End\" not working in some scenarios\n- Fixed \"Auto Scroll to End\" toggle button not scrolling to the end when toggled on\n- Fixed/reverted output folder name changes for Automatic1111\n- Fixed xformers being uninstalled with every ComfyUI update\n- Fixed missing progress text during package installs\n\n## v2.10.2\n### Changed\n- Updated translations for Spanish and Turkish\n### Fixed\n- Fixed more crashes when loading invalid connected model info files\n- Fixed pip installs not parsing comments properly\n- Fixed crash when sending input to a process that isn't running\n- Fixed breadcrumb on console page showing incorrect running package name\n- Fixed [#576](https://github.com/LykosAI/StabilityMatrix/issues/576) - drag & drop crashes on macOS & Linux\n- Fixed [#594](https://github.com/LykosAI/StabilityMatrix/issues/594) - missing thumbnails in Inference model selector\n- Fixed [#600](https://github.com/LykosAI/StabilityMatrix/issues/600) - kohya_ss v24+ not launching\n- Downgraded Avalonia back to 11.0.9 to fix [#589](https://github.com/LykosAI/StabilityMatrix/issues/589) and possibly other rendering issues\n\n## v2.10.1\n### Added\n- Added SVD Shared Model & Output Folders for Forge (fixes [#580](https://github.com/LykosAI/StabilityMatrix/issues/580))\n### Changed\n- Improved error message when logging in with a Lykos account fails due to incorrect email or password\n- Model Browser & Workflow Browser now auto-load when first navigating to those pages\n- Removed update confirmation dialog, instead showing the new version in the update button tooltip\n### Fixed\n- Fixed package launch not working when environment variable `SETUPTOOLS_USE_DISTUTILS` is set due to conflict with a default environment variable. User environment variables will now correctly override any default environment variables.\n- Fixed \"No refresh token found\" error when failing to login with Lykos account in some cases\n- Fixed blank entries appearing in the Categories dropdown on the Checkpoints page\n- Fixed crash when loading invalid connected model info files\n- Fixed [#585](https://github.com/LykosAI/StabilityMatrix/issues/585) - Crash when drag & drop source and destination are the same\n- Fixed [#584](https://github.com/LykosAI/StabilityMatrix/issues/584) - `--launch-package` argument not working\n- Fixed [#581](https://github.com/LykosAI/StabilityMatrix/issues/581) - Inference teaching tip showing more often than it should\n- Fixed [#578](https://github.com/LykosAI/StabilityMatrix/issues/578) - \"python setup.py egg_info did not run successfully\" failure when installing Auto1111 or SDWebUI Forge\n- Fixed [#574](https://github.com/LykosAI/StabilityMatrix/issues/574) - local images not showing on macOS or Linux\n\n## v2.10.0\n### Added\n- Added Reference-Only mode for Inference ControlNet, used for guiding the sampler with an image without a pretrained model. Part of the latent and attention layers will be connected to the reference image, similar to Image to Image or Inpainting.\n- Inference ControlNet module now supports over 42 preprocessors, a new button next to the preprocessors dropdown allows previewing the output of the selected preprocessor on the image.\n- Added resolution selection for Inference ControlNet module, this controls preprocessor resolution too.\n- Added Layer Diffuse sampler addon to Inference, allows generating foreground with transparency with SD1.5 and SDXL.\n- Added support for deep links from the new Stability Matrix Chrome extension\n- Added OpenArt.AI workflow browser for ComfyUI workflows\n- Added more metadata to the image dialog info flyout\n- Added Output Sharing toggle in Advanced Options during install flow\n### Changed\n- Revamped the Packages page to enable running multiple packages at the same time\n- Changed the Outputs Page to use a TreeView for the directory selection instead of a dropdown selector\n- Model download location selector now searches all subfolders\n- Inference Primary Sampler Addons (i.e. ControlNet, FreeU) are now inherited by Hires Fix Samplers, this can be overriden from the Hires Fix module's settings menu by disabling the \"Inherit Primary Sampler Addons\" option.\n- Revisited the way images are loaded on the outputs page, with improvements to loading speed & not freezing the UI while loading\n- Updated translations for French, Spanish, and Turkish\n- Changed to a new image control for pages with many images\n- (Internal) Updated to Avalonia 11.0.10\n### Fixed\n- Fixed [#559](https://github.com/LykosAI/StabilityMatrix/issues/559) - \"Unable to load bitmap from provided data\" error in Checkpoints page\n- Fixed [#522](https://github.com/LykosAI/StabilityMatrix/issues/522) - Incorrect output directory path for latest Auto1111\n- Fixed [#529](https://github.com/LykosAI/StabilityMatrix/issues/529) - OneTrainer requesting input during update\n- Fixed Civitai model browser error when sorting by Installed with more than 100 installed models\n- Fixed CLIP Install errors due to setuptools distutils conflict, added default environment variable setting `SETUPTOOLS_USE_DISTUTILS=stdlib`\n- Fixed progress bars not displaying properly during package installs & updates\n- Fixed ComfyUI extension updates not running install.py / updating requirements.txt\n- Improved performance when deleting many images from the Outputs page\n- Fixed ComfyUI torch downgrading to 2.1.2 when updating\n- Fixed Inference HiresFix module \"Inherit Primary Sampler Addons\" setting not effectively disabling when unchecked\n- Fixed model download location options for VAEs in the CivitAI Model Browser\n### Removed\n- Removed the main Launch page, as it is no longer needed with the new Packages page\n\n## v2.10.0-pre.2\n### Added\n- Added more metadata to the image dialog info flyout\n- Added Restart button to console page\n### Changed\n- Model download location selector now searches all subfolders\n### Fixed\n- Fixed Civitai model browser not showing images when \"Show NSFW\" is disabled\n- Fixed crash when Installed Workflows page is opened with no Workflows folder\n- Fixed progress bars not displaying properly during package installs & updates\n- Fixed ComfyUI extension updates not running install.py / updating requirements.txt\n\n## v2.10.0-pre.1\n### Added\n- Added OpenArt.AI workflow browser for ComfyUI workflows\n- Added Output Sharing toggle in Advanced Options during install flow\n### Changed\n- Changed to a new image control for pages with many images\n- Removed Symlink option for InvokeAI due to changes with InvokeAI v4.0+\n- Output sharing is now enabled by default for new installations\n- (Internal) Updated to Avalonia 11.0.10\n### Fixed\n- Improved performance when deleting many images from the Outputs page\n- Fixed ComfyUI torch downgrading to 2.1.2 when updating\n- Fixed [#529](https://github.com/LykosAI/StabilityMatrix/issues/529) - OneTrainer requesting input during update\n- Fixed \"Could not find entry point for InvokeAI\" error on InvokeAI v4.0+\n\n## v2.10.0-dev.3\n### Added\n- Added support for deep links from the new Stability Matrix Chrome extension\n### Changed\n- Due to changes on the CivitAI API, you can no longer select a specific page in the CivitAI Model Browser\n- Due to the above API changes, new pages are now loaded via \"infinite scrolling\"\n### Fixed\n- Fixed Inference HiresFix module \"Inherit Primary Sampler Addons\" setting not effectively disabling when unchecked\n- Fixed model download location options for VAEs in the CivitAI Model Browser\n- Fixed crash on startup when library directory is not set\n- Fixed One-Click install progress dialog not disappearing after completion\n- Fixed ComfyUI with Inference pop-up during one-click install appearing below the visible scroll area\n- Fixed no packages being available for one-click install on PCs without a GPU\n- Fixed models not being removed from the installed models cache when deleting them from the Checkpoints page\n- Fixed missing ratings on some models in the CivitAI Model Browser\n- Fixed missing favorite count in the CivitAI Model Browser\n- Fixed recommended models not showing all SDXL models\n\n## v2.10.0-dev.2\n### Added\n- Added Reference-Only mode for Inference ControlNet, used for guiding the sampler with an image without a pretrained model. Part of the latent and attention layers will be connected to the reference image, similar to Image to Image or Inpainting.\n### Changed\n- Inference Primary Sampler Addons (i.e. ControlNet, FreeU) are now inherited by Hires Fix Samplers, this can be overriden from the Hires Fix module's settings menu by disabling the \"Inherit Primary Sampler Addons\" option.\n- Revisited the way images are loaded on the outputs page, with improvements to loading speed & not freezing the UI while loading\n### Fixed\n- Fixed Outputs page not remembering where the user last was in the TreeView in certain circumstances\n- Fixed Inference extension upgrades not being added to missing extensions list for prompted install\n- Fixed \"The Open Web UI button has moved\" teaching tip spam\n\n## v2.10.0-dev.1\n### Added\n- Inference ControlNet module now supports over 42 preprocessors, a new button next to the preprocessors dropdown allows previewing the output of the selected preprocessor on the image.\n- Added resolution selection for Inference ControlNet module, this controls preprocessor resolution too.\n### Changed\n- Revamped the Packages page to enable running multiple packages at the same time\n- Changed the Outputs Page to use a TreeView for the directory selection instead of a dropdown selector\n### Removed\n- Removed the main Launch page, as it is no longer needed with the new Packages page\n\n## v2.9.3\n### Changed\n- Removed Symlink option for InvokeAI to prevent InvokeAI from moving models into its own directories (will be replaced with a Config option in a future update)\n### Fixed\n- Fixed images not appearing in Civitai Model Browser when \"Show NSFW\" was disabled\n- Fixed [#556](https://github.com/LykosAI/StabilityMatrix/issues/556) - \"Could not find entry point for InvokeAI\" error\n\n## v2.9.2\n### Changed\n- Due to changes with the CivitAI API, you can no longer select a specific page in the CivitAI Model Browser\n- Due to the above API changes, new pages are now loaded via \"infinite scrolling\"\n### Fixed\n- Fixed models not being removed from the installed models cache when deleting them from the Checkpoints page\n- Fixed model download location options for VAEs in the CivitAI Model Browser\n- Fixed One-Click install progress dialog not disappearing after completion\n- Fixed ComfyUI with Inference pop-up during one-click install appearing below the visible scroll area\n- Fixed no packages being available for one-click install on PCs without a GPU\n\n## v2.9.1\n### Added\n- Fixed [#498](https://github.com/LykosAI/StabilityMatrix/issues/498) Added \"Pony\" category to CivitAI Model Browser\n### Changed\n- Changed package deletion warning dialog to require additional confirmation\n### Fixed\n- Fixed [#502](https://github.com/LykosAI/StabilityMatrix/issues/502) - missing launch options for Forge\n- Fixed [#500](https://github.com/LykosAI/StabilityMatrix/issues/500) - missing output images in Forge when using output sharing\n- Fixed [#490](https://github.com/LykosAI/StabilityMatrix/issues/490) - `mpmath has no attribute 'rational'` error on macOS\n- Fixed [#510](https://github.com/ionite34/StabilityMatrix/pull/564/files) - kohya_ss packages with v23.0.x failing to install due to missing 'packaging' dependency\n- Fixed incorrect progress text when deleting a checkpoint from the Checkpoints page\n- Fixed incorrect icon colors on macOS\n\n## v2.9.0\n### Added\n- Added new package: [StableSwarmUI](https://github.com/Stability-AI/StableSwarmUI) by Stability AI\n- Added new package: [Stable Diffusion WebUI Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge) by lllyasviel\n- Added extension management for SD.Next and Stable Diffusion WebUI-UX\n- Added the ability to choose where CivitAI model downloads are saved\n- Added `--launch-package` argument to launch a specific package on startup, using display name or package ID (i.e. `--launch-package \"Stable Diffusion WebUI Forge\"` or `--launch-package c0b3ecc5-9664-4be9-952d-a10b3dcaee14`)\n- Added more Base Model search options to the CivitAI Model Browser\n- Added Stable Cascade to the HuggingFace Model Browser\n#### Inference\n- Added Inference Prompt Styles, with Prompt Expansion model support (i.e. Fooocus V2)\n- Added option to load a .yaml config file next to the model with the same name. Can be used with VPred and other models that require a config file.\n- Added copy image support on linux and macOS for Inference outputs viewer menu\n### Changed\n- Updated translations for German, Spanish, French, Japanese, Portuguese, and Turkish\n- (Internal) Updated to Avalonia 11.0.9\n### Fixed\n- Fixed StableSwarmUI not installing properly on macOS\n- Fixed [#464](https://github.com/LykosAI/StabilityMatrix/issues/464) - error when installing InvokeAI on macOS\n- Fixed [#335](https://github.com/LykosAI/StabilityMatrix/issues/335) Update hanging indefinitely after git step for Auto1111 and SDWebUI Forge\n- Fixed Inference output viewer menu \"Copy\" not copying image\n- Fixed image viewer dialog arrow key navigation not working\n- Fixed CivitAI login prompt not showing when downloading models that require CivitAI logins\n- Fixed unknown model types not showing on checkpoints page (thanks Jerry!)\n- Improved error handling for Inference Select Image hash calculation in case file is being written to while being read\n\n## v2.9.0-pre.2\n### Added\n- Added `--launch-package` argument to launch a specific package on startup, using display name or package ID (i.e. `--launch-package \"Stable Diffusion WebUI Forge\"` or `--launch-package c0b3ecc5-9664-4be9-952d-a10b3dcaee14`)\n- Added more Base Model search options to the CivitAI Model Browser\n- Added Stable Cascade to the HuggingFace Model Browser\n### Changed\n- (Internal) Updated to Avalonia 11.0.9\n### Fixed\n- Fixed image viewer dialog arrow key navigation not working\n- Fixed CivitAI login prompt not showing when downloading models that require CivitAI logins\n\n## v2.9.0-pre.1\n### Added\n- Added Inference Prompt Styles, with Prompt Expansion model support (i.e. Fooocus V2)\n- Added copy image support on linux and macOS for Inference outputs viewer menu\n### Fixed\n- Fixed StableSwarmUI not installing properly on macOS\n- Fixed output sharing for Stable Diffusion WebUI Forge\n- Hopefully actually fixed [#464](https://github.com/LykosAI/StabilityMatrix/issues/464) - error when installing InvokeAI on macOS\n- Fixed default command line args for SDWebUI Forge on macOS\n- Fixed output paths and output sharing for SDWebUI Forge\n- Maybe fixed update hanging for Auto1111 and SDWebUI Forge\n- Fixed Inference output viewer menu \"Copy\" not copying image \n\n## v2.9.0-dev.2\n### Added\n#### Inference\n- Added option to load a .yaml config file next to the model with the same name. Can be used with VPred and other models that require a config file.\n### Fixed\n- Fixed icon sizes of Inference Addons and Steps buttons\n\n## v2.9.0-dev.1\n### Added\n- Added new package: [StableSwarmUI](https://github.com/Stability-AI/StableSwarmUI) by Stability AI\n- Added new package: [Stable Diffusion WebUI Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge) by lllyasviel\n- Added extension management for SD.Next and Stable Diffusion WebUI-UX\n- Added the ability to choose where CivitAI model downloads are saved\n\n## v2.8.4\n### Fixed\n- Hopefully actually fixed [#464](https://github.com/LykosAI/StabilityMatrix/issues/464) - error when installing InvokeAI on macOS\n\n## v2.8.3\n### Fixed\n- Fixed user tokens read error causing failed downloads\n- Failed downloads will now log error messages\n- Fixed [#458](https://github.com/LykosAI/StabilityMatrix/issues/458) - Save Intermediate Image not working\n- Fixed [#453](https://github.com/LykosAI/StabilityMatrix/issues/453) - Update Fooocus `--output-directory` argument to `--output-path`\n\n## v2.8.2\n### Added\n- Added missing GFPGAN link to Automatic1111 packages\n### Fixed\n- Fixed Inference Image to Image Denoise setting becoming hidden after changing schedulers\n- Fixed Inference ControlNet models showing as downloadable even when they are already installed\n- Fixed Inference Sampler Addon conditioning not applying (i.e. ControlNet)\n- Fixed extension modification dialog not showing any progress messages\n\n## v2.8.1\n### Fixed\n- Fixed model links not working in RuinedFooocus for new installations\n- Fixed incorrect nodejs download link on Linux (thanks to slogonomo for the fix)\n- Fixed failing InvokeAI install on macOS due to missing nodejs\n- Increased timeout on Recommended Models call to prevent potential timeout errors on slow connections\n- Fixed SynchronizationLockException when saving settings\n- Improved error messages with process output for 7z extraction errors\n- Fixed missing tkinter dependency for OneTrainer on Windows\n- Fixed auto-update on macOS not starting new version from an issue in starting .app bundles with arguments\n- Fixed [#436](https://github.com/LykosAI/StabilityMatrix/issues/436) - Crash on invalid json files during checkpoint indexing\n\n## v2.8.0\n### Added\n- Added Image to Video project type\n- Added CLIP Skip setting to inference, toggleable from the model settings button\n- Added image and model details in model selection boxes\n- Added new package: [OneTrainer](https://github.com/Nerogar/OneTrainer)\n- Added native desktop push notifications for some events (i.e. Downloads, Package installs, Inference generation)\n  - Currently available on Windows and Linux, macOS support is pending\n- Added Package Extensions (Plugins) management - accessible from the Packages' 3-dot menu. Currently supports ComfyUI and Automatic1111.\n- Added new launch argument options for Fooocus\n- Added \"Config\" Shared Model Folder option for Fooocus\n- Added Recommended Models dialog after one-click installer\n- Added \"Copy Details\" button to Unexpected Error dialog\n- Added German language option, thanks to Mario da Graca for the translation\n- Added Portuguese language options, thanks to nextosai for the translation\n- Added base model filter to Checkpoints page\n- Added \"Compatible Images\" category when selecting images for Inference projects\n- Added \"Find in Model Browser\" option to the right-click menu on the Checkpoints page\n- Added `--use-directml` launch argument for SDWebUI DirectML fork\n- Added release builds for macOS (Apple Silicon)\n- Added ComfyUI launch argument configs: Cross Attention Method, Force Floating Point Precision, VAE Precision\n- Added Delete button to the CivitAI Model Browser details dialog\n- Added \"Copy Link to Clipboard\" for connected models in the Checkpoints page\n- Added support for webp files to the Output Browser\n- Added \"Send to Image to Image\" and \"Send to Image to Video\" options to the context menu\n### Changed\n- New package installation flow\n- Changed one-click installer to match the new package installation style\n- Automatic1111 packages will now use PyTorch v2.1.2. Upgrade will occur during the next package update or upon fresh installation.\n- Search box on Checkpoints page now searches tags and trigger words\n- Changed the Close button on the package install dialog to \"Hide\"\n  - Functionality remains the same, just a name change\n- Updated translations for the following languages:\n  - Spanish\n  - French\n  - Japanese\n  - Turkish\n- Inference file name patterns with directory separator characters will now have the subdirectories created automatically\n- Changed how settings file is written to disk to reduce potential data loss risk\n- (Internal) Updated to Avalonia 11.0.7\n### Fixed\n- Fixed error when ControlNet module image paths are not found, even if the module is disabled\n- Fixed error when finding metadata for archived models\n- Fixed error when extensions folder is missing\n- Fixed crash when model was not selected in Inference\n- Fixed Fooocus Config shared folder mode overwriting unknown config keys\n- Fixed potential SD.Next update issues by moving to shared update process\n- Fixed crash on startup when Outputs page failed to load categories properly\n- Fixed image gallery arrow key navigation requiring clicking before responding\n- Fixed crash when loading extensions list with no internet connection\n- Fixed crash when invalid launch arguments are passed\n- Fixed missing up/downgrade buttons on the Python Packages dialog when the version was not semver compatible\n\n\n## v2.8.0-pre.5\n### Fixed\n- Fixed error when ControlNet module image paths are not found, even if the module is disabled\n- Fixed error when finding metadata for archived models\n- Fixed error when extensions folder is missing\n- Fixed error when webp files have incorrect metadata\n- Fixed crash when model was not selected in Inference\n- Fixed Fooocus Config shared folder mode overwriting unknown config keys\n\n## v2.8.0-pre.4\n### Added\n- Added Recommended Models dialog after one-click installer\n- Added native desktop push notifications for some events (i.e. Downloads, Package installs, Inference generation)\n  - Currently available on Windows and Linux, macOS support is pending\n- Added settings options for notifications\n- Added new launch argument options for Fooocus\n- Added Automatic1111 & Stable Diffusion WebUI-UX to the compatible macOS packages\n### Changed\n- Changed one-click installer to match the new package installation style\n- Automatic1111 packages will now use PyTorch v2.1.2. Upgrade will occur during the next package update or upon fresh installation.\n- Updated French translation with the latest changes\n### Fixed\n- Fixed [#413](https://github.com/LykosAI/StabilityMatrix/issues/413) - Environment Variables are editable again\n- Fixed potential SD.Next update issues by moving to shared update process\n- Fixed Invoke install trying to use system nodejs\n- Fixed crash on startup when Outputs page failed to load categories properly\n\n## v2.8.0-pre.3\n### Added\n- Added \"Config\" Shared Model Folder option for Fooocus\n- Added \"Copy Details\" button to Unexpected Error dialog\n### Changed\n- (Internal) Updated to Avalonia 11.0.7\n- Changed the Close button on the package install dialog to \"Hide\" \n  - Functionality remains the same, just a name change\n- Updated French translation (thanks Greg!)\n### Fixed\n- Webp static images can now be shown alongside existing webp animation support\n- Fixed image gallery arrow key navigation requiring clicking before responding\n- Fixed crash when loading extensions list with no internet connection\n- Fixed crash when invalid launch arguments are passed\n- Fixed \"must give at least one requirement to install\" error when installing extensions with empty requirements.txt\n\n## v2.8.0-pre.2\n### Added\n- Added German language option, thanks to Mario da Graca for the translation\n- Added Portuguese language options, thanks to nextosai for the translation\n### Changed\n- Updated translations for the following languages:\n  - Spanish\n  - French\n  - Japanese\n  - Turkish\n### Fixed\n- Fixed Auto-update failing to start new version on Windows and Linux when path contains spaces\n- Fixed InvokeAI v3.6.0 `\"detail\": \"Not Found\"` error when opening the UI\n- Install button will now be properly disabled when the duplicate warning is shown\n\n## v2.8.0-pre.1\n### Added\n- Added Package Extensions (Plugins) management - accessible from the Packages' 3-dot menu. Currently supports ComfyUI and A1111.\n- Added base model filter to Checkpoints page\n- Search box on Checkpoints page now searches tags and trigger words\n- Added \"Compatible Images\" category when selecting images for Inference projects\n- Added \"Find in Model Browser\" option to the right-click menu on the Checkpoints page\n### Changed\n- Removed \"Failed to load image\" notification when loading some images on the Checkpoints page\n- Installed models will no longer be selectable on the Hugging Face tab of the model browser\n### Fixed\n- Inference file name patterns with directory separator characters will now have the subdirectories created automatically\n- Fixed missing up/downgrade buttons on the Python Packages dialog when the version was not semver compatible\n- Automatic1111 package installs will now install the missing `jsonmerge` package\n\n## v2.8.0-dev.4\n### Added\n- Auto-update support for macOS\n- New package installation flow\n- Added `--use-directml` launch argument for SDWebUI DirectML fork\n### Changed\n- Changed default Period to \"AllTime\" in the Model Browser\n### Fixed\n- Fixed SDTurboScheduler's missing denoise parameter\n\n## v2.8.0-dev.3\n### Added\n- Added release builds for macOS (Apple Silicon)\n- Added new package: [OneTrainer](https://github.com/Nerogar/OneTrainer)\n- Added ComfyUI launch argument configs: Cross Attention Method, Force Floating Point Precision, VAE Precision\n- Added Delete button to the CivitAI Model Browser details dialog\n- Added \"Copy Link to Clipboard\" for connected models in the Checkpoints page\n### Changed\n- Python Packages install dialog now allows entering multiple arguments or option flags\n### Fixed\n- Fixed environment variables grid not being editable related to [Avalonia #13843](https://github.com/AvaloniaUI/Avalonia/issues/13843)\n\n## v2.8.0-dev.2\n### Added\n#### Inference\n- Added Image to Video project type\n#### Output Browser\n- Added support for webp files\n- Added \"Send to Image to Image\" and \"Send to Image to Video\" options to the context menu\n### Changed\n- Changed how settings file is written to disk to reduce potential data loss risk\n\n## v2.8.0-dev.1\n### Added\n#### Inference\n- Added image and model details in model selection boxes\n- Added CLIP Skip setting, toggleable from the model settings button\n\n## v2.7.9\n### Fixed\n- Fixed InvokeAI v3.6.0 `\"detail\": \"Not Found\"` error when opening the UI\n\n## v2.7.8\n### Changed\n- Python Packages install dialog now allows entering multiple arguments or option flags\n### Fixed\n- Fixed InvokeAI Package dependency versions ([#395](https://github.com/LykosAI/StabilityMatrix/pull/395))\n\n## v2.7.7\n### Added\n- Added `--use-directml` launch argument for SDWebUI DirectML fork\n### Changed\n- Model Browser downloads will no longer be disabled if the free drive space is unavailable\n- Default Linux installation folder changed to prevent issues with hidden folders\n- Changed default Period to \"AllTime\" in the Model Browser\n### Fixed\n- Fixed error where Environment Variables were not editable\n- Fixed SDTurboScheduler's missing denoise parameter\n\n## v2.7.6\n### Added\n- Added SDXL Turbo and Stable Video Diffusion to the Hugging Face tab\n### Changed\n- ControlNet model selector will now show the parent directory of a model when relevant\n### Fixed\n- Fixed Python Packages dialog crash due to pip commands including warnings\n- Fixed Base Model downloads from the Hugging Face tab downloading to the wrong folder\n- Fixed InvokeAI `! [rejected] v3.4.0post2 -> v3.4.0post2 (would clobber existing tag)` error on updating to the latest version \n- Fixed settings not saving in some scenarios, such as when the `settings.json` file existed but was empty\n\n## v2.7.5\n### Fixed\n- Fixed Python Packages manager crash when pip list returns warnings in json\n- Fixed slowdown when loading PNGs with large amounts of metadata\n- Fixed crash when scanning directories for missing metadata\n\n## v2.7.4\n### Changed\n- Improved low disk space handling\n### Fixed\n- Fixed denoise strength in Inference Text to Image\n- Fixed PathTooLongException for IPAdapter folders when using ComfyUI in Symlink mode\n- Fixed configs and symlinks not being cleaned up when switched to the opposite mode\n- Fixed model indexing stopping when encountering paths longer than 1021 bytes in length\n- Fixed repeated nested folders being created in `Models/ControlNet` when using ComfyUI in Symlink mode. Existing folders will be repaired to their original structure on launch.\n\n## v2.7.3\n### Added\n- Added missing IPAdapter and CLIP Vision folder links for ComfyUI\n### Fixed\n- Fixed UnicodeDecodeError when using extra_model_paths.yaml in ComfyUI on certain locales\n- Fixed SDXL CLIP Vision model directory name conflict\n- Fixed [#334](https://github.com/LykosAI/StabilityMatrix/issues/334) - Win32Exception if Settings are opened\n\n## v2.7.2\n### Changed\n- Changed Symlink shared folder link targets for Automatic1111 and ComfyUI. From `ControlNet -> models/controlnet` to `ControlNet -> models/controlnet/ControlNet` and `T2IAdapter -> models/controlnet/T2IAdapter`.\n- Changed FreeU defaults to match recommended SD1.5 defaults\n- Changed default denoise strength from 1.0 to 0.7\n### Fixed\n- Fixed ControlNet / T2IAdapter shared folder links for Automatic1111 conflicting with each other\n- Fixed URIScheme registration errors on Linux\n- Fixed RuinedFooocus missing output folder on startup\n- Fixed incorrect Fooocus VRAM launch arguments\n\n## v2.7.1\n### Added\n- Added Turkish UI language option, thanks to Progesor for the translation\n### Fixed\n- Fixed Inference Image to Image projects missing denoise strength setting\n\n## v2.7.0\n### Added\n#### General\n- New package: [RuinedFooocus](https://github.com/runew0lf/RuinedFooocus)\n- Added an X button to all search fields to instantly clear them (Esc key also works)\n- Added System Information section to Settings\n#### Inference\n- Added Image to Image project type\n- Added Modular custom steps\n  - Use the plus button to add new steps (Hires Fix, Upscaler, and Save Image are currently available), and the edit button to enable removing or dragging steps to reorder them. This enables multi-pass Hires Fix, mixing different upscalers, and saving intermediate images at any point in the pipeline.\n- Added Sampler addons\n  - Addons usually affect guidance like ControlNet, T2I, FreeU, and other addons to come. They apply to the individual sampler, so you can mix and match different ControlNets for Base and Hires Fix, or use the current output from a previous sampler as ControlNet guidance image for HighRes passes.\n- Added SD Turbo Scheduler\n- Added display names for new samplers (\"Heun++ 2\", \"DDPM\", \"LCM\")\n- Added Ctrl+Enter as a shortcut for the Generate Image button\n#### Accounts Settings Subpage\n- Lykos Account sign-up and login - currently for Patreon OAuth connections but GitHub requests caching and settings sync are planned\n- Supporters can now connect your Patreon accounts, then head to the Updates page to choose to receive auto-updates from the Dev or Preview channels\n- CivitAI Account login with API key - enables downloading models from the Browser page that require CivitAI logins, more integrations like liking and commenting are also planned\n#### Updates Settings Subpage\n- Toggle auto-update notifications and manually check for updates\n- Choose between Stable, Preview, and Dev update channels\n#### Inference Settings Subpage\n- Moved Inference settings to subpage\n- Updated with more localized labels\n#### Outputs Page\n- Added Refresh button to update gallery from file system changes\n#### Checkpoints Page\n- Added the ability to drag & drop checkpoints between different folders \n- Added \"Copy Trigger Words\" option to the three-dots menu on the Checkpoints page (when data is available)\n- Added trigger words on checkpoint card and tooltip\n- Added \"Find Connected Metadata\" options for root-level and file-level scans\n- Added \"Update Existing Metadata\" button\n#### Model Browser\n- Added Hugging Face tab to the Model Browser\n- Added additional base model filter options for CivitAI (\"SD 1.5 LCM\", \"SDXL 1.0 LCM\", \"SDXL Turbo\", \"Other\")\n- Added the ability to type in a specific page number in the CivitAI Model Browser\n- Right clicking anywhere on the model card will open the same menu as the three-dots button\n- New model downloads will save trigger words in metadata, if available\n- Model author username and avatar display, with clickable link to their profile\n### Changed\n#### General\n- Model Browser page has been redesigned, featuring more information like rating and download counts\n- Model Browser navigation has improved animations on hover and compact number formatting\n- Updated Outputs Page button and menu layout\n- Rearranged Add Package dialog slightly to accommodate longer package list\n- Folder-level \"Find Connected Metadata\" now scans the selected folder and its subfolders\n- Model Browser now split into \"CivitAI\" and \"Hugging Face\" tabs\n#### Inference\n- Selected images (i.e. Image2Image, Upscale, ControlNet) will now save their source paths saved and restored on load. If the image is moved or deleted, the selection will show as missing and can be reselected\n- Project files (.smproj) have been updated to v3, existing projects will be upgraded on load and will no longer be compatible with older versions of Stability Matrix\n### Fixed\n- Fixed Outputs page reverting back to Shared Output Folder every time the page is reloaded\n- Potentially fixed updates sometimes clearing settings or launching in the wrong directory\n- Improved startup time and window load time after exiting dialogs\n- Fixed control character decoding that caused some progress bars to show as `\\u2588`\n- Fixed Python `rich` package's progress bars not showing in console\n- Optimized ProgressRing animation bindings to reduce CPU usage\n- Improved safety checks in custom control rendering to reduce potential graphical artifacts\n- Improved console rendering safety with cursor line increment clamping, as potential fix for [#111](https://github.com/LykosAI/StabilityMatrix/issues/111)\n- Fixed [#290](https://github.com/LykosAI/StabilityMatrix/issues/290) - Model browser crash due to text trimming certain unicode characters\n- Fixed crash when loading an empty settings file\n- Improve Settings save and load performance with .NET 8 Source Generating Serialization\n- Fixed ApplicationException during database shutdown\n- InvokeAI model links for T2I/IpAdapters now point to the correct folders\n- Added extra checks to help prevent settings resetting in certain scenarios\n- Fixed Refiner model enabled state not saving to Inference project files\n- Fixed NullReference error labels when clearing the Inference batch size settings, now shows improved message with minimum and maximum value constraints\n\n## v2.7.0-pre.4\n### Added\n#### Inference\n- Added Image to Image project type\n- Added Modular custom steps\n  - Use the plus button to add new steps (Hires Fix, Upscaler, and Save Image are currently available), and the edit button to enable removing or dragging steps to reorder them. This enables multi-pass Hires Fix, mixing different upscalers, and saving intermediate images at any point in the pipeline.\n- Added Sampler addons\n  - Addons usually affect guidance like ControlNet, T2I, FreeU, and other addons to come. They apply to the individual sampler, so you can mix and match different ControlNets for Base and Hires Fix, or use the current output from a previous sampler as ControlNet guidance image for HighRes passes.\n- Added SD Turbo Scheduler\n- Added display names for new samplers (\"Heun++ 2\", \"DDPM\", \"LCM\")\n#### Model Browser\n- Added additional base model filter options (\"SD 1.5 LCM\", \"SDXL 1.0 LCM\", \"SDXL Turbo\", \"Other\")\n### Changed\n#### Inference\n- Selected images (i.e. Image2Image, Upscale, ControlNet) will now save their source paths saved and restored on load. If the image is moved or deleted, the selection will show as missing and can be reselected\n- Project files (.smproj) have been updated to v3, existing projects will be upgraded on load and will no longer be compatible with older versions of Stability Matrix\n### Fixed\n- Fixed Refiner model enabled state not saving to Inference project files\n \n## v2.7.0-pre.3\n### Added\n- Added \"Find Connected Metadata\" options for root-level and file-level scans to the Checkpoints page\n- Added \"Update Existing Metadata\" button to the Checkpoints page\n- Added Hugging Face tab to the Model Browser\n- Added the ability to type in a specific page number in the CivitAI Model Browser\n### Changed\n- Folder-level \"Find Connected Metadata\" now scans the selected folder and its subfolders\n- Model Browser now split into \"CivitAI\" and \"Hugging Face\" tabs\n### Fixed\n- InvokeAI model links for T2I/IpAdapters now point to the correct folders\n- Added extra checks to help prevent settings resetting in certain scenarios\n\n## v2.7.0-pre.2\n### Added\n- Added System Information section to Settings\n### Changed\n- Moved Inference Settings to subpage\n### Fixed\n- Fixed crash when loading an empty settings file\n- Improve Settings save and load performance with .NET 8 Source Generating Serialization\n- Fixed ApplicationException during database shutdown\n\n## v2.7.0-pre.1\n### Fixed\n- Fixed control character decoding that caused some progress bars to show as `\\u2588`\n- Fixed Python `rich` package's progress bars not showing in console\n- Optimized ProgressRing animation bindings to reduce CPU usage\n- Improved safety checks in custom control rendering to reduce potential graphical artifacts\n- Improved console rendering safety with cursor line increment clamping, as potential fix for [#111](https://github.com/LykosAI/StabilityMatrix/issues/111)\n\n## v2.7.0-dev.4\n### Fixed\n- Fixed [#290](https://github.com/LykosAI/StabilityMatrix/issues/290) - Model browser crash due to text trimming certain unicode characters \n\n## v2.7.0-dev.3\n### Added\n- New package: [RuinedFooocus](https://github.com/runew0lf/RuinedFooocus)\n#### Model Browser\n- Right clicking anywhere on the model card will open the same menu as the three-dots button\n- New model downloads will save trigger words in metadata, if available\n- Model author username and avatar display, with clickable link to their profile\n#### Checkpoints Page\n- Added \"Copy Trigger Words\" option to the three-dots menu on the Checkpoints page (when data is available)\n- Added trigger words on checkpoint card and tooltip\n### Changed\n#### Model Browser\n- Improved number formatting with K/M suffixes for download and favorite counts\n- Animated zoom effect on hovering over model images\n#### Checkpoints Page\n- Rearranged top row layout to use CommandBar\n### Fixed\n- Improved startup time and window load time after exiting dialogs\n\n## v2.7.0-dev.2\n### Added\n#### General\n- Added an X button to all search fields to instantly clear them (Esc key also works) \n#### Outputs Page \n- Added Refresh button to update gallery from file system changes\n#### Checkpoints Page\n- Added the ability to drag & drop checkpoints between different folders\n### Changed\n#### Outputs Page\n- Updated button and menu layout\n#### Packages Page\n- Rearranged Add Package dialog slightly to accommodate longer package list\n### Fixed\n- Fixed InvalidOperation errors when signing into accounts shortly after signing out, while the previous account update is still running\n- Fixed Outputs page reverting back to Shared Output Folder every time the page is reloaded\n- Potentially fixed updates sometimes clearing settings or launching in the wrong directory\n\n## v2.7.0-dev.1\n### Added\n- Accounts Settings Subpage\n  - Lykos Account sign-up and login - currently for Patreon OAuth connections but GitHub requests caching and settings sync are planned\n  - Supporters can now connect your Patreon accounts, then head to the Updates page to choose to receive auto-updates from the Dev or Preview channels\n  - CivitAI Account login with API key - enables downloading models from the Browser page that require CivitAI logins, more integrations like liking and commenting are also planned\n- Updates Settings Subpage\n  - Toggle auto-update notifications and manually check for updates\n  - Choose between Stable, Preview, and Dev update channels\n### Changed\n- Model Browser page has been redesigned, featuring more information like rating and download counts\n\n## v2.6.7\n### Fixed\n- Fixed prerequisite install not unpacking due to improperly formatted 7z argument (Caused the \"python310._pth FileNotFoundException\")\n- Fixed [#301](https://github.com/LykosAI/StabilityMatrix/issues/301) - Package updates failing silently because of a PortableGit error\n\n## v2.6.6\n### Fixed\n- Fixed [#297](https://github.com/LykosAI/StabilityMatrix/issues/297) - Model browser LiteAsyncException occuring when fetching entries with unrecognized values from enum name changes\n\n## v2.6.5\n### Fixed\n- Fixed error when receiving unknown model format values from the Model Browser\n- Fixed process errors when installing or updating Pip packages using the Python packages dialog\n\n## v2.6.4\n### Fixed\n- Fixed errors preventing Model Browser from finding results with certain search queries\n\n## v2.6.3\n### Fixed\n- Fixed InvalidOperationException during prerequisite installs on certain platforms where process name and duration reporting are not supported\n\n## v2.6.2\n### Changed\n- Backend changes for auto-update schema v3, supporting customizable release channels and faster downloads with zip compression\n### Fixed\n- Better error reporting including outputs for git subprocess errors during package install / update\n- Fixed `'accelerate' is not recognized as an internal or external command` error when starting training in kohya_ss\n- Fixed some instances of `ModuleNotFoundError: No module named 'bitsandbytes.cuda_setup.paths'` error when using 8-bit optimizers in kohya_ss\n- Fixed errors preventing Inference outputs from loading in the img2img tabs of other packages \n\n## v2.6.1\n### Changed\n- NVIDIA GPU users will be updated to use CUDA 12.1 for the InvokeAI package for a slight performance improvement\n  - Update will occur the next time the package is updated, or on a fresh install\n  - Note: CUDA 12.1 is only available on Maxwell (GTX 900 series) and newer GPUs  \n### Fixed\n- Reduced the amount of calls to GitHub to help prevent rate limiting\n- Fixed rate limit crash on startup preventing app from starting\n\n## v2.6.0\n### Added\n- Added **Output Sharing** option for all packages in the three-dots menu on the Packages page\n  - This will link the package's output folders to the relevant subfolders in the \"Outputs\" directory\n    - When a package only has a generic \"outputs\" folder, all generated images from that package will be linked to the \"Outputs\\Text2Img\" folder when this option is enabled\n- Added **Outputs page** for viewing generated images from any package, or the shared output folder\n- Added [Stable Diffusion WebUI/UX](https://github.com/anapnoe/stable-diffusion-webui-ux) package\n- Added [Stable Diffusion WebUI-DirectML](https://github.com/lshqqytiger/stable-diffusion-webui-directml) package\n- Added [kohya_ss](https://github.com/bmaltais/kohya_ss) package\n- Added [Fooocus-ControlNet-SDXL](https://github.com/fenneishi/Fooocus-ControlNet-SDXL) package\n- Added GPU compatibility badges to the installers\n- Added filtering of \"incompatible\" packages (ones that do not support your GPU) to all installers \n  - This can be overridden by checking the new \"Show All Packages\" checkbox\n- Added more launch options for Fooocus, such as the `--preset` option\n- Added Ctrl+ScrollWheel to change image size in the inference output gallery and new Outputs page\n- Added \"No Images Found\" placeholder for non-connected models on the Checkpoints tab\n- Added \"Open on GitHub\" option to the three-dots menu on the Packages page\n### Changed\n- If ComfyUI for Inference is chosen during the One-Click Installer, the Inference page will be opened after installation instead of the Launch page\n- Changed all package installs & updates to use git commands instead of downloading zip files\n- The One-Click Installer now uses the new progress dialog with console\n- NVIDIA GPU users will be updated to use CUDA 12.1 for ComfyUI & Fooocus packages for a slight performance improvement\n  - Update will occur the next time the package is updated, or on a fresh install\n  - Note: CUDA 12.1 is only available on Maxwell (GTX 900 series) and newer GPUs\n- Improved Model Browser download stability with automatic retries for download errors\n- Optimized page navigation and syntax formatting configurations to improve startup time\n### Fixed\n- Fixed crash when clicking Inference gallery image after the image is deleted externally in file explorer\n- Fixed Inference popup Install button not working on One-Click Installer\n- Fixed Inference Prompt Completion window sometimes not showing while typing\n- Fixed \"Show Model Images\" toggle on Checkpoints page sometimes displaying cut-off model images\n- Fixed missing httpx package during Automatic1111 install\n- Fixed some instances of localized text being cut off from controls being too small\n\n## v2.5.7\n### Fixed\n- Fixed error `got an unexpected keyword argument 'socket_options'` on fresh installs of Automatic1111 Stable Diffusion WebUI due to missing httpx dependency specification from gradio\n\n## v2.5.6\n### Added\n- Added Russian UI language option, thanks to aolko for the translation\n\n## v2.5.5\n### Added\n- Added Spanish UI language options, thanks to Carlos Baena and Lautaroturina for the translations\n- Manual input prompt popup on package input requests besides Y/n confirmations\n- Added `--disable-gpu` launch argument to disable hardware accelerated rendering\n### Fixed\n- Fixed infinite progress wheel when package uninstall fails\n\n## v2.5.4\n### Fixed\n- Fixed [#208](https://github.com/LykosAI/StabilityMatrix/issues/208) - error when installing xformers\n\n## v2.5.3\n### Added\n- Added French UI language option, thanks to eephyne for the translation\n### Fixed\n- Fixed Automatic 1111 missing dependencies on startup by no longer enabling `--skip-install` by default.\n\n## v2.5.2\n### Added\n- Right click Inference Batch options to enable selecting a \"Batch Index\". This can be used to reproduce a specific image from a batch generation. The field will be automatically populated in metadata of individual images from a batch generation.\n  - The index is 1-based, so the first image in a batch is index 1, and the last image is the batch size.\n  - Currently this generates different individual images for batches using Ancestral samplers, due to an upstream ComfyUI issue with noise masking. Looking into fixing this.\n- Inference Batches option now is implemented, previously the setting had no effect\n### Changed\n- Default upscale factor for Inference is now 2x instead of 1x\n### Fixed\n- Fixed batch combined image grids not showing metadata and not being importable\n- Fixed \"Call from invalid thread\" errors that sometimes occured during update notifications\n\n## v2.5.1\n### Added\n- `--skip-install` default launch argument for Automatic1111 Package\n### Fixed\n- Fixed Prompt weights showing syntax error in locales where decimal separator is not a period\n\n## v2.5.0\n### Added\n- Added Inference, a built-in native Stable Diffusion interface, powered by ComfyUI\n- Added option to change the Shared Folder method for packages using the three-dots menu on the Packages page\n- Added the ability to Favorite models in the Model Browser\n- Added \"Favorites\" sort option to the Model Browser\n- Added notification flyout for new available updates. Dismiss to hide until the next update version.\n- Added Italian UI language options, thanks to Marco Capelli for the translations\n### Changed\n- Model Browser page size is now 20 instead of 14  \n- Update changelog now only shows the difference between the current version and the latest version\n### Fixed\n- Fixed [#141](https://github.com/LykosAI/StabilityMatrix/issues/141) - Search not working when sorting by Installed on Model Browser\n- Fixed SD.Next not showing \"Open Web UI\" button when finished loading\n- Fixed model index startup errors when `./Models` contains unknown custom folder names\n- Fixed ストップ button being cut off in Japanese translation \n- Fixed update progress freezing in some cases\n- Fixed light theme being default in first time setup window\n- Fixed shared folder links not recreating fully when partially missing\n\n## v2.4.6\n### Added\n- LDSR / ADetailer shared folder links for Automatic1111 Package\n### Changed\n- Made Dark Mode background slightly lighter\n\n## v2.4.5\n### Fixed\n- Fixed \"Library Dir not set\" error on launch\n\n## v2.4.4\n### Added\n- Added button to toggle automatic scrolling of console output\n### Fixed\n- Fixed [#130](https://github.com/LykosAI/StabilityMatrix/issues/130) ComfyUI extra_model_paths.yaml file being overwritten on each launch\n- Fixed some package updates not showing any console output\n- Fixed auto-close of update dialog when package update is complete \n\n## v2.4.3\n### Added\n- Added \"--no-download-sd-model\" launch argument option for Stable Diffusion Web UI\n- Added Chinese (Simplified) and Chinese (Traditional) UI language options, thanks to jimlovewine for the translations\n### Changed\n- Package updates now use the new progress dialog with console output\n### Fixed\n- Updated Japanese translation for some terms\n\n## v2.4.2\n### Added\n- Added Japanese UI language option, thanks to kgmkm_mkgm for the translation\n- Language selection available in Settings, and defaults to system language if supported\n\n## v2.4.1\n### Fixed\n- Fixed deleting checkpoints not updating the visual grid until the page is refreshed\n- Fixed updates sometimes freezing on \"Installing Requirements\" step\n\n## v2.4.0\n### Added\n- New installable Package - [Fooocus-MRE](https://github.com/MoonRide303/Fooocus-MRE)\n- Added toggle to show connected model images in the Checkpoints tab\n- Added \"Find Connected Metadata\" option to the context menu of Checkpoint Folders in the Checkpoints tab to connect models that don't have any metadata\n### Changed\n- Revamped package installer\n  - Added \"advanced options\" section for commit, shared folder method, and pytorch options\n  - Can be run in the background\n  - Shows progress in the Downloads tab\n- Even more performance improvements for loading and searching the Checkpoints page\n### Fixed\n- Fixed [#97](https://github.com/LykosAI/StabilityMatrix/issues/97) - Codeformer folder should now get linked correctly\n- Fixed [#106](https://github.com/LykosAI/StabilityMatrix/issues/106) - ComfyUI should now install correctly on Windows machines with an AMD GPU using DirectML\n- Fixed [#107](https://github.com/LykosAI/StabilityMatrix/issues/107) - Added `--autolaunch` option to SD.Next\n- Fixed [#110](https://github.com/LykosAI/StabilityMatrix/issues/110) - Model Browser should properly navigate to the next page of Installed models\n- Installed tag on model browser should now show for connected models imported via drag & drop\n\n## v2.3.4\n### Fixed\n- Fixed [#108](https://github.com/LykosAI/StabilityMatrix/issues/108) - (Linux) Fixed permission error on updates [#103](https://github.com/LykosAI/StabilityMatrix/pull/103)\n\n## v2.3.3\n### Fixed\n- Fixed GPU recognition for Nvidia Tesla GPUs\n- Fixed checkpoint file index extension identification with some path names\n- Fixed issue where config file may be overwritten during Automatic1111 package updates\n- Fixed \"Directory Not Found\" error on startup when previously selected Data directory does not exist\n- Fixed [#83](https://github.com/LykosAI/StabilityMatrix/issues/83) - Display of packages with long names in the Package Manager\n- Fixed [#64](https://github.com/LykosAI/StabilityMatrix/issues/64) - Package install error if venv already exists\n\n## v2.3.2\n### Added\n- Added warning for exFAT / FAT32 drives when selecting a data directory\n### Fixed\n- Automatic1111 and ComfyUI should now install the correct version of pytorch for AMD GPUs\n- Fixed \"Call from invalid thread\" exceptions preventing download completion notifications from showing\n- Fixed model preview image downloading with incorrect name\n### Changed\n- Redesigned \"Select Model Version\" dialog to include model description and all preview images\n\n## v2.3.1\n### Fixed\n- Fixed Auto update not appearing in some regions due to date formatting issues\n- Local package import now migrates venvs and existing models\n\n## v2.3.0\n### Added\n- New installable Package - [Fooocus](https://github.com/lllyasviel/Fooocus)\n- Added \"Select New Data Directory\" button to Settings\n- Added \"Skip to First/Last Page\" buttons to the Model Browser\n- Added VAE as a checkpoint category in the Model Browser\n- Pause/Resume/Cancel buttons on downloads popup. Paused downloads persists and may be resumed after restarting the app\n- Unknown Package installs in the Package directory will now show up with a button to import them\n### Fixed\n- Fixed issue where model version wouldn't be selected in the \"All Versions\" section of the Model Browser\n- Improved Checkpoints page indexing performance\n- Fixed issue where Checkpoints page may not show all checkpoints after clearing search filter\n- Fixed issue where Checkpoints page may show incorrect checkpoints for the given filter after changing pages\n- Fixed issue where Open Web UI button would try to load 0.0.0.0 addresses\n- Fixed Dictionary error when launch arguments saved with duplicate arguments\n- Fixed Launch arguments search not working\n### Changed\n- Changed update method for SD.Next to use the built-in upgrade functionality\n- Model Browser navigation buttons are no longer disabled while changing pages\n\n## v2.2.1\n### Fixed\n- Fixed SD.Next shared folders config not working with new config format, reverted to Junctions / Symlinks\n\n## v2.2.1\n\n### Fixed\n- Fixed SD.Next shared folders config not working with new config format, reverted to Junctions / Symlinks\n\n## v2.2.0\n\n### Added\n- Added option to search by Base Model in the Model Browser\n- Animated page transitions\n\n### Fixed\n- Fixed [#59](https://github.com/LykosAI/StabilityMatrix/issues/61) - `GIT` environment variable is now set for the embedded portable git on Windows as A1111 uses it instead of default `PATH` resolution\n- Fixed embedded Python install check on Linux when an incompatible windows DLL is in the Python install directory\n- Fixed \"ObjectDisposed\" database errors that sometimes appeared when closing the app\n\n### Changed\n- Revamped Package Manager UI\n- InvokeAI installations can now use checkpoints from the Shared Models folder\n\n## v2.1.2\n\n### Changed\n- SD.Next install now uses ROCm PyTorch backend on Linux AMD GPU machines for better performance over DirectML\n\n## v2.1.1\n\n### Added\n- Discord Rich Presence support can now be enabled in Settings\n\n### Fixed\n- Launch Page selected package now persists in settings\n\n## v2.1.0\n\n### Added\n- New installable Package - [VoltaML](https://github.com/VoltaML/voltaML-fast-stable-diffusion)\n- New installable Package - [InvokeAI](https://github.com/invoke-ai/InvokeAI)\n- Launch button can now support alternate commands / modes - currently only for InvokeAI\n  > ![](https://github.com/LykosAI/StabilityMatrix/assets/13956642/16a8ffdd-a3cb-4f4f-acc5-c062d3ade363)\n- Settings option to set global environment variables for Packages\n  > ![](https://github.com/LykosAI/StabilityMatrix/assets/13956642/d577918e-82bb-46d4-9a3a-9b5318d3d4d8)\n\n### Changed\n- Compatible packages (ComfyUI, Vlad/SD.Next) now use config files / launch args instead of symbolic links for shared model folder redirect\n\n### Fixed\n- Fixed [#48](https://github.com/LykosAI/StabilityMatrix/issues/48) - model folders not showing in UI when they were empty\n- Updater now shows correct current version without trailing `.0`\n- Fixed program sometimes starting off-screen on multi-monitor setups \n- Fixed console input box transparency\n- Fixed [#52](https://github.com/LykosAI/StabilityMatrix/issues/52) - A1111 default approx-vae model download errors by switching default preview method to TAESD\n- Fixes [#50](https://github.com/LykosAI/StabilityMatrix/issues/50) - model browser crash when no model versions exist\n- Fixed [#31](https://github.com/LykosAI/StabilityMatrix/issues/31) - missing ControlNet link to Shared Models Folder for SD.Next\n- Fixed [#49](https://github.com/LykosAI/StabilityMatrix/issues/49) - download progress disappearing when changing pages in Model Browser\n\n## v2.0.4\n\n### Fixed\n- Fixed Model Browser downloading files without extensions\n\n## v2.0.3\n\n### Added\n- (Windows) New settings option to add Stability Matrix to the start menu\n- (Windows) Improved background \"Mica\" effect on Windows 11, should be smoother with less banding artifacts\n\n### Fixed\n- Fixed model categories sometimes not showing if they are empty\n- Improved model download hash verification performance\n- Fixed some text wrapping visuals on expanded model version dialog on model browser\n- Added cancel button for create folder dialog\n- One click first time installer now defaults to using the \"Package\" name instead of the display name (\"stable-diffusion-webui\" instead of \"Stable Diffusion WebUI\") for the install folder name - probably safer against upstream issues on folder names with spaces.\n\n## v2.0.2\n\n### Fixed\n- (Linux) Updater now sets correct execute permissions\n- Image loading (i.e. Checkpoints File preview thumbnail) now has a notification for unsupported local image formats instead of crashing\n- Fix unable to start app issues on some machines and dropdowns showing wrong categories - disabled assembly trimming\n\n## v2.0.1\n\n### Added\n- Fully rewritten using Avalonia for improved UI and cross-platform support, our biggest update so far, with over 18,000 lines of code.\n- Release support for Windows and Linux, with macOS coming soon\n- Model Browser now indicates models that are already downloaded / need updates\n- Checkpoints Manager now supports filtering/searching\n- One-click installer now suggests all 3 WebUI packages for selection\n- Hardware compatibility and GPU detection is now more accurate\n- Download Indicator on the nav menu for ongoing downloads and progress; supports multiple concurrent model downloads\n- Improved console with syntax highlighting, and provisional ANSI rendering for progress bars and advanced graphics\n- Input can now be sent to the running package process using the top-right keyboard button on the Launch page. Package input requests for a (y/n) response will now have an interactive popup.\n\n### Fixed\n- Fixed crash on exit\n- Fixed updating from versions prior to 2.x.x\n- Fixed page duplication memory leak that caused increased memory usage when switching between pages\n- Package page launch button will now navigate and launch the package, instead of just navigating to launch page\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Building\n## Running & Debug\n- If building using managed IDEs like Rider or Visual Studio, ensure that a valid `--runtime ...` argument is being passed to `dotnet`, or `RuntimeIdentifier=...` is set for calling `msbuild`. This is required for runtime-specific resources to be included in the build. Stability Matrix currently supports building for the `win-x64`, `linux-x64` and `osx-arm64` runtimes.\n- You can also build the `StabilityMatrix.Avalonia` project using `dotnet`:\n```bash\ndotnet build ./StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj -r win-x64 -c Debug\n```\n- Note that on Windows, the `net8.0-windows10.0.17763.0` framework is used, build outputs will be in `StabilityMatrix.Avalonia/bin/Debug/net8.0-windows10.0.17763.0/win-x64`. On other platforms the `net8.0` framework is used.\n\n## Building to single file for release\n(Replace `$RELEASE_VERSION` with a non v-prefixed semver version number, e.g. `2.10.0`, `2.11.0-dev.1`, etc.)\n### Windows\n```bash\ndotnet publish ./StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj -r win-x64 -c Release -p:Version=$env:RELEASE_VERSION -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishReadyToRun=true\n```\n### macOS\nThe `output_dir` environment variable can be specified or defaults to `./out/osx-arm64/`\n```bash\n./Build/build_macos_app.sh -v $RELEASE_VERSION\n```\n### Linux\n```bash\nsudo apt-get -y install libfuse2\ndotnet tool install -g KuiperZone.PupNet\npupnet -r linux-x64 -c Release --kind appimage --app-version $RELEASE_VERSION --clean\n```\n\n# Scripts\n## Install Husky.Net & Pre-commit hooks\n- Building the `StabilityMatrix.Avalonia` project once should also install Husky.Net, or run the following command:\n```bash\ndotnet tool restore && dotnet husky install\n```\n## Adding Husky pre-commit hooks\n```bash\ndotnet husky install\n```\n## Generated OpenApi clients\n- Refitter is used to generate some OpenApi clients. New clients should be added to `./.husky/task-runner.json`.\n- To regenerate clients, run the following command:\n```bash\ndotnet husky run -g generate-openapi\n```\n\n# Style Guidelines\nThese are just guidelines, mostly following the official C# style guidelines, except in a few cases. We might not adhere to these 100% ourselves, but lets try our best :)\n\n## Naming conventions\n#### Pascal Case\n- Use pascal casing (\"PascalCasing\") when naming a `class`, `record`, `struct`, or `public` members of types, such as fields, properties, methods, and local functions.\n- When naming an `interface`, use pascal casing in addition to prefixing the name with the letter `I` to clearly indicate to consumers that it's an `interface`.\n#### Camel Case\n- Use camel casing (\"camelCasing\") when naming `private` or `internal` fields.\n- **Do not** prefix them with an underscore `_`\n## `using` Directives\n- Please do not check in code with unused using statements.\n## File-scoped Namespaces\n- Always use file-scoped namespaces. For example:\n```csharp\nusing System;\n\nnamespace X.Y.Z;\n\nclass Foo\n{\n}\n```\n## Implicitly typed local variables\n- Use implicit typing (`var`) for local variables when the type of the variable is obvious from the right side of the assignment, or when the precise type is not important.\n\n## Optional Curly Braces\n- Only omit curly braces from `if` statements if the statement immediately following is a `return`. \n\nFor example, the following snippet is acceptable:\n```csharp\nif (alreadyAteBreakfast)\n    return;\n```\n\nOtherwise, it must be wrapped in curly braces, like so:\n```csharp\nif (alreadyAteLunch)\n{\n    mealsEaten++;\n}\n```\n\n## Project Structure\n- Try to follow our existing structure, such as putting model classes in the `Models\\` directory, ViewModels in `ViewModels\\`, etc.\n- Static classes with only extension methods should be in `Extensions\\`\n- Mock data for XAML Designer should go in `DesignData\\`\n- The `Helper\\` and `Services\\` folder don't really have guidelines, use your best judgment\n- XAML & JSON converters should go in the `Converters\\` and `Converters\\Json\\` directories respectively\n- Refit interfaces should go in the `Api\\` folder\n"
  },
  {
    "path": "ConditionalSymbols.props",
    "content": "<Project>\n  <PropertyGroup Label=\"Compile Options\">\n    <!-- true to include all conditional paths / attributes, mainly for testing -->\n    <SM_IncludeAllConditionals>false</SM_IncludeAllConditionals>\n    <!-- Mode for ServiceProvider registrations: CodeGeneration, Reflection -->\n    <SM_RegisterServiceMode>CodeGeneration</SM_RegisterServiceMode>\n    <!-- true to include StabilityMatrix.Avalonia.Diagnostic for log window -->\n    <SM_IncludeLogWindow>false</SM_IncludeLogWindow>\n  </PropertyGroup>\n  \n  <PropertyGroup Condition=\"'$(SM_IncludeAllConditionals)' == 'true'\">\n    <!-- REGISTER_SERVICE_USAGES: Include Injectio attributes -->\n    <DefineConstants>$(DefineConstants);REGISTER_SERVICE_USAGES</DefineConstants>\n  </PropertyGroup>\n  \n  <PropertyGroup Condition=\"'$(SM_IncludeLogWindow)' == 'true'\">\n    <!-- SM_LOG_WINDOW: Include Avalonia diagnostic window -->\n    <DefineConstants>$(DefineConstants);SM_LOG_WINDOW</DefineConstants>\n  </PropertyGroup>\n\n  <PropertyGroup Label=\"RegisterServiceMode Definitions\">\n    <DefineConstants Condition=\"'$(SM_RegisterServiceMode)' == 'Reflection'\">\n      $(DefineConstants);REGISTER_SERVICE_REFLECTION;REGISTER_SERVICE_USAGES\n    </DefineConstants>\n  </PropertyGroup>\n</Project>\n"
  },
  {
    "path": "Directory.Build.props",
    "content": "﻿<Project>\n    <PropertyGroup>\n        <TargetFramework>net9.0</TargetFramework>\n        <LangVersion>preview</LangVersion>\n        <Nullable>enable</Nullable>\n        <ImplicitUsings>enable</ImplicitUsings>\n        <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>\n        <CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>\n        <WarningsAsErrors>CS0108</WarningsAsErrors>\n    </PropertyGroup>\n    \n    <PropertyGroup>\n        <AvaloniaVersion>11.3.7</AvaloniaVersion>\n    </PropertyGroup>\n    \n    <PropertyGroup>\n        <!-- AVLN3001: Not using the default Avalonia runtime loader so this doesn't matter -->\n        <NoWarn>$(NoWarn);AVLN3001</NoWarn>\n    </PropertyGroup>\n    \n    <PropertyGroup>\n        <!--Some bug with SDK 8.0.403 \n        https://github.com/dotnet/sdk/issues/44026-->\n        <NoWarn>$(NoWarn);CsWinRT1028</NoWarn>\n    </PropertyGroup>\n</Project>\n"
  },
  {
    "path": "Directory.Packages.props",
    "content": "<Project>\n  <ItemGroup>\n    <PackageVersion Include=\"Apizr\" Version=\"6.3.0\" />\n    <PackageVersion Include=\"Apizr.Extensions.Microsoft.Caching\" Version=\"6.3.0\" />\n    <PackageVersion Include=\"Apizr.Extensions.Microsoft.DependencyInjection\" Version=\"6.3.0\" />\n    <PackageVersion Include=\"Apizr.Integrations.Fusillade\" Version=\"6.3.0\" />\n    <PackageVersion Include=\"AsyncAwaitBestPractices\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"AutoComplete.Net\" Version=\"1.2211.2014.42\" />\n    <PackageVersion Include=\"AutoCtor\" Version=\"2.4.1\" />\n    <PackageVersion Include=\"Blake3\" Version=\"1.1.0\" />\n    <PackageVersion Include=\"CompiledExpressions\" Version=\"1.1.0\" />\n    <PackageVersion Include=\"CommandLineParser\" Version=\"2.9.1\" />\n    <PackageVersion Include=\"CommunityToolkit.Mvvm\" Version=\"8.4.0\" />\n    <PackageVersion Include=\"Crc32.NET\" Version=\"1.2.0\" />\n    <PackageVersion Include=\"CSharpDiscriminatedUnion\" Version=\"2.0.1\" />\n    <PackageVersion Include=\"DeviceId\" Version=\"6.7.0\" />\n    <PackageVersion Include=\"DeviceId.Linux\" Version=\"6.4.0\" />\n    <PackageVersion Include=\"DeviceId.Mac\" Version=\"6.3.0\" />\n    <PackageVersion Include=\"DeviceId.Windows\" Version=\"6.6.0\" />\n    <PackageVersion Include=\"DeviceId.Windows.Wmi\" Version=\"6.6.0\" />\n    <PackageVersion Include=\"DiscordRichPresence\" Version=\"1.2.1.24\" />\n    <PackageVersion Include=\"DotNet.Bundle\" Version=\"0.9.13\" />\n    <PackageVersion Include=\"DotNext\" Version=\"5.8.0\" />\n    <PackageVersion Include=\"DynamicData\" Version=\"9.3.1\" />\n    <PackageVersion Include=\"ExifLibNet\" Version=\"2.1.4\" />\n    <PackageVersion Include=\"Exceptionless.DateTimeExtensions\" Version=\"3.4.3\" />\n    <PackageVersion Include=\"FreneticLLC.FreneticUtilities\" Version=\"1.0.32\" />\n    <PackageVersion Include=\"FuzzySharp\" Version=\"2.0.2\" />\n    <PackageVersion Include=\"Hardware.Info\" Version=\"100.1.0.1\" />\n    <PackageVersion Include=\"Injectio\" Version=\"4.0.0\" />\n    <PackageVersion Include=\"JetBrains.Annotations\" Version=\"2024.2.0\" />\n    <PackageVersion Include=\"KeyedSemaphores\" Version=\"5.0.0\" />\n    <PackageVersion Include=\"KGySoft.Drawing.Core\" Version=\"8.1.0\" />\n    <PackageVersion Include=\"KGySoft.Drawing.SkiaSharp\" Version=\"8.1.0\" />\n    <PackageVersion Include=\"LiteDB\" Version=\"5.0.21\" />\n    <PackageVersion Include=\"LiteDB.Async\" Version=\"0.1.8\" />\n    <PackageVersion Include=\"Markdig\" Version=\"0.38.0\" />\n    <PackageVersion Include=\"MetadataExtractor\" Version=\"2.8.1\" />\n    <PackageVersion Include=\"MessagePipe\" Version=\"1.8.1\" />\n    <PackageVersion Include=\"MessagePipe.Interprocess\" Version=\"1.8.1\" />\n    <PackageVersion Include=\"MessagePack\" Version=\"2.5.192\" Label=\"CVE-2024-48924\" />\n    <PackageVersion Include=\"Microsoft.Extensions.Caching.Memory\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"Microsoft.Extensions.DependencyInjection\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"Microsoft.Extensions.Hosting.Abstractions\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"Microsoft.Extensions.Options.ConfigurationExtensions\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"Microsoft.Extensions.Configuration\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"Microsoft.Extensions.Configuration.EnvironmentVariables\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"Microsoft.Extensions.Configuration.FileExtensions\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"Microsoft.Extensions.Configuration.Json\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"Microsoft.Extensions.Hosting\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"Microsoft.Extensions.Http.Polly\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"Microsoft.Extensions.Logging.Abstractions\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"NLog\" Version=\"5.3.2\" />\n    <PackageVersion Include=\"NLog.Extensions.Logging\" Version=\"5.3.11\" />\n    <PackageVersion Include=\"NSubstitute\" Version=\"5.1.0\" />\n    <PackageVersion Include=\"NSec.Cryptography\" Version=\"24.4.0\" />\n    <PackageVersion Include=\"Octokit\" Version=\"13.0.1\" />\n    <PackageVersion Include=\"OneOf\" Version=\"3.0.271\" />\n    <PackageVersion Include=\"OneOf.SourceGenerator\" Version=\"3.0.271\" />\n    <PackageVersion Include=\"OpenIddict.Client\" Version=\"5.8.0\" />\n    <PackageVersion Include=\"OpenIddict.Client.SystemNetHttp\" Version=\"5.8.0\" />\n    <PackageVersion Include=\"Polly\" Version=\"8.5.0\" />\n    <PackageVersion Include=\"Polly.Contrib.WaitAndRetry\" Version=\"1.1.1\" />\n    <PackageVersion Include=\"Polly.Extensions.Http\" Version=\"3.0.0\" />\n    <PackageVersion Include=\"pythonnet\" Version=\"3.0.3\" />\n    <PackageVersion Include=\"Refit\" Version=\"8.0.0\" />\n    <PackageVersion Include=\"Refit.HttpClientFactory\" Version=\"8.0.0\" />\n    <PackageVersion Include=\"RockLib.Reflection.Optimized\" Version=\"3.0.0\" />\n    <PackageVersion Include=\"Nito.AsyncEx\" Version=\"5.1.2\" />\n    <PackageVersion Include=\"Salaros.ConfigParser\" Version=\"0.3.8\" />\n    <PackageVersion Include=\"Semi.Avalonia\" Version=\"11.2.0\" />\n    <PackageVersion Include=\"Semver\" Version=\"3.0.0-beta.1\" />\n    <PackageVersion Include=\"Sentry\" Version=\"5.5.1\" />\n    <PackageVersion Include=\"Sentry.NLog\" Version=\"5.5.1\" />\n    <PackageVersion Include=\"SharpCompress\" Version=\"0.37.2\" />\n    <PackageVersion Include=\"SkiaSharp\" Version=\"3.0.0-preview.4.1\" />\n    <PackageVersion Include=\"Sylvan.Common\" Version=\"0.4.3\" />\n    <PackageVersion Include=\"Sylvan.Data\" Version=\"0.2.16\" />\n    <PackageVersion Include=\"Sylvan.Data.Csv\" Version=\"1.3.9\" />\n    <PackageVersion Include=\"System.Drawing.Common\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"System.IO.Hashing\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"System.Runtime.Serialization.Formatters\" Version=\"9.0.3\" />\n    <PackageVersion Include=\"System.Text.Json\" Version=\"9.0.0\" />\n    <PackageVersion Include=\"System.Private.Uri\" Version=\"4.3.2\" Label=\"CVE-2019-0981\" />\n    <PackageVersion Include=\"URISchemeTools\" Version=\"1.0.2\" />\n    <PackageVersion Include=\"Websocket.Client\" Version=\"5.1.2\" />\n    <PackageVersion Include=\"YamlDotNet\" Version=\"16.0.0\" />\n    <PackageVersion Include=\"Yoh.Text.Json.NamingPolicies\" Version=\"1.1.2\" />\n  </ItemGroup>\n  <ItemGroup Label=\"Avalonia\">\n    <PackageVersion Include=\"AsyncImageLoader.Avalonia\" Version=\"3.2.1\" />\n    <PackageVersion Include=\"Avalonia.AvaloniaEdit\" Version=\"11.1.0\" />\n    <PackageVersion Include=\"Avalonia.Controls.DataGrid\" Version=\"$(AvaloniaVersion)\" />\n    <PackageVersion Include=\"Avalonia.Controls.ItemsRepeater\" Version=\"11.1.4\" />\n    <PackageVersion Include=\"Avalonia.Controls.PanAndZoom\" Version=\"11.2.0\" />\n    <PackageVersion Include=\"Avalonia\" Version=\"$(AvaloniaVersion)\" />\n    <PackageVersion Include=\"Avalonia.Desktop\" Version=\"$(AvaloniaVersion)\" />\n    <PackageVersion Include=\"Avalonia.Fonts.Inter\" Version=\"$(AvaloniaVersion)\" />\n    <PackageVersion Include=\"Avalonia.Svg\" Version=\"11.2.0\" />\n    <PackageVersion Include=\"Avalonia.Diagnostics\" Version=\"$(AvaloniaVersion)\" />\n    <PackageVersion Include=\"Avalonia.HtmlRenderer\" Version=\"11.0.0\" />\n    <PackageVersion Include=\"Avalonia.Labs.Controls\" Version=\"11.2.0\" />\n    <PackageVersion Include=\"Avalonia.Xaml.Behaviors\" Version=\"11.2.0\" />\n    <PackageVersion Include=\"AvaloniaEdit.TextMate\" Version=\"11.0.6\" />\n    <PackageVersion Include=\"TextMateSharp.Grammars\" Version=\"1.0.56\" />\n    <PackageVersion Include=\"bodong.Avalonia.PropertyGrid\" Version=\"11.1.1.1\" />\n    <PackageVersion Include=\"bodong.PropertyModels\" Version=\"11.1.1.1\" />\n    <PackageVersion Include=\"DesktopNotifications\" Version=\"1.3.1\" />\n    <PackageVersion Include=\"DesktopNotifications.Avalonia\" Version=\"1.3.1\" />\n    <PackageVersion Include=\"Dock.Avalonia\" Version=\"11.2.0\" />\n    <PackageVersion Include=\"Dock.Model.Avalonia\" Version=\"11.2.0\" />\n    <PackageVersion Include=\"Dock.Serializer\" Version=\"11.2.0\" />\n    <PackageVersion Include=\"FluentAvalonia.BreadcrumbBar\" Version=\"2.0.2\" />\n    <PackageVersion Include=\"FluentAvaloniaUI\" Version=\"2.3.0\" />\n    <PackageVersion Include=\"FluentIcons.Avalonia\" Version=\"1.1.293\" />\n    <PackageVersion Include=\"FluentIcons.Avalonia.Fluent\" Version=\"1.1.293\" />\n    <PackageVersion Include=\"Markdown.Avalonia\" Version=\"11.0.3-a1\" />\n    <PackageVersion Include=\"Projektanker.Icons.Avalonia.FontAwesome\" Version=\"9.4.0\" />\n    <PackageVersion Include=\"SpacedGrid-Avalonia\" Version=\"11.0.0\" />\n    <PackageVersion Include=\"SkiaSharp.NativeAssets.Linux\" Version=\"3.0.0-preview.4.1\" />\n  </ItemGroup>\n  <ItemGroup Label=\"Tests\">\n    <PackageVersion Include=\"coverlet.collector\" Version=\"6.0.2\" />\n    <PackageVersion Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.12.0\" />\n    <PackageVersion Include=\"MSTest.TestAdapter\" Version=\"3.6.3\" />\n    <PackageVersion Include=\"MSTest.TestFramework\" Version=\"3.6.3\" />\n    <PackageVersion Include=\"Avalonia.Headless.XUnit\" Version=\"$(AvaloniaVersion)\" />\n    <PackageVersion Include=\"Verify\" Version=\"26.1.2\" />\n    <PackageVersion Include=\"Verify.CommunityToolkit.Mvvm\" Version=\"0.1.0\" />\n    <PackageVersion Include=\"Verify.Avalonia\" Version=\"1.1.0\" />\n    <PackageVersion Include=\"Verify.Phash\" Version=\"3.1.0\" />\n    <PackageVersion Include=\"Verify.Xunit\" Version=\"26.1.2\" />\n    <PackageVersion Include=\"xunit\" Version=\"2.9.0\" />\n    <PackageVersion Include=\"xunit.runner.visualstudio\" Version=\"2.8.2\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "Jenkinsfile",
    "content": "node(\"Diligence\") {\n    def repoName = \"StabilityMatrix\"\n    def author = \"ionite34\"\n    def version = \"\"\n\n    stage('Clean') {\n        deleteDir()\n    }\n\n    stage('Checkout') {\n        git branch: env.BRANCH_NAME, credentialsId: 'Ionite', url: \"https://github.com/${author}/${repoName}.git\"\n    }\n    \n    try {    \n        stage('Test') {\n            sh \"dotnet test StabilityMatrix.Tests\"\n        }\n\n        if (env.BRANCH_NAME == 'main') {\n        \n            stage('Set Version') {\n                script {\n                    if (env.TAG_NAME) {\n                        version = env.TAG_NAME.replaceFirst(/^v/, '')\n                    } else {\n                        version = VersionNumber projectStartDate: '2023-06-21', versionNumberString: '${BUILDS_ALL_TIME}', worstResultForIncrement: 'SUCCESS'\n                    }\n                }\n            }\n            \n            stage('Publish Windows') {\n                sh \"dotnet publish ./StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj -c Release -o out -r win-x64 -p:PublishSingleFile=true -p:VersionPrefix=2.0.0 -p:VersionSuffix=${version} -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableWindowsTargeting=true\"\n            }\n\n            stage('Publish Linux') {\n                sh \"rm -rf StabilityMatrix.Avalonia/bin/*\"\n                sh \"rm -rf StabilityMatrix.Avalonia/obj/*\"\n                sh \"/home/jenkins/.dotnet/tools/pupnet --runtime linux-x64 --kind appimage --app-version ${version} --clean -y\"\n            }\n        }\n    } finally {\n        stage('Cleanup') {\n            cleanWs()\n        }\n    }\n\n    \n}\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>."
  },
  {
    "path": "NuGet.Config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n    <packageSources>\n        <add key=\"nuget.org\" value=\"https://api.nuget.org/v3/index.json\" protocolVersion=\"3\" />\n        <add key=\"Avalonia Nightly\" value=\"https://nuget-feed-nightly.avaloniaui.net/v3/index.json\" />\n    </packageSources>\n\n    <packageSourceMapping>\n        <packageSource key=\"nuget.org\">\n            <package pattern=\"*\" />\n        </packageSource>\n        <packageSource key=\"Avalonia Nightly\">\n            <package pattern=\"Avalonia.Nightly.*\" />\n        </packageSource>\n    </packageSourceMapping>\n</configuration>\n"
  },
  {
    "path": "README.md",
    "content": "# Stability Matrix\n\n[![Build](https://github.com/LykosAI/StabilityMatrix/actions/workflows/build.yml/badge.svg)](https://github.com/LykosAI/StabilityMatrix/actions/workflows/build.yml)\n[![Discord Server](https://img.shields.io/discord/1115555685476868168?logo=discord&logoColor=white&label=Discord%20Server)](https://discord.com/invite/TUrgfECxHz)\n\n[![Latest Stable](https://img.shields.io/github/v/release/LykosAI/StabilityMatrix?label=Latest%20Stable&link=https%3A%2F%2Fgithub.com%2FLykosAI%2FStabilityMatrix%2Freleases%2Flatest)][release]\n[![Latest Preview](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fcdn.lykos.ai%2Fupdate-v3.json&query=%24.updates.preview%5B%22win-x64%22%5D.version&prefix=v&label=Latest%20Preview&color=b57400&cacheSeconds=60&link=https%3A%2F%2Flykos.ai%2Fdownloads)](https://lykos.ai/downloads)\n[![Latest Dev](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fcdn.lykos.ai%2Fupdate-v3.json&query=%24.updates.development%5B%22win-x64%22%5D.version&prefix=v&label=Latest%20Dev&color=880c21&cacheSeconds=60&link=https%3A%2F%2Flykos.ai%2Fdownloads)](https://lykos.ai/downloads)\n\n[release]: https://github.com/LykosAI/StabilityMatrix/releases/latest\n[download-win-x64]: https://github.com/LykosAI/StabilityMatrix/releases/latest/download/StabilityMatrix-win-x64.zip\n[download-linux-appimage-x64]: https://github.com/LykosAI/StabilityMatrix/releases/latest/download/StabilityMatrix-linux-x64.zip\n[download-linux-aur-x64]: https://aur.archlinux.org/packages/stabilitymatrix\n[download-macos-arm64]: https://github.com/LykosAI/StabilityMatrix/releases/latest/download/StabilityMatrix-macos-arm64.dmg\n\n[auto1111]: https://github.com/AUTOMATIC1111/stable-diffusion-webui\n[auto1111-directml]: https://github.com/lshqqytiger/stable-diffusion-webui-directml\n[webui-ux]: https://github.com/anapnoe/stable-diffusion-webui-ux\n[comfy]: https://github.com/comfyanonymous/ComfyUI\n[sdnext]: https://github.com/vladmandic/automatic\n[voltaml]: https://github.com/VoltaML/voltaML-fast-stable-diffusion\n[invokeai]: https://github.com/invoke-ai/InvokeAI\n[fooocus]: https://github.com/lllyasviel/Fooocus\n[fooocus-mre]: https://github.com/MoonRide303/Fooocus-MRE\n[ruined-fooocus]: https://github.com/runew0lf/RuinedFooocus\n[fooocus-controlnet]: https://github.com/fenneishi/Fooocus-ControlNet-SDXL\n[kohya-ss]: https://github.com/bmaltais/kohya_ss\n[onetrainer]: https://github.com/Nerogar/OneTrainer\n[forge]: https://github.com/lllyasviel/stable-diffusion-webui-forge\n[stable-swarm]: https://github.com/Stability-AI/StableSwarmUI\n[sdfx]: https://github.com/sdfxai/sdfx\n[fooocus-mashb1t]: https://github.com/mashb1t/Fooocus\n[reforge]: https://github.com/Panchovix/stable-diffusion-webui-reForge\n[simplesdxl]: https://github.com/metercai/SimpleSDXL/\n[fluxgym]: https://github.com/cocktailpeanut/fluxgym\n[cogvideo]: https://github.com/THUDM/CogVideo\n[cogstudio]: https://github.com/pinokiofactory/cogstudio\n[amdforge]: https://github.com/lshqqytiger/stable-diffusion-webui-amdgpu-forge\n\n[civitai]: https://civitai.com/\n[huggingface]: https://huggingface.co/\n\n![Header image for Stability Matrix, Multi-Platform Package Manager and Inference UI for Stable Diffusion](https://cdn.lykos.ai/static/sm-banner-rounded.webp)\n\n[![Windows](https://img.shields.io/badge/Windows%2010,%2011-%230079d5.svg?style=for-the-badge&logo=Windows%2011&logoColor=white)][download-win-x64]\n[![Linux (AppImage)](https://img.shields.io/badge/Linux%20(AppImage)-FCC624?style=for-the-badge&logo=linux&logoColor=black)][download-linux-appimage-x64]\n[![Arch Linux (AUR)](https://img.shields.io/badge/Arch%20Linux%20(AUR)-1793D1?style=for-the-badge&logo=archlinux&logoColor=white)][download-linux-aur-x64]\n[![macOS](https://img.shields.io/badge/mac%20os%20%28apple%20silicon%29-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0)][download-macos-arm64]\n\nMulti-Platform Package Manager and Inference UI for Stable Diffusion\n\n### 🖱️ One click install and update for Stable Diffusion Web UI Packages\n- Supports:\n  - [Stable Diffusion WebUI reForge][reforge], [Stable Diffusion WebUI Forge][forge], [Stable Diffusion WebUI AMDGPU Forge][amdforge] [Automatic 1111][auto1111], [Automatic 1111 DirectML][auto1111-directml], [SD Web UI-UX][webui-ux], [SD.Next][sdnext]\n  - [Fooocus][fooocus], [Fooocus MRE][fooocus-mre], [Fooocus ControlNet SDXL][fooocus-controlnet], [Ruined Fooocus][ruined-fooocus], [Fooocus - mashb1t's 1-Up Edition][fooocus-mashb1t], [SimpleSDXL][simplesdxl]\n  - [ComfyUI][comfy]\n  - [StableSwarmUI][stable-swarm]\n  - [VoltaML][voltaml]\n  - [InvokeAI][invokeai]\n  - [SDFX][sdfx]\n  - [Kohya's GUI][kohya-ss]\n  - [OneTrainer][onetrainer]\n  - [FluxGym][fluxgym]\n  - [CogVideo][cogvideo] via [CogStudio][cogstudio]\n- Manage plugins / extensions for supported packages ([Automatic1111][auto1111], [Comfy UI][comfy], [SD Web UI-UX][webui-ux], and [SD.Next][sdnext])\n- Easily install or update Python dependencies for each package\n- Embedded Git and Python dependencies, with no need for either to be globally installed\n- Fully portable - move Stability Matrix's Data Directory to a new drive or computer at any time\n\n### ✨ Inference - A Reimagined Interface for Stable Diffusion, Built-In to Stability Matrix\n- Powerful auto-completion and syntax highlighting using a formal language grammar\n- Workspaces open in tabs that save and load from `.smproj` project files\n\n![](https://cdn.lykos.ai/static/sm-banner-inference-rounded.webp)\n\n- Customizable dockable and float panels\n- Generated images contain Inference Project, ComfyUI Nodes, and A1111-compatible metadata\n- Drag and drop gallery images or files to load states\n\n<p align=\"center\">\n  <img style=\"width: 80%; height: 80%\" src=\"https://github.com/LykosAI/StabilityMatrix/assets/13956642/4341cc34-a584-4e9c-bb3b-276009bdae80\" alt=\"\"/>\n</p>\n\n### 🚀 Launcher with syntax highlighted terminal emulator, routed GUI input prompts\n- Launch arguments editor with predefined or custom options for each Package install\n- Configurable Environment Variables\n\n<p align=\"center\">\n  <img style=\"width: 80%; height: 80%\" src=\"https://github.com/LykosAI/StabilityMatrix/assets/13956642/75456866-9d95-47c6-8c0a-fdc19443ee02\" alt=\"\"/>\n</p>\n\n### 🗃️ Checkpoint Manager, configured to be shared by all Package installs\n- Option to find CivitAI metadata and preview thumbnails for new local imports\n\n### ☁️ Model Browser to import from [CivitAI][civitai] and [HuggingFace][huggingface]\n- Automatically imports to the associated model folder depending on the model type\n- Downloads relevant metadata files and preview image\n- Pause and resume downloads, even after closing the app\n\n<p align=\"center\">\n  <img style=\"width: 80%; height: 80%\" src=\"https://github.com/LykosAI/StabilityMatrix/assets/13956642/30b9f610-6033-4307-8d92-7d72b93cd73e\" alt=\"\"/>\n</p>\n\n### Shared model directory for all your packages\n- Import local models by simple drag and drop\n- Option to automatically find CivitAI metadata and preview thumbnails for new local imports\n\n<p align=\"center\">\n  <img style=\"width: 80%; height: 80%\" src=\"https://github.com/LykosAI/StabilityMatrix/assets/13956642/d42d1c53-67a4-45a0-b009-21400d44e17e\" alt=\"\"/>\n</p>\n\n- Find connected metadata for existing models\n<p align=\"center\">\n  <img style=\"width: 80%; height: 80%\" src=\"https://cdn.lykos.ai/static/sc-checkpoints-find-connected.gif\" alt=\"\"/>\n</p>\n\n## Localization\nStability Matrix is now available in the following languages, thanks to our community contributors:\n- 🇺🇸 English\n- 🇯🇵 日本語 \n  - kgmkm_mkgm\n- 🇨🇳 中文（简体，繁体）\n  - jimlovewine\n- 🇮🇹 Italiano\n  - Marco Capelli\n- 🇫🇷 Français\n  - eephyne\n  - Greg\n- 🇪🇸 Español\n  - Carlos Baena \n  - Lautaroturina\n- 🇷🇺 Русский\n  - aolko\n  - den1251\n  - vanja-san\n- 🇹🇷 Türkçe\n  - Progesor\n- 🇩🇪 Deutsch\n  - Mario da Graca\n- 🇵🇹 Português\n  - nextosai\n- 🇧🇷 Português (Brasil)\n  - jbostroski\n  - thiagojramos\n- 🇰🇷 한국어\n  - maakcode\n- 🇺🇦 Українська\n  - rodtty\n- 🇨🇿 Čeština\n  - PEKArt!\n\nIf you would like to contribute a translation, please create an issue or contact us on Discord. Include an email where we'll send an invite to our [POEditor](https://poeditor.com/) project.\n\n## Disclaimers\nAll trademarks, logos, and brand names are the property of their respective owners. All company, product and service names used in this document and licensed applications are for identification purposes only. Use of these names, trademarks, and brands does not imply endorsement.\nPlease note that we do not have any involvement in cryptocurrencies. Any accounts you see claiming otherwise are scams. Please be careful. The only official source of information for Lykos AI is https://lykos.ai or our [Discord Server](https://discord.com/invite/TUrgfECxHz).\n\n## License\n\nThis repository maintains the latest source code release for Stability Matrix, and is licensed under the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.en.html). Binaries and executable releases are licensed under the [End User License Agreement](https://lykos.ai/license).\n"
  },
  {
    "path": "Runtimes.Default.props",
    "content": "<Project>\n  <PropertyGroup>\n    <RuntimeIdentifiers>win-x64;linux-x64;osx-x64;osx-arm64</RuntimeIdentifiers>\n  </PropertyGroup>\n  \n  <!-- If a runtime identifier is not specified, \n  the default runtime identifier is decided using the current OS platform. -->\n  <Choose>\n    <When Condition=\"$([MSBuild]::IsOSPlatform('Windows'))\">\n      <PropertyGroup Condition=\"'$(RuntimeIdentifier)' == ''\">\n        <RuntimeIdentifier>win-x64</RuntimeIdentifier>\n      </PropertyGroup>\n    </When>\n    <When Condition=\"$([MSBuild]::IsOSPlatform('Linux'))\">\n      <PropertyGroup Condition=\"'$(RuntimeIdentifier)' == ''\">\n        <RuntimeIdentifier>linux-x64</RuntimeIdentifier>\n      </PropertyGroup>\n    </When>\n    <When Condition=\"$([MSBuild]::IsOSPlatform('OSX'))\">\n      <PropertyGroup Condition=\"'$(RuntimeIdentifier)' == ''\">\n        <RuntimeIdentifier>osx-arm64</RuntimeIdentifier>\n      </PropertyGroup>\n    </When>\n  </Choose>\n\n  <!-- On windows we also need a platform specific target framework -->\n  <PropertyGroup Condition=\"'$(RuntimeIdentifier)' == 'win-x64'\">\n    <TargetFramework>net9.0-windows10.0.17763.0</TargetFramework>\n  </PropertyGroup>\n</Project>\n"
  },
  {
    "path": "StabilityMatrix/App.xaml",
    "content": "<Application\n    Exit=\"App_OnExit\"\n    Startup=\"App_OnStartup\"\n    x:Class=\"StabilityMatrix.App\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:local=\"clr-namespace:StabilityMatrix\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n    <Application.Resources>\n        <ResourceDictionary>\n            <ResourceDictionary.MergedDictionaries>\n                <ui:ThemesDictionary Theme=\"Dark\" />\n                <ui:ControlsDictionary />\n                <ResourceDictionary Source=\"Styles/Styles.xaml\" />\n            </ResourceDictionary.MergedDictionaries>\n        </ResourceDictionary>\n    </Application.Resources>\n</Application>\n"
  },
  {
    "path": "StabilityMatrix/App.xaml.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.IO;\nusing System.Linq;\nusing System.Net;\nusing System.Net.Http;\nusing System.Text.Json.Serialization;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing AsyncAwaitBestPractices;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.DependencyInjection.Extensions;\nusing Microsoft.Extensions.Http;\nusing Microsoft.Extensions.Logging;\nusing NLog;\nusing NLog.Config;\nusing NLog.Extensions.Logging;\nusing NLog.Targets;\nusing Octokit;\nusing Polly;\nusing Polly.Contrib.WaitAndRetry;\nusing Polly.Extensions.Http;\nusing Polly.Timeout;\nusing Refit;\nusing Sentry;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Converters.Json;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Configs;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Core.Updater;\nusing StabilityMatrix.Helper;\nusing StabilityMatrix.Services;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Services;\nusing Application = System.Windows.Application;\nusing ISnackbarService = StabilityMatrix.Helper.ISnackbarService;\nusing LogLevel = Microsoft.Extensions.Logging.LogLevel;\nusing SnackbarService = StabilityMatrix.Helper.SnackbarService;\n\nnamespace StabilityMatrix\n{\n    /// <summary>\n    /// Interaction logic for App.xaml\n    /// </summary>\n    public partial class App : Application\n    {\n        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n        private ServiceProvider? serviceProvider;\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public static bool IsSentryEnabled => !Debugger.IsAttached || Environment\n            .GetEnvironmentVariable(\"DEBUG_SENTRY\")?.ToLowerInvariant() == \"true\";\n        // ReSharper disable once MemberCanBePrivate.Global\n        public static bool IsExceptionWindowEnabled => !Debugger.IsAttached || Environment\n            .GetEnvironmentVariable(\"DEBUG_EXCEPTION_WINDOW\")?.ToLowerInvariant() == \"true\";\n\n        public static IConfiguration Config { get; set; } = null!;\n\n        private readonly LoggingConfiguration logConfig;\n\n        public App()\n        {\n            Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;\n            Config = new ConfigurationBuilder()\n                .SetBasePath(Directory.GetCurrentDirectory())\n                .AddJsonFile(\"appsettings.json\", optional: true, reloadOnChange: true)\n                .AddJsonFile(\"appsettings.Development.json\", optional: true, reloadOnChange: true)\n                .Build();\n            // This needs to be done before OnStartup\n            // Or Sentry will not be initialized correctly\n            ConfigureErrorHandling();\n            // Setup logging\n            logConfig = ConfigureLogging();\n        }\n\n        private void ConfigureErrorHandling()\n        {\n            if (IsSentryEnabled)\n            {\n                SentrySdk.Init(o =>\n                {\n                    o.Dsn = \"https://eac7a5ea065d44cf9a8565e0f1817da2@o4505314753380352.ingest.sentry.io/4505314756067328\";\n                    o.StackTraceMode = StackTraceMode.Enhanced;\n                    o.TracesSampleRate = 1.0;\n                    o.IsGlobalModeEnabled = true;\n                    // Enables Sentry's \"Release Health\" feature.\n                    o.AutoSessionTracking = true;\n                    // 1.0 to capture 100% of transactions for performance monitoring.\n                    o.TracesSampleRate = 1.0;\n#if DEBUG\n                    o.Environment = \"Development\";\n#endif\n                });\n            }\n\n            if (IsSentryEnabled || IsExceptionWindowEnabled)\n            {\n                DispatcherUnhandledException += App_DispatcherUnhandledException;\n            }\n        }\n\n        private static LoggingConfiguration ConfigureLogging()\n        {\n            var logConfig = new LoggingConfiguration();\n\n            var fileTarget = new FileTarget(\"logfile\")\n            {\n                ArchiveOldFileOnStartup = true,\n                FileName = \"${specialfolder:folder=ApplicationData}/StabilityMatrix/app.log\",\n                ArchiveFileName = \"${specialfolder:folder=ApplicationData}/StabilityMatrix/app.{#}.log\",\n                ArchiveNumbering = ArchiveNumberingMode.Rolling,\n                MaxArchiveFiles = 2\n            };\n            var debugTarget = new DebuggerTarget(\"debugger\") { Layout = \"${message}\" };\n            logConfig.AddRule(NLog.LogLevel.Debug, NLog.LogLevel.Fatal, fileTarget);\n            logConfig.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, debugTarget);\n\n            NLog.LogManager.Configuration = logConfig;\n            // Add Sentry to NLog if enabled\n            if (IsSentryEnabled)\n            {\n                logConfig.AddSentry(o =>\n                {\n                    o.InitializeSdk = false;\n                    o.Layout = \"${message}\";\n                    o.IncludeEventDataOnBreadcrumbs = true;\n                    o.BreadcrumbLayout = \"${logger}: ${message}\";\n                    // Debug and higher are stored as breadcrumbs (default is Info)\n                    o.MinimumBreadcrumbLevel = NLog.LogLevel.Debug;\n                    // Error and higher is sent as event (default is Error)\n                    o.MinimumEventLevel = NLog.LogLevel.Error;\n                });\n            }\n\n            return logConfig;\n        }\n\n\n\n        private void App_OnStartup(object sender, StartupEventArgs e)\n        {\n            if (AppDomain.CurrentDomain.BaseDirectory.EndsWith(\"Update\\\\\"))\n            {\n                var delays = Backoff.DecorrelatedJitterBackoffV2(\n                    TimeSpan.FromMilliseconds(150), retryCount: 3);\n                foreach (var dlay in delays) \n                {\n                    try\n                    {\n                        File.Copy(\n                            Path.Combine(AppDomain.CurrentDomain.BaseDirectory,\n                                \"StabilityMatrix.exe\"),\n                            Path.Combine(AppDomain.CurrentDomain.BaseDirectory, \"..\",\n                                \"StabilityMatrix.exe\"), true);\n                        \n                        Process.Start(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, \"..\",\n                            \"StabilityMatrix.exe\"));\n                        \n                        Current.Shutdown();\n                    }\n                    catch (Exception)\n                    {\n                        Thread.Sleep(dlay);\n                    }\n                }\n                return;\n            }\n\n            var updateDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, \"Update\");\n            if (Directory.Exists(updateDir))\n            {\n                try\n                {\n                    Directory.Delete(updateDir, true);\n                }\n                catch (Exception exception)\n                {\n                    Logger.Error(exception, \"Failed to delete update file\");\n                }\n            }\n\n            var serviceCollection = new ServiceCollection();\n            serviceCollection.AddSingleton<IPageService, PageService>();\n            serviceCollection.AddSingleton<IContentDialogService, ContentDialogService>();\n            serviceCollection.AddSingleton<PageContentDialogService>();\n            serviceCollection.AddSingleton<InstallerWindowDialogService>();\n            serviceCollection.AddSingleton<Wpf.Ui.Contracts.ISnackbarService, Wpf.Ui.Services.SnackbarService>();\n            serviceCollection.AddSingleton<IPackageFactory, PackageFactory>();\n            serviceCollection.AddSingleton<IPyRunner, PyRunner>();\n            serviceCollection.AddSingleton<ISharedFolders, SharedFolders>();\n            serviceCollection.AddTransient<IDialogFactory, DialogFactory>();\n\n            serviceCollection.AddTransient<MainWindow>();\n            serviceCollection.AddTransient<SettingsPage>();\n            serviceCollection.AddTransient<LaunchPage>();\n            serviceCollection.AddTransient<PackageManagerPage>();\n            serviceCollection.AddTransient<TextToImagePage>();\n            serviceCollection.AddTransient<CheckpointManagerPage>();\n            serviceCollection.AddTransient<CheckpointBrowserPage>();\n            serviceCollection.AddTransient<InstallerWindow>();\n            serviceCollection.AddTransient<FirstLaunchSetupWindow>();\n\n            serviceCollection.AddTransient<MainWindowViewModel>();\n            serviceCollection.AddTransient<SnackbarViewModel>();\n            serviceCollection.AddTransient<LaunchOptionsDialogViewModel>();\n            serviceCollection.AddSingleton<SettingsViewModel>();\n            serviceCollection.AddSingleton<LaunchViewModel>();\n            serviceCollection.AddSingleton<PackageManagerViewModel>();\n            serviceCollection.AddSingleton<TextToImageViewModel>();\n            serviceCollection.AddTransient<UpdateWindowViewModel>();\n            serviceCollection.AddTransient<InstallerViewModel>();\n            serviceCollection.AddTransient<SelectInstallLocationsViewModel>();\n            serviceCollection.AddTransient<DataDirectoryMigrationViewModel>();\n            serviceCollection.AddTransient<WebLoginViewModel>();\n            serviceCollection.AddTransient<OneClickInstallViewModel>();\n            serviceCollection.AddTransient<CheckpointManagerViewModel>();\n            serviceCollection.AddSingleton<CheckpointBrowserViewModel>();\n            serviceCollection.AddSingleton<FirstLaunchSetupViewModel>();\n\n            serviceCollection.Configure<DebugOptions>(Config.GetSection(nameof(DebugOptions)));\n\n            serviceCollection.AddSingleton<IUpdateHelper, UpdateHelper>();\n            serviceCollection.AddSingleton<ISettingsManager, SettingsManager>();\n            serviceCollection.AddSingleton<BasePackage, A3WebUI>();\n            serviceCollection.AddSingleton<BasePackage, VladAutomatic>();\n            serviceCollection.AddSingleton<BasePackage, ComfyUI>();\n            serviceCollection.AddSingleton<Wpf.Ui.Contracts.ISnackbarService, Wpf.Ui.Services.SnackbarService>();\n            serviceCollection.AddSingleton<IPrerequisiteHelper, PrerequisiteHelper>();\n            serviceCollection.AddSingleton<ISnackbarService, SnackbarService>();\n            serviceCollection.AddSingleton<INotificationBarService, NotificationBarService>();\n            serviceCollection.AddSingleton<IDownloadService, DownloadService>();\n            serviceCollection.AddTransient<IGitHubClient, GitHubClient>(_ =>\n            {\n                var client = new GitHubClient(new ProductHeaderValue(\"StabilityMatrix\"));\n                var githubApiKey = Config[\"GithubApiKey\"];\n                if (string.IsNullOrWhiteSpace(githubApiKey))\n                    return client;\n\n                client.Credentials = new Credentials(githubApiKey);\n                return client;\n            });\n            serviceCollection.AddSingleton<ModelFinder>();\n\n            // Database\n            serviceCollection.AddSingleton<ILiteDbContext, LiteDbContext>();\n\n            // Caches\n            serviceCollection.AddMemoryCache();\n            serviceCollection.AddSingleton<IGithubApiCache, GithubApiCache>();\n\n            // Configure Refit and Polly\n            var defaultSystemTextJsonSettings =\n                SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions();\n            defaultSystemTextJsonSettings.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;\n\n            var defaultRefitSettings = new RefitSettings\n            {\n                ContentSerializer =\n                    new SystemTextJsonContentSerializer(defaultSystemTextJsonSettings),\n            };\n\n            // HTTP Policies\n            var retryStatusCodes = new[] {\n                HttpStatusCode.RequestTimeout, // 408\n                HttpStatusCode.InternalServerError, // 500\n                HttpStatusCode.BadGateway, // 502\n                HttpStatusCode.ServiceUnavailable, // 503\n                HttpStatusCode.GatewayTimeout // 504\n            };\n            var delay = Backoff\n                .DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromMilliseconds(80), retryCount: 5);\n            var retryPolicy = HttpPolicyExtensions\n                .HandleTransientHttpError()\n                .Or<TimeoutRejectedException>()\n                .OrResult(r => retryStatusCodes.Contains(r.StatusCode))\n                .WaitAndRetryAsync(delay);\n            \n            // Shorter timeout for local requests\n            var localTimeout = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(3));\n            var localDelay = Backoff\n                .DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromMilliseconds(50), retryCount: 3);\n            var localRetryPolicy = HttpPolicyExtensions\n                .HandleTransientHttpError()\n                .Or<TimeoutRejectedException>()\n                .OrResult(r => retryStatusCodes.Contains(r.StatusCode))\n                .WaitAndRetryAsync(localDelay, onRetryAsync: (x, y) =>\n                {\n                    Debug.WriteLine(\"Retrying local request...\");\n                    return Task.CompletedTask;\n                });\n            \n            // named client for update\n            serviceCollection.AddHttpClient(\"UpdateClient\")\n                .AddPolicyHandler(retryPolicy);\n\n            // Add Refit clients\n            serviceCollection.AddRefitClient<ICivitApi>(defaultRefitSettings)\n                .ConfigureHttpClient(c =>\n                {\n                    c.BaseAddress = new Uri(\"https://civitai.com\");\n                    c.Timeout = TimeSpan.FromSeconds(15);\n                })\n                .AddPolicyHandler(retryPolicy);\n\n            // Add Refit client managers\n            serviceCollection.AddHttpClient(\"A3Client\")\n                .AddPolicyHandler(localTimeout.WrapAsync(localRetryPolicy));\n            \n            serviceCollection.AddSingleton<IA3WebApiManager>(services =>\n                new A3WebApiManager(services.GetRequiredService<ISettingsManager>(),\n                    services.GetRequiredService<IHttpClientFactory>())\n                {\n                    RefitSettings = defaultRefitSettings,\n                });\n\n            // Add logging\n            serviceCollection.AddLogging(builder =>\n            {\n                builder.ClearProviders();\n                builder.AddFilter(\"Microsoft.Extensions.Http\", LogLevel.Warning)\n                       .AddFilter(\"Microsoft\", LogLevel.Warning)\n                       .AddFilter(\"System\", LogLevel.Warning);\n                builder.SetMinimumLevel(LogLevel.Debug);\n                builder.AddNLog(logConfig);\n            });\n\n            // Remove HTTPClientFactory logging\n            serviceCollection.RemoveAll<IHttpMessageHandlerBuilderFilter>();\n\n            // Default error handling for 'SafeFireAndForget'\n            SafeFireAndForgetExtensions.Initialize();\n            SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex =>\n            {\n                Logger?.Warn(ex, \"Background Task failed: {ExceptionMessage}\", ex.Message);\n            });\n\n            serviceProvider = serviceCollection.BuildServiceProvider();\n\n            var settingsManager = serviceProvider.GetRequiredService<ISettingsManager>();\n\n            // First time setup if needed\n            if (!settingsManager.IsEulaAccepted())\n            {\n                var setupWindow = serviceProvider.GetRequiredService<FirstLaunchSetupWindow>();\n                if (setupWindow.ShowDialog() ?? false)\n                {\n                    settingsManager.SetEulaAccepted();\n                }\n                else\n                {\n                    Current.Shutdown();\n                    return;\n                }\n            }\n\n            var window = serviceProvider.GetRequiredService<MainWindow>();\n            window.Show();\n        }\n\n        private void App_OnExit(object sender, ExitEventArgs e)\n        {\n            serviceProvider?.GetRequiredService<LaunchViewModel>().OnShutdown();\n            var settingsManager = serviceProvider?.GetRequiredService<ISettingsManager>();\n\n            // Skip remaining steps if no library is set\n            if (!(settingsManager?.TryFindLibrary() ?? false)) return;\n            \n            // If RemoveFolderLinksOnShutdown is set, delete all package junctions\n            if (settingsManager.Settings.RemoveFolderLinksOnShutdown)\n            {\n                var sharedFolders = serviceProvider?.GetRequiredService<ISharedFolders>();\n                sharedFolders?.RemoveLinksForAllPackages();\n            }\n            \n            // Dispose of database\n            serviceProvider?.GetRequiredService<ILiteDbContext>().Dispose();\n        }\n\n        [DoesNotReturn]\n        private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)\n        {\n            if (SentrySdk.IsEnabled)\n            {\n                SentrySdk.CaptureException(e.Exception);\n            }\n\n            var logger = serviceProvider?.GetRequiredService<ILogger<App>>();\n            logger?.LogCritical(e.Exception, \"Unhandled Exception: {ExceptionMessage}\", e.Exception.Message);\n\n            if (IsExceptionWindowEnabled)\n            {\n                var vm = new ExceptionWindowViewModel\n                {\n                    Exception = e.Exception\n                };\n                var exceptionWindow = new ExceptionWindow\n                {\n                    DataContext = vm\n                };\n\n                if (MainWindow?.IsActive ?? false)\n                {\n                    exceptionWindow.Owner = MainWindow;\n                }\n                exceptionWindow.ShowDialog();\n            }\n            e.Handled = true;\n            Current.Shutdown(1);\n            Environment.Exit(1);\n        }\n    }\n}\n\n"
  },
  {
    "path": "StabilityMatrix/AppxManifest.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Package xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10\" xmlns:uap=\"http://schemas.microsoft.com/appx/manifest/uap/windows10\" xmlns:uap10=\"http://schemas.microsoft.com/appx/manifest/uap/windows10/10\" xmlns:rescap=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities\" IgnorableNamespaces=\"uap uap10 rescap\">\n  <!--Package created by MSIX Packaging Tool version: 1.2023.319.0-->\n  <Identity Name=\"StabilityMatrix\" Publisher=\"CN=Lykos, O=Lykos, C=US\" Version=\"1.0.2.0\" ProcessorArchitecture=\"x64\" />\n  <Properties>\n    <DisplayName>Stability Matrix</DisplayName>\n    <PublisherDisplayName>Stability Matrix LLC</PublisherDisplayName>\n    <Description>None</Description>\n    <Logo>Assets\\StoreLogo.png</Logo>\n    <uap10:PackageIntegrity>\n      <uap10:Content Enforcement=\"on\" />\n    </uap10:PackageIntegrity>\n  </Properties>\n  <Resources>\n    <Resource Language=\"en-us\" />\n  </Resources>\n  <Dependencies>\n    <TargetDeviceFamily Name=\"Windows.Desktop\" MinVersion=\"10.0.17763.0\" MaxVersionTested=\"10.0.22000.1\" />\n  </Dependencies>\n  <Applications>\n    <Application Id=\"STABILITYMATRIX\" Executable=\"StabilityMatrix.exe\" EntryPoint=\"Windows.FullTrustApplication\">\n      <uap:VisualElements BackgroundColor=\"transparent\" DisplayName=\"StabilityMatrix\" Square150x150Logo=\"Assets\\STABILITYMATRIX-Square150x150Logo.png\" Square44x44Logo=\"Assets\\STABILITYMATRIX-Square44x44Logo.png\" Description=\"StabilityMatrix\">\n        <uap:DefaultTile Wide310x150Logo=\"Assets\\STABILITYMATRIX-Wide310x150Logo.png\" Square310x310Logo=\"Assets\\STABILITYMATRIX-Square310x310Logo.png\" Square71x71Logo=\"Assets\\STABILITYMATRIX-Square71x71Logo.png\" />\n      </uap:VisualElements>\n    </Application>\n  </Applications>\n  <Capabilities>\n    <rescap:Capability Name=\"runFullTrust\" />\n  </Capabilities>\n</Package>"
  },
  {
    "path": "StabilityMatrix/AssemblyInfo.cs",
    "content": "using System.Windows;\n\n[assembly: ThemeInfo(\n    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located\n                                     //(used if a resource is not found in the page,\n                                     // or application resource dictionaries)\n    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located\n                                              //(used if a resource is not found in the page,\n                                              // app, or any theme specific resource dictionaries)\n)]\n"
  },
  {
    "path": "StabilityMatrix/Assets/7za - LICENSE.txt",
    "content": "7-Zip Extra 18.01\n-----------------\n\n7-Zip Extra is package of extra modules of 7-Zip. \n\n7-Zip Copyright (C) 1999-2018 Igor Pavlov.\n\n7-Zip is free software. Read License.txt for more information about license.\n\nSource code of binaries can be found at:\n  http://www.7-zip.org/\n\n\n7-Zip Extra\n~~~~~~~~~~~\nLicense for use and distribution\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nCopyright (C) 1999-2018 Igor Pavlov.\n\n7-Zip Extra files are under the GNU LGPL license.\n\n\nNotes: \n  You can use 7-Zip Extra on any computer, including a computer in a commercial \n  organization. You don't need to register or pay for 7-Zip.\n\n\nGNU LGPL information\n--------------------\n\n  This library is free software; you can redistribute it and/or\n  modify it under the terms of the GNU Lesser General Public\n  License as published by the Free Software Foundation; either\n  version 2.1 of the License, or (at your option) any later version.\n\n  This library is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n  Lesser General Public License for more details.\n\n  You can receive a copy of the GNU Lesser General Public License from \n  http://www.gnu.org/\n"
  },
  {
    "path": "StabilityMatrix/Assets/Python310/LICENSE.txt",
    "content": "A. HISTORY OF THE SOFTWARE\n==========================\n\nPython was created in the early 1990s by Guido van Rossum at Stichting\nMathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands\nas a successor of a language called ABC.  Guido remains Python's\nprincipal author, although it includes many contributions from others.\n\nIn 1995, Guido continued his work on Python at the Corporation for\nNational Research Initiatives (CNRI, see https://www.cnri.reston.va.us)\nin Reston, Virginia where he released several versions of the\nsoftware.\n\nIn May 2000, Guido and the Python core development team moved to\nBeOpen.com to form the BeOpen PythonLabs team.  In October of the same\nyear, the PythonLabs team moved to Digital Creations, which became\nZope Corporation.  In 2001, the Python Software Foundation (PSF, see\nhttps://www.python.org/psf/) was formed, a non-profit organization\ncreated specifically to own Python-related Intellectual Property.\nZope Corporation was a sponsoring member of the PSF.\n\nAll Python releases are Open Source (see https://opensource.org for\nthe Open Source Definition).  Historically, most, but not all, Python\nreleases have also been GPL-compatible; the table below summarizes\nthe various releases.\n\n    Release         Derived     Year        Owner       GPL-\n                    from                                compatible? (1)\n\n    0.9.0 thru 1.2              1991-1995   CWI         yes\n    1.3 thru 1.5.2  1.2         1995-1999   CNRI        yes\n    1.6             1.5.2       2000        CNRI        no\n    2.0             1.6         2000        BeOpen.com  no\n    1.6.1           1.6         2001        CNRI        yes (2)\n    2.1             2.0+1.6.1   2001        PSF         no\n    2.0.1           2.0+1.6.1   2001        PSF         yes\n    2.1.1           2.1+2.0.1   2001        PSF         yes\n    2.1.2           2.1.1       2002        PSF         yes\n    2.1.3           2.1.2       2002        PSF         yes\n    2.2 and above   2.1.1       2001-now    PSF         yes\n\nFootnotes:\n\n(1) GPL-compatible doesn't mean that we're distributing Python under\n    the GPL.  All Python licenses, unlike the GPL, let you distribute\n    a modified version without making your changes open source.  The\n    GPL-compatible licenses make it possible to combine Python with\n    other software that is released under the GPL; the others don't.\n\n(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,\n    because its license has a choice of law clause.  According to\n    CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1\n    is \"not incompatible\" with the GPL.\n\nThanks to the many outside volunteers who have worked under Guido's\ndirection to make these releases possible.\n\n\nB. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON\n===============================================================\n\nPython software and documentation are licensed under the\nPython Software Foundation License Version 2.\n\nStarting with Python 3.8.6, examples, recipes, and other code in\nthe documentation are dual licensed under the PSF License Version 2\nand the Zero-Clause BSD license.\n\nSome software incorporated into Python is under different licenses.\nThe licenses are listed with code falling under that license.\n\n\nPYTHON SOFTWARE FOUNDATION LICENSE VERSION 2\n--------------------------------------------\n\n1. This LICENSE AGREEMENT is between the Python Software Foundation\n(\"PSF\"), and the Individual or Organization (\"Licensee\") accessing and\notherwise using this software (\"Python\") in source or binary form and\nits associated documentation.\n\n2. Subject to the terms and conditions of this License Agreement, PSF hereby\ngrants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,\nanalyze, test, perform and/or display publicly, prepare derivative works,\ndistribute, and otherwise use Python alone or in any derivative version,\nprovided, however, that PSF's License Agreement and PSF's notice of copyright,\ni.e., \"Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,\n2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;\nAll Rights Reserved\" are retained in Python alone or in any derivative version\nprepared by Licensee.\n\n3. In the event Licensee prepares a derivative work that is based on\nor incorporates Python or any part thereof, and wants to make\nthe derivative work available to others as provided herein, then\nLicensee hereby agrees to include in any such work a brief summary of\nthe changes made to Python.\n\n4. PSF is making Python available to Licensee on an \"AS IS\"\nbasis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR\nIMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND\nDISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS\nFOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT\nINFRINGE ANY THIRD PARTY RIGHTS.\n\n5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON\nFOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS\nA RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,\nOR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.\n\n6. This License Agreement will automatically terminate upon a material\nbreach of its terms and conditions.\n\n7. Nothing in this License Agreement shall be deemed to create any\nrelationship of agency, partnership, or joint venture between PSF and\nLicensee.  This License Agreement does not grant permission to use PSF\ntrademarks or trade name in a trademark sense to endorse or promote\nproducts or services of Licensee, or any third party.\n\n8. By copying, installing or otherwise using Python, Licensee\nagrees to be bound by the terms and conditions of this License\nAgreement.\n\n\nBEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0\n-------------------------------------------\n\nBEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1\n\n1. This LICENSE AGREEMENT is between BeOpen.com (\"BeOpen\"), having an\noffice at 160 Saratoga Avenue, Santa Clara, CA 95051, and the\nIndividual or Organization (\"Licensee\") accessing and otherwise using\nthis software in source or binary form and its associated\ndocumentation (\"the Software\").\n\n2. Subject to the terms and conditions of this BeOpen Python License\nAgreement, BeOpen hereby grants Licensee a non-exclusive,\nroyalty-free, world-wide license to reproduce, analyze, test, perform\nand/or display publicly, prepare derivative works, distribute, and\notherwise use the Software alone or in any derivative version,\nprovided, however, that the BeOpen Python License is retained in the\nSoftware, alone or in any derivative version prepared by Licensee.\n\n3. BeOpen is making the Software available to Licensee on an \"AS IS\"\nbasis.  BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR\nIMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND\nDISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS\nFOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT\nINFRINGE ANY THIRD PARTY RIGHTS.\n\n4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE\nSOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS\nAS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY\nDERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.\n\n5. This License Agreement will automatically terminate upon a material\nbreach of its terms and conditions.\n\n6. This License Agreement shall be governed by and interpreted in all\nrespects by the law of the State of California, excluding conflict of\nlaw provisions.  Nothing in this License Agreement shall be deemed to\ncreate any relationship of agency, partnership, or joint venture\nbetween BeOpen and Licensee.  This License Agreement does not grant\npermission to use BeOpen trademarks or trade names in a trademark\nsense to endorse or promote products or services of Licensee, or any\nthird party.  As an exception, the \"BeOpen Python\" logos available at\nhttp://www.pythonlabs.com/logos.html may be used according to the\npermissions granted on that web page.\n\n7. By copying, installing or otherwise using the software, Licensee\nagrees to be bound by the terms and conditions of this License\nAgreement.\n\n\nCNRI LICENSE AGREEMENT FOR PYTHON 1.6.1\n---------------------------------------\n\n1. This LICENSE AGREEMENT is between the Corporation for National\nResearch Initiatives, having an office at 1895 Preston White Drive,\nReston, VA 20191 (\"CNRI\"), and the Individual or Organization\n(\"Licensee\") accessing and otherwise using Python 1.6.1 software in\nsource or binary form and its associated documentation.\n\n2. Subject to the terms and conditions of this License Agreement, CNRI\nhereby grants Licensee a nonexclusive, royalty-free, world-wide\nlicense to reproduce, analyze, test, perform and/or display publicly,\nprepare derivative works, distribute, and otherwise use Python 1.6.1\nalone or in any derivative version, provided, however, that CNRI's\nLicense Agreement and CNRI's notice of copyright, i.e., \"Copyright (c)\n1995-2001 Corporation for National Research Initiatives; All Rights\nReserved\" are retained in Python 1.6.1 alone or in any derivative\nversion prepared by Licensee.  Alternately, in lieu of CNRI's License\nAgreement, Licensee may substitute the following text (omitting the\nquotes): \"Python 1.6.1 is made available subject to the terms and\nconditions in CNRI's License Agreement.  This Agreement together with\nPython 1.6.1 may be located on the internet using the following\nunique, persistent identifier (known as a handle): 1895.22/1013.  This\nAgreement may also be obtained from a proxy server on the internet\nusing the following URL: http://hdl.handle.net/1895.22/1013\".\n\n3. In the event Licensee prepares a derivative work that is based on\nor incorporates Python 1.6.1 or any part thereof, and wants to make\nthe derivative work available to others as provided herein, then\nLicensee hereby agrees to include in any such work a brief summary of\nthe changes made to Python 1.6.1.\n\n4. CNRI is making Python 1.6.1 available to Licensee on an \"AS IS\"\nbasis.  CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR\nIMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND\nDISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS\nFOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT\nINFRINGE ANY THIRD PARTY RIGHTS.\n\n5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON\n1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS\nA RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,\nOR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.\n\n6. This License Agreement will automatically terminate upon a material\nbreach of its terms and conditions.\n\n7. This License Agreement shall be governed by the federal\nintellectual property law of the United States, including without\nlimitation the federal copyright law, and, to the extent such\nU.S. federal law does not apply, by the law of the Commonwealth of\nVirginia, excluding Virginia's conflict of law provisions.\nNotwithstanding the foregoing, with regard to derivative works based\non Python 1.6.1 that incorporate non-separable material that was\npreviously distributed under the GNU General Public License (GPL), the\nlaw of the Commonwealth of Virginia shall govern this License\nAgreement only as to issues arising under or with respect to\nParagraphs 4, 5, and 7 of this License Agreement.  Nothing in this\nLicense Agreement shall be deemed to create any relationship of\nagency, partnership, or joint venture between CNRI and Licensee.  This\nLicense Agreement does not grant permission to use CNRI trademarks or\ntrade name in a trademark sense to endorse or promote products or\nservices of Licensee, or any third party.\n\n8. By clicking on the \"ACCEPT\" button where indicated, or by copying,\ninstalling or otherwise using Python 1.6.1, Licensee agrees to be\nbound by the terms and conditions of this License Agreement.\n\n        ACCEPT\n\n\nCWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2\n--------------------------------------------------\n\nCopyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,\nThe Netherlands.  All rights reserved.\n\nPermission to use, copy, modify, and distribute this software and its\ndocumentation for any purpose and without fee is hereby granted,\nprovided that the above copyright notice appear in all copies and that\nboth that copyright notice and this permission notice appear in\nsupporting documentation, and that the name of Stichting Mathematisch\nCentrum or CWI not be used in advertising or publicity pertaining to\ndistribution of the software without specific, written prior\npermission.\n\nSTICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO\nTHIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE\nFOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT\nOF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\nZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION\n----------------------------------------------------------------------\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n\n\n\nAdditional Conditions for this Windows binary build\n---------------------------------------------------\n\nThis program is linked with and uses Microsoft Distributable Code,\ncopyrighted by Microsoft Corporation. The Microsoft Distributable Code\nis embedded in each .exe, .dll and .pyd file as a result of running\nthe code through a linker.\n\nIf you further distribute programs that include the Microsoft\nDistributable Code, you must comply with the restrictions on\ndistribution specified by Microsoft. In particular, you must require\ndistributors and external end users to agree to terms that protect the\nMicrosoft Distributable Code at least as much as Microsoft's own\nrequirements for the Distributable Code. See Microsoft's documentation\n(included in its developer tools and on its website at microsoft.com)\nfor specific details.\n\nRedistribution of the Windows binary build of the Python interpreter\ncomplies with this agreement, provided that you do not:\n\n- alter any copyright, trademark or patent notice in Microsoft's\nDistributable Code;\n\n- use Microsoft's trademarks in your programs' names or in a way that\nsuggests your programs come from or are endorsed by Microsoft;\n\n- distribute Microsoft's Distributable Code to run on a platform other\nthan Microsoft operating systems, run-time technologies or application\nplatforms; or\n\n- include Microsoft Distributable Code in malicious, deceptive or\nunlawful programs.\n\nThese restrictions apply only to the Microsoft Distributable Code as\ndefined above, not to Python itself or any programs running on the\nPython interpreter. The redistribution of the Python interpreter and\nlibraries is governed by the Python Software License included with this\nfile, or by other licenses as marked.\n\n\n\n--------------------------------------------------------------------------\n\nThis program, \"bzip2\", the associated library \"libbzip2\", and all\ndocumentation, are copyright (C) 1996-2019 Julian R Seward.  All\nrights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n\n2. The origin of this software must not be misrepresented; you must \n   not claim that you wrote the original software.  If you use this \n   software in a product, an acknowledgment in the product \n   documentation would be appreciated but is not required.\n\n3. Altered source versions must be plainly marked as such, and must\n   not be misrepresented as being the original software.\n\n4. The name of the author may not be used to endorse or promote \n   products derived from this software without specific prior written \n   permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS\nOR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE\nGOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\nWHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nJulian Seward, jseward@acm.org\nbzip2/libbzip2 version 1.0.8 of 13 July 2019\n\n--------------------------------------------------------------------------\n\n\n  LICENSE ISSUES\n  ==============\n\n  The OpenSSL toolkit stays under a double license, i.e. both the conditions of\n  the OpenSSL License and the original SSLeay license apply to the toolkit.\n  See below for the actual license texts.\n\n  OpenSSL License\n  ---------------\n\n/* ====================================================================\n * Copyright (c) 1998-2019 The OpenSSL Project.  All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n * 3. All advertising materials mentioning features or use of this\n *    software must display the following acknowledgment:\n *    \"This product includes software developed by the OpenSSL Project\n *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)\"\n *\n * 4. The names \"OpenSSL Toolkit\" and \"OpenSSL Project\" must not be used to\n *    endorse or promote products derived from this software without\n *    prior written permission. For written permission, please contact\n *    openssl-core@openssl.org.\n *\n * 5. Products derived from this software may not be called \"OpenSSL\"\n *    nor may \"OpenSSL\" appear in their names without prior written\n *    permission of the OpenSSL Project.\n *\n * 6. Redistributions of any form whatsoever must retain the following\n *    acknowledgment:\n *    \"This product includes software developed by the OpenSSL Project\n *    for use in the OpenSSL Toolkit (http://www.openssl.org/)\"\n *\n * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY\n * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR\n * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n * ====================================================================\n *\n * This product includes cryptographic software written by Eric Young\n * (eay@cryptsoft.com).  This product includes software written by Tim\n * Hudson (tjh@cryptsoft.com).\n *\n */\n\n Original SSLeay License\n -----------------------\n\n/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)\n * All rights reserved.\n *\n * This package is an SSL implementation written\n * by Eric Young (eay@cryptsoft.com).\n * The implementation was written so as to conform with Netscapes SSL.\n *\n * This library is free for commercial and non-commercial use as long as\n * the following conditions are aheared to.  The following conditions\n * apply to all code found in this distribution, be it the RC4, RSA,\n * lhash, DES, etc., code; not just the SSL code.  The SSL documentation\n * included with this distribution is covered by the same copyright terms\n * except that the holder is Tim Hudson (tjh@cryptsoft.com).\n *\n * Copyright remains Eric Young's, and as such any Copyright notices in\n * the code are not to be removed.\n * If this package is used in a product, Eric Young should be given attribution\n * as the author of the parts of the library used.\n * This can be in the form of a textual message at program startup or\n * in documentation (online or textual) provided with the package.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n * 3. All advertising materials mentioning features or use of this software\n *    must display the following acknowledgement:\n *    \"This product includes cryptographic software written by\n *     Eric Young (eay@cryptsoft.com)\"\n *    The word 'cryptographic' can be left out if the rouines from the library\n *    being used are not cryptographic related :-).\n * 4. If you include any Windows specific code (or a derivative thereof) from\n *    the apps directory (application code) you must include an acknowledgement:\n *    \"This product includes software written by Tim Hudson (tjh@cryptsoft.com)\"\n *\n * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n * The licence and distribution terms for any publically available version or\n * derivative of this code cannot be changed.  i.e. this code cannot simply be\n * copied and put under another distribution licence\n * [including the GNU Public Licence.]\n */\n\n\nlibffi - Copyright (c) 1996-2014  Anthony Green, Red Hat, Inc and others.\nSee source files for details.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n``Software''), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nThis software is copyrighted by the Regents of the University of\nCalifornia, Sun Microsystems, Inc., Scriptics Corporation, ActiveState\nCorporation and other parties.  The following terms apply to all files\nassociated with the software unless explicitly disclaimed in\nindividual files.\n\nThe authors hereby grant permission to use, copy, modify, distribute,\nand license this software and its documentation for any purpose, provided\nthat existing copyright notices are retained in all copies and that this\nnotice is included verbatim in any distributions. No written agreement,\nlicense, or royalty fee is required for any of the authorized uses.\nModifications to this software may be copyrighted by their authors\nand need not follow the licensing terms described here, provided that\nthe new terms are clearly indicated on the first page of each file where\nthey apply.\n\nIN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY\nFOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES\nARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY\nDERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n\nTHE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,\nINCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE\nIS PROVIDED ON AN \"AS IS\" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE\nNO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR\nMODIFICATIONS.\n\nGOVERNMENT USE: If you are acquiring this software on behalf of the\nU.S. government, the Government shall have only \"Restricted Rights\"\nin the software and related documentation as defined in the Federal\nAcquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you\nare acquiring the software on behalf of the Department of Defense, the\nsoftware shall be classified as \"Commercial Computer Software\" and the\nGovernment shall have only \"Restricted Rights\" as defined in Clause\n252.227-7014 (b) (3) of DFARs.  Notwithstanding the foregoing, the\nauthors grant the U.S. Government and others acting in its behalf\npermission to use and distribute the software in accordance with the\nterms specified in this license.\n\nThis software is copyrighted by the Regents of the University of\nCalifornia, Sun Microsystems, Inc., Scriptics Corporation, ActiveState\nCorporation, Apple Inc. and other parties.  The following terms apply to\nall files associated with the software unless explicitly disclaimed in\nindividual files.\n\nThe authors hereby grant permission to use, copy, modify, distribute,\nand license this software and its documentation for any purpose, provided\nthat existing copyright notices are retained in all copies and that this\nnotice is included verbatim in any distributions. No written agreement,\nlicense, or royalty fee is required for any of the authorized uses.\nModifications to this software may be copyrighted by their authors\nand need not follow the licensing terms described here, provided that\nthe new terms are clearly indicated on the first page of each file where\nthey apply.\n\nIN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY\nFOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES\nARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY\nDERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n\nTHE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,\nINCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE\nIS PROVIDED ON AN \"AS IS\" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE\nNO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR\nMODIFICATIONS.\n\nGOVERNMENT USE: If you are acquiring this software on behalf of the\nU.S. government, the Government shall have only \"Restricted Rights\"\nin the software and related documentation as defined in the Federal\nAcquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you\nare acquiring the software on behalf of the Department of Defense, the\nsoftware shall be classified as \"Commercial Computer Software\" and the\nGovernment shall have only \"Restricted Rights\" as defined in Clause\n252.227-7013 (b) (3) of DFARs.  Notwithstanding the foregoing, the\nauthors grant the U.S. Government and others acting in its behalf\npermission to use and distribute the software in accordance with the\nterms specified in this license.\n\nCopyright (c) 1993-1999 Ioi Kim Lam.\nCopyright (c) 2000-2001 Tix Project Group.\nCopyright (c) 2004 ActiveState\n\nThis software is copyrighted by the above entities\nand other parties.  The following terms apply to all files associated\nwith the software unless explicitly disclaimed in individual files.\n\nThe authors hereby grant permission to use, copy, modify, distribute,\nand license this software and its documentation for any purpose, provided\nthat existing copyright notices are retained in all copies and that this\nnotice is included verbatim in any distributions. No written agreement,\nlicense, or royalty fee is required for any of the authorized uses.\nModifications to this software may be copyrighted by their authors\nand need not follow the licensing terms described here, provided that\nthe new terms are clearly indicated on the first page of each file where\nthey apply.\n\nIN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY\nFOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES\nARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY\nDERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n\nTHE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,\nINCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE\nIS PROVIDED ON AN \"AS IS\" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE\nNO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR\nMODIFICATIONS.\n\nGOVERNMENT USE: If you are acquiring this software on behalf of the\nU.S. government, the Government shall have only \"Restricted Rights\"\nin the software and related documentation as defined in the Federal \nAcquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you\nare acquiring the software on behalf of the Department of Defense, the\nsoftware shall be classified as \"Commercial Computer Software\" and the\nGovernment shall have only \"Restricted Rights\" as defined in Clause\n252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing, the\nauthors grant the U.S. Government and others acting in its behalf\npermission to use and distribute the software in accordance with the\nterms specified in this license. \n\n----------------------------------------------------------------------\n\nParts of this software are based on the Tcl/Tk software copyrighted by\nthe Regents of the University of California, Sun Microsystems, Inc.,\nand other parties. The original license terms of the Tcl/Tk software\ndistribution is included in the file docs/license.tcltk.\n\nParts of this software are based on the HTML Library software\ncopyrighted by Sun Microsystems, Inc. The original license terms of\nthe HTML Library software distribution is included in the file\ndocs/license.html_lib.\n\n"
  },
  {
    "path": "StabilityMatrix/Assets/Python310/python310._pth",
    "content": "python310.zip\n.\n\n# Uncomment to run site.main() automatically\nimport site\n"
  },
  {
    "path": "StabilityMatrix/Assets/automatic_vladmandic.sm-package.yml",
    "content": "﻿name: automatic\ndisplay-name: SD.Next Web UI\nauthor: vladmandic\n\ndownload:\n  steps:\n    - uses: git\n      with:\n        args: clone ${git_repo_url} ${install_dir}\n    - uses: git\n      with:\n        args: checkout ${version_commit_sha}\n\ninstall:\n  steps:\n    - name: Install PyTorch (CUDA)\n      uses: venv-run\n      if: system.has_nvidia_gpu\n      with:\n        args:\n          - pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu118\n          - pip install xformers\n\n    - name: Install PyTorch (DirectML)\n      uses: venv-run\n      if: not system.has_nvidia_gpu and system.has_amd_gpu\n      with:\n        args: pip install torch-directml\n\n    - name: Install Requirements\n      uses: venv-run\n      with:\n        args: pip install -r requirements.txt\n"
  },
  {
    "path": "StabilityMatrix/Assets/licenses.json",
    "content": "[{\"PackageName\":\"7-Zip.CommandLine\",\"PackageVersion\":\"18.1.0\",\"PackageUrl\":\"http://www.7-zip.org/\",\"Copyright\":\"7-Zip Copyright (C) 1999-2018 Igor Pavlov.\",\"Authors\":[\"Igor Pavlov\"],\"Description\":\"7-Zip is a file archiver with a high compression ratio.\\r\\n\\r\\n7za.exe is a standalone console version of 7-Zip with reduced formats support.\\r\\n\\r\\n7za.exe features:\\r\\n\\r\\n- High compression ratio in 7z format\\r\\n- Supported formats:\\r\\n  - Packing / unpacking: 7z, xz, ZIP, GZIP, BZIP2 and TAR \\r\\n  - Unpacking only: Z, lzma, CAB.\\r\\n- Highest compression ratio for ZIP and GZIP formats.\\r\\n- Fast compression and decompression\\r\\n- Strong AES-256 encryption in 7z and ZIP formats.\",\"LicenseUrl\":\"http://www.7-zip.org/license.txt\",\"LicenseType\":\"\"},{\"PackageName\":\"CommunityToolkit.Mvvm\",\"PackageVersion\":\"8.2.0\",\"PackageUrl\":\"https://github.com/CommunityToolkit/dotnet\",\"Copyright\":\"(c) .NET Foundation and Contributors. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"This package includes a .NET MVVM library with helpers such as:\\r\\n      - ObservableObject: a base class for objects implementing the INotifyPropertyChanged interface.\\r\\n      - ObservableRecipient: a base class for observable objects with support for the IMessenger service.\\r\\n      - ObservableValidator: a base class for objects implementing the INotifyDataErrorInfo interface.\\r\\n      - RelayCommand: a simple delegate command implementing the ICommand interface.\\r\\n      - AsyncRelayCommand: a delegate command supporting asynchronous operations and cancellation.\\r\\n      - WeakReferenceMessenger: a messaging system to exchange messages through different loosely-coupled objects.\\r\\n      - StrongReferenceMessenger: a high-performance messaging system that trades weak references for speed.\\r\\n      - Ioc: a helper class to configure dependency injection service containers.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\"},{\"PackageName\":\"FuzzySharp\",\"PackageVersion\":\"2.0.2\",\"PackageUrl\":\"https://github.com/JakeBayer/FuzzySharp\",\"Copyright\":\"\",\"Authors\":[\"Jacob Bayer\"],\"Description\":\"Fuzzy string matcher based on FuzzyWuzzy algorithm from SeatGeek\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\"},{\"PackageName\":\"Markdown.Xaml\",\"PackageVersion\":\"1.0.0\",\"PackageUrl\":\"https://github.com/theunrepentantgeek/Markdown.XAML\",\"Copyright\":\"Copyright (c) 2010 Bevan Arps\",\"Authors\":[\"Bevan Arps\"],\"Description\":\"Markdown XAML processor\",\"LicenseUrl\":\"https://github.com/theunrepentantgeek/Markdown.XAML/blob/master/License.txt\",\"LicenseType\":\"\"},{\"PackageName\":\"NCode.ReparsePoints\",\"PackageVersion\":\"1.0.2\",\"PackageUrl\":\"https://github.com/NCodeGroup/NCode.ReparsePoints\",\"Copyright\":\"Copyright © 2015 NCode Group\",\"Authors\":[\"NCode Group\"],\"Description\":\"This library provides an API to create and inspect win32 file and folder reparse points such as hard links, junctions (aka soft links), and symbolic links.\",\"LicenseUrl\":\"https://raw.githubusercontent.com/NCodeGroup/NCode.ReparsePoints/master/LICENSE.txt\",\"LicenseType\":\"\"},{\"PackageName\":\"NLog\",\"PackageVersion\":\"5.1.4\",\"PackageUrl\":\"https://nlog-project.org/\",\"Copyright\":\"Copyright (c) 2004-2023 NLog Project - https://nlog-project.org/\",\"Authors\":[\"Jarek Kowalski\",\"Kim Christensen\",\"Julian Verdurmen\"],\"Description\":\"NLog is a logging platform for .NET with rich log routing and management capabilities.\\r\\nNLog supports traditional logging, structured logging and the combination of both.\\r\\n\\r\\nSupported platforms:\\r\\n\\r\\n- .NET 5, 6 and 7\\r\\n- .NET Core 1, 2 and 3\\r\\n- .NET Standard 1.3+ and 2.0+\\r\\n- .NET Framework 3.5 - 4.8\\r\\n- Xamarin Android + iOS (.NET Standard)\\r\\n- Mono 4\\r\\n\\r\\nFor ASP.NET Core, check: https://www.nuget.org/packages/NLog.Web.AspNetCore\",\"LicenseUrl\":\"https://licenses.nuget.org/BSD-3-Clause\",\"LicenseType\":\"BSD-3-Clause\"},{\"PackageName\":\"NLog.Extensions.Logging\",\"PackageVersion\":\"5.2.3\",\"PackageUrl\":\"https://github.com/NLog/NLog.Extensions.Logging\",\"Copyright\":\"\",\"Authors\":[\"Microsoft\",\"Julian Verdurmen\"],\"Description\":\"NLog LoggerProvider for Microsoft.Extensions.Logging for logging in .NET Standard libraries and .NET Core applications.\\r\\n\\r\\nFor ASP.NET Core, check: https://www.nuget.org/packages/NLog.Web.AspNetCore\",\"LicenseUrl\":\"https://licenses.nuget.org/BSD-2-Clause\",\"LicenseType\":\"BSD-2-Clause\"},{\"PackageName\":\"Octokit\",\"PackageVersion\":\"6.0.0\",\"PackageUrl\":\"https://github.com/octokit/octokit.net\",\"Copyright\":\"Copyright GitHub 2017\",\"Authors\":[\"GitHub\"],\"Description\":\"An async-based GitHub API client library for .NET and .NET Core\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\"},{\"PackageName\":\"Ookii.Dialogs.Wpf\",\"PackageVersion\":\"5.0.1\",\"PackageUrl\":\"https://github.com/ookii-dialogs/ookii-dialogs-wpf\",\"Copyright\":\"Copyright (c) 2009-2021 Ookii Dialogs Contributors\",\"Authors\":[\"Ookii Dialogs Contributors\"],\"Description\":\"Ookii.Dialogs.Wpf is a class library for WPF applications providing several common dialogs. Included are classes for task dialogs, credential dialogs, progress dialogs, and common file dialogs.\",\"LicenseUrl\":\"https://licenses.nuget.org/BSD-3-Clause\",\"LicenseType\":\"BSD-3-Clause\"},{\"PackageName\":\"Polly\",\"PackageVersion\":\"7.2.3\",\"PackageUrl\":\"https://github.com/App-vNext/Polly\",\"Copyright\":\"Copyright (c) 2022, App vNext\",\"Authors\":[\"Michael Wolfenden\",\" App vNext\"],\"Description\":\"Polly is a library that allows developers to express resilience and transient fault handling policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.\",\"LicenseUrl\":\"https://licenses.nuget.org/BSD-3-Clause\",\"LicenseType\":\"BSD-3-Clause\"},{\"PackageName\":\"Polly.Contrib.WaitAndRetry\",\"PackageVersion\":\"1.1.1\",\"PackageUrl\":\"https://github.com/Polly-Contrib/Polly.Contrib.WaitAndRetry\",\"Copyright\":\"Copyright (c) 2020, App vNext and contributors\",\"Authors\":[\"Grant Dickinson\",\" App vNext\"],\"Description\":\"Polly.Contrib.WaitAndRetry is an extension library for Polly containing helper methods for a variety of wait-and-retry strategies.\",\"LicenseUrl\":\"https://licenses.nuget.org/BSD-3-Clause\",\"LicenseType\":\"BSD-3-Clause\"},{\"PackageName\":\"pythonnet\",\"PackageVersion\":\"3.0.1\",\"PackageUrl\":\"https://pythonnet.github.io/\",\"Copyright\":\"\",\"Authors\":[\"Python.Runtime\"],\"Description\":\"Python and CLR (.NET and Mono) cross-platform language interop\",\"LicenseUrl\":\"https://www.nuget.org/packages/pythonnet/3.0.1/License\",\"LicenseType\":\"LICENSE\"},{\"PackageName\":\"Refit\",\"PackageVersion\":\"6.3.2\",\"PackageUrl\":\"https://github.com/reactiveui/refit\",\"Copyright\":\"\",\"Authors\":[\".NET Foundation and Contributors\"],\"Description\":\"The automatic type-safe REST library for Xamarin and .NET\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\"},{\"PackageName\":\"Refit.HttpClientFactory\",\"PackageVersion\":\"6.3.2\",\"PackageUrl\":\"https://github.com/reactiveui/refit\",\"Copyright\":\"\",\"Authors\":[\".NET Foundation and Contributors\"],\"Description\":\"Refit HTTP Client Factory Extensions\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\"},{\"PackageName\":\"SharpCompress\",\"PackageVersion\":\"0.33.0\",\"PackageUrl\":\"https://github.com/adamhathcock/sharpcompress\",\"Copyright\":\"Copyright (c) 2014  Adam Hathcock\",\"Authors\":[\"Adam Hathcock\"],\"Description\":\"SharpCompress is a compression library for NET Standard 2.0/2.1/NET 6.0/NET 7.0 that can unrar, decompress 7zip, decompress xz, zip/unzip, tar/untar lzip/unlzip, bzip2/unbzip2 and gzip/ungzip with forward-only reading and file random access APIs. Write support for zip/tar/bzip2/gzip is implemented.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\"},{\"PackageName\":\"WPF-UI\",\"PackageVersion\":\"3.0.0-preview.2\",\"PackageUrl\":\"https://github.com/lepoco/wpfui\",\"Copyright\":\"Copyright (C) 2021-2022 Leszek Pomianowski and WPF UI Contributors\",\"Authors\":[\"lepo.co\"],\"Description\":\"A simple way to make your application written in WPF keep up with modern design trends. Library changes the base elements like Page, ToggleButton or List, and also includes additional controls like Navigation, NumberBox, Dialog or Snackbar.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\"}]\n"
  },
  {
    "path": "StabilityMatrix/Assets/sm-package.schema.json",
    "content": "﻿{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": { \"type\": \"string\" },\n    \"display-name\": { \"type\": \"string\" },\n    \"author\": { \"type\": \"string\" },\n    \"download\": { \"$ref\": \"#/definitions/steps\" },\n    \"install\": { \"$ref\": \"#/definitions/steps\" }\n  },\n  \"required\": [\"name\", \"display-name\", \"author\", \"download\", \"install\"],\n  \"definitions\": {\n    \"steps\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"steps\": {\n          \"type\": \"array\",\n          \"items\": { \"$ref\": \"#/definitions/step\" }\n        }\n      },\n      \"required\": [\"steps\"]\n    },\n    \"step\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": { \"type\": \"string\" },\n        \"uses\": { \"enum\": [\"venv\", \"venv-run\", \"git\"] },\n        \"if\": { \"type\": \"string\" },\n        \"with\": {}\n      },\n      \"required\": [\"uses\", \"with\"],\n      \"allOf\": [\n        {\n          \"if\": { \"properties\": { \"uses\": { \"const\": \"venv\" } } },\n          \"then\": { \"properties\": { \"with\": { \"$ref\": \"#/definitions/with-path\" } } }\n        },\n        {\n          \"if\": { \"properties\": { \"uses\": { \"const\": \"venv-run\" } } },\n          \"then\": { \"properties\": { \"with\": { \"$ref\": \"#/definitions/with-args\" } } }\n        },\n        {\n          \"if\": { \"properties\": { \"uses\": { \"const\": \"git\" } } },\n          \"then\": { \"properties\": { \"with\": { \"$ref\": \"#/definitions/with-args\" } } }\n        }\n      ]\n    },\n    \"with-path\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"path\": { \"type\": \"string\" }\n      },\n      \"required\": [\"path\"],\n      \"additionalProperties\": false\n    },\n    \"with-args\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"args\": {\n          \"oneOf\": [\n            { \"type\": \"string\" },\n            {\n              \"type\": \"array\",\n              \"items\": { \"type\": \"string\" }\n            }\n          ]\n        }\n      },\n      \"required\": [\"args\"],\n      \"additionalProperties\": false\n    }\n  }\n}\n"
  },
  {
    "path": "StabilityMatrix/Assets/venv/__init__.py",
    "content": "\"\"\"\nVirtual environment (venv) package for Python. Based on PEP 405.\n\nCopyright (C) 2011-2014 Vinay Sajip.\nLicensed to the PSF under a contributor agreement.\n\"\"\"\nimport logging\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport sysconfig\nimport types\n\n\nCORE_VENV_DEPS = ('pip', 'setuptools')\nlogger = logging.getLogger(__name__)\n\n\nclass EnvBuilder:\n    \"\"\"\n    This class exists to allow virtual environment creation to be\n    customized. The constructor parameters determine the builder's\n    behaviour when called upon to create a virtual environment.\n\n    By default, the builder makes the system (global) site-packages dir\n    *un*available to the created environment.\n\n    If invoked using the Python -m option, the default is to use copying\n    on Windows platforms but symlinks elsewhere. If instantiated some\n    other way, the default is to *not* use symlinks.\n\n    :param system_site_packages: If True, the system (global) site-packages\n                                 dir is available to created environments.\n    :param clear: If True, delete the contents of the environment directory if\n                  it already exists, before environment creation.\n    :param symlinks: If True, attempt to symlink rather than copy files into\n                     virtual environment.\n    :param upgrade: If True, upgrade an existing virtual environment.\n    :param with_pip: If True, ensure pip is installed in the virtual\n                     environment\n    :param prompt: Alternative terminal prefix for the environment.\n    :param upgrade_deps: Update the base venv modules to the latest on PyPI\n    \"\"\"\n\n    def __init__(self, system_site_packages=False, clear=False,\n                 symlinks=False, upgrade=False, with_pip=False, prompt=None,\n                 upgrade_deps=False):\n        self.system_site_packages = system_site_packages\n        self.clear = clear\n        self.symlinks = symlinks\n        self.upgrade = upgrade\n        self.with_pip = with_pip\n        if prompt == '.':  # see bpo-38901\n            prompt = os.path.basename(os.getcwd())\n        self.prompt = prompt\n        self.upgrade_deps = upgrade_deps\n\n    def create(self, env_dir):\n        \"\"\"\n        Create a virtual environment in a directory.\n\n        :param env_dir: The target directory to create an environment in.\n\n        \"\"\"\n        env_dir = os.path.abspath(env_dir)\n        context = self.ensure_directories(env_dir)\n        # See issue 24875. We need system_site_packages to be False\n        # until after pip is installed.\n        true_system_site_packages = self.system_site_packages\n        self.system_site_packages = False\n        self.create_configuration(context)\n        self.setup_python(context)\n        if self.with_pip:\n            self._setup_pip(context)\n        if not self.upgrade:\n            self.setup_scripts(context)\n            self.post_setup(context)\n        if true_system_site_packages:\n            # We had set it to False before, now\n            # restore it and rewrite the configuration\n            self.system_site_packages = True\n            self.create_configuration(context)\n        if self.upgrade_deps:\n            self.upgrade_dependencies(context)\n\n    def clear_directory(self, path):\n        for fn in os.listdir(path):\n            fn = os.path.join(path, fn)\n            if os.path.islink(fn) or os.path.isfile(fn):\n                os.remove(fn)\n            elif os.path.isdir(fn):\n                shutil.rmtree(fn)\n\n    def ensure_directories(self, env_dir):\n        \"\"\"\n        Create the directories for the environment.\n\n        Returns a context object which holds paths in the environment,\n        for use by subsequent logic.\n        \"\"\"\n\n        def create_if_needed(d):\n            if not os.path.exists(d):\n                os.makedirs(d)\n            elif os.path.islink(d) or os.path.isfile(d):\n                raise ValueError('Unable to create directory %r' % d)\n\n        if os.path.exists(env_dir) and self.clear:\n            self.clear_directory(env_dir)\n        context = types.SimpleNamespace()\n        context.env_dir = env_dir\n        context.env_name = os.path.split(env_dir)[1]\n        prompt = self.prompt if self.prompt is not None else context.env_name\n        context.prompt = '(%s) ' % prompt\n        create_if_needed(env_dir)\n        executable = sys._base_executable\n        if not executable:  # see gh-96861\n            raise ValueError('Unable to determine path to the running '\n                             'Python interpreter. Provide an explicit path or '\n                             'check that your PATH environment variable is '\n                             'correctly set.')\n        dirname, exename = os.path.split(os.path.abspath(executable))\n        context.executable = executable\n        context.python_dir = dirname\n        context.python_exe = exename\n        if sys.platform == 'win32':\n            binname = 'Scripts'\n            incpath = 'Include'\n            libpath = os.path.join(env_dir, 'Lib', 'site-packages')\n        else:\n            binname = 'bin'\n            incpath = 'include'\n            libpath = os.path.join(env_dir, 'lib',\n                                   'python%d.%d' % sys.version_info[:2],\n                                   'site-packages')\n        context.inc_path = path = os.path.join(env_dir, incpath)\n        create_if_needed(path)\n        create_if_needed(libpath)\n        # Issue 21197: create lib64 as a symlink to lib on 64-bit non-OS X POSIX\n        if ((sys.maxsize > 2**32) and (os.name == 'posix') and\n            (sys.platform != 'darwin')):\n            link_path = os.path.join(env_dir, 'lib64')\n            if not os.path.exists(link_path):   # Issue #21643\n                os.symlink('lib', link_path)\n        context.bin_path = binpath = os.path.join(env_dir, binname)\n        context.bin_name = binname\n        context.env_exe = os.path.join(binpath, exename)\n        create_if_needed(binpath)\n        # Assign and update the command to use when launching the newly created\n        # environment, in case it isn't simply the executable script (e.g. bpo-45337)\n        context.env_exec_cmd = context.env_exe\n        if sys.platform == 'win32':\n            # bpo-45337: Fix up env_exec_cmd to account for file system redirections.\n            # Some redirects only apply to CreateFile and not CreateProcess\n            real_env_exe = os.path.realpath(context.env_exe)\n            if os.path.normcase(real_env_exe) != os.path.normcase(context.env_exe):\n                logger.warning('Actual environment location may have moved due to '\n                               'redirects, links or junctions.\\n'\n                               '  Requested location: \"%s\"\\n'\n                               '  Actual location:    \"%s\"',\n                               context.env_exe, real_env_exe)\n                context.env_exec_cmd = real_env_exe\n        return context\n\n    def create_configuration(self, context):\n        \"\"\"\n        Create a configuration file indicating where the environment's Python\n        was copied from, and whether the system site-packages should be made\n        available in the environment.\n\n        :param context: The information for the environment creation request\n                        being processed.\n        \"\"\"\n        context.cfg_path = path = os.path.join(context.env_dir, 'pyvenv.cfg')\n        with open(path, 'w', encoding='utf-8') as f:\n            f.write('home = %s\\n' % context.python_dir)\n            if self.system_site_packages:\n                incl = 'true'\n            else:\n                incl = 'false'\n            f.write('include-system-site-packages = %s\\n' % incl)\n            f.write('version = %d.%d.%d\\n' % sys.version_info[:3])\n            if self.prompt is not None:\n                f.write(f'prompt = {self.prompt!r}\\n')\n\n    if os.name != 'nt':\n        def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):\n            \"\"\"\n            Try symlinking a file, and if that fails, fall back to copying.\n            \"\"\"\n            force_copy = not self.symlinks\n            if not force_copy:\n                try:\n                    if not os.path.islink(dst): # can't link to itself!\n                        if relative_symlinks_ok:\n                            assert os.path.dirname(src) == os.path.dirname(dst)\n                            os.symlink(os.path.basename(src), dst)\n                        else:\n                            os.symlink(src, dst)\n                except Exception:   # may need to use a more specific exception\n                    logger.warning('Unable to symlink %r to %r', src, dst)\n                    force_copy = True\n            if force_copy:\n                shutil.copyfile(src, dst)\n    else:\n        def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):\n            \"\"\"\n            Try symlinking a file, and if that fails, fall back to copying.\n            \"\"\"\n            bad_src = os.path.lexists(src) and not os.path.exists(src)\n            if self.symlinks and not bad_src and not os.path.islink(dst):\n                try:\n                    if relative_symlinks_ok:\n                        assert os.path.dirname(src) == os.path.dirname(dst)\n                        os.symlink(os.path.basename(src), dst)\n                    else:\n                        os.symlink(src, dst)\n                    return\n                except Exception:   # may need to use a more specific exception\n                    logger.warning('Unable to symlink %r to %r', src, dst)\n\n            # On Windows, we rewrite symlinks to our base python.exe into\n            # copies of venvlauncher.exe\n            basename, ext = os.path.splitext(os.path.basename(src))\n            srcfn = os.path.join(os.path.dirname(__file__),\n                                 \"scripts\",\n                                 \"nt\",\n                                 basename + ext)\n            # Builds or venv's from builds need to remap source file\n            # locations, as we do not put them into Lib/venv/scripts\n            if sysconfig.is_python_build(True) or not os.path.isfile(srcfn):\n                if basename.endswith('_d'):\n                    ext = '_d' + ext\n                    basename = basename[:-2]\n                if basename == 'python':\n                    basename = 'venvlauncher'\n                elif basename == 'pythonw':\n                    basename = 'venvwlauncher'\n                src = os.path.join(os.path.dirname(src), basename + ext)\n            else:\n                src = srcfn\n            if not os.path.exists(src):\n                if not bad_src:\n                    logger.warning('Unable to copy %r', src)\n                return\n\n            shutil.copyfile(src, dst)\n\n    def setup_python(self, context):\n        \"\"\"\n        Set up a Python executable in the environment.\n\n        :param context: The information for the environment creation request\n                        being processed.\n        \"\"\"\n        binpath = context.bin_path\n        path = context.env_exe\n        copier = self.symlink_or_copy\n        dirname = context.python_dir\n        if os.name != 'nt':\n            copier(context.executable, path)\n            if not os.path.islink(path):\n                os.chmod(path, 0o755)\n            for suffix in ('python', 'python3', f'python3.{sys.version_info[1]}'):\n                path = os.path.join(binpath, suffix)\n                if not os.path.exists(path):\n                    # Issue 18807: make copies if\n                    # symlinks are not wanted\n                    copier(context.env_exe, path, relative_symlinks_ok=True)\n                    if not os.path.islink(path):\n                        os.chmod(path, 0o755)\n        else:\n            if self.symlinks:\n                # For symlinking, we need a complete copy of the root directory\n                # If symlinks fail, you'll get unnecessary copies of files, but\n                # we assume that if you've opted into symlinks on Windows then\n                # you know what you're doing.\n                suffixes = [\n                    f for f in os.listdir(dirname) if\n                    os.path.normcase(os.path.splitext(f)[1]) in ('.exe', '.dll')\n                ]\n                if sysconfig.is_python_build(True):\n                    suffixes = [\n                        f for f in suffixes if\n                        os.path.normcase(f).startswith(('python', 'vcruntime'))\n                    ]\n            else:\n                suffixes = {'python.exe', 'python_d.exe', 'pythonw.exe', 'pythonw_d.exe'}\n                base_exe = os.path.basename(context.env_exe)\n                suffixes.add(base_exe)\n\n            for suffix in suffixes:\n                src = os.path.join(dirname, suffix)\n                if os.path.lexists(src):\n                    copier(src, os.path.join(binpath, suffix))\n\n            if sysconfig.is_python_build(True):\n                # copy init.tcl\n                for root, dirs, files in os.walk(context.python_dir):\n                    if 'init.tcl' in files:\n                        tcldir = os.path.basename(root)\n                        tcldir = os.path.join(context.env_dir, 'Lib', tcldir)\n                        if not os.path.exists(tcldir):\n                            os.makedirs(tcldir)\n                        src = os.path.join(root, 'init.tcl')\n                        dst = os.path.join(tcldir, 'init.tcl')\n                        shutil.copyfile(src, dst)\n                        break\n\n    def _call_new_python(self, context, *py_args, **kwargs):\n        \"\"\"Executes the newly created Python using safe-ish options\"\"\"\n        # gh-98251: We do not want to just use '-I' because that masks\n        # legitimate user preferences (such as not writing bytecode). All we\n        # really need is to ensure that the path variables do not overrule\n        # normal venv handling.\n        args = [context.env_exec_cmd, *py_args]\n        kwargs['env'] = env = os.environ.copy()\n        env['VIRTUAL_ENV'] = context.env_dir\n        env.pop('PYTHONHOME', None)\n        env.pop('PYTHONPATH', None)\n        kwargs['cwd'] = context.env_dir\n        kwargs['executable'] = context.env_exec_cmd\n        subprocess.check_output(args, **kwargs)\n\n    def _setup_pip(self, context):\n        \"\"\"Installs or upgrades pip in a virtual environment\"\"\"\n        self._call_new_python(context, '-m', 'ensurepip', '--upgrade',\n                              '--default-pip', stderr=subprocess.STDOUT)\n\n    def setup_scripts(self, context):\n        \"\"\"\n        Set up scripts into the created environment from a directory.\n\n        This method installs the default scripts into the environment\n        being created. You can prevent the default installation by overriding\n        this method if you really need to, or if you need to specify\n        a different location for the scripts to install. By default, the\n        'scripts' directory in the venv package is used as the source of\n        scripts to install.\n        \"\"\"\n        path = os.path.abspath(os.path.dirname(__file__))\n        path = os.path.join(path, 'scripts')\n        self.install_scripts(context, path)\n\n    def post_setup(self, context):\n        \"\"\"\n        Hook for post-setup modification of the venv. Subclasses may install\n        additional packages or scripts here, add activation shell scripts, etc.\n\n        :param context: The information for the environment creation request\n                        being processed.\n        \"\"\"\n        pass\n\n    def replace_variables(self, text, context):\n        \"\"\"\n        Replace variable placeholders in script text with context-specific\n        variables.\n\n        Return the text passed in , but with variables replaced.\n\n        :param text: The text in which to replace placeholder variables.\n        :param context: The information for the environment creation request\n                        being processed.\n        \"\"\"\n        text = text.replace('__VENV_DIR__', context.env_dir)\n        text = text.replace('__VENV_NAME__', context.env_name)\n        text = text.replace('__VENV_PROMPT__', context.prompt)\n        text = text.replace('__VENV_BIN_NAME__', context.bin_name)\n        text = text.replace('__VENV_PYTHON__', context.env_exe)\n        return text\n\n    def install_scripts(self, context, path):\n        \"\"\"\n        Install scripts into the created environment from a directory.\n\n        :param context: The information for the environment creation request\n                        being processed.\n        :param path:    Absolute pathname of a directory containing script.\n                        Scripts in the 'common' subdirectory of this directory,\n                        and those in the directory named for the platform\n                        being run on, are installed in the created environment.\n                        Placeholder variables are replaced with environment-\n                        specific values.\n        \"\"\"\n        binpath = context.bin_path\n        plen = len(path)\n        for root, dirs, files in os.walk(path):\n            if root == path: # at top-level, remove irrelevant dirs\n                for d in dirs[:]:\n                    if d not in ('common', os.name):\n                        dirs.remove(d)\n                continue # ignore files in top level\n            for f in files:\n                if (os.name == 'nt' and f.startswith('python')\n                        and f.endswith(('.exe', '.pdb'))):\n                    continue\n                srcfile = os.path.join(root, f)\n                suffix = root[plen:].split(os.sep)[2:]\n                if not suffix:\n                    dstdir = binpath\n                else:\n                    dstdir = os.path.join(binpath, *suffix)\n                if not os.path.exists(dstdir):\n                    os.makedirs(dstdir)\n                dstfile = os.path.join(dstdir, f)\n                with open(srcfile, 'rb') as f:\n                    data = f.read()\n                if not srcfile.endswith(('.exe', '.pdb')):\n                    try:\n                        data = data.decode('utf-8')\n                        data = self.replace_variables(data, context)\n                        data = data.encode('utf-8')\n                    except UnicodeError as e:\n                        data = None\n                        logger.warning('unable to copy script %r, '\n                                       'may be binary: %s', srcfile, e)\n                if data is not None:\n                    with open(dstfile, 'wb') as f:\n                        f.write(data)\n                    shutil.copymode(srcfile, dstfile)\n\n    def upgrade_dependencies(self, context):\n        logger.debug(\n            f'Upgrading {CORE_VENV_DEPS} packages in {context.bin_path}'\n        )\n        self._call_new_python(context, '-m', 'pip', 'install', '--upgrade',\n                              *CORE_VENV_DEPS)\n\n\ndef create(env_dir, system_site_packages=False, clear=False,\n           symlinks=False, with_pip=False, prompt=None, upgrade_deps=False):\n    \"\"\"Create a virtual environment in a directory.\"\"\"\n    builder = EnvBuilder(system_site_packages=system_site_packages,\n                         clear=clear, symlinks=symlinks, with_pip=with_pip,\n                         prompt=prompt, upgrade_deps=upgrade_deps)\n    builder.create(env_dir)\n\ndef main(args=None):\n    compatible = True\n    if sys.version_info < (3, 3):\n        compatible = False\n    elif not hasattr(sys, 'base_prefix'):\n        compatible = False\n    if not compatible:\n        raise ValueError('This script is only for use with Python >= 3.3')\n    else:\n        import argparse\n\n        parser = argparse.ArgumentParser(prog=__name__,\n                                         description='Creates virtual Python '\n                                                     'environments in one or '\n                                                     'more target '\n                                                     'directories.',\n                                         epilog='Once an environment has been '\n                                                'created, you may wish to '\n                                                'activate it, e.g. by '\n                                                'sourcing an activate script '\n                                                'in its bin directory.')\n        parser.add_argument('dirs', metavar='ENV_DIR', nargs='+',\n                            help='A directory to create the environment in.')\n        parser.add_argument('--system-site-packages', default=False,\n                            action='store_true', dest='system_site',\n                            help='Give the virtual environment access to the '\n                                 'system site-packages dir.')\n        if os.name == 'nt':\n            use_symlinks = False\n        else:\n            use_symlinks = True\n        group = parser.add_mutually_exclusive_group()\n        group.add_argument('--symlinks', default=use_symlinks,\n                           action='store_true', dest='symlinks',\n                           help='Try to use symlinks rather than copies, '\n                                'when symlinks are not the default for '\n                                'the platform.')\n        group.add_argument('--copies', default=not use_symlinks,\n                           action='store_false', dest='symlinks',\n                           help='Try to use copies rather than symlinks, '\n                                'even when symlinks are the default for '\n                                'the platform.')\n        parser.add_argument('--clear', default=False, action='store_true',\n                            dest='clear', help='Delete the contents of the '\n                                               'environment directory if it '\n                                               'already exists, before '\n                                               'environment creation.')\n        parser.add_argument('--upgrade', default=False, action='store_true',\n                            dest='upgrade', help='Upgrade the environment '\n                                               'directory to use this version '\n                                               'of Python, assuming Python '\n                                               'has been upgraded in-place.')\n        parser.add_argument('--without-pip', dest='with_pip',\n                            default=True, action='store_false',\n                            help='Skips installing or upgrading pip in the '\n                                 'virtual environment (pip is bootstrapped '\n                                 'by default)')\n        parser.add_argument('--prompt',\n                            help='Provides an alternative prompt prefix for '\n                                 'this environment.')\n        parser.add_argument('--upgrade-deps', default=False, action='store_true',\n                            dest='upgrade_deps',\n                            help='Upgrade core dependencies: {} to the latest '\n                                 'version in PyPI'.format(\n                                 ' '.join(CORE_VENV_DEPS)))\n        options = parser.parse_args(args)\n        if options.upgrade and options.clear:\n            raise ValueError('you cannot supply --upgrade and --clear together.')\n        builder = EnvBuilder(system_site_packages=options.system_site,\n                             clear=options.clear,\n                             symlinks=options.symlinks,\n                             upgrade=options.upgrade,\n                             with_pip=options.with_pip,\n                             prompt=options.prompt,\n                             upgrade_deps=options.upgrade_deps)\n        for d in options.dirs:\n            builder.create(d)\n\nif __name__ == '__main__':\n    rc = 1\n    try:\n        main()\n        rc = 0\n    except Exception as e:\n        print('Error: %s' % e, file=sys.stderr)\n    sys.exit(rc)\n"
  },
  {
    "path": "StabilityMatrix/Assets/venv/__main__.py",
    "content": "import sys\nfrom . import main\n\nrc = 1\ntry:\n    main()\n    rc = 0\nexcept Exception as e:\n    print('Error: %s' % e, file=sys.stderr)\nsys.exit(rc)\n"
  },
  {
    "path": "StabilityMatrix/Assets/venv/scripts/common/Activate.ps1",
    "content": "<#\n.Synopsis\nActivate a Python virtual environment for the current PowerShell session.\n\n.Description\nPushes the python executable for a virtual environment to the front of the\n$Env:PATH environment variable and sets the prompt to signify that you are\nin a Python virtual environment. Makes use of the command line switches as\nwell as the `pyvenv.cfg` file values present in the virtual environment.\n\n.Parameter VenvDir\nPath to the directory that contains the virtual environment to activate. The\ndefault value for this is the parent of the directory that the Activate.ps1\nscript is located within.\n\n.Parameter Prompt\nThe prompt prefix to display when this virtual environment is activated. By\ndefault, this prompt is the name of the virtual environment folder (VenvDir)\nsurrounded by parentheses and followed by a single space (ie. '(.venv) ').\n\n.Example\nActivate.ps1\nActivates the Python virtual environment that contains the Activate.ps1 script.\n\n.Example\nActivate.ps1 -Verbose\nActivates the Python virtual environment that contains the Activate.ps1 script,\nand shows extra information about the activation as it executes.\n\n.Example\nActivate.ps1 -VenvDir C:\\Users\\MyUser\\Common\\.venv\nActivates the Python virtual environment located in the specified location.\n\n.Example\nActivate.ps1 -Prompt \"MyPython\"\nActivates the Python virtual environment that contains the Activate.ps1 script,\nand prefixes the current prompt with the specified string (surrounded in\nparentheses) while the virtual environment is active.\n\n.Notes\nOn Windows, it may be required to enable this Activate.ps1 script by setting the\nexecution policy for the user. You can do this by issuing the following PowerShell\ncommand:\n\nPS C:\\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser\n\nFor more information on Execution Policies: \nhttps://go.microsoft.com/fwlink/?LinkID=135170\n\n#>\nParam(\n    [Parameter(Mandatory = $false)]\n    [String]\n    $VenvDir,\n    [Parameter(Mandatory = $false)]\n    [String]\n    $Prompt\n)\n\n<# Function declarations --------------------------------------------------- #>\n\n<#\n.Synopsis\nRemove all shell session elements added by the Activate script, including the\naddition of the virtual environment's Python executable from the beginning of\nthe PATH variable.\n\n.Parameter NonDestructive\nIf present, do not remove this function from the global namespace for the\nsession.\n\n#>\nfunction global:deactivate ([switch]$NonDestructive) {\n    # Revert to original values\n\n    # The prior prompt:\n    if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {\n        Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt\n        Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT\n    }\n\n    # The prior PYTHONHOME:\n    if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {\n        Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME\n        Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME\n    }\n\n    # The prior PATH:\n    if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {\n        Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH\n        Remove-Item -Path Env:_OLD_VIRTUAL_PATH\n    }\n\n    # Just remove the VIRTUAL_ENV altogether:\n    if (Test-Path -Path Env:VIRTUAL_ENV) {\n        Remove-Item -Path env:VIRTUAL_ENV\n    }\n\n    # Just remove VIRTUAL_ENV_PROMPT altogether.\n    if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {\n        Remove-Item -Path env:VIRTUAL_ENV_PROMPT\n    }\n\n    # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:\n    if (Get-Variable -Name \"_PYTHON_VENV_PROMPT_PREFIX\" -ErrorAction SilentlyContinue) {\n        Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force\n    }\n\n    # Leave deactivate function in the global namespace if requested:\n    if (-not $NonDestructive) {\n        Remove-Item -Path function:deactivate\n    }\n}\n\n<#\n.Description\nGet-PyVenvConfig parses the values from the pyvenv.cfg file located in the\ngiven folder, and returns them in a map.\n\nFor each line in the pyvenv.cfg file, if that line can be parsed into exactly\ntwo strings separated by `=` (with any amount of whitespace surrounding the =)\nthen it is considered a `key = value` line. The left hand string is the key,\nthe right hand is the value.\n\nIf the value starts with a `'` or a `\"` then the first and last character is\nstripped from the value before being captured.\n\n.Parameter ConfigDir\nPath to the directory that contains the `pyvenv.cfg` file.\n#>\nfunction Get-PyVenvConfig(\n    [String]\n    $ConfigDir\n) {\n    Write-Verbose \"Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg\"\n\n    # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).\n    $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue\n\n    # An empty map will be returned if no config file is found.\n    $pyvenvConfig = @{ }\n\n    if ($pyvenvConfigPath) {\n\n        Write-Verbose \"File exists, parse `key = value` lines\"\n        $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath\n\n        $pyvenvConfigContent | ForEach-Object {\n            $keyval = $PSItem -split \"\\s*=\\s*\", 2\n            if ($keyval[0] -and $keyval[1]) {\n                $val = $keyval[1]\n\n                # Remove extraneous quotations around a string value.\n                if (\"'\"\"\".Contains($val.Substring(0, 1))) {\n                    $val = $val.Substring(1, $val.Length - 2)\n                }\n\n                $pyvenvConfig[$keyval[0]] = $val\n                Write-Verbose \"Adding Key: '$($keyval[0])'='$val'\"\n            }\n        }\n    }\n    return $pyvenvConfig\n}\n\n\n<# Begin Activate script --------------------------------------------------- #>\n\n# Determine the containing directory of this script\n$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition\n$VenvExecDir = Get-Item -Path $VenvExecPath\n\nWrite-Verbose \"Activation script is located in path: '$VenvExecPath'\"\nWrite-Verbose \"VenvExecDir Fullname: '$($VenvExecDir.FullName)\"\nWrite-Verbose \"VenvExecDir Name: '$($VenvExecDir.Name)\"\n\n# Set values required in priority: CmdLine, ConfigFile, Default\n# First, get the location of the virtual environment, it might not be\n# VenvExecDir if specified on the command line.\nif ($VenvDir) {\n    Write-Verbose \"VenvDir given as parameter, using '$VenvDir' to determine values\"\n}\nelse {\n    Write-Verbose \"VenvDir not given as a parameter, using parent directory name as VenvDir.\"\n    $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd(\"\\\\/\")\n    Write-Verbose \"VenvDir=$VenvDir\"\n}\n\n# Next, read the `pyvenv.cfg` file to determine any required value such\n# as `prompt`.\n$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir\n\n# Next, set the prompt from the command line, or the config file, or\n# just use the name of the virtual environment folder.\nif ($Prompt) {\n    Write-Verbose \"Prompt specified as argument, using '$Prompt'\"\n}\nelse {\n    Write-Verbose \"Prompt not specified as argument to script, checking pyvenv.cfg value\"\n    if ($pyvenvCfg -and $pyvenvCfg['prompt']) {\n        Write-Verbose \"  Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'\"\n        $Prompt = $pyvenvCfg['prompt'];\n    }\n    else {\n        Write-Verbose \"  Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)\"\n        Write-Verbose \"  Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'\"\n        $Prompt = Split-Path -Path $venvDir -Leaf\n    }\n}\n\nWrite-Verbose \"Prompt = '$Prompt'\"\nWrite-Verbose \"VenvDir='$VenvDir'\"\n\n# Deactivate any currently active virtual environment, but leave the\n# deactivate function in place.\ndeactivate -nondestructive\n\n# Now set the environment variable VIRTUAL_ENV, used by many tools to determine\n# that there is an activated venv.\n$env:VIRTUAL_ENV = $VenvDir\n\nif (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {\n\n    Write-Verbose \"Setting prompt to '$Prompt'\"\n\n    # Set the prompt to include the env name\n    # Make sure _OLD_VIRTUAL_PROMPT is global\n    function global:_OLD_VIRTUAL_PROMPT { \"\" }\n    Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT\n    New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description \"Python virtual environment prompt prefix\" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt\n\n    function global:prompt {\n        Write-Host -NoNewline -ForegroundColor Green \"($_PYTHON_VENV_PROMPT_PREFIX) \"\n        _OLD_VIRTUAL_PROMPT\n    }\n    $env:VIRTUAL_ENV_PROMPT = $Prompt\n}\n\n# Clear PYTHONHOME\nif (Test-Path -Path Env:PYTHONHOME) {\n    Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME\n    Remove-Item -Path Env:PYTHONHOME\n}\n\n# Add the venv to the PATH\nCopy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH\n$Env:PATH = \"$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH\"\n"
  },
  {
    "path": "StabilityMatrix/Assets/venv/scripts/common/activate",
    "content": "# This file must be used with \"source bin/activate\" *from bash*\n# you cannot run it directly\n\ndeactivate () {\n    # reset old environment variables\n    if [ -n \"${_OLD_VIRTUAL_PATH:-}\" ] ; then\n        PATH=\"${_OLD_VIRTUAL_PATH:-}\"\n        export PATH\n        unset _OLD_VIRTUAL_PATH\n    fi\n    if [ -n \"${_OLD_VIRTUAL_PYTHONHOME:-}\" ] ; then\n        PYTHONHOME=\"${_OLD_VIRTUAL_PYTHONHOME:-}\"\n        export PYTHONHOME\n        unset _OLD_VIRTUAL_PYTHONHOME\n    fi\n\n    # This should detect bash and zsh, which have a hash command that must\n    # be called to get it to forget past commands.  Without forgetting\n    # past commands the $PATH changes we made may not be respected\n    if [ -n \"${BASH:-}\" -o -n \"${ZSH_VERSION:-}\" ] ; then\n        hash -r 2> /dev/null\n    fi\n\n    if [ -n \"${_OLD_VIRTUAL_PS1:-}\" ] ; then\n        PS1=\"${_OLD_VIRTUAL_PS1:-}\"\n        export PS1\n        unset _OLD_VIRTUAL_PS1\n    fi\n\n    unset VIRTUAL_ENV\n    unset VIRTUAL_ENV_PROMPT\n    if [ ! \"${1:-}\" = \"nondestructive\" ] ; then\n    # Self destruct!\n        unset -f deactivate\n    fi\n}\n\n# unset irrelevant variables\ndeactivate nondestructive\n\nVIRTUAL_ENV=\"__VENV_DIR__\"\nexport VIRTUAL_ENV\n\n_OLD_VIRTUAL_PATH=\"$PATH\"\nPATH=\"$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH\"\nexport PATH\n\n# unset PYTHONHOME if set\n# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)\n# could use `if (set -u; : $PYTHONHOME) ;` in bash\nif [ -n \"${PYTHONHOME:-}\" ] ; then\n    _OLD_VIRTUAL_PYTHONHOME=\"${PYTHONHOME:-}\"\n    unset PYTHONHOME\nfi\n\nif [ -z \"${VIRTUAL_ENV_DISABLE_PROMPT:-}\" ] ; then\n    _OLD_VIRTUAL_PS1=\"${PS1:-}\"\n    PS1=\"__VENV_PROMPT__${PS1:-}\"\n    export PS1\n    VIRTUAL_ENV_PROMPT=\"__VENV_PROMPT__\"\n    export VIRTUAL_ENV_PROMPT\nfi\n\n# This should detect bash and zsh, which have a hash command that must\n# be called to get it to forget past commands.  Without forgetting\n# past commands the $PATH changes we made may not be respected\nif [ -n \"${BASH:-}\" -o -n \"${ZSH_VERSION:-}\" ] ; then\n    hash -r 2> /dev/null\nfi\n"
  },
  {
    "path": "StabilityMatrix/Assets/venv/scripts/nt/activate.bat",
    "content": "@echo off\n\nrem This file is UTF-8 encoded, so we need to update the current code page while executing it\nfor /f \"tokens=2 delims=:.\" %%a in ('\"%SystemRoot%\\System32\\chcp.com\"') do (\n    set _OLD_CODEPAGE=%%a\n)\nif defined _OLD_CODEPAGE (\n    \"%SystemRoot%\\System32\\chcp.com\" 65001 > nul\n)\n\nset VIRTUAL_ENV=__VENV_DIR__\n\nif not defined PROMPT set PROMPT=$P$G\n\nif defined _OLD_VIRTUAL_PROMPT set PROMPT=%_OLD_VIRTUAL_PROMPT%\nif defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%\n\nset _OLD_VIRTUAL_PROMPT=%PROMPT%\nset PROMPT=__VENV_PROMPT__%PROMPT%\n\nif defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%\nset PYTHONHOME=\n\nif defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH%\nif not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH%\n\nset PATH=%VIRTUAL_ENV%\\__VENV_BIN_NAME__;%PATH%\nset VIRTUAL_ENV_PROMPT=__VENV_PROMPT__\n\n:END\nif defined _OLD_CODEPAGE (\n    \"%SystemRoot%\\System32\\chcp.com\" %_OLD_CODEPAGE% > nul\n    set _OLD_CODEPAGE=\n)\n"
  },
  {
    "path": "StabilityMatrix/Assets/venv/scripts/nt/deactivate.bat",
    "content": "@echo off\n\nif defined _OLD_VIRTUAL_PROMPT (\n    set \"PROMPT=%_OLD_VIRTUAL_PROMPT%\"\n)\nset _OLD_VIRTUAL_PROMPT=\n\nif defined _OLD_VIRTUAL_PYTHONHOME (\n    set \"PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%\"\n    set _OLD_VIRTUAL_PYTHONHOME=\n)\n\nif defined _OLD_VIRTUAL_PATH (\n    set \"PATH=%_OLD_VIRTUAL_PATH%\"\n)\n\nset _OLD_VIRTUAL_PATH=\n\nset VIRTUAL_ENV=\nset VIRTUAL_ENV_PROMPT=\n\n:END\n"
  },
  {
    "path": "StabilityMatrix/Assets/venv/scripts/posix/activate.csh",
    "content": "# This file must be used with \"source bin/activate.csh\" *from csh*.\n# You cannot run it directly.\n# Created by Davide Di Blasi <davidedb@gmail.com>.\n# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>\n\nalias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH \"$_OLD_VIRTUAL_PATH\" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt=\"$_OLD_VIRTUAL_PROMPT\" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test \"\\!:*\" != \"nondestructive\" && unalias deactivate'\n\n# Unset irrelevant variables.\ndeactivate nondestructive\n\nsetenv VIRTUAL_ENV \"__VENV_DIR__\"\n\nset _OLD_VIRTUAL_PATH=\"$PATH\"\nsetenv PATH \"$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH\"\n\n\nset _OLD_VIRTUAL_PROMPT=\"$prompt\"\n\nif (! \"$?VIRTUAL_ENV_DISABLE_PROMPT\") then\n    set prompt = \"__VENV_PROMPT__$prompt\"\n    setenv VIRTUAL_ENV_PROMPT \"__VENV_PROMPT__\"\nendif\n\nalias pydoc python -m pydoc\n\nrehash\n"
  },
  {
    "path": "StabilityMatrix/Assets/venv/scripts/posix/activate.fish",
    "content": "# This file must be used with \"source <venv>/bin/activate.fish\" *from fish*\n# (https://fishshell.com/); you cannot run it directly.\n\nfunction deactivate  -d \"Exit virtual environment and return to normal shell environment\"\n    # reset old environment variables\n    if test -n \"$_OLD_VIRTUAL_PATH\"\n        set -gx PATH $_OLD_VIRTUAL_PATH\n        set -e _OLD_VIRTUAL_PATH\n    end\n    if test -n \"$_OLD_VIRTUAL_PYTHONHOME\"\n        set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME\n        set -e _OLD_VIRTUAL_PYTHONHOME\n    end\n\n    if test -n \"$_OLD_FISH_PROMPT_OVERRIDE\"\n        set -e _OLD_FISH_PROMPT_OVERRIDE\n        # prevents error when using nested fish instances (Issue #93858)\n        if functions -q _old_fish_prompt\n            functions -e fish_prompt\n            functions -c _old_fish_prompt fish_prompt\n            functions -e _old_fish_prompt\n        end\n    end\n\n    set -e VIRTUAL_ENV\n    set -e VIRTUAL_ENV_PROMPT\n    if test \"$argv[1]\" != \"nondestructive\"\n        # Self-destruct!\n        functions -e deactivate\n    end\nend\n\n# Unset irrelevant variables.\ndeactivate nondestructive\n\nset -gx VIRTUAL_ENV \"__VENV_DIR__\"\n\nset -gx _OLD_VIRTUAL_PATH $PATH\nset -gx PATH \"$VIRTUAL_ENV/__VENV_BIN_NAME__\" $PATH\n\n# Unset PYTHONHOME if set.\nif set -q PYTHONHOME\n    set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME\n    set -e PYTHONHOME\nend\n\nif test -z \"$VIRTUAL_ENV_DISABLE_PROMPT\"\n    # fish uses a function instead of an env var to generate the prompt.\n\n    # Save the current fish_prompt function as the function _old_fish_prompt.\n    functions -c fish_prompt _old_fish_prompt\n\n    # With the original prompt function renamed, we can override with our own.\n    function fish_prompt\n        # Save the return status of the last command.\n        set -l old_status $status\n\n        # Output the venv prompt; color taken from the blue of the Python logo.\n        printf \"%s%s%s\" (set_color 4B8BBE) \"__VENV_PROMPT__\" (set_color normal)\n\n        # Restore the return status of the previous command.\n        echo \"exit $old_status\" | .\n        # Output the original/\"old\" prompt.\n        _old_fish_prompt\n    end\n\n    set -gx _OLD_FISH_PROMPT_OVERRIDE \"$VIRTUAL_ENV\"\n    set -gx VIRTUAL_ENV_PROMPT \"__VENV_PROMPT__\"\nend\n"
  },
  {
    "path": "StabilityMatrix/CheckpointBrowserPage.xaml",
    "content": "﻿<Page\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Loaded=\"CheckpointBrowserPage_OnLoaded\"\n    d:DataContext=\"{d:DesignInstance designData:MockCheckpointBrowserViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"650\"\n    d:DesignWidth=\"750\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.CheckpointBrowserPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.DesignData\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n\n    <Page.Resources>\n        <converters:ValueConverterGroup x:Key=\"InvertAndVisibilitate\">\n            <converters:BoolNegationConverter />\n            <BooleanToVisibilityConverter />\n        </converters:ValueConverterGroup>\n        \n        <converters:BoolNegationConverter x:Key=\"BoolNegationConverter\"/>\n        <BooleanToVisibilityConverter x:Key=\"BoolToVisibilityConverter\" />\n\n        <DataTemplate DataType=\"{x:Type viewModels:CheckpointBrowserCardViewModel}\" x:Key=\"CivitModelTemplate\">\n            <ui:Card\n                Margin=\"8\"\n                MaxHeight=\"450\"\n                Name=\"ModelCard\"\n                Width=\"330\">\n\n                <StackPanel Orientation=\"Vertical\">\n                    <TextBlock\n                        Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n                        Margin=\"0,0,0,0\"\n                        Text=\"{Binding CivitModel.Name}\"\n                        VerticalAlignment=\"Center\" />\n                    <TextBlock\n                        FontSize=\"11\"\n                        Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                        Margin=\"0,2,0,0\"\n                        Text=\"{Binding CivitModel.ModelVersions[0].Name, FallbackValue=''}\"\n                        VerticalAlignment=\"Center\" />\n                    <Grid>\n                        <Image\n                            Margin=\"0,8,0,8\"\n                            MaxHeight=\"300\"\n                            Source=\"{Binding CardImage}\"\n                            Stretch=\"UniformToFill\" />\n                        <ui:Button\n                            Appearance=\"Info\"\n                            Command=\"{Binding OpenModelCommand}\"\n                            CommandParameter=\"{Binding CivitModel}\"\n                            HorizontalAlignment=\"Right\"\n                            Margin=\"0,16,8,0\"\n                            VerticalAlignment=\"Top\">\n                            <ui:Button.Icon>\n                                <ui:SymbolIcon Symbol=\"Open24\" />\n                            </ui:Button.Icon>\n                        </ui:Button>\n\n                        <ui:Card\n                            Background=\"#AA1467B5\"\n                            Height=\"20\"\n                            HorizontalAlignment=\"Left\"\n                            Margin=\"4,16,0,0\"\n                            Padding=\"3\"\n                            VerticalAlignment=\"Top\">\n\n                            <TextBlock\n                                FontSize=\"10\"\n                                FontWeight=\"Medium\"\n                                Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                HorizontalAlignment=\"Center\"\n                                Text=\"{Binding CivitModel.Type}\"\n                                VerticalAlignment=\"Center\" />\n                        </ui:Card>\n\n                        <Rectangle\n                            Fill=\"#DD000000\"\n                            HorizontalAlignment=\"Stretch\"\n                            Margin=\"0,8,0,8\"\n                            VerticalAlignment=\"Stretch\"\n                            Visibility=\"{Binding IsImporting, Converter={StaticResource BoolToVisibilityConverter}, FallbackValue=Hidden}\" />\n                        <StackPanel\n                            HorizontalAlignment=\"Stretch\"\n                            Orientation=\"Vertical\"\n                            VerticalAlignment=\"Center\"\n                            Visibility=\"{Binding IsImporting, Converter={StaticResource BoolToVisibilityConverter}}\">\n                            <ui:ProgressRing\n                                HorizontalAlignment=\"Center\"\n                                IsIndeterminate=\"False\"\n                                Progress=\"{Binding Value}\"\n                                VerticalAlignment=\"Center\" />\n                            <TextBlock\n                                HorizontalAlignment=\"Center\"\n                                Margin=\"0,8,0,0\"\n                                Text=\"{Binding Text, FallbackValue=Importing...}\"\n                                VerticalAlignment=\"Center\" />\n                        </StackPanel>\n                    </Grid>\n                    <Grid>\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"1*\" />\n                            <ColumnDefinition Width=\"Auto\"/>\n                        </Grid.ColumnDefinitions>\n                        <ui:Button\n                            Appearance=\"Primary\"\n                            Command=\"{Binding ImportCommand}\"\n                            CommandParameter=\"{Binding CivitModel}\"\n                            IsEnabled=\"{Binding IsImporting, Converter={StaticResource BoolNegationConverter}}\"\n                            HorizontalAlignment=\"Stretch\"\n                            Margin=\"0,8,0,0\">\n                            <ui:Button.Content>\n                                <StackPanel Orientation=\"Horizontal\">\n                                    <TextBlock Text=\"Import Latest -\"/>\n                                    <TextBlock Margin=\"4,0,0,0\"\n                                               Text=\"{Binding CivitModel.FullFilesSize}\"/>\n                                </StackPanel>\n                            </ui:Button.Content>\n                        </ui:Button>\n                        \n                        <ui:Button Grid.Column=\"1\"\n                                   Margin=\"8,8,0,0\"\n                                   Appearance=\"Primary\"\n                                   IsEnabled=\"{Binding IsImporting, Converter={StaticResource BoolNegationConverter}}\"\n                                   Command=\"{Binding ShowVersionDialogCommand}\"\n                                   CommandParameter=\"{Binding CivitModel}\"\n                                   Content=\"All Versions\"/>\n                    </Grid>\n                </StackPanel>\n            </ui:Card>\n        </DataTemplate>\n    </Page.Resources>\n\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n            <RowDefinition Height=\"Auto\" />\n        </Grid.RowDefinitions>\n\n        <StackPanel Margin=\"8\" Orientation=\"Vertical\">\n            <Grid>\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition Width=\"*\" />\n                    <ColumnDefinition Width=\"Auto\" />\n                </Grid.ColumnDefinitions>\n\n                <ui:TextBox\n                    HorizontalAlignment=\"Stretch\"\n                    Margin=\"8,0,0,0\"\n                    PlaceholderText=\"Search models, #tags, or @users\"\n                    Text=\"{Binding SearchQuery, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\" />\n\n                <ui:Button\n                    Appearance=\"Primary\"\n                    Command=\"{Binding SearchModelsCommand}\"\n                    Grid.Column=\"1\"\n                    IsDefault=\"True\"\n                    Margin=\"8,0,8,0\"\n                    VerticalAlignment=\"Stretch\"\n                    Width=\"80\">\n                    <StackPanel Orientation=\"Horizontal\">\n                        <ui:ProgressRing\n                            Height=\"20\"\n                            IsIndeterminate=\"True\"\n                            VerticalAlignment=\"Center\"\n                            Visibility=\"{Binding SearchModelsCommand.IsRunning, Converter={StaticResource BoolToVisibilityConverter}}\"\n                            Width=\"20\" />\n                        <TextBlock\n                            Text=\"Search\"\n                            VerticalAlignment=\"Center\"\n                            Visibility=\"{Binding SearchModelsCommand.IsRunning, Converter={StaticResource InvertAndVisibilitate}}\" />\n                    </StackPanel>\n                </ui:Button>\n            </Grid>\n            <DockPanel>\n                <StackPanel Margin=\"8\" Orientation=\"Vertical\">\n                    <Label Content=\"Sort\" />\n                    <ComboBox\n                        ItemsSource=\"{Binding AllSortModes}\"\n                        MinWidth=\"100\"\n                        SelectedItem=\"{Binding SortMode}\" />\n                </StackPanel>\n\n                <StackPanel Margin=\"0,8\" Orientation=\"Vertical\">\n                    <Label Content=\"Period\" />\n                    <ComboBox\n                        ItemsSource=\"{Binding AllCivitPeriods}\"\n                        MinWidth=\"100\"\n                        SelectedItem=\"{Binding SelectedPeriod}\" />\n                </StackPanel>\n\n                <StackPanel Margin=\"8\" Orientation=\"Vertical\">\n                    <Label Content=\"Model Type\" />\n                    <ComboBox\n                        ItemsSource=\"{Binding AllModelTypes}\"\n                        MinWidth=\"100\"\n                        SelectedItem=\"{Binding SelectedModelType}\" />\n                </StackPanel>\n\n                <CheckBox\n                    Content=\"Show NSFW Content\"\n                    FontSize=\"12\"\n                    HorizontalAlignment=\"Right\"\n                    IsChecked=\"{Binding ShowNsfw, Mode=TwoWay}\"\n                    Margin=\"8,8,8,0\" />\n            </DockPanel>\n\n        </StackPanel>\n\n        <ui:DynamicScrollViewer CanContentScroll=\"True\" Grid.Row=\"1\">\n            <ui:VirtualizingItemsControl\n                ItemTemplate=\"{StaticResource CivitModelTemplate}\"\n                ItemsSource=\"{Binding ModelCardsView}\"\n                PreviewMouseWheel=\"VirtualizingGridView_OnPreviewMouseWheel\"\n                RequestBringIntoView=\"FrameworkElement_OnRequestBringIntoView\" />\n        </ui:DynamicScrollViewer>\n\n        <TextBlock\n            Grid.Row=\"2\"\n            Margin=\"16,8\"\n            Text=\"Data provided by CivitAI\"\n            VerticalAlignment=\"Bottom\" />\n\n        <StackPanel\n            Grid.Row=\"2\"\n            HorizontalAlignment=\"Center\"\n            Margin=\"8\"\n            Orientation=\"Vertical\"\n            Visibility=\"{Binding HasSearched, Converter={StaticResource BoolToVisibilityConverter}}\">\n            <TextBlock Margin=\"0,0,4,4\" TextAlignment=\"Center\">\n                <Run Text=\"Page\" />\n                <Run Text=\"{Binding CurrentPageNumber, FallbackValue=1}\" />\n                <Run Text=\"/\" />\n                <Run Text=\"{Binding TotalPages, FallbackValue=5}\" />\n            </TextBlock>\n            <StackPanel HorizontalAlignment=\"Center\" Orientation=\"Horizontal\">\n                <ui:Button\n                    Command=\"{Binding PreviousPageCommand}\"\n                    IsEnabled=\"{Binding CanGoToPreviousPage}\"\n                    Margin=\"0,0,8,0\">\n                    <ui:SymbolIcon Symbol=\"ArrowPrevious24\" />\n                </ui:Button>\n                <ui:Button Command=\"{Binding NextPageCommand}\" IsEnabled=\"{Binding CanGoToNextPage}\">\n                    <ui:SymbolIcon Symbol=\"ArrowNext24\" />\n                </ui:Button>\n            </StackPanel>\n        </StackPanel>\n\n        <TextBlock\n            Grid.Row=\"0\"\n            Grid.RowSpan=\"3\"\n            FontSize=\"20\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            Text=\"{Binding NoResultsText, FallbackValue=No results found}\"\n            Visibility=\"{Binding NoResultsFound, Converter={StaticResource BoolToVisibilityConverter}}\" />\n        \n        <ui:ProgressRing\n            Grid.Row=\"0\"\n            Grid.RowSpan=\"3\"\n            IsIndeterminate=\"True\"\n            Visibility=\"{Binding ShowMainLoadingSpinner, Converter={StaticResource BoolToVisibilityConverter}}\" />\n    </Grid>\n</Page>\n"
  },
  {
    "path": "StabilityMatrix/CheckpointBrowserPage.xaml.cs",
    "content": "﻿using System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Input;\nusing StabilityMatrix.ViewModels;\n\nnamespace StabilityMatrix;\n\npublic partial class CheckpointBrowserPage : Page\n{\n    public CheckpointBrowserPage(CheckpointBrowserViewModel viewModel)\n    {\n        InitializeComponent();\n        DataContext = viewModel;\n    }\n    \n    private void VirtualizingGridView_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)\n    {\n        if (e.Handled) return;\n        \n        e.Handled = true;\n        var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)\n        {\n            RoutedEvent = MouseWheelEvent,\n            Source = sender\n        };\n        if (((Control)sender).Parent is UIElement parent)\n        {\n            parent.RaiseEvent(eventArg);\n        }\n    }\n\n    private void FrameworkElement_OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)\n    {\n        e.Handled = true;\n    }\n\n    private void CheckpointBrowserPage_OnLoaded(object sender, RoutedEventArgs e)\n    {\n        (DataContext as CheckpointBrowserViewModel)?.OnLoaded();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/CheckpointManagerPage.xaml",
    "content": "﻿<Page\n    Loaded=\"CheckpointManagerPage_OnLoaded\"\n    d:DataContext=\"{d:DesignInstance Type=designData:MockCheckpointManagerViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"650\"\n    d:DesignWidth=\"1000\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.CheckpointManagerPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.DesignData\"\n    xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n    xmlns:ci=\"clr-namespace:StabilityMatrix.Interactions\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Models\">\n\n    <Page.Resources>\n        <BooleanToVisibilityConverter x:Key=\"BoolToVisibilityConverter\" />\n        <converters:StringNullOrEmptyToVisibilityConverter x:Key=\"StringNullOrEmptyToVisibilityConverter\" />\n        <converters:BooleanToHiddenVisibleConverter x:Key=\"BoolToHiddenVisibleConverter\" />\n        <converters:NullToVisibilityConverter x:Key=\"NullToVisibilityConverter\" />\n        <converters:ValueConverterGroup x:Key=\"InvertAndVisibleOrHidden\">\n            <converters:BoolNegationConverter />\n            <converters:BooleanToHiddenVisibleConverter />\n        </converters:ValueConverterGroup>\n        <converters:ValueConverterGroup x:Key=\"InvertBoolToVisibilityConverter\">\n            <converters:BoolNegationConverter />\n            <BooleanToVisibilityConverter />\n        </converters:ValueConverterGroup>\n\n        <DropShadowEffect\n            BlurRadius=\"8\"\n            Color=\"#FF000000\"\n            Direction=\"0\"\n            Opacity=\"0.2\"\n            ShadowDepth=\"0\"\n            x:Key=\"TextDropShadowEffect\" />\n\n        <!--  Template for a single badge  -->\n        <DataTemplate DataType=\"{x:Type system:String}\" x:Key=\"BadgeTemplate\">\n            <ui:Card\n                Height=\"18\"\n                Margin=\"4,0,0,0\"\n                Padding=\"3\"\n                Visibility=\"{Binding Converter={StaticResource StringNullOrEmptyToVisibilityConverter}, FallbackValue=Collapsed}\"\n                Width=\"40\">\n                <TextBlock\n                    FontSize=\"10\"\n                    FontWeight=\"Medium\"\n                    Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                    HorizontalAlignment=\"Center\"\n                    Text=\"{Binding}\"\n                    VerticalAlignment=\"Center\" />\n            </ui:Card>\n        </DataTemplate>\n\n        <DataTemplate DataType=\"{x:Type models:CheckpointFile}\" x:Key=\"CheckpointFileDataTemplate\">\n            <Border\n                Background=\"Transparent\"\n                BorderThickness=\"0\"\n                Margin=\"8\">\n                <i:Interaction.Behaviors>\n                    <i:MouseDragElementBehavior />\n                </i:Interaction.Behaviors>\n                <ui:Card Width=\"260\">\n                    <ui:Card.ContextMenu>\n                        <ContextMenu>\n                            <!--  ReSharper disable once Xaml.RedundantResource  -->\n                            <MenuItem Command=\"{Binding RenameCommand}\" Header=\"Rename\" />\n                            <!--  ReSharper disable once Xaml.RedundantResource  -->\n                            <MenuItem Command=\"{Binding DeleteCommand}\" Header=\"Delete\" />\n                            <!--  ReSharper disable once Xaml.RedundantResource  -->\n                            <MenuItem Header=\"Open on CivitAI\" \n                                      Command=\"{Binding OpenOnCivitAiCommand}\"\n                                      Visibility=\"{Binding IsConnectedModel, Converter={StaticResource BoolToVisibilityConverter}}\"/>\n                        </ContextMenu>\n                    </ui:Card.ContextMenu>\n                    <Grid>\n                        <!--  Main contents, hidden when IsLoading is true  -->\n                        <StackPanel MinHeight=\"70\">\n                            <ui:Image\n                                CornerRadius=\"4\"\n                                Margin=\"0,0,0,8\"\n                                Source=\"{Binding PreviewImage}\"\n                                Stretch=\"Uniform\"\n                                Visibility=\"Collapsed\" />\n                            <Grid Name=\"ModelInfoGrid\" Visibility=\"{Binding IsLoading, Converter={StaticResource InvertAndVisibleOrHidden}}\">\n                                <Grid.ColumnDefinitions>\n                                    <ColumnDefinition Width=\"*\" />\n                                    <ColumnDefinition Width=\"*\" />\n                                    <ColumnDefinition Width=\"*\" />\n                                    <ColumnDefinition Width=\"*\" />\n                                    <ColumnDefinition Width=\"*\" />\n                                </Grid.ColumnDefinitions>\n                                <Grid.RowDefinitions>\n                                    <RowDefinition Height=\"*\" />\n                                    <RowDefinition Height=\"0.1*\" />\n                                </Grid.RowDefinitions>\n                                <StackPanel\n                                    Grid.ColumnSpan=\"4\"\n                                    HorizontalAlignment=\"Left\"\n                                    IsEnabled=\"True\"\n                                    Margin=\"4\"\n                                    Orientation=\"Vertical\"\n                                    ToolTipService.BetweenShowDelay=\"100\"\n                                    ToolTipService.InitialShowDelay=\"300\">\n                                    <!--  Tooltip for title and version  -->\n                                    <StackPanel.ToolTip>\n                                        <ToolTip Padding=\"8\">\n                                            <VirtualizingStackPanel>\n                                                <!--  Title  -->\n                                                <TextBlock\n                                                    FontSize=\"14\"\n                                                    FontWeight=\"Medium\"\n                                                    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n                                                    HorizontalAlignment=\"Left\"\n                                                    Margin=\"0,0,0,4\"\n                                                    Text=\"{Binding Title}\"\n                                                    TextWrapping=\"WrapWithOverflow\" />\n                                                <!--  Version  -->\n                                                <TextBlock\n                                                    FontSize=\"13\"\n                                                    Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                                    HorizontalAlignment=\"Left\"\n                                                    Margin=\"0,0,0,4\"\n                                                    Text=\"{Binding ConnectedModel.VersionName, FallbackValue=''}\"\n                                                    TextWrapping=\"WrapWithOverflow\"\n                                                    Visibility=\"{Binding IsConnectedModel, Converter={StaticResource BoolToVisibilityConverter}}\" />\n                                                <!--  Image  -->\n                                                <ui:Image\n                                                    CornerRadius=\"4\"\n                                                    Margin=\"0,0,0,4\"\n                                                    Source=\"{Binding PreviewImage}\"\n                                                    Stretch=\"Uniform\"\n                                                    Visibility=\"{Binding IsConnectedModel, Converter={StaticResource BoolToVisibilityConverter}}\" />\n                                            </VirtualizingStackPanel>\n                                        </ToolTip>\n                                    </StackPanel.ToolTip>\n                                    <TextBlock\n                                        Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n                                        HorizontalAlignment=\"Left\"\n                                        IsEnabled=\"True\"\n                                        MaxWidth=\"150\"\n                                        Text=\"{Binding Title}\"\n                                        TextTrimming=\"CharacterEllipsis\"\n                                        TextWrapping=\"NoWrap\" />\n                                    <TextBlock\n                                        FontSize=\"13\"\n                                        Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                        HorizontalAlignment=\"Left\"\n                                        IsEnabled=\"True\"\n                                        Text=\"{Binding ConnectedModel.VersionName, FallbackValue=''}\"\n                                        TextWrapping=\"WrapWithOverflow\"\n                                        Visibility=\"{Binding IsConnectedModel, Converter={StaticResource BoolToVisibilityConverter}}\" />\n                                </StackPanel>\n                                <Grid Grid.Column=\"4\" Grid.Row=\"0\">\n                                    <Button\n                                        Background=\"Transparent\"\n                                        BorderBrush=\"Transparent\"\n                                        FontSize=\"20\"\n                                        HorizontalAlignment=\"Right\"\n                                        IsEnabled=\"True\"\n                                        MaxHeight=\"48\"\n                                        MaxWidth=\"64\"\n                                        Padding=\"0\"\n                                        ToolTip=\"Connected Model\"\n                                        VerticalAlignment=\"Top\"\n                                        Visibility=\"{Binding IsConnectedModel, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}\">\n                                        <ui:SymbolIcon\n                                            FontSize=\"12\"\n                                            Foreground=\"LightGreen\"\n                                            Symbol=\"CloudCheckmark24\"\n                                            ToolTip=\"Connected Model\" />\n                                    </Button>\n                                    <Button\n                                        Background=\"Transparent\"\n                                        BorderBrush=\"Transparent\"\n                                        FontSize=\"20\"\n                                        HorizontalAlignment=\"Right\"\n                                        IsEnabled=\"True\"\n                                        MaxHeight=\"48\"\n                                        MaxWidth=\"64\"\n                                        Padding=\"0\"\n                                        ToolTip=\"Local Model\"\n                                        VerticalAlignment=\"Top\"\n                                        Visibility=\"{Binding IsConnectedModel, Mode=OneWay, Converter={StaticResource InvertBoolToVisibilityConverter}}\">\n                                        <ui:SymbolIcon\n                                            FontSize=\"12\"\n                                            Foreground=\"DimGray\"\n                                            Symbol=\"CloudOff24\"\n                                            ToolTip=\"Local Model\" />\n                                    </Button>\n                                    <ui:Button\n                                        Background=\"Transparent\"\n                                        BorderBrush=\"Transparent\"\n                                        FontSize=\"20\"\n                                        HorizontalAlignment=\"Right\"\n                                        IsEnabled=\"False\"\n                                        MaxHeight=\"48\"\n                                        MaxWidth=\"64\"\n                                        Padding=\"0\"\n                                        VerticalAlignment=\"Top\"\n                                        Visibility=\"Collapsed\">\n                                        <ui:Button.Icon>\n                                            <ui:SymbolIcon\n                                                FontSize=\"12\"\n                                                Foreground=\"OrangeRed\"\n                                                Symbol=\"CloudArrowUp24\"\n                                                ToolTip=\"Update Available\" />\n                                        </ui:Button.Icon>\n                                    </ui:Button>\n                                </Grid>\n                                <TextBlock\n                                    FontSize=\"11\"\n                                    Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                    Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"3\"\n                                    Grid.Row=\"1\"\n                                    IsEnabled=\"True\"\n                                    Margin=\"4,4\"\n                                    Text=\"{Binding FileName}\"\n                                    TextTrimming=\"CharacterEllipsis\"\n                                    ToolTip=\"{Binding FileName}\"\n                                    VerticalAlignment=\"Bottom\" />\n                                <ItemsControl\n                                    Grid.Column=\"3\"\n                                    Grid.ColumnSpan=\"2\"\n                                    Grid.Row=\"1\"\n                                    ItemTemplate=\"{StaticResource BadgeTemplate}\"\n                                    ItemsSource=\"{Binding Badges}\"\n                                    VerticalAlignment=\"Bottom\">\n                                    <ItemsControl.ItemsPanel>\n                                        <ItemsPanelTemplate>\n                                            <StackPanel\n                                                HorizontalAlignment=\"Right\"\n                                                Orientation=\"Horizontal\"\n                                                VerticalAlignment=\"Bottom\" />\n                                        </ItemsPanelTemplate>\n                                    </ItemsControl.ItemsPanel>\n                                </ItemsControl>\n                            </Grid>\n                        </StackPanel>\n\n                        <!--  Progress ring  -->\n                        <ui:ProgressRing\n                            Grid.Row=\"0\"\n                            Height=\"32\"\n                            HorizontalAlignment=\"Center\"\n                            IsEnabled=\"{Binding IsLoading}\"\n                            IsIndeterminate=\"True\"\n                            Padding=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Visibility=\"{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}\"\n                            Width=\"32\" />\n                    </Grid>\n                </ui:Card>\n            </Border>\n        </DataTemplate>\n\n        <DataTemplate DataType=\"{x:Type models:CheckpointFolder}\" x:Key=\"CheckpointFolderGridDataTemplate\">\n            <Expander\n                AllowDrop=\"True\"\n                Header=\"{Binding Title}\"\n                IsExpanded=\"True\"\n                Margin=\"8\"\n                Padding=\"8,8,8,8\"\n                Visibility=\"{Binding IsCategoryEnabled, Converter={StaticResource BoolToVisibilityConverter}, FallbackValue=Visible}\">\n                <Expander.ContextMenu>\n                    <ContextMenu>\n                        <!-- ReSharper disable once Xaml.RedundantResource -->\n                        <MenuItem Header=\"Show in Explorer\" \n                                  Command=\"{Binding ShowInExplorerCommand}\"\n                                  CommandParameter=\"{Binding DirectoryPath}\"/>\n                    </ContextMenu>\n                </Expander.ContextMenu>\n                <StackPanel Orientation=\"Vertical\">\n                    <!-- Subfolders -->\n                    <StackPanel Orientation=\"Vertical\">\n                        <ItemsControl\n                            HorizontalAlignment=\"Stretch\"\n                            ItemTemplate=\"{DynamicResource CheckpointFolderGridDataTemplate}\"\n                            ItemsSource=\"{Binding SubFolders, Mode=OneWay}\"\n                            Margin=\"8,0,8,8\" />\n                    </StackPanel>\n                    <!-- Files -->\n                    <Grid>\n                        <i:Interaction.Triggers>\n                            <ci:EventTriggerWithoutPropagation EventName=\"PreviewDragEnter\">\n                                <i:InvokeCommandAction Command=\"{Binding OnPreviewDragEnterCommand}\" />\n                            </ci:EventTriggerWithoutPropagation>\n                            <ci:EventTriggerWithoutPropagation EventName=\"PreviewDragLeave\">\n                                <i:InvokeCommandAction Command=\"{Binding OnPreviewDragLeaveCommand}\" />\n                            </ci:EventTriggerWithoutPropagation>\n                            <ci:EventTriggerWithoutPropagation EventName=\"PreviewDrop\">\n                                <i:InvokeCommandAction Command=\"{Binding PreviewDropCommand}\" PassEventArgsToCommand=\"True\" />\n                            </ci:EventTriggerWithoutPropagation>\n                        </i:Interaction.Triggers>\n                        <ui:VirtualizingItemsControl\n                            ItemTemplate=\"{StaticResource CheckpointFileDataTemplate}\"\n                            ItemsSource=\"{Binding CheckpointFiles}\"\n                            Padding=\"5\"\n                            PreviewMouseWheel=\"VirtualizingGridView_OnPreviewMouseWheel\" />\n                        <Border\n                            CornerRadius=\"8\"\n                            Grid.RowSpan=\"4\"\n                            IsEnabled=\"False\"\n                            Name=\"OnDragBlurBorder\"\n                            Visibility=\"{Binding IsDragBlurEnabled, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}\">\n                            <Border.Background>\n                                <SolidColorBrush Color=\"#EE202020\" />\n                            </Border.Background>\n                        </Border>\n                        <Border\n                            BorderThickness=\"1,1,1,1\"\n                            CornerRadius=\"8\"\n                            Grid.RowSpan=\"4\"\n                            IsEnabled=\"False\"\n                            Name=\"OnDragDashBorder\"\n                            Visibility=\"{Binding IsCurrentDragTarget, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}\">\n                            <Border.BorderBrush>\n                                <DrawingBrush\n                                    TileMode=\"Tile\"\n                                    Viewport=\"0,0,8,8\"\n                                    ViewportUnits=\"Absolute\">\n                                    <DrawingBrush.Drawing>\n                                        <DrawingGroup>\n                                            <GeometryDrawing Brush=\"White\">\n                                                <GeometryDrawing.Geometry>\n                                                    <GeometryGroup>\n                                                        <RectangleGeometry Rect=\"0,0,50,50\" />\n                                                        <RectangleGeometry Rect=\"50,50,50,50\" />\n                                                    </GeometryGroup>\n                                                </GeometryDrawing.Geometry>\n                                            </GeometryDrawing>\n                                        </DrawingGroup>\n                                    </DrawingBrush.Drawing>\n                                </DrawingBrush>\n                            </Border.BorderBrush>\n                        </Border>\n                        <VirtualizingStackPanel\n                            Margin=\"0,8\"\n                            Orientation=\"Vertical\"\n                            VerticalAlignment=\"Center\">\n                            <TextBlock\n                                Effect=\"{StaticResource TextDropShadowEffect}\"\n                                FontSize=\"24\"\n                                HorizontalAlignment=\"Center\"\n                                Name=\"OnDragText\"\n                                Text=\"Drop a file here to import\"\n                                VerticalAlignment=\"Center\"\n                                Visibility=\"{Binding IsCurrentDragTarget, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Visible}\" />\n                            <TextBlock\n                                Effect=\"{StaticResource TextDropShadowEffect}\"\n                                FontSize=\"18\"\n                                HorizontalAlignment=\"Center\"\n                                Name=\"ImportProgressText\"\n                                Text=\"{Binding Progress.Text, FallbackValue=Importing checkpoint.safetensors}\"\n                                VerticalAlignment=\"Center\"\n                                Visibility=\"{Binding Progress.TextVisibility}\" />\n                            <ProgressBar\n                                Effect=\"{StaticResource TextDropShadowEffect}\"\n                                HorizontalAlignment=\"Stretch\"\n                                IsIndeterminate=\"{Binding Progress.IsIndeterminate, FallbackValue=False}\"\n                                Margin=\"64,8\"\n                                Name=\"ImportProgressBar\"\n                                VerticalAlignment=\"Center\"\n                                Visibility=\"{Binding Progress.ProgressVisibility, FallbackValue=Visible}\"\n                                controls:ProgressBarSmoother.SmoothValue=\"{Binding Progress.Value, FallbackValue=20}\" />\n                        </VirtualizingStackPanel>\n                    </Grid>\n                </StackPanel>\n            </Expander>\n        </DataTemplate>\n\n        <DataTemplate DataType=\"{x:Type models:CheckpointFolder}\" x:Key=\"CheckpointFolderDataTemplate\">\n            <ContentControl Content=\"{Binding}\">\n                <ContentControl.Style>\n                    <Style TargetType=\"{x:Type ContentControl}\">\n                        <Setter Property=\"ContentTemplate\" Value=\"{StaticResource CheckpointFolderGridDataTemplate}\" />\n                    </Style>\n                </ContentControl.Style>\n            </ContentControl>\n        </DataTemplate>\n\n        <!--  Template for dropdown category checkbox item  -->\n        <DataTemplate DataType=\"{x:Type models:CheckpointFolder}\" x:Key=\"CategoryItemTemplate\">\n            <CheckBox Content=\"{Binding Title}\" IsChecked=\"{Binding IsCategoryEnabled, Mode=TwoWay}\" />\n        </DataTemplate>\n\n        <!--  Template for dropdown box  -->\n        <ControlTemplate TargetType=\"{x:Type ComboBoxItem}\" x:Key=\"CategoryDropDownStyle\">\n            <Grid>\n                <Border\n                    CornerRadius=\"{TemplateBinding Border.CornerRadius}\"\n                    Margin=\"{TemplateBinding Margin}\"\n                    Name=\"ContentBorder\"\n                    Padding=\"0\"\n                    SnapsToDevicePixels=\"True\"\n                    VerticalAlignment=\"Stretch\">\n                    <Grid>\n                        <Rectangle\n                            Height=\"16\"\n                            HorizontalAlignment=\"Left\"\n                            Margin=\"0\"\n                            RadiusX=\"2\"\n                            RadiusY=\"2\"\n                            VerticalAlignment=\"Center\"\n                            Visibility=\"Collapsed\"\n                            Width=\"3\"\n                            x:Name=\"ActiveRectangle\">\n                            <Rectangle.Fill>\n                                <SolidColorBrush Color=\"{DynamicResource SystemAccentColorSecondary}\" />\n                            </Rectangle.Fill>\n                        </Rectangle>\n                        <Grid>\n                            <Grid.ColumnDefinitions>\n                                <ColumnDefinition Width=\"*\" />\n                                <ColumnDefinition Width=\"Auto\" />\n                            </Grid.ColumnDefinitions>\n                            <!--  Setup grid rows  -->\n                            <StackPanel\n                                Grid.Column=\"0\"\n                                Margin=\"10,0,0,0\"\n                                VerticalAlignment=\"Top\"\n                                d:DataContext=\"{d:DesignInstance Type=models:CheckpointFolder,\n                                                                 IsDesignTimeCreatable=True}\">\n                                <CheckBox Content=\"{Binding TitleWithFilesCount}\" IsChecked=\"{Binding IsCategoryEnabled, Mode=TwoWay}\" />\n                            </StackPanel>\n                        </Grid>\n                    </Grid>\n                </Border>\n            </Grid>\n        </ControlTemplate>\n\n    </Page.Resources>\n\n    <Grid>\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"Auto\" />\n            <ColumnDefinition Width=\"*\" />\n        </Grid.ColumnDefinitions>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"0.1*\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n        <!--  Top settings bar  -->\n        <ui:ToggleSwitch\n            Content=\"Import as Connected\"\n            Grid.Column=\"0\"\n            IsChecked=\"{Binding IsImportAsConnected}\"\n            Margin=\"24,8\"\n            ToolTip=\"Search for connected metadata on new local imports\" />\n        <StackPanel\n            Grid.Column=\"1\"\n            Grid.Row=\"0\"\n            HorizontalAlignment=\"Right\"\n            Margin=\"16,0\"\n            Orientation=\"Horizontal\">\n            <ui:Button \n                VerticalAlignment=\"Stretch\"\n                Command=\"{Binding OpenModelsFolderCommand}\"\n                Height=\"46\"\n                Margin=\"8,0\">\n                <ui:Button.Icon>\n                    <ui:SymbolIcon Symbol=\"FolderOpen24\"/>\n                </ui:Button.Icon>\n                <ui:Button.Content>\n                    <TextBlock Text=\"Open Models Folder\"/>\n                </ui:Button.Content>\n            </ui:Button>\n            <ComboBox\n                HorizontalAlignment=\"Right\"\n                ItemsSource=\"{Binding CheckpointFolders}\"\n                MinWidth=\"180\"\n                SelectedIndex=\"0\">\n                <ComboBox.ItemContainerStyle>\n                    <Style TargetType=\"ComboBoxItem\">\n                        <Setter Property=\"Template\" Value=\"{StaticResource CategoryDropDownStyle}\" />\n                    </Style>\n                </ComboBox.ItemContainerStyle>\n\n                <ComboBox.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type models:CheckpointFolder}\">\n                        <StackPanel Margin=\"10,0,0,0\" VerticalAlignment=\"Top\">\n                            <TextBlock Margin=\"0,5,0,5\" Text=\"Categories\" />\n                        </StackPanel>\n                    </DataTemplate>\n                </ComboBox.ItemTemplate>\n            </ComboBox>\n        </StackPanel>\n\n        <!--  Main view with model cards  -->\n        <ui:DynamicScrollViewer\n            CanContentScroll=\"True\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            Grid.Row=\"1\"\n            HorizontalScrollBarVisibility=\"Disabled\"\n            VerticalScrollBarVisibility=\"Auto\">\n            <Grid>\n                <StackPanel Orientation=\"Vertical\">\n                    <ItemsControl\n                        HorizontalAlignment=\"Stretch\"\n                        ItemTemplate=\"{StaticResource CheckpointFolderGridDataTemplate}\"\n                        ItemsSource=\"{Binding CheckpointFolders, Mode=OneWay}\"\n                        Margin=\"8,0,8,8\" />\n                </StackPanel>\n            </Grid>\n        </ui:DynamicScrollViewer>\n    </Grid>\n\n</Page>\n"
  },
  {
    "path": "StabilityMatrix/CheckpointManagerPage.xaml.cs",
    "content": "﻿using System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Input;\nusing StabilityMatrix.ViewModels;\n\nnamespace StabilityMatrix;\n\npublic partial class CheckpointManagerPage : Page\n{\n    private readonly CheckpointManagerViewModel viewModel;\n    public CheckpointManagerPage(CheckpointManagerViewModel viewModel)\n    {\n        this.viewModel = viewModel;\n        InitializeComponent();\n        DataContext = viewModel;\n    }\n\n    private async void CheckpointManagerPage_OnLoaded(object sender, RoutedEventArgs e)\n    {\n        await viewModel.OnLoaded();\n    }\n\n    /// <summary>\n    /// Bubbles the mouse wheel event up to the parent.\n    /// </summary>\n    /// <param name=\"sender\"></param>\n    /// <param name=\"e\"></param>\n    private void VirtualizingGridView_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)\n    {\n        if (e.Handled) return;\n        \n        e.Handled = true;\n        var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)\n        {\n            RoutedEvent = MouseWheelEvent,\n            Source = sender\n        };\n        if (((Control)sender).Parent is UIElement parent)\n        {\n            parent.RaiseEvent(eventArg);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Controls/AppBrushes.cs",
    "content": "﻿using System.Windows.Media;\n\nnamespace StabilityMatrix.Controls;\n\npublic static class AppBrushes\n{\n    public static readonly SolidColorBrush SuccessGreen = FromHex(\"#4caf50\")!;\n    public static readonly SolidColorBrush FailedRed = FromHex(\"#f44336\")!;\n    public static readonly SolidColorBrush WarningYellow = FromHex(\"#ffeb3b\")!;\n\n    private static SolidColorBrush? FromHex(string hex)\n    {\n        return new BrushConverter().ConvertFrom(hex) as SolidColorBrush;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Controls/ProgressBarSmoother.cs",
    "content": "﻿using System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Controls.Primitives;\nusing System.Windows.Media.Animation;\nusing System;\n\nnamespace StabilityMatrix.Controls;\n\npublic class ProgressBarSmoother\n{\n    public static double GetSmoothValue(DependencyObject obj)\n    {\n        return (double)obj.GetValue(SmoothValueProperty);\n    }\n\n    public static void SetSmoothValue(DependencyObject obj, double value)\n    {\n        obj.SetValue(SmoothValueProperty, value);\n    }\n\n    public static readonly DependencyProperty SmoothValueProperty =\n        DependencyProperty.RegisterAttached(\"SmoothValue\", typeof(double),\n            typeof(ProgressBarSmoother), new PropertyMetadata(0.0, Changing));\n\n    private static void Changing(DependencyObject d, DependencyPropertyChangedEventArgs e)\n    {\n        var anim = new DoubleAnimation((double)e.OldValue, (double)e.NewValue, TimeSpan.FromMilliseconds(150));\n        (d as ProgressBar)?.BeginAnimation(RangeBase.ValueProperty, anim, HandoffBehavior.Compose);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Controls/RefreshBadge.xaml",
    "content": "﻿<UserControl\n    HorizontalAlignment=\"Stretch\"\n    VerticalAlignment=\"Stretch\"\n    d:DataContext=\"{d:DesignInstance Type=viewModels:RefreshBadgeViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"150\"\n    d:DesignWidth=\"150\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.Controls.RefreshBadge\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:StabilityMatrix.Controls\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n\n    <UserControl.Resources>\n        <BooleanToVisibilityConverter x:Key=\"BoolToVisibilityConverter\" />\n        <converters:ValueConverterGroup x:Key=\"InvertAndVisibleOrHidden\">\n            <converters:BoolNegationConverter />\n            <converters:BooleanToHiddenVisibleConverter />\n        </converters:ValueConverterGroup>\n    </UserControl.Resources>\n\n    <Grid>\n        <ui:Button\n            Appearance=\"Transparent\"\n            BorderThickness=\"0\"\n            Command=\"{Binding RefreshCommand}\"\n            FontSize=\"26\"\n            Foreground=\"{Binding ColorBrush}\"\n            HorizontalAlignment=\"Center\"\n            Icon=\"{Binding Icon, FallbackValue=CheckmarkCircle12}\"\n            Margin=\"4\"\n            Padding=\"2\"\n            ToolTip=\"{Binding CurrentToolTip}\"\n            VerticalAlignment=\"Center\"\n            Visibility=\"{Binding IsWorking, Converter={StaticResource InvertAndVisibleOrHidden}}\" />\n        <ui:ProgressRing\n            FontSize=\"14\"\n            Grid.Row=\"0\"\n            Height=\"20\"\n            HorizontalAlignment=\"Center\"\n            IsEnabled=\"{Binding IsWorking, Converter={StaticResource BoolToVisibilityConverter}}\"\n            IsIndeterminate=\"{Binding IsWorking}\"\n            ToolTip=\"{Binding CurrentToolTip}\"\n            VerticalAlignment=\"Center\"\n            Visibility=\"{Binding IsWorking, Converter={StaticResource BoolToVisibilityConverter}}\"\n            Width=\"20\" />\n    </Grid>\n\n</UserControl>\n"
  },
  {
    "path": "StabilityMatrix/Controls/RefreshBadge.xaml.cs",
    "content": "﻿using System.Windows.Controls;\n\nnamespace StabilityMatrix.Controls;\n\npublic partial class RefreshBadge : UserControl\n{\n    public RefreshBadge()\n    {\n        InitializeComponent();\n    }\n}"
  },
  {
    "path": "StabilityMatrix/Converters/BoolNegationConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing System.Windows.Data;\n\nnamespace StabilityMatrix.Converters;\n\n[ValueConversion(typeof(bool), typeof(bool))]\npublic class BoolNegationConverter : IValueConverter\n{\n    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n    {\n        if (value is bool?)\n        {\n            var boolVal = value as bool?;\n            return !boolVal ?? false;\n        }\n\n        return false;\n    }\n\n    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n    {\n        return Convert(value, targetType, parameter, culture);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Converters/BooleanToHiddenVisibleConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing System.Windows;\nusing System.Windows.Data;\n\nnamespace StabilityMatrix.Converters;\n\npublic class BooleanToHiddenVisibleConverter : IValueConverter\n{\n    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n    {\n        var bValue = false;\n        if (value is bool b)\n        {\n            bValue = b;\n        }\n        else if (value is bool)\n        {\n            var tmp = (bool?) value;\n            bValue = tmp.Value;\n        }\n        return bValue ? Visibility.Visible : Visibility.Hidden;\n    }\n    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n    {\n        if (value is Visibility visibility)\n        {\n            return visibility == Visibility.Visible;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Converters/IntDoubleConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing System.Windows.Data;\n\nnamespace StabilityMatrix.Converters;\n\npublic class IntDoubleConverter : IValueConverter\n{\n    // Convert from int to double\n    public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture)\n    {   \n        if (targetType == typeof(double?))\n        {\n            if (value == null)\n            {\n                return null;\n            }\n            return System.Convert.ToDouble(value);\n        }\n\n        throw new ArgumentException($\"Unsupported type {targetType}\");\n    }\n\n    // Convert from double to int (floor)\n    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n    {\n        if (targetType == typeof(int?))\n        {\n            return System.Convert.ToInt32(value);\n        }\n\n        throw new ArgumentException($\"Unsupported type {targetType}\");\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Converters/IsStringNullOrWhitespaceConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing System.Windows.Data;\n\nnamespace StabilityMatrix.Converters;\n\n[ValueConversion(typeof(string), typeof(bool))]\npublic class IsStringNullOrWhitespaceConverter : IValueConverter\n{\n    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n    {\n        if (value is string strValue)\n        {\n            return string.IsNullOrWhiteSpace(strValue);\n        }\n\n        throw new InvalidOperationException(\"Cannot convert non-string value\");\n    }\n\n    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n    {\n        throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Converters/LaunchOptionConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing System.Windows.Data;\n\nnamespace StabilityMatrix.Converters;\n\npublic class LaunchOptionConverter : IValueConverter\n{\n    public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture)\n    {\n        if (targetType == typeof(string))\n        {\n            return value?.ToString() ?? \"\";\n        }\n\n        if (targetType == typeof(bool?))\n        {\n            return bool.TryParse(value?.ToString(), out var boolValue) && boolValue;\n        }\n\n        if (targetType == typeof(double?))\n        {\n            if (value == null)\n            {\n                return null;\n            }\n            return double.TryParse(value?.ToString(), out var doubleValue) ? doubleValue : 0;\n        }\n        \n        if (targetType == typeof(int?))\n        {\n            if (value == null)\n            {\n                return null;\n            }\n            return int.TryParse(value?.ToString(), out var intValue) ? intValue : 0;\n        }\n\n        throw new ArgumentException(\"Unsupported type\");\n    }\n\n    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n    {\n        return value;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Converters/LaunchOptionIntDoubleConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing System.Windows.Data;\n\nnamespace StabilityMatrix.Converters;\n\npublic class LaunchOptionIntDoubleConverter : IValueConverter\n{\n    // Convert from int to double\n    public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture)\n    {   \n        if (targetType == typeof(double?))\n        {\n            if (value == null)\n            {\n                return null;\n            }\n            return System.Convert.ToDouble(value);\n        }\n\n        throw new ArgumentException($\"Unsupported type {targetType}\");\n    }\n\n    // Convert from double to object int (floor)\n    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n    {\n        if (targetType == typeof(int?) || targetType == typeof(object))\n        {\n            return System.Convert.ToInt32(value);\n        }\n\n        throw new ArgumentException($\"Unsupported type {targetType}\");\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Converters/NullToVisibilityConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing System.Windows;\nusing System.Windows.Data;\n\nnamespace StabilityMatrix.Converters;\n\npublic class NullToVisibilityConverter : IValueConverter\n{\n    public object Convert(object? value, Type targetType, object parameter, CultureInfo culture)\n    {\n        return value == null ? Visibility.Collapsed : Visibility.Visible;\n    }\n\n    public object ConvertBack(object? value, Type targetType, object parameter, CultureInfo culture)\n    {\n        throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Converters/StringNullOrEmptyToVisibilityConverter.cs",
    "content": "﻿using System;\nusing System.Windows;\nusing System.Windows.Data;\n\nnamespace StabilityMatrix.Converters;\n\npublic class StringNullOrEmptyToVisibilityConverter : IValueConverter\n{\n    public object Convert(object? value, Type targetType, object parameter, System.Globalization.CultureInfo culture)\n    {\n        return string.IsNullOrEmpty(value as string) ? Visibility.Collapsed : Visibility.Visible;\n    }\n    public object? ConvertBack(object? value, Type targetType, object parameter, System.Globalization.CultureInfo culture)\n    {\n        return null;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Converters/UriToBitmapConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing System.Windows.Data;\nusing System.Windows.Media.Imaging;\n\nnamespace StabilityMatrix.Converters;\n\npublic class UriToBitmapConverter : IValueConverter\n{\n    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n    {\n        if (value is Uri uri)\n        {\n            return new BitmapImage(uri);\n        }\n\n        if (value is string uriString)\n        {\n            return new BitmapImage(new Uri(uriString));\n        }\n\n        return null;\n    }\n\n    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n    {\n        throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Converters/ValueConverterGroup.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Windows.Data;\n\nnamespace StabilityMatrix.Converters;\n\npublic class ValueConverterGroup : List<IValueConverter>, IValueConverter\n{\n    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)\n    {\n        return this.Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, culture));\n    }\n\n    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)\n    {\n        throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/DataDirectoryMigrationDialog.xaml",
    "content": "﻿<ui:ContentDialog\n    CloseButtonText=\"Close\"\n    Loaded=\"DataDirectoryMigrationDialog_OnLoaded\"\n    Title=\"Package Information Update\"\n    d:DataContext=\"{d:DesignInstance Type=viewModels:DataDirectoryMigrationViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"512\"\n    d:DesignWidth=\"640\"\n    mc:Ignorable=\"d\"\n    UseLayoutRounding=\"True\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.DataDirectoryMigrationDialog\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:StabilityMatrix\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n\n    <ui:ContentDialog.Resources>\n        <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n        <Style BasedOn=\"{StaticResource {x:Type ui:ContentDialog}}\" TargetType=\"{x:Type local:DataDirectoryMigrationDialog}\" />\n        <BooleanToVisibilityConverter x:Key=\"BooleanToVisibilityConverter\" />\n        <converters:BoolNegationConverter x:Key=\"BoolNegationConverter\"/> \n        <converters:ValueConverterGroup x:Key=\"InvertIsStringNullOrWhitespaceConverter\">\n            <converters:IsStringNullOrWhitespaceConverter />\n            <converters:BoolNegationConverter />\n        </converters:ValueConverterGroup>\n        <converters:ValueConverterGroup x:Key=\"InvertAndVisibilitate\">\n            <converters:BoolNegationConverter />\n            <BooleanToVisibilityConverter />\n        </converters:ValueConverterGroup>\n    </ui:ContentDialog.Resources>\n\n    <Grid\n        Margin=\"16\"\n        MaxWidth=\"700\"\n        MinHeight=\"300\"\n        VerticalAlignment=\"Stretch\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"*\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n        </Grid.RowDefinitions>\n\n        <StackPanel>\n            <TextBlock\n                FontSize=\"16\"\n                Margin=\"8,8,8,8\"\n                TextAlignment=\"Center\"\n                Text=\"It looks like you have data from an older version of Stability Matrix before v1.1.0, would you like to migrate it?\"\n                TextWrapping=\"Wrap\" />\n            <ui:InfoBar\n                IsClosable=\"False\"\n                IsOpen=\"{Binding HasFreeSpaceError}\"\n                Margin=\"8\"\n                Severity=\"Warning\"\n                Title=\"The drive you selected doesn't have enough space for migration. If you don't want to migrate, simply click 'No Thanks'. Otherwise, please choose a different directory before proceeding with the migration.\" />\n            <ui:InfoBar\n                IsClosable=\"False\"\n                IsOpen=\"{Binding AutoMigrateText, Converter={StaticResource InvertIsStringNullOrWhitespaceConverter}}\"\n                Margin=\"8\"\n                Severity=\"Success\"\n                Title=\"{Binding AutoMigrateText, FallbackValue=5 Packages will be automatically migrated to the new format}\" />\n            <ui:InfoBar\n                IsClosable=\"False\"\n                IsOpen=\"{Binding NeedsMoveMigrateText, Converter={StaticResource InvertIsStringNullOrWhitespaceConverter}}\"\n                Margin=\"8\"\n                Severity=\"Informational\"\n                Title=\"{Binding NeedsMoveMigrateText, FallbackValue=2 Packages are not relative to the Data Directory and will be moved this may take a few minutes}\" />\n        </StackPanel>\n\n        <!--  Progress for moving  -->\n        <StackPanel\n            Grid.Row=\"1\"\n            HorizontalAlignment=\"Center\"\n            Margin=\"8\"\n            Orientation=\"Horizontal\"\n            Visibility=\"{Binding MigrateCommand.IsRunning, Converter={StaticResource BooleanToVisibilityConverter}}\">\n            <ui:ProgressRing\n                Height=\"32\"\n                IsEnabled=\"{Binding MigrateCommand.IsRunning, Converter={StaticResource BooleanToVisibilityConverter}}\"\n                IsIndeterminate=\"True\"\n                Width=\"32\" />\n            <TextBlock\n                FontSize=\"16\"\n                Margin=\"8\"\n                Text=\"{Binding MigrateProgressText, FallbackValue=Migrating 1 of 2 Packages}\"\n                VerticalAlignment=\"Center\" />\n        </StackPanel>\n        \n        <StackPanel Grid.Row=\"2\" \n                    Orientation=\"Horizontal\" \n                    HorizontalAlignment=\"Center\">\n            \n            <ui:Button\n                Appearance=\"Secondary\"\n                Click=\"NoThanks_OnClick\"\n                Content=\"No Thanks\"\n                FontSize=\"16\"\n                HorizontalAlignment=\"Center\"\n                Margin=\"8\"\n                Visibility=\"{Binding CanShowNoThanksButton, Converter={StaticResource BooleanToVisibilityConverter}}\"\n                IsEnabled=\"{Binding IsMigrating, Converter={StaticResource BoolNegationConverter}}\"/>\n            <ui:Button Appearance=\"Info\"\n                       Click=\"Back_OnClick\"\n                       Content=\"Select Directory\"\n                       FontSize=\"16\"\n                       HorizontalAlignment=\"Center\"\n                       Margin=\"8\"\n                       Visibility=\"{Binding HasFreeSpaceError, Converter={StaticResource BooleanToVisibilityConverter}}\"\n                       IsEnabled=\"{Binding IsMigrating, Converter={StaticResource BoolNegationConverter}}\"/>\n            <ui:Button\n                Appearance=\"Success\"\n                Click=\"ContinueButton_OnClick\"\n                Content=\"Continue\"\n                FontSize=\"16\"\n                HorizontalAlignment=\"Center\"\n                Margin=\"8\"\n                Visibility=\"{Binding HasFreeSpaceError, Converter={StaticResource InvertAndVisibilitate}}\"\n                IsEnabled=\"{Binding IsMigrating, Converter={StaticResource BoolNegationConverter}}\"/>\n        </StackPanel>\n    </Grid>\n\n</ui:ContentDialog>\n"
  },
  {
    "path": "StabilityMatrix/DataDirectoryMigrationDialog.xaml.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing AsyncAwaitBestPractices;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls.ContentDialogControl;\n\nnamespace StabilityMatrix;\n\npublic partial class DataDirectoryMigrationDialog : ContentDialog\n{\n    private readonly DataDirectoryMigrationViewModel viewModel;\n\n    public DataDirectoryMigrationDialog(IContentDialogService dialogService, DataDirectoryMigrationViewModel viewModel) : base(\n        dialogService.GetContentPresenter())\n    {\n        this.viewModel = viewModel;\n        InitializeComponent();\n        DataContext = viewModel;\n    }\n\n    private async void ContinueButton_OnClick(object sender, RoutedEventArgs e)\n    {\n        await viewModel.MigrateCommand.ExecuteAsync(null);\n        Hide(ContentDialogResult.Primary);\n    }\n\n    private void DataDirectoryMigrationDialog_OnLoaded(object sender, RoutedEventArgs e)\n    {\n        viewModel.OnLoaded().SafeFireAndForget();\n    }\n\n    private async void NoThanks_OnClick(object sender, RoutedEventArgs e)\n    {\n        try\n        {\n            await Task.Run(viewModel.CleanupOldInstall);\n        }\n        catch (Exception)\n        {\n            // ignored\n        }\n\n        Hide(ContentDialogResult.Primary);\n    }\n\n    private void Back_OnClick(object sender, RoutedEventArgs e)\n    {\n        Hide(ContentDialogResult.Secondary);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/DesignData/MockCheckpointBrowserViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.ComponentModel;\nusing System.Windows.Data;\nusing System.Windows.Media.Imaging;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.ViewModels;\n\nnamespace StabilityMatrix.DesignData;\n\n[DesignOnly(true)]\npublic class MockCheckpointBrowserViewModel : CheckpointBrowserViewModel\n{\n    public MockCheckpointBrowserViewModel() : base(null!, null!, null!, null!, null!, null!)\n    {\n        ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>\n        {\n            new(null!, null!, null!, null!, null!,\n                fixedImage: new BitmapImage(new Uri(\"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/01bb1e4b-f88b-441a-b2bf-c1ec0bd91727/width=450/00015-1007989405.jpeg\")))\n            {\n                CivitModel = new CivitModel\n                {\n                    Name = \"Ghibli Background\",\n                    Type = CivitModelType.Checkpoint,\n                    ModelVersions = new List<CivitModelVersion>\n                    {\n                        new()\n                        {\n                            Name = \"v5.0\",\n                            Files = new List<CivitFile>\n                            {\n                                new()\n                                {\n                                    Name = \"File\",\n                                    SizeKb = 844110,\n                                }\n                            }\n                        }\n                    }\n                },\n            },\n            new(null!, null!, null!, null!, null!,\n                fixedImage: new BitmapImage(new Uri(\"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/fcb56b61-2cd6-40f4-a75d-e9448c0822d6/width=1152/29933-2136606019-masterpiece.jpeg\")))\n            {\n                CivitModel = new CivitModel\n                {\n                    Name = \"Indigo Furry mix\",\n                    Type = CivitModelType.Checkpoint,\n                    Nsfw = false,\n                    ModelVersions = new List<CivitModelVersion>\n                    {\n                        new()\n                        {\n                            Name = \"v45_hybrid\",\n                            Files = new List<CivitFile>\n                            {\n                                new()\n                                {\n                                    Name = \"Pruned Model fp16\",\n                                    SizeKb = 1990000,\n                                }\n                            }\n                        }\n                    }\n                },\n            }\n        };\n        ModelCardsView = CollectionViewSource.GetDefaultView(ModelCards);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/DesignData/MockCheckpointFolder.cs",
    "content": "﻿using StabilityMatrix.Models;\n\nnamespace StabilityMatrix.DesignData;\n\npublic class MockCheckpointFolder : CheckpointFolder\n{\n    public MockCheckpointFolder() : base(null!, null!, null!, null!, useCategoryVisibility: false)\n    {\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/DesignData/MockCheckpointManagerViewModel.cs",
    "content": "﻿using System.ComponentModel;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.ViewModels;\n\nnamespace StabilityMatrix.DesignData;\n\n[DesignOnly(true)]\npublic class MockCheckpointManagerViewModel : CheckpointManagerViewModel\n{\n    public MockCheckpointManagerViewModel() : base(null!, null!, null!, null!, null!)\n    {\n        CheckpointFolders = new()\n        {\n            new MockCheckpointFolder\n            {\n                Title = \"Stable Diffusion\",\n                CheckpointFiles = new()\n                {\n                    new(null!)\n                    {\n                        Title = \"Stable Diffusion v1.5\",\n                        FilePath = \"v1-5-pruned-emaonly.safetensors\",\n                    },\n                    new(null!)\n                    {\n                        Title = \"Scenery Mix\",\n                        FilePath = \"scenery-mix.pt\",\n                    },\n                    new(null!)\n                    {\n                        Title = \"Some Model\",\n                        FilePath = \"exr-v3.safetensors\",\n                        ConnectedModel = new()\n                        {\n                            ModelName = \"Example Realistic\",\n                            VersionName = \"v3.0-Inpainting\",\n                            ModelDescription = \"Example Description\",\n                            BaseModel = \"SD 1.5\",\n                            FileMetadata = new()\n                            {\n                                Fp = CivitModelFpType.fp32,\n                            }\n                        }\n                    },\n                    new(null!)\n                    {\n                        Title = \"Painting e12\",\n                        FilePath = \"painting-e12.pt\",\n                        ConnectedModel = new()\n                        {\n                            ModelName = \"Long Name Model (Stuff / More Content)\",\n                            VersionName = \"v42-Advanced-Hybrid\",\n                            ModelDescription = \"Example Description\",\n                            BaseModel = \"SD 2.0\",\n                            FileMetadata = new()\n                            {\n                                Fp = CivitModelFpType.fp16,\n                            }\n                        }\n                    },\n                }\n            },\n            new MockCheckpointFolder\n            {\n                Title = \"Lora\",\n                IsCurrentDragTarget = true,\n                CheckpointFiles = new()\n                {\n                    new(null!)\n                    {\n                        Title = \"Detail Tweaker LoRA\",\n                        FilePath = \"add_detail.safetensors\",\n                    },\n                    new(null!)\n                    {\n                        Title = \"Armor Suit LoRa\",\n                        FilePath = \"ArmorSuit_v1.safetensors\",\n                    },\n                }\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/DesignData/MockFirstLaunchSetupViewModel.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.ViewModels;\n\nnamespace StabilityMatrix.DesignData;\n\npublic class MockFirstLaunchSetupViewModel : FirstLaunchSetupViewModel\n{\n    public MockFirstLaunchSetupViewModel()\n    {\n        GpuInfoText = \"Nvidia Geforce RTX 3090 (24.0 GiB)\";\n        CheckHardwareBadge.State = ProgressState.Working;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/DesignData/MockLaunchViewModel.cs",
    "content": "﻿using System.ComponentModel;\nusing StabilityMatrix.ViewModels;\n\nnamespace StabilityMatrix.DesignData;\n\n[DesignOnly(true)]\npublic class MockLaunchViewModel : LaunchViewModel\n{\n    public MockLaunchViewModel() : base(\n        null!, null!, null!, \n        null!, null!, null!,\n        null!, null!, null!)\n    {\n        InstalledPackages = new()\n        {\n            new()\n            {\n                DisplayName = \"Mock Package\",\n                PackageName = \"mock-package\",\n                PackageVersion = \"1.0.0\",\n                DisplayVersion = \"1.0.0\",\n                InstalledBranch = \"main\",\n                LibraryPath = @\"Packages\\mock-package\",\n            }\n        };\n        SelectedPackage = InstalledPackages[0];\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/DesignData/MockModelVersionDialogViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.ViewModels;\n\nnamespace StabilityMatrix.DesignData;\n\npublic class MockModelVersionDialogViewModel : SelectModelVersionDialogViewModel\n{\n    public MockModelVersionDialogViewModel() : base(new CivitModel {Name = \"Indigo furry mix\", ModelVersions = new List<CivitModelVersion>\n    {\n        new()\n        {\n            Name = \"v45_hybrid\",\n            Images = new List<CivitImage>\n            {\n                new ()\n                {\n                    Url = \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/fcb56b61-2cd6-40f4-a75d-e9448c0822d6/width=450/29933-2136606019-masterpiece,%20high%20quality,%20absurd%20res,%20digital%20painting%20_(artwork_),%20solo,%20(kemono_1.4),%20male%20anthro%20dragon,%20blue%20body,%20ice,%20(wa.jpeg\"\n                }\n            },\n            Files = new List<CivitFile>\n            {\n                new()\n                {\n                    Name = \"indigoFurryMix_v45Hybrid.safetensors\",\n                    Metadata = new CivitFileMetadata\n                    {\n                        Fp = CivitModelFpType.fp16,\n                        Size = CivitModelSize.pruned\n                    },\n                    SizeKb = 1312645\n                },\n                new()\n                {\n                    Name = \"indigoFurryMix_v45Hybrid.safetensors\",\n                    Metadata = new CivitFileMetadata\n                    {\n                        Fp = CivitModelFpType.fp32,\n                        Size = CivitModelSize.pruned\n                    },\n                    SizeKb = 5972132\n                }\n            }\n        }\n    }}, new SettingsManager {Settings = { ModelBrowserNsfwEnabled = true }})\n    {\n\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ExceptionWindow.xaml",
    "content": "﻿<ui:FluentWindow\n    ExtendsContentIntoTitleBar=\"True\"\n    Height=\"500\"\n    Loaded=\"ExceptionWindow_OnLoaded\"\n    ResizeMode=\"NoResize\"\n    Title=\"Unexpected Application Error\"\n    Width=\"800\"\n    WindowStartupLocation=\"CenterOwner\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.ExceptionWindow\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Converters\">\n\n    <Window.DataContext>\n        <viewModels:ExceptionWindowViewModel />\n    </Window.DataContext>\n\n    <Window.Resources>\n        <converters:NullToVisibilityConverter x:Key=\"NullToVisibilityConverter\" />\n        <Style TargetType=\"TextBlock\" x:Key=\"Label\">\n            <Setter Property=\"FontWeight\" Value=\"Bold\" />\n            <Setter Property=\"Margin\" Value=\"0,8,0,4\" />\n            <Setter Property=\"TextWrapping\" Value=\"Wrap\" />\n        </Style>\n        <Style TargetType=\"TextBlock\" x:Key=\"Content\">\n            <Setter Property=\"Margin\" Value=\"0,4,0,8\" />\n            <Setter Property=\"TextWrapping\" Value=\"Wrap\" />\n        </Style>\n    </Window.Resources>\n\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"auto\" />\n            <RowDefinition Height=\"*\" />\n            <RowDefinition Height=\"auto\" />\n        </Grid.RowDefinitions>\n\n        <ui:TitleBar Background=\"DarkOrange\">\n            <ui:TitleBar.Header>\n                <TextBlock Margin=\"16,8\" Text=\"Unexpected error\" />\n            </ui:TitleBar.Header>\n        </ui:TitleBar>\n\n        <Grid Grid.Row=\"1\">\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"auto\" />\n                <RowDefinition Height=\"*\" />\n                <RowDefinition Height=\"auto\" />\n            </Grid.RowDefinitions>\n            <!--  Info  -->\n            <TextBlock\n                FontSize=\"14\"\n                Grid.Row=\"0\"\n                Margin=\"16\"\n                Text=\"An unexpected error occured.\"\n                TextWrapping=\"Wrap\"\n                VerticalAlignment=\"Top\" />\n\n            <!--  Exception details  -->\n            <Expander Grid.Row=\"1\"\n                      VerticalAlignment=\"Top\"\n                      Header=\"{Binding ExceptionType, Mode=OneWay, TargetNullValue=-, FallbackValue=Unknown Exception}\"\n                      IsExpanded=\"True\"\n                      Padding=\"12,12,12,12\"\n                      Margin=\"16,0,16,0\">\n                <ScrollViewer VerticalScrollBarVisibility=\"Auto\" MaxHeight=\"250\">\n                    <StackPanel>\n                        <!--  Exception Message  -->\n                        <TextBlock Style=\"{StaticResource Label}\" Text=\"Details\" />\n                        <TextBlock Style=\"{StaticResource Content}\"\n                                   Text=\"{Binding Exception.Message, Mode=OneWay, TargetNullValue=-}\" />\n                        <!--  Callstack  -->\n                        <TextBlock Style=\"{StaticResource Label}\" Text=\"Callstack\" />\n                        <TextBlock Style=\"{StaticResource Content}\"\n                                   Text=\"{Binding Exception.StackTrace, Mode=OneWay, TargetNullValue=-}\" />\n                        <!--  Inner exception  -->\n                        <TextBlock Style=\"{StaticResource Label}\" \n                                   Visibility=\"{Binding Exception.InnerException, Converter={StaticResource NullToVisibilityConverter}}\"\n                                   Text=\"Inner exception\" />\n                        <TextBlock Style=\"{StaticResource Content}\"\n                                      Visibility=\"{Binding Exception.InnerException, Converter={StaticResource NullToVisibilityConverter}}\"\n                                   Text=\"{Binding Exception.InnerException, Mode=OneWay, TargetNullValue=-}\" />\n                    </StackPanel>\n                </ScrollViewer>\n            </Expander>\n            <!--  Close Button  -->\n            <Button\n                Content=\"Exit Application\"\n                Grid.Row=\"2\"\n                HorizontalAlignment=\"Right\"\n                Margin=\"16\"\n                Padding=\"8\">\n                <i:Interaction.Triggers>\n                    <i:EventTrigger EventName=\"Click\">\n                        <i:CallMethodAction MethodName=\"Close\"\n                                            TargetObject=\"{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}\" />\n                    </i:EventTrigger>\n                </i:Interaction.Triggers>\n            </Button>\n        </Grid>\n    </Grid>\n\n</ui:FluentWindow>\n"
  },
  {
    "path": "StabilityMatrix/ExceptionWindow.xaml.cs",
    "content": "﻿using System.Windows;\nusing Wpf.Ui.Controls.Window;\n\nnamespace StabilityMatrix;\n\npublic partial class ExceptionWindow : FluentWindow\n{\n    public ExceptionWindow()\n    {\n        InitializeComponent();\n    }\n\n    private void ExceptionWindow_OnLoaded(object sender, RoutedEventArgs e)\n    {\n        System.Media.SystemSounds.Hand.Play();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/FirstLaunchSetupWindow.xaml",
    "content": "﻿<ui:FluentWindow\n    ExtendsContentIntoTitleBar=\"True\"\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Height=\"450\"\n    Loaded=\"FirstLaunchSetupWindow_OnLoaded\"\n    ResizeMode=\"NoResize\"\n    Title=\"FirstLaunchSetupWindow\"\n    Width=\"700\"\n    WindowBackdropType=\"Mica\"\n    WindowCornerPreference=\"Round\"\n    WindowStartupLocation=\"CenterScreen\"\n    d:DataContext=\"{d:DesignInstance Type=designData:MockFirstLaunchSetupViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.FirstLaunchSetupWindow\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.DesignData\"\n    xmlns:local=\"clr-namespace:StabilityMatrix\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n\n    <ui:FluentWindow.Resources>\n        <converters:StringNullOrEmptyToVisibilityConverter x:Key=\"StringToVisibilityConverter\" />\n    </ui:FluentWindow.Resources>\n\n    <Grid>\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"0.8*\" />\n            <ColumnDefinition Width=\"*\" />\n        </Grid.ColumnDefinitions>\n        <StackPanel\n            Margin=\"64\"\n            Orientation=\"Vertical\"\n            VerticalAlignment=\"Center\">\n            <ui:Image Source=\"pack://application:,,,/Assets/Icon.png\" Width=\"128\" />\n        </StackPanel>\n        <Grid Grid.Column=\"1\">\n            <Grid Margin=\"0,16,32,32\">\n                <Grid.RowDefinitions>\n                    <RowDefinition Height=\"*\" />\n                    <RowDefinition Height=\"0.6*\" />\n                </Grid.RowDefinitions>\n                <!--  Texts  -->\n                <StackPanel Margin=\"32,64,0,0\">\n                    <TextBlock FontSize=\"24\" Text=\"Let's get started\" />\n                    <!--  Loading icon and text  -->\n                    <Grid Margin=\"0,8,0,0\">\n                        <Grid.ColumnDefinitions>\n                            <ColumnDefinition Width=\"0.15*\" />\n                            <ColumnDefinition Width=\"*\" />\n                        </Grid.ColumnDefinitions>\n                        <controls:RefreshBadge DataContext=\"{Binding CheckHardwareBadge}\" VerticalAlignment=\"Center\" />\n                        <TextBlock\n                            FontSize=\"14\"\n                            Grid.Column=\"1\"\n                            Margin=\"8,8\"\n                            Text=\"{Binding CheckHardwareBadge.CurrentToolTip}\"\n                            TextWrapping=\"WrapWithOverflow\"\n                            VerticalAlignment=\"Center\" />\n                    </Grid>\n                    <BulletDecorator Margin=\"0,8\" Visibility=\"{Binding GpuInfoText, Converter={StaticResource StringToVisibilityConverter}}\">\n                        <BulletDecorator.Bullet>\n                            <Ellipse\n                                Fill=\"{DynamicResource TextFillColorPrimaryBrush}\"\n                                Height=\"8\"\n                                Width=\"8\" />\n                        </BulletDecorator.Bullet>\n                        <TextBlock\n                            FontSize=\"16\"\n                            Margin=\"8,0\"\n                            Text=\"{Binding GpuInfoText, FallbackValue=Nvidia RTX 4070}\"\n                            TextWrapping=\"Wrap\" />\n                    </BulletDecorator>\n\n                </StackPanel>\n                <!--  Checkbox  -->\n                <Grid Grid.Row=\"1\" HorizontalAlignment=\"Center\">\n                    <Grid.ColumnDefinitions>\n                        <ColumnDefinition Width=\"*\" />\n                        <ColumnDefinition Width=\"Auto\" />\n                    </Grid.ColumnDefinitions>\n                    <CheckBox\n                        Grid.Column=\"1\"\n                        HorizontalAlignment=\"Left\"\n                        IsChecked=\"{Binding EulaAccepted, Mode=TwoWay}\"\n                        Margin=\"16,0,0,0\">\n                        <TextBlock FontSize=\"13\" VerticalAlignment=\"Center\">\n                            <Run>I have read and agree to the</Run>\n                            <Hyperlink Command=\"{Binding OpenLicenseLinkCommand}\" NavigateUri=\"https://lykos.ai/matrix/license\">License Agreement.</Hyperlink>\n                        </TextBlock>\n                    </CheckBox>\n                </Grid>\n            </Grid>\n            <!--  Buttons  -->\n            <UniformGrid\n                HorizontalAlignment=\"Right\"\n                Margin=\"32,8\"\n                MaxWidth=\"170\"\n                VerticalAlignment=\"Bottom\">\n                <ui:Button\n                    Appearance=\"Danger\"\n                    Click=\"QuitButton_OnClick\"\n                    Content=\"Quit\" />\n                <ui:Button\n                    Appearance=\"Info\"\n                    Click=\"ContinueButton_OnClick\"\n                    Content=\"Continue\"\n                    IsEnabled=\"{Binding EulaAccepted, Mode=OneWay}\" />\n            </UniformGrid>\n\n        </Grid>\n    </Grid>\n</ui:FluentWindow>\n"
  },
  {
    "path": "StabilityMatrix/FirstLaunchSetupWindow.xaml.cs",
    "content": "﻿using System.Windows;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Controls.Window;\n\nnamespace StabilityMatrix;\n\npublic sealed partial class FirstLaunchSetupWindow : FluentWindow\n{\n    public FirstLaunchSetupWindow(FirstLaunchSetupViewModel viewModel)\n    {\n        InitializeComponent();\n        DataContext = viewModel;\n    }\n\n    private void QuitButton_OnClick(object sender, RoutedEventArgs e)\n    {\n        DialogResult = false;\n        Close();\n    }\n\n    private void ContinueButton_OnClick(object sender, RoutedEventArgs e)\n    {\n        DialogResult = true;\n        Hide();\n    }\n\n    private void FirstLaunchSetupWindow_OnLoaded(object sender, RoutedEventArgs e)\n    {\n        (DataContext as FirstLaunchSetupViewModel)!.OnLoaded();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Helper/AsyncDispatchTimer.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.Threading.Tasks;\nusing System.Windows.Threading;\n\nnamespace StabilityMatrix.Helper;\n\npublic class AsyncDispatcherTimer : DispatcherTimer\n{\n    public AsyncDispatcherTimer()\n    {\n        Tick += AsyncDispatcherTimer_Tick;\n    }\n\n    private async void AsyncDispatcherTimer_Tick(object? sender, EventArgs e)\n    {\n        if (TickTask == null)\n        {\n            // no task to run\n            return;\n        }\n\n        if (IsRunning && !IsReentrant)\n        {\n            // previous task hasn't completed\n            return;\n        }\n\n        try\n        {\n            IsRunning = true;\n            await TickTask.Invoke();\n        }\n        catch (Exception)\n        {\n            Debug.WriteLine(\"Task Failed\");\n            throw;\n        }\n        finally\n        {\n            // allow it to run again\n            IsRunning = false;\n        }\n    }\n\n    public bool IsReentrant { get; set; }\n    public bool IsRunning { get; private set; }\n\n    public Func<Task>? TickTask { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix/Helper/DialogFactory.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing System.Windows.Controls;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Services;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls;\nusing Wpf.Ui.Controls.ContentDialogControl;\nusing TextBox = Wpf.Ui.Controls.TextBox;\n\nnamespace StabilityMatrix.Helper;\n\npublic class DialogFactory : IDialogFactory\n{\n    private readonly IContentDialogService contentDialogService;\n    private readonly LaunchOptionsDialogViewModel launchOptionsDialogViewModel;\n    private readonly InstallerViewModel installerViewModel;\n    private readonly OneClickInstallViewModel oneClickInstallViewModel;\n    private readonly SelectInstallLocationsViewModel selectInstallLocationsViewModel;\n    private readonly DataDirectoryMigrationViewModel dataDirectoryMigrationViewModel;\n    private readonly WebLoginViewModel webLoginViewModel;\n    private readonly InstallerWindowDialogService installerWindowDialogService;\n    private readonly ISettingsManager settingsManager;\n\n    public DialogFactory(IContentDialogService contentDialogService,\n        LaunchOptionsDialogViewModel launchOptionsDialogViewModel,\n        ISettingsManager settingsManager, InstallerViewModel installerViewModel,\n        OneClickInstallViewModel oneClickInstallViewModel,\n        SelectInstallLocationsViewModel selectInstallLocationsViewModel,\n        DataDirectoryMigrationViewModel dataDirectoryMigrationViewModel,\n        InstallerWindowDialogService installerWindowDialogService,\n        WebLoginViewModel webLoginViewModel)\n    {\n        this.contentDialogService = contentDialogService;\n        this.launchOptionsDialogViewModel = launchOptionsDialogViewModel;\n        this.installerViewModel = installerViewModel;\n        this.oneClickInstallViewModel = oneClickInstallViewModel;\n        this.selectInstallLocationsViewModel = selectInstallLocationsViewModel;\n        this.dataDirectoryMigrationViewModel = dataDirectoryMigrationViewModel;\n        this.webLoginViewModel = webLoginViewModel;\n        this.installerWindowDialogService = installerWindowDialogService;\n        this.settingsManager = settingsManager;\n    }\n\n    public LaunchOptionsDialog CreateLaunchOptionsDialog(IEnumerable<LaunchOptionDefinition> definitions, InstalledPackage installedPackage)\n    {\n        // Load user settings\n        var userLaunchArgs = settingsManager.GetLaunchArgs(installedPackage.Id);\n        launchOptionsDialogViewModel.Initialize(definitions, userLaunchArgs);\n        return new LaunchOptionsDialog(contentDialogService, launchOptionsDialogViewModel);\n    }\n\n    /// <summary>\n    /// Creates a dialog that allows the user to enter text for each field name.\n    /// Return a list of strings that correspond to the field names.\n    /// If cancel is pressed, return null.\n    /// <param name=\"fields\">List of (fieldName, placeholder)</param>\n    /// </summary>\n    public async Task<List<string>?> ShowTextEntryDialog(string title,\n        IEnumerable<(string, string)> fields,\n        string closeButtonText = \"Cancel\",\n        string saveButtonText = \"Save\")\n    {\n        var dialog = contentDialogService.CreateDialog();\n        dialog.Title = title;\n        dialog.PrimaryButtonAppearance = ControlAppearance.Primary;\n        dialog.CloseButtonText = closeButtonText;\n        dialog.PrimaryButtonText = saveButtonText;\n        dialog.IsPrimaryButtonEnabled = true;\n\n        var textBoxes = new List<TextBox>();\n        var stackPanel = new StackPanel();\n        dialog.Content = stackPanel;\n\n        foreach (var (fieldName, fieldPlaceholder) in fields)\n        {\n            var textBox = new TextBox\n            {\n                PlaceholderText = fieldPlaceholder,\n                PlaceholderEnabled = true,\n                MinWidth = 200,\n            };\n            textBoxes.Add(textBox);\n            stackPanel.Children.Add(new Card\n            {\n                Content = new StackPanel\n                {\n                    Children =\n                    {\n                        new TextBlock\n                        {\n                            Text = fieldName,\n                            Margin = new Thickness(0, 0, 0, 4)\n                        },\n                        textBox\n                    }\n                },\n                Margin = new Thickness(16)\n            });\n        }\n\n        var result = await dialog.ShowAsync();\n        if (result == ContentDialogResult.Primary)\n        {\n            return textBoxes.Select(x => x.Text).ToList();\n        }\n        return null;\n    }\n\n    /// <summary>\n    /// Creates and shows a confirmation dialog.\n    /// Return true if the user clicks the primary button.\n    /// </summary>\n    public async Task<bool> ShowConfirmationDialog(string title, string message, string closeButtonText = \"Cancel\", string primaryButtonText = \"Confirm\")\n    {\n        var dialog = contentDialogService.CreateDialog();\n        dialog.Title = title;\n        dialog.PrimaryButtonAppearance = ControlAppearance.Primary;\n        dialog.CloseButtonText = closeButtonText;\n        dialog.PrimaryButtonText = primaryButtonText;\n        dialog.IsPrimaryButtonEnabled = true;\n        dialog.Content = new TextBlock\n        {\n            Text = message,\n            Margin = new Thickness(16)\n        };\n        var result = await dialog.ShowAsync();\n        return result == ContentDialogResult.Primary;\n    }\n\n    public OneClickInstallDialog CreateOneClickInstallDialog()\n    {\n        return new OneClickInstallDialog(contentDialogService, oneClickInstallViewModel);\n    }\n\n    public InstallerWindow CreateInstallerWindow()\n    {\n        return new InstallerWindow(installerViewModel, installerWindowDialogService);\n    }\n\n    public SelectInstallLocationsDialog CreateInstallLocationsDialog()\n    {\n        var dialog = new SelectInstallLocationsDialog(contentDialogService,\n            selectInstallLocationsViewModel)\n        {\n            IsPrimaryButtonEnabled = false,\n            IsSecondaryButtonEnabled = false,\n            IsFooterVisible = false\n        };\n        return dialog;\n    }\n\n    public DataDirectoryMigrationDialog CreateDataDirectoryMigrationDialog()\n    {\n        var dialog = new DataDirectoryMigrationDialog(contentDialogService,\n            dataDirectoryMigrationViewModel)\n        {\n            IsPrimaryButtonEnabled = false,\n            IsSecondaryButtonEnabled = false,\n            IsFooterVisible = false\n        };\n        return dialog;\n    }\n\n    public WebLoginDialog CreateWebLoginDialog()\n    {\n        return new WebLoginDialog(contentDialogService, webLoginViewModel)\n        {\n            CloseButtonText = \"Cancel\",\n        };\n    }\n    \n    public SelectModelVersionDialog CreateSelectModelVersionDialog(CivitModel model)\n    {\n        return new SelectModelVersionDialog(contentDialogService,\n            new SelectModelVersionDialogViewModel(model, settingsManager))\n        {\n            IsPrimaryButtonEnabled = false,\n            IsSecondaryButtonEnabled = false,\n            IsFooterVisible = false\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Helper/IDialogFactory.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\n\nnamespace StabilityMatrix.Helper;\n\npublic interface IDialogFactory\n{\n    LaunchOptionsDialog CreateLaunchOptionsDialog(IEnumerable<LaunchOptionDefinition> definitions, InstalledPackage installedPackage);\n\n    /// <summary>\n    /// Creates a dialog that allows the user to enter text for each field name.\n    /// Return a list of strings that correspond to the field names.\n    /// If cancel is pressed, return null.\n    /// <param name=\"fields\">List of (fieldName, placeholder)</param>\n    /// </summary>\n    Task<List<string>?> ShowTextEntryDialog(string title, \n        IEnumerable<(string, string)> fields, \n        string closeButtonText = \"Cancel\",\n        string saveButtonText = \"Save\");\n\n    /// <summary>\n    /// Creates and shows a confirmation dialog.\n    /// Return true if the user clicks the primary button.\n    /// </summary>\n    Task<bool> ShowConfirmationDialog(string title, string message, string closeButtonText = \"Cancel\", string primaryButtonText = \"Confirm\");\n\n    OneClickInstallDialog CreateOneClickInstallDialog();\n    InstallerWindow CreateInstallerWindow();\n    SelectInstallLocationsDialog CreateInstallLocationsDialog();\n    DataDirectoryMigrationDialog CreateDataDirectoryMigrationDialog();\n    WebLoginDialog CreateWebLoginDialog();\n    SelectModelVersionDialog CreateSelectModelVersionDialog(CivitModel model);\n}\n"
  },
  {
    "path": "StabilityMatrix/Helper/ISnackbarService.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Core.Models;\nusing Wpf.Ui.Common;\nusing Wpf.Ui.Controls;\n\nnamespace StabilityMatrix.Helper;\n\npublic interface ISnackbarService\n{\n    /// <summary>\n    /// Default timeout for snackbar messages.\n    /// </summary>\n    public TimeSpan DefaultTimeout { get; }\n    \n    /// <summary>\n    /// Shows a generic error snackbar with the given message.\n    /// </summary>\n    /// <param name=\"title\">The title to show in the snackbar.</param>\n    /// <param name=\"message\">The message to show</param>\n    /// <param name=\"appearance\">The appearance of the snackbar.</param>\n    /// <param name=\"icon\">The icon to show in the snackbar.</param>\n    /// <param name=\"timeout\">Snackbar timeout, defaults to class DefaultTimeout</param>\n    public Task ShowSnackbarAsync(\n        string title, \n        string message,\n        ControlAppearance appearance = ControlAppearance.Danger, \n        SymbolRegular icon = SymbolRegular.ErrorCircle24,\n        TimeSpan? timeout = null);\n\n    /// <summary>\n    /// Attempt to run the given task, showing a generic error snackbar if it fails.\n    /// </summary>\n    /// <param name=\"task\">The task to run.</param>\n    /// <param name=\"title\">The title to show in the snackbar.</param>\n    /// <param name=\"message\">The message to show, default to exception.Message</param>\n    /// <param name=\"appearance\">The appearance of the snackbar.</param>\n    /// <param name=\"icon\">The icon to show in the snackbar.</param>\n    /// <param name=\"timeout\">Snackbar timeout, defaults to class DefaultTimeout</param>\n    public Task<TaskResult<T>> TryAsync<T>(\n        Task<T> task,\n        string title = \"Error\",\n        string? message = null,\n        ControlAppearance appearance = ControlAppearance.Danger,\n        SymbolRegular icon = SymbolRegular.ErrorCircle24,\n        TimeSpan? timeout = null);\n\n    /// <summary>\n    /// Attempt to run the given void task, showing a generic error snackbar if it fails.\n    /// Return a TaskResult with true if the task succeeded, false if it failed.\n    /// </summary>\n    /// <param name=\"task\">The task to run.</param>\n    /// <param name=\"title\">The title to show in the snackbar.</param>\n    /// <param name=\"message\">The message to show, default to exception.Message</param>\n    /// <param name=\"appearance\">The appearance of the snackbar.</param>\n    /// <param name=\"icon\">The icon to show in the snackbar.</param>\n    /// <param name=\"timeout\">Snackbar timeout, defaults to class DefaultTimeout</param>\n    Task<TaskResult<bool>> TryAsync(\n        Task task,\n        string title = \"Error\",\n        string? message = null,\n        ControlAppearance appearance = ControlAppearance.Danger,\n        SymbolRegular icon = SymbolRegular.ErrorCircle24,\n        TimeSpan? timeout = null);\n}\n"
  },
  {
    "path": "StabilityMatrix/Helper/ScreenExtensions.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Runtime.InteropServices;\nusing System.Runtime.Versioning;\n\nnamespace StabilityMatrix.Helper;\n\npublic static class ScreenExtensions\n{\n    public const string User32 = \"user32.dll\";\n    public const string Shcore = \"Shcore.dll\";\n\n    public static void GetDpi(this System.Windows.Forms.Screen screen, DpiType dpiType,\n        out uint dpiX, out uint dpiY)\n    {\n        var pnt = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);\n        var mon = MonitorFromPoint(pnt, 2 /*MONITOR_DEFAULTTONEAREST*/);\n        GetDpiForMonitor(mon, dpiType, out dpiX, out dpiY);\n    }\n\n    public static double GetScalingForPoint(System.Drawing.Point aPoint)\n    {\n        var mon = MonitorFromPoint(aPoint, 2 /*MONITOR_DEFAULTTONEAREST*/);\n        uint dpiX, dpiY;\n        GetDpiForMonitor(mon, DpiType.Effective, out dpiX, out dpiY);\n        return (double) dpiX / 96.0;\n    }\n\n\n    [DllImport(User32)]\n    private static extern IntPtr MonitorFromPoint([In] System.Drawing.Point pt, [In] uint dwFlags);\n\n\n    [DllImport(Shcore)]\n    private static extern IntPtr GetDpiForMonitor([In] IntPtr hmonitor, [In] DpiType dpiType,\n        [Out] out uint dpiX, [Out] out uint dpiY);\n\n    [DllImport(User32, CharSet = CharSet.Auto)]\n    [ResourceExposure(ResourceScope.None)]\n    [return: MarshalAs(UnmanagedType.Bool)]\n    private static extern bool GetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);\n\n    [DllImport(User32, CharSet = CharSet.Auto, SetLastError = true)]\n    [ResourceExposure(ResourceScope.None)]\n    [return: MarshalAs(UnmanagedType.Bool)]\n    private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WindowPlacement lpwndpl);\n\n    public enum DpiType\n    {\n        Effective = 0,\n        Angular = 1,\n        Raw = 2,\n    }\n\n    public static WindowPlacement GetPlacement(IntPtr hWnd)\n    {\n        var placement = new WindowPlacement();\n        placement.length = Marshal.SizeOf(placement);\n        GetWindowPlacement(hWnd, ref placement);\n        return placement;\n    }\n\n    public static bool SetPlacement(IntPtr hWnd, WindowPlacement aPlacement)\n    {\n        var erg = SetWindowPlacement(hWnd, ref aPlacement);\n        return erg;\n    }\n\n    [StructLayout(LayoutKind.Sequential)]\n    public struct Pointstruct\n    {\n        public int x;\n        public int y;\n\n        public Pointstruct(int x, int y)\n        {\n            this.x = x;\n            this.y = y;\n        }\n    }\n\n    [StructLayout(LayoutKind.Sequential)]\n    public struct Rect\n    {\n        public int left;\n        public int top;\n        public int right;\n        public int bottom;\n\n        public Rect(int left, int top, int right, int bottom)\n        {\n            this.left = left;\n            this.top = top;\n            this.right = right;\n            this.bottom = bottom;\n        }\n\n        public Rect(System.Windows.Rect r)\n        {\n            left = (int) r.Left;\n            top = (int) r.Top;\n            right = (int) r.Right;\n            bottom = (int) r.Bottom;\n        }\n\n        public static Rect FromXywh(int x, int y, int width, int height)\n        {\n            return new Rect(x, y, x + width, y + height);\n        }\n\n        public System.Windows.Size Size => new(right - left, bottom - top);\n    }\n\n    [StructLayout(LayoutKind.Sequential)]\n    public struct WindowPlacement\n    {\n        public int length;\n        public uint flags;\n        public uint showCmd;\n        public Pointstruct ptMinPosition;\n        public Pointstruct ptMaxPosition;\n        public Rect rcNormalPosition;\n\n        public override string ToString()\n        {\n            var structBytes = RawSerialize(this);\n            return Convert.ToBase64String(structBytes);\n        }\n\n        public void ReadFromBase64String(string aB64)\n        {\n            var b64 = Convert.FromBase64String(aB64);\n            var newWp = ReadStruct<WindowPlacement>(b64, 0);\n            length = newWp.length;\n            flags = newWp.flags;\n            showCmd = newWp.showCmd;\n            ptMinPosition.x = newWp.ptMinPosition.x;\n            ptMinPosition.y = newWp.ptMinPosition.y;\n            ptMaxPosition.x = newWp.ptMaxPosition.x;\n            ptMaxPosition.y = newWp.ptMaxPosition.y;\n            rcNormalPosition.left = newWp.rcNormalPosition.left;\n            rcNormalPosition.top = newWp.rcNormalPosition.top;\n            rcNormalPosition.right = newWp.rcNormalPosition.right;\n            rcNormalPosition.bottom = newWp.rcNormalPosition.bottom;\n        }\n\n        public static T ReadStruct<T>(byte[] aSrcBuffer, int aOffset)\n        {\n            var buffer = new byte[Marshal.SizeOf(typeof(T))];\n            Buffer.BlockCopy(aSrcBuffer, aOffset, buffer, 0, Marshal.SizeOf(typeof(T)));\n            var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);\n            var temp = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));\n            handle.Free();\n            return temp;\n        }\n\n        public static T ReadStruct<T>(Stream fs)\n        {\n            var buffer = new byte[Marshal.SizeOf(typeof(T))];\n            fs.Read(buffer, 0, Marshal.SizeOf(typeof(T)));\n            var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);\n            var temp = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));\n            handle.Free();\n            return temp;\n        }\n\n        public static byte[] RawSerialize(object anything)\n        {\n            var rawsize = Marshal.SizeOf(anything);\n            var rawdata = new byte[rawsize];\n            var handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned);\n            Marshal.StructureToPtr(anything, handle.AddrOfPinnedObject(), false);\n            handle.Free();\n            return rawdata;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Helper/SnackbarService.cs",
    "content": "﻿using System.Threading.Tasks;\nusing System;\nusing AsyncAwaitBestPractices;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Common;\nusing Wpf.Ui.Controls;\nusing Wpf.Ui.Controls.IconElements;\n\nnamespace StabilityMatrix.Helper;\n\n/// <summary>\n/// Generic recoverable error handler using content dialogs.\n/// </summary>\npublic class SnackbarService : ISnackbarService\n{\n    private readonly Wpf.Ui.Contracts.ISnackbarService snackbarService;\n    private readonly SnackbarViewModel snackbarViewModel;\n    public TimeSpan DefaultTimeout { get; } = TimeSpan.FromSeconds(5);\n\n    public SnackbarService(Wpf.Ui.Contracts.ISnackbarService snackbarService, SnackbarViewModel snackbarViewModel)\n    {\n        this.snackbarService = snackbarService;\n        this.snackbarViewModel = snackbarViewModel;\n    }\n    \n    /// <inheritdoc />\n    public async Task ShowSnackbarAsync(\n        string title, \n        string message,\n        ControlAppearance appearance = ControlAppearance.Danger, \n        SymbolRegular icon = SymbolRegular.ErrorCircle24,\n        TimeSpan? timeout = null)\n    {\n        snackbarService.Timeout = (int) (timeout ?? DefaultTimeout).TotalMilliseconds;\n        await snackbarService.ShowAsync(title, message, new SymbolIcon(icon), appearance);\n    }\n    \n    /// <inheritdoc />\n    public async Task<TaskResult<T>> TryAsync<T>(\n        Task<T> task,\n        string title = \"Error\",\n        string? message = null,\n        ControlAppearance appearance = ControlAppearance.Danger,\n        SymbolRegular icon = SymbolRegular.ErrorCircle24,\n        TimeSpan? timeout = null)\n    {\n        try\n        {\n            return new TaskResult<T>(await task);\n        }\n        catch (Exception e)\n        {\n            ShowSnackbarAsync(title, message ?? e.Message, appearance, icon, timeout).SafeFireAndForget();\n            return TaskResult<T>.FromException(e);\n        }\n    }\n    \n    /// <inheritdoc />\n    public async Task<TaskResult<bool>> TryAsync(\n        Task task,\n        string title = \"Error\",\n        string? message = null,\n        ControlAppearance appearance = ControlAppearance.Danger,\n        SymbolRegular icon = SymbolRegular.ErrorCircle24,\n        TimeSpan? timeout = null)\n    {\n        try\n        {\n            await task;\n            return new TaskResult<bool>(true);\n        }\n        catch (Exception e)\n        {\n            ShowSnackbarAsync(title, message ?? e.Message, appearance, icon, timeout).SafeFireAndForget();\n            return new TaskResult<bool>(false, e);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/InstallerWindow.xaml",
    "content": "<ui:FluentWindow\n    ExtendsContentIntoTitleBar=\"True\"\n    Height=\"700\"\n    Icon=\"pack://application:,,,/Assets/Icon.ico\"\n    Loaded=\"InstallPage_OnLoaded\"\n    Title=\"Stability Matrix - Installer\"\n    Width=\"1100\"\n    WindowBackdropType=\"Mica\"\n    WindowStartupLocation=\"CenterOwner\"\n    d:DataContext=\"{d:DesignInstance Type=viewModels:InstallerViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"700\"\n    d:DesignWidth=\"800\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.InstallerWindow\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:octokit=\"clr-namespace:Octokit;assembly=Octokit\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:xaml=\"clr-namespace:MdXaml;assembly=MdXaml\"\n    xmlns:packages=\"clr-namespace:StabilityMatrix.Core.Models.Packages;assembly=StabilityMatrix.Core\">\n\n    <ui:FluentWindow.Resources>\n        <converters:ValueConverterGroup x:Key=\"InvertAndVisibilitate\">\n            <converters:BoolNegationConverter />\n            <BooleanToVisibilityConverter />\n        </converters:ValueConverterGroup>\n\n        <converters:BoolNegationConverter x:Key=\"BoolNegationConverter\" />\n        <BooleanToVisibilityConverter x:Key=\"BoolToVisConverter\" />\n        <converters:UriToBitmapConverter x:Key=\"UriToBitmapConverter\" />\n\n        <xaml:Markdown\n            AssetPathRoot=\"{x:Static system:Environment.CurrentDirectory}\"\n            DocumentStyle=\"{StaticResource DocumentStyle}\"\n            Heading1Style=\"{StaticResource H1Style}\"\n            Heading2Style=\"{StaticResource H2Style}\"\n            Heading3Style=\"{StaticResource H3Style}\"\n            Heading4Style=\"{StaticResource H4Style}\"\n            ImageStyle=\"{StaticResource ImageStyle}\"\n            LinkStyle=\"{StaticResource LinkStyle}\"\n            SeparatorStyle=\"{StaticResource SeparatorStyle}\"\n            x:Key=\"Markdown\" />\n        <xaml:TextToFlowDocumentConverter Markdown=\"{StaticResource Markdown}\" x:Key=\"TextToFlowDocumentConverter\" />\n    </ui:FluentWindow.Resources>\n\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n\n        <ui:TitleBar Background=\"{ui:ThemeResource ApplicationBackgroundBrush}\">\n            <ui:TitleBar.Header>\n                <TextBlock Margin=\"16,8\" Text=\"Stability Matrix\" />\n            </ui:TitleBar.Header>\n        </ui:TitleBar>\n\n        <StackPanel\n            Grid.Row=\"1\"\n            Margin=\"16,8,16,0\"\n            Orientation=\"Vertical\"\n            Visibility=\"{Binding ProgressBarVisibility, FallbackValue=Visible}\">\n\n            <TextBlock\n                HorizontalAlignment=\"Center\"\n                Padding=\"8\"\n                Text=\"{Binding ProgressText, FallbackValue=Installing...}\" />\n            <ProgressBar\n                IsIndeterminate=\"{Binding IsIndeterminate, FallbackValue=False}\"\n                Maximum=\"100\"\n                Width=\"500\"\n                controls:ProgressBarSmoother.SmoothValue=\"{Binding ProgressValue, FallbackValue=20}\" />\n            <TextBlock\n                FontSize=\"10\"\n                HorizontalAlignment=\"Center\"\n                Padding=\"4\"\n                Text=\"{Binding SecondaryProgressText, FallbackValue=Installing...}\"\n                TextWrapping=\"Wrap\" />\n        </StackPanel>\n\n        <Grid Grid.Row=\"2\" HorizontalAlignment=\"Left\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"Auto\" />\n                <ColumnDefinition Width=\"Auto\" />\n                <ColumnDefinition Width=\"*\" />\n            </Grid.ColumnDefinitions>\n            <ListView\n                ItemsSource=\"{Binding AvailablePackages}\"\n                Margin=\"16\"\n                SelectedItem=\"{Binding SelectedPackage, Mode=TwoWay}\">\n\n                <ListView.Style>\n                    <Style TargetType=\"ListView\">\n                        <Setter Property=\"Background\" Value=\"#191919\" />\n                    </Style>\n                </ListView.Style>\n\n                <ListView.Template>\n                    <ControlTemplate>\n                        <Border\n                            BorderBrush=\"{ui:ThemeResource KeyboardFocusBorderColorBrush}\"\n                            BorderThickness=\"1\"\n                            CornerRadius=\"5\">\n                            <ItemsPresenter />\n                        </Border>\n                    </ControlTemplate>\n                </ListView.Template>\n\n                <ListView.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type packages:BasePackage}\">\n                        <StackPanel Margin=\"8\" VerticalAlignment=\"Top\">\n                            <TextBlock Margin=\"0,5,0,5\" Text=\"{Binding DisplayName}\" />\n                            <TextBlock Margin=\"0,0,0,5\" Text=\"{Binding ByAuthor}\" />\n                        </StackPanel>\n                    </DataTemplate>\n                </ListView.ItemTemplate>\n            </ListView>\n\n            <StackPanel\n                Grid.Column=\"1\"\n                Margin=\"16,16,0,16\"\n                Orientation=\"Vertical\">\n                <TextBlock\n                    FontSize=\"24\"\n                    FontWeight=\"Bold\"\n                    Text=\"{Binding SelectedPackage.DisplayName, FallbackValue=Stable Diffusion Web UI}\" />\n                <TextBlock FontSize=\"12\" Text=\"{Binding SelectedPackage.ByAuthor, FallbackValue=by Automatic111}\" />\n\n                <ui:Button\n                    Appearance=\"Transparent\"\n                    BorderBrush=\"Transparent\"\n                    Command=\"{Binding ShowPreviewCommand}\"\n                    Content=\"UI Preview\"\n                    Margin=\"0,8,0,0\">\n                    <ui:Button.Style>\n                        <Style BasedOn=\"{StaticResource {x:Type ui:Button}}\" TargetType=\"{x:Type ui:Button}\">\n                            <Setter Property=\"Foreground\">\n                                <Setter.Value>\n                                    <SolidColorBrush Color=\"{DynamicResource SystemAccentColorSecondary}\" />\n                                </Setter.Value>\n                            </Setter>\n                        </Style>\n                    </ui:Button.Style>\n                </ui:Button>\n\n                <ui:Hyperlink Margin=\"0,0,0,8\" NavigateUri=\"{Binding SelectedPackage.GithubUrl}\">\n                    <TextBlock TextWrapping=\"Wrap\">\n                        <Run Text=\"GitHub Page:\" />\n                        <Run Text=\"{Binding SelectedPackage.GithubUrl, Mode=OneWay}\" TextDecorations=\"Underline\" />\n                    </TextBlock>\n                </ui:Hyperlink>\n\n                <StackPanel Orientation=\"Horizontal\">\n                    <ToggleButton\n                        Content=\"Releases\"\n                        IsChecked=\"{Binding IsReleaseMode}\"\n                        IsEnabled=\"{Binding IsReleaseModeEnabled, FallbackValue=true}\" />\n                    <ToggleButton\n                        Content=\"Branches\"\n                        IsChecked=\"{Binding IsReleaseMode, Converter={StaticResource BoolNegationConverter}}\"\n                        Margin=\"8,0,0,0\" />\n                </StackPanel>\n\n                <StackPanel Margin=\"0,16,0,0\" Orientation=\"Horizontal\">\n                    <StackPanel Orientation=\"Vertical\">\n                        <Label Content=\"{Binding ReleaseLabelText, FallbackValue=Version}\" />\n                        <ComboBox\n                            ItemsSource=\"{Binding AvailableVersions}\"\n                            MinWidth=\"200\"\n                            SelectedItem=\"{Binding SelectedVersion}\">\n                            <ComboBox.ItemTemplate>\n                                <DataTemplate DataType=\"{x:Type models:PackageVersion}\">\n                                    <StackPanel Margin=\"8,0,0,0\" VerticalAlignment=\"Top\">\n                                        <TextBlock\n                                            Margin=\"0,4,0,4\"\n                                            Name=\"NameTextBlock\"\n                                            Text=\"{Binding TagName}\" />\n                                    </StackPanel>\n                                </DataTemplate>\n                            </ComboBox.ItemTemplate>\n                        </ComboBox>\n                    </StackPanel>\n\n                    <StackPanel\n                        Margin=\"8,0,0,0\"\n                        Orientation=\"Vertical\"\n                        Visibility=\"{Binding IsReleaseMode, Converter={StaticResource InvertAndVisibilitate}}\">\n                        <Label Content=\"Commit\" />\n                        <ComboBox\n                            ItemsSource=\"{Binding AvailableCommits}\"\n                            MinWidth=\"100\"\n                            SelectedItem=\"{Binding SelectedCommit}\">\n                            <ComboBox.ItemTemplate>\n                                <DataTemplate DataType=\"{x:Type octokit:GitHubCommit}\">\n                                    <StackPanel Margin=\"8,0,0,0\" VerticalAlignment=\"Top\">\n                                        <TextBlock\n                                            Margin=\"0,4,0,4\"\n                                            Name=\"NameTextBlock\"\n                                            Text=\"{Binding Sha}\" />\n                                    </StackPanel>\n                                </DataTemplate>\n                            </ComboBox.ItemTemplate>\n                        </ComboBox>\n                    </StackPanel>\n                </StackPanel>\n\n                <Label Content=\"Display Name\" Margin=\"0,16,0,0\" />\n                <StackPanel Orientation=\"Horizontal\" Visibility=\"{Binding ShowDuplicateWarning, Converter={StaticResource BoolToVisConverter}}\">\n                    <ui:SymbolIcon\n                        Foreground=\"{ui:ThemeResource SystemFillColorCriticalBrush}\"\n                        Margin=\"8\"\n                        Symbol=\"ErrorCircle24\" />\n                    <TextBlock\n                        Foreground=\"{ui:ThemeResource SystemFillColorCriticalBrush}\"\n                        Margin=\"0,8,8,8\"\n                        TextAlignment=\"Left\"\n                        TextWrapping=\"Wrap\">\n                        <Run Text=\"An installation with this name already exists.\" />\n                        <LineBreak />\n                        <Run Text=\"Please choose a different name or select a different Install Location.\" />\n                    </TextBlock>\n                </StackPanel>\n                <ui:TextBox Margin=\"0,0,0,8\" Text=\"{Binding InstallName, UpdateSourceTrigger=PropertyChanged}\" />\n\n                <StackPanel Orientation=\"Horizontal\">\n                    <ui:Button\n                        Appearance=\"Primary\"\n                        Command=\"{Binding InstallCommand}\"\n                        Content=\"{Binding InstallButtonText, FallbackValue=Install}\"\n                        Height=\"50\"\n                        IsEnabled=\"{Binding ShowDuplicateWarning, Converter={StaticResource BoolNegationConverter}}\"\n                        Margin=\"0,16,0,0\"\n                        VerticalAlignment=\"Top\"\n                        Width=\"100\" />\n                    <ui:ProgressRing\n                        Height=\"25\"\n                        IsIndeterminate=\"True\"\n                        Margin=\"8,16,0,0\"\n                        VerticalAlignment=\"Center\"\n                        Visibility=\"{Binding ProgressBarVisibility}\"\n                        Width=\"25\" />\n                    <TextBlock\n                        Margin=\"8,16,0,0\"\n                        Text=\"Installing...\"\n                        VerticalAlignment=\"Center\"\n                        Visibility=\"{Binding ProgressBarVisibility}\" />\n                </StackPanel>\n            </StackPanel>\n\n            <FlowDocumentScrollViewer\n                Document=\"{Binding ReleaseNotes, Converter={StaticResource TextToFlowDocumentConverter}}\"\n                Grid.Column=\"2\"\n                HorizontalAlignment=\"Stretch\"\n                Margin=\"16\"\n                VerticalAlignment=\"Stretch\" />\n            <ContentPresenter\n                Grid.Column=\"0\"\n                Grid.ColumnSpan=\"3\"\n                x:Name=\"InstallerContentDialog\" />\n        </Grid>\n    </Grid>\n</ui:FluentWindow>\n"
  },
  {
    "path": "StabilityMatrix/InstallerWindow.xaml.cs",
    "content": "﻿using System.Windows;\nusing StabilityMatrix.Services;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Controls.Window;\n\n// To learn more about WinUI, the WinUI project structure,\n// and more about our project templates, see: http://aka.ms/winui-project-info.\n\nnamespace StabilityMatrix\n{\n    /// <summary>\n    /// An empty page that can be used on its own or navigated to within a Frame.\n    /// </summary>\n    public sealed partial class InstallerWindow : FluentWindow\n    {\n        private readonly InstallerViewModel viewModel;\n        private readonly InstallerWindowDialogService dialogService;\n\n        public InstallerWindow(InstallerViewModel viewModel, InstallerWindowDialogService dialogService)\n        {\n            this.viewModel = viewModel;\n            this.dialogService = dialogService;\n            InitializeComponent();\n            DataContext = viewModel;\n            viewModel.PackageInstalled += (_, _) => Close();\n            \n            dialogService.SetContentPresenter(InstallerContentDialog);\n        }\n\n        private async void InstallPage_OnLoaded(object sender, RoutedEventArgs e)\n        {\n            await viewModel.OnLoaded();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Interactions/EventTriggerWithoutPropogation.cs",
    "content": "﻿using System.Windows;\n\nnamespace StabilityMatrix.Interactions;\n\npublic class EventTriggerWithoutPropagation : Microsoft.Xaml.Behaviors.EventTrigger\n{\n    protected override void OnEvent(System.EventArgs eventArgs)\n    {\n        // Prevent event from propagating to parent\n        if (eventArgs is RoutedEventArgs routedEventArgs)\n        {\n            routedEventArgs.Handled = true;\n        }\n        base.OnEvent(eventArgs);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/LaunchOptionsDialog.xaml",
    "content": "﻿<ui:ContentDialog\n    CloseButtonText=\"Close\"\n    DialogHeight=\"616\"\n    DialogWidth=\"760\"\n    Loaded=\"LaunchOptionsDialog_OnLoaded\"\n    d:DataContext=\"{d:DesignInstance Type=viewModels:LaunchOptionsDialogViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"616\"\n    d:DesignWidth=\"760\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.LaunchOptionsDialog\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:StabilityMatrix\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n\n    <ui:ContentDialog.Resources>\n        <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n        <Style BasedOn=\"{StaticResource {x:Type ui:ContentDialog}}\" TargetType=\"{x:Type local:LaunchOptionsDialog}\" />\n\n        <converters:LaunchOptionConverter x:Key=\"LaunchOptionConverter\" />\n        <converters:LaunchOptionIntDoubleConverter x:Key=\"LaunchOptionIntDoubleConverter\" />\n        <converters:ValueConverterGroup x:Key=\"LaunchOptionIntToStringConverter\">\n            <converters:LaunchOptionConverter />\n            <converters:LaunchOptionIntDoubleConverter />\n        </converters:ValueConverterGroup>\n\n        <!--  Bool type card (checkboxes)  -->\n        <DataTemplate DataType=\"{x:Type models:LaunchOptionCard}\" x:Key=\"LaunchOptionCardBooleanDataTemplate\">\n            <ui:Card Margin=\"16,8,8,8\">\n                <StackPanel\n                    HorizontalAlignment=\"Left\"\n                    Margin=\"8,0,8,0\"\n                    Orientation=\"Vertical\">\n                    <TextBlock\n                        FontSize=\"16\"\n                        FontWeight=\"Bold\"\n                        Margin=\"0,8\"\n                        Text=\"{Binding Title}\"\n                        TextWrapping=\"Wrap\" />\n                    <StackPanel Orientation=\"Horizontal\">\n                        <ItemsControl ItemsSource=\"{Binding Options}\">\n                            <ItemsControl.ItemTemplate>\n                                <DataTemplate>\n                                    <CheckBox Content=\"{Binding Name}\" IsChecked=\"{Binding OptionValue, Converter={StaticResource LaunchOptionConverter}}\" />\n                                </DataTemplate>\n                            </ItemsControl.ItemTemplate>\n                        </ItemsControl>\n                    </StackPanel>\n                </StackPanel>\n            </ui:Card>\n        </DataTemplate>\n\n        <!--  String type card (textboxes)  -->\n        <DataTemplate DataType=\"{x:Type models:LaunchOptionCard}\" x:Key=\"LaunchOptionCardStringDataTemplate\">\n            <ui:Card Margin=\"16,8,8,8\">\n                <StackPanel\n                    HorizontalAlignment=\"Stretch\"\n                    Margin=\"8,0,8,0\"\n                    Orientation=\"Vertical\">\n                    <TextBlock\n                        FontSize=\"16\"\n                        FontWeight=\"Bold\"\n                        Margin=\"0,8\"\n                        Text=\"{Binding Title}\"\n                        TextWrapping=\"Wrap\" />\n                    <ItemsControl ItemsSource=\"{Binding Options}\">\n                        <ItemsControl.ItemTemplate>\n                            <DataTemplate>\n                                <StackPanel HorizontalAlignment=\"Stretch\" Orientation=\"Vertical\">\n                                    <Label Content=\"{Binding Name}\" />\n                                    <ui:TextBox\n                                        HorizontalAlignment=\"Stretch\"\n                                        Margin=\"8\"\n                                        PlaceholderEnabled=\"{Binding HasDefaultValue}\"\n                                        PlaceholderText=\"{Binding DefaultValue}\"\n                                        Text=\"{Binding OptionValue, Converter={StaticResource LaunchOptionConverter}}\"\n                                        VerticalAlignment=\"Stretch\" />\n                                </StackPanel>\n                            </DataTemplate>\n                        </ItemsControl.ItemTemplate>\n                    </ItemsControl>\n                </StackPanel>\n            </ui:Card>\n        </DataTemplate>\n\n        <!--  Int type card (textboxes)  -->\n        <DataTemplate DataType=\"{x:Type models:LaunchOptionCard}\" x:Key=\"LaunchOptionCardIntDataTemplate\">\n            <ui:Card Margin=\"16,8,8,8\">\n                <StackPanel\n                    HorizontalAlignment=\"Stretch\"\n                    Margin=\"8,0,8,0\"\n                    Orientation=\"Vertical\">\n                    <TextBlock\n                        FontSize=\"16\"\n                        FontWeight=\"Bold\"\n                        Margin=\"0,8\"\n                        Text=\"{Binding Title}\"\n                        TextWrapping=\"Wrap\" />\n                    <ItemsControl ItemsSource=\"{Binding Options}\">\n                        <ItemsControl.ItemTemplate>\n                            <DataTemplate>\n                                <StackPanel HorizontalAlignment=\"Stretch\" Orientation=\"Vertical\">\n                                    <Label Content=\"{Binding Name}\" />\n                                    <ui:NumberBox\n                                        HorizontalAlignment=\"Stretch\"\n                                        Margin=\"8\"\n                                        PlaceholderText=\"{Binding DefaultValue, Mode=OneWay, Converter={StaticResource LaunchOptionConverter}}\"\n                                        SpinButtonPlacementMode=\"Compact\"\n                                        ValidationMode=\"Disabled\"\n                                        Value=\"{Binding OptionValue, Converter={StaticResource LaunchOptionIntDoubleConverter}, Mode=TwoWay}\"\n                                        VerticalAlignment=\"Stretch\" />\n                                </StackPanel>\n                            </DataTemplate>\n                        </ItemsControl.ItemTemplate>\n                    </ItemsControl>\n                </StackPanel>\n            </ui:Card>\n        </DataTemplate>\n\n        <DataTemplate DataType=\"{x:Type models:LaunchOptionCard}\" x:Key=\"LaunchOptionCardDataTemplate\">\n            <ContentControl Content=\"{Binding}\">\n                <ContentControl.Style>\n                    <Style TargetType=\"{x:Type ContentControl}\">\n                        <Setter Property=\"ContentTemplate\" Value=\"{StaticResource LaunchOptionCardBooleanDataTemplate}\" />\n                        <Style.Triggers>\n                            <DataTrigger Binding=\"{Binding Type}\" Value=\"string\">\n                                <Setter Property=\"ContentTemplate\" Value=\"{StaticResource LaunchOptionCardStringDataTemplate}\" />\n                            </DataTrigger>\n                            <DataTrigger Binding=\"{Binding Type}\" Value=\"int\">\n                                <Setter Property=\"ContentTemplate\" Value=\"{StaticResource LaunchOptionCardIntDataTemplate}\" />\n                            </DataTrigger>\n                        </Style.Triggers>\n                    </Style>\n                </ContentControl.Style>\n            </ContentControl>\n        </DataTemplate>\n    </ui:ContentDialog.Resources>\n\n    <ui:ContentDialog.Title>\n        <StackPanel\n            HorizontalAlignment=\"Stretch\"\n            Margin=\"8,0,8,0\"\n            Orientation=\"Vertical\">\n            <!--  Title  -->\n            <TextBlock\n                FontSize=\"24\"\n                FontWeight=\"Bold\"\n                Margin=\"8,0,8,8\"\n                Text=\"{Binding Title}\"\n                TextWrapping=\"Wrap\" />\n            <!--  Search box  -->\n            <ui:TextBox\n                HorizontalAlignment=\"Stretch\"\n                IconPlacement=\"Right\"\n                Margin=\"0,8,0,0\"\n                MaxWidth=\"300\"\n                PlaceholderEnabled=\"True\"\n                PlaceholderText=\"Search...\"\n                Text=\"{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n                VerticalAlignment=\"Top\"\n                Visibility=\"{Binding IsSearchBoxEnabled, Converter={StaticResource BooleanToVisibilityConverter}}\"\n                x:Name=\"SearchBox\">\n                <ui:TextBox.Icon>\n                    <ui:SymbolIcon Symbol=\"Search28\" />\n                </ui:TextBox.Icon>\n            </ui:TextBox>\n        </StackPanel>\n    </ui:ContentDialog.Title>\n\n    <!--  Option Cards  -->\n    <ItemsControl\n        HorizontalAlignment=\"Stretch\"\n        HorizontalContentAlignment=\"Center\"\n        ItemTemplate=\"{StaticResource LaunchOptionCardDataTemplate}\"\n        ItemsSource=\"{Binding FilteredCards}\"\n        Margin=\"16,0,0,0\"\n        MaxWidth=\"400\"\n        MinWidth=\"300\" />\n\n</ui:ContentDialog>\n"
  },
  {
    "path": "StabilityMatrix/LaunchOptionsDialog.xaml.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Windows;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls.ContentDialogControl;\n\nnamespace StabilityMatrix;\n\npublic partial class LaunchOptionsDialog : ContentDialog\n{\n    private readonly LaunchOptionsDialogViewModel viewModel;\n\n    public List<LaunchOption> AsLaunchArgs() => viewModel.AsLaunchArgs();\n\n    public LaunchOptionsDialog(IContentDialogService dialogService, LaunchOptionsDialogViewModel viewModel) : base(\n        dialogService.GetContentPresenter())\n    {\n        this.viewModel = viewModel;\n        InitializeComponent();\n        DataContext = viewModel;\n    }\n\n    private void LaunchOptionsDialog_OnLoaded(object sender, RoutedEventArgs e)\n    {\n        viewModel.OnLoad(); \n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/LaunchPage.xaml",
    "content": "﻿<Page\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Loaded=\"LaunchPage_OnLoaded\"\n    Title=\"LaunchPage\"\n    d:DataContext=\"{d:DesignInstance Type=designViewModels:MockLaunchViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"700\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.LaunchPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:designViewModels=\"clr-namespace:StabilityMatrix.DesignData\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:terminal=\"clr-namespace:Simple.Wpf.Terminal;assembly=Simple.Wpf.Terminal\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n\n    <Page.Resources>\n        <BooleanToVisibilityConverter x:Key=\"BoolToVisConverter\" />\n\n        <!--  Manually copied style since can't inherit: https://github.com/lepoco/wpfui/blob/main/src/Wpf.Ui/Styles/Controls/ComboBox.xaml  -->\n        <ControlTemplate TargetType=\"{x:Type ComboBoxItem}\" x:Key=\"SelectPackageDropDownStyle\">\n            <Grid>\n                <Border\n                    CornerRadius=\"{TemplateBinding Border.CornerRadius}\"\n                    Margin=\"{TemplateBinding Margin}\"\n                    Name=\"ContentBorder\"\n                    Padding=\"0\"\n                    SnapsToDevicePixels=\"True\"\n                    VerticalAlignment=\"Stretch\">\n                    <Grid>\n                        <Rectangle\n                            Height=\"16\"\n                            HorizontalAlignment=\"Left\"\n                            Margin=\"0\"\n                            RadiusX=\"2\"\n                            RadiusY=\"2\"\n                            VerticalAlignment=\"Center\"\n                            Visibility=\"Collapsed\"\n                            Width=\"3\"\n                            x:Name=\"ActiveRectangle\">\n                            <Rectangle.Fill>\n                                <SolidColorBrush Color=\"{DynamicResource SystemAccentColorSecondary}\" />\n                            </Rectangle.Fill>\n                        </Rectangle>\n                        <Grid>\n                            <Grid.ColumnDefinitions>\n                                <ColumnDefinition Width=\"*\" />\n                                <ColumnDefinition Width=\"Auto\" />\n                            </Grid.ColumnDefinitions>\n                            <!--  Setup grid rows  -->\n                            <StackPanel\n                                Grid.Column=\"0\"\n                                Margin=\"10,0,0,0\"\n                                VerticalAlignment=\"Top\"\n                                d:DataContext=\"{d:DesignInstance Type=models:InstalledPackage,\n                                                                 IsDesignTimeCreatable=True}\">\n                                <TextBlock Margin=\"0,5,0,5\" Text=\"{Binding DisplayName, Mode=OneWay}\" />\n                                <TextBlock Margin=\"0,0,0,5\" Text=\"{Binding FullPath, Mode=OneWay}\" />\n                            </StackPanel>\n                        </Grid>\n                    </Grid>\n                </Border>\n            </Grid>\n            <ControlTemplate.Triggers>\n                <Trigger Property=\"IsHighlighted\" Value=\"True\">\n                    <Setter Property=\"Background\" TargetName=\"ContentBorder\">\n                        <Setter.Value>\n                            <SolidColorBrush Color=\"{DynamicResource ControlFillColorSecondary}\" />\n                        </Setter.Value>\n                    </Setter>\n                </Trigger>\n                <Trigger Property=\"IsSelected\" Value=\"True\">\n                    <Setter Property=\"Visibility\" TargetName=\"ActiveRectangle\" Value=\"Visible\" />\n                </Trigger>\n                <Trigger Property=\"IsEnabled\" Value=\"False\">\n                    <Setter Property=\"Foreground\">\n                        <Setter.Value>\n                            <SolidColorBrush Color=\"{DynamicResource TextFillColorTertiary}\" />\n                        </Setter.Value>\n                    </Setter>\n                </Trigger>\n            </ControlTemplate.Triggers>\n        </ControlTemplate>\n    </Page.Resources>\n\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n            <RowDefinition Height=\"Auto\" />\n        </Grid.RowDefinitions>\n\n        <Grid>\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"Auto\" />\n                <ColumnDefinition Width=\"*\" />\n            </Grid.ColumnDefinitions>\n            <Grid>\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition Width=\"0.8*\" />\n                    <ColumnDefinition Width=\"0.2*\" />\n                </Grid.ColumnDefinitions>\n                <ui:Flyout\n                    Background=\"{DynamicResource SystemAccentColorPrimaryBrush}\"\n                    FontSize=\"18\"\n                    Grid.Column=\"0\"\n                    Grid.Row=\"0\"\n                    IsOpen=\"{Binding IsLaunchTeachingTipsOpen, Mode=TwoWay}\"\n                    Margin=\"24,8,0,0\"\n                    Placement=\"Bottom\">\n                    <StackPanel Orientation=\"Horizontal\">\n                        <Grid HorizontalAlignment=\"Left\">\n                            <ui:SymbolIcon Symbol=\"ArrowCurveUpLeft20\" />\n                        </Grid>\n                        <TextBlock\n                            HorizontalAlignment=\"Left\"\n                            Text=\"Click Launch to get started!\"\n                            TextWrapping=\"WrapWithOverflow\"\n                            Width=\"280\" />\n                    </StackPanel>\n\n                </ui:Flyout>\n                <ui:Button\n                    Appearance=\"Success\"\n                    Command=\"{Binding LaunchCommand}\"\n                    Content=\"Launch\"\n                    Grid.Column=\"0\"\n                    Grid.Row=\"0\"\n                    HorizontalAlignment=\"Left\"\n                    Margin=\"24,8,0,0\"\n                    VerticalAlignment=\"Stretch\"\n                    Visibility=\"{Binding LaunchButtonVisibility, FallbackValue=Visible}\"\n                    Width=\"72\"\n                    x:Name=\"LaunchButton\" />\n                <ui:Button\n                    Appearance=\"Danger\"\n                    Command=\"{Binding StopCommand}\"\n                    Content=\"Stop\"\n                    Grid.Column=\"0\"\n                    Grid.Row=\"0\"\n                    HorizontalAlignment=\"Left\"\n                    Margin=\"24,8,0,0\"\n                    VerticalAlignment=\"Stretch\"\n                    Visibility=\"{Binding StopButtonVisibility, FallbackValue=Hidden}\"\n                    Width=\"72\"\n                    x:Name=\"StopButton\" />\n                <ui:Button\n                    Command=\"{Binding ConfigCommand}\"\n                    FontSize=\"16\"\n                    Grid.Column=\"1\"\n                    Grid.Row=\"0\"\n                    HorizontalAlignment=\"Left\"\n                    Margin=\"8,8,0,0\"\n                    VerticalAlignment=\"Stretch\"\n                    Width=\"48\"\n                    x:Name=\"ConfigButton\">\n                    <ui:Button.Icon>\n                        <ui:SymbolIcon Symbol=\"Settings32\" />\n                    </ui:Button.Icon>\n                </ui:Button>\n            </Grid>\n            <ComboBox\n                Grid.Column=\"1\"\n                Grid.Row=\"0\"\n                HorizontalAlignment=\"Stretch\"\n                ItemsSource=\"{Binding InstalledPackages}\"\n                Margin=\"8,8,24,0\"\n                SelectedValue=\"{Binding SelectedPackage}\"\n                VerticalAlignment=\"Top\"\n                x:Name=\"SelectPackageComboBox\">\n                <ComboBox.ItemContainerStyle>\n                    <Style TargetType=\"ComboBoxItem\">\n                        <Setter Property=\"Template\" Value=\"{StaticResource SelectPackageDropDownStyle}\" />\n                    </Style>\n                </ComboBox.ItemContainerStyle>\n\n                <ComboBox.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type models:InstalledPackage}\">\n                        <StackPanel Margin=\"10,0,0,0\" VerticalAlignment=\"Top\">\n                            <TextBlock\n                                Margin=\"0,5,0,5\"\n                                Name=\"NameTextBlock\"\n                                Text=\"{Binding DisplayName, Mode=OneWay}\" />\n                        </StackPanel>\n                    </DataTemplate>\n                </ComboBox.ItemTemplate>\n            </ComboBox>\n        </Grid>\n        <terminal:Terminal Grid.Row=\"1\"\n                           Margin=\"24,8,26,10\"\n                           Background=\"{DynamicResource ControlFillColorDisabledBrush}\"\n                           IsReadOnly=\"True\"\n                           AutoScroll=\"True\"\n                           FontFamily=\"Consolas\"\n                           ItemHeight=\"22\"\n                           Foreground=\"White\"\n                           BorderThickness=\"0\"\n                           VerticalScrollBarVisibility=\"Auto\"\n                           ItemsSource=\"{Binding ConsoleHistory, Mode=OneWay}\"/>\n        \n        <ui:Button\n            Appearance=\"Success\"\n            Command=\"{Binding LaunchWebUiCommand}\"\n            Content=\"Open Web UI\"\n            FontSize=\"12\"\n            Grid.ColumnSpan=\"2\"\n            Grid.Row=\"2\"\n            HorizontalAlignment=\"Stretch\"\n            Margin=\"24,0,24,8\"\n            Visibility=\"{Binding ShowWebUiButton, Converter={StaticResource BoolToVisConverter}}\" />\n    </Grid>\n</Page>\n"
  },
  {
    "path": "StabilityMatrix/LaunchPage.xaml.cs",
    "content": "﻿using System.Windows;\nusing System.Windows.Controls;\nusing StabilityMatrix.ViewModels;\n\nnamespace StabilityMatrix;\n\npublic sealed partial class LaunchPage : Page\n{\n    private readonly LaunchViewModel viewModel;\n\n    public LaunchPage(LaunchViewModel viewModel)\n    {\n        this.viewModel = viewModel;\n        InitializeComponent();\n        DataContext = viewModel;\n    }\n\n    private void LaunchPage_OnLoaded(object sender, RoutedEventArgs e)\n    {\n        viewModel.OnLoaded();\n        SelectPackageComboBox.ItemsSource = viewModel.InstalledPackages;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/MainWindow.xaml",
    "content": "<ui:FluentWindow\n    Closing=\"MainWindow_OnClosing\"\n    Closed=\"MainWindow_OnClosed\"\n    ExtendsContentIntoTitleBar=\"True\"\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Icon=\"pack://application:,,,/Assets/Icon.ico\"\n    Loaded=\"MainWindow_OnLoaded\"\n    Title=\"Stability Matrix\"\n    UseLayoutRounding=\"True\"\n    WindowBackdropType=\"Mica\"\n    WindowStartupLocation=\"CenterScreen\"\n    d:DataContext=\"{d:DesignInstance Type=viewModels:MainWindowViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"750\"\n    d:DesignWidth=\"1100\"\n    MinHeight=\"400\"\n    MinWidth=\"600\"\n    Height=\"700\"\n    Width=\"1100\"\n    mc:Ignorable=\"d\"\n    ResizeMode=\"CanResize\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.MainWindow\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:StabilityMatrix\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n\n    <ui:FluentWindow.Resources>\n        <BooleanToVisibilityConverter x:Key=\"BooleanToVisibilityConverter\" />\n        <!--  Discord Icon  -->\n        <DrawingImage x:Key=\"DiscordIconWhite\">\n            <DrawingImage.Drawing>\n                <DrawingGroup ClipGeometry=\"M0,0 V96.36 H127.14 V0 H0 Z\">\n                    <DrawingGroup Opacity=\"1\">\n                        <DrawingGroup Opacity=\"1\">\n                            <DrawingGroup Opacity=\"1\">\n                                <GeometryDrawing Brush=\"{DynamicResource TextFillColorPrimaryBrush}\" Geometry=\"F1 M127.14,96.36z M0,0z M107.7,8.07A105.15,105.15,0,0,0,81.47,0A72.06,72.06,0,0,0,78.11,6.83A97.68,97.68,0,0,0,49,6.83A72.37,72.37,0,0,0,45.64,0A105.89,105.89,0,0,0,19.39,8.09C2.79,32.65,-1.71,56.6,0.54,80.21L0.54,80.21A105.73,105.73,0,0,0,32.71,96.36A77.7,77.7,0,0,0,39.6,85.25A68.42,68.42,0,0,1,28.75,80.07C29.66,79.41,30.55,78.73,31.41,78.07A75.57,75.57,0,0,0,95.73,78.07C96.6,78.78,97.49,79.46,98.39,80.07A68.68,68.68,0,0,1,87.52,85.26A77,77,0,0,0,94.41,96.36A105.25,105.25,0,0,0,126.6,80.22L126.6,80.22C129.24,52.84,122.09,29.11,107.7,8.07z M42.45,65.69C36.18,65.69 31,60 31,53 31,46 36,40.26 42.43,40.26 48.86,40.26 54,46 53.89,53 53.78,60 48.84,65.69 42.45,65.69z M84.69,65.69C78.41,65.69 73.25,60 73.25,53 73.25,46 78.25,40.26 84.69,40.26 91.13,40.26 96.23,46 96.12,53 96.01,60 91.08,65.69 84.69,65.69z\" />\n                            </DrawingGroup>\n                        </DrawingGroup>\n                    </DrawingGroup>\n                </DrawingGroup>\n            </DrawingImage.Drawing>\n        </DrawingImage>\n    </ui:FluentWindow.Resources>\n\n    <ui:FluentWindow.TaskbarItemInfo>\n        <TaskbarItemInfo ProgressState=\"{Binding ProgressState}\" ProgressValue=\"{Binding ProgressValue}\" />\n    </ui:FluentWindow.TaskbarItemInfo>\n\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n\n        <ui:TitleBar Background=\"{ui:ThemeResource ApplicationBackgroundBrush}\">\n            <ui:TitleBar.Header>\n                <TextBlock Margin=\"16,8\" Text=\"Stability Matrix\" />\n            </ui:TitleBar.Header>\n        </ui:TitleBar>\n        <Grid Grid.Row=\"1\">\n            <ui:NavigationView\n                IsBackButtonVisible=\"Collapsed\"\n                OpenPaneLength=\"260\"\n                PaneClosed=\"RootNavigation_OnPaneClosed\"\n                PaneOpened=\"RootNavigation_OnPaneOpened\"\n                UseLayoutRounding=\"True\"\n                x:Name=\"RootNavigation\">\n                <ui:NavigationView.Header>\n                    <ui:BreadcrumbBar\n                        FontSize=\"24\"\n                        FontWeight=\"DemiBold\"\n                        Margin=\"24,16,0,16\" />\n                </ui:NavigationView.Header>\n                <ui:NavigationView.MenuItems>\n                    <!--  Main pages  -->\n                    <ui:NavigationViewItem\n                        Content=\"Launch\"\n                        Icon=\"{ui:SymbolIcon Rocket24}\"\n                        IsActive=\"true\"\n                        TargetPageType=\"{x:Type local:LaunchPage}\" />\n                    <ui:NavigationViewItem\n                        Content=\"Packages\"\n                        Icon=\"{ui:SymbolIcon Box24}\"\n                        TargetPageType=\"{x:Type local:PackageManagerPage}\" />\n                    <ui:NavigationViewItem\n                        Content=\"Checkpoints\"\n                        Icon=\"{ui:SymbolIcon Notebook24}\"\n                        TargetPageType=\"{x:Type local:CheckpointManagerPage}\" />\n                    <ui:NavigationViewItem\n                        Content=\"Model Browser\"\n                        Icon=\"{ui:SymbolIcon BrainCircuit24}\"\n                        TargetPageType=\"{x:Type local:CheckpointBrowserPage}\" />\n                    <ui:NavigationViewItem\n                        Content=\"Text2Image (Beta™)\"\n                        Icon=\"{ui:SymbolIcon Image24}\"\n                        TargetPageType=\"{x:Type local:TextToImagePage}\"\n                        Visibility=\"{Binding IsTextToImagePageEnabled, Converter={StaticResource BooleanToVisibilityConverter}}\" />\n                </ui:NavigationView.MenuItems>\n                <ui:NavigationView.FooterMenuItems>\n                    <!--  Footer menus  -->\n                    <ui:NavigationViewItem Content=\"Update Available\"\n                                           Visibility=\"{Binding IsUpdateAvailable, Converter={StaticResource BooleanToVisibilityConverter}}\"\n                                           Command=\"{Binding DoUpdateCommand}\">\n                        <ui:NavigationViewItem.Icon>\n                            <ui:SymbolIcon Symbol=\"ArrowDownload24\"\n                                           Foreground=\"LimeGreen\" />\n                        </ui:NavigationViewItem.Icon>\n                    </ui:NavigationViewItem>\n                    <ui:NavigationViewItem Command=\"{Binding OpenLinkPatreonCommand}\" Content=\"Become a Patron\">\n                        <ui:NavigationViewItem.Icon>\n                            <ui:ImageIcon\n                                Height=\"15\"\n                                HorizontalAlignment=\"Center\"\n                                Source=\"{StaticResource PatreonIconColored}\"\n                                Width=\"15\" />\n                        </ui:NavigationViewItem.Icon>\n                    </ui:NavigationViewItem>\n\n                    <ui:NavigationViewItem Command=\"{Binding OpenLinkDiscordCommand}\" Content=\"Join our Discord Server\">\n                        <ui:NavigationViewItem.Icon>\n                            <ui:ImageIcon\n                                Height=\"12\"\n                                HorizontalAlignment=\"Center\"\n                                Source=\"{StaticResource DiscordIconWhite}\"\n                                Width=\"16\" />\n                        </ui:NavigationViewItem.Icon>\n                    </ui:NavigationViewItem>\n\n                    <ui:NavigationViewItem Content=\"Settings\" TargetPageType=\"{x:Type local:SettingsPage}\">\n                        <ui:NavigationViewItem.Icon>\n                            <ui:SymbolIcon Symbol=\"Settings24\" />\n                        </ui:NavigationViewItem.Icon>\n                    </ui:NavigationViewItem>\n                </ui:NavigationView.FooterMenuItems>\n                <ui:NavigationView.ContentOverlay>\n                    <Grid>\n                        <!--  Main snackbar  -->\n                        <ui:Snackbar x:Name=\"RootSnackbar\" />\n                        <!--  Notification snackbar  -->\n                        <Grid Grid.Row=\"0\">\n                            <Grid.ColumnDefinitions>\n                                <ColumnDefinition Width=\"0.4*\" />\n                                <ColumnDefinition Width=\"0.6*\" />\n                            </Grid.ColumnDefinitions>\n                            <ui:Snackbar\n                                Grid.Column=\"1\"\n                                MinHeight=\"130\"\n                                MinWidth=\"200\"\n                                x:Name=\"NotificationSnackbar\" />\n                        </Grid>\n                    </Grid>\n                </ui:NavigationView.ContentOverlay>\n            </ui:NavigationView>\n        </Grid>\n\n        <ContentPresenter Grid.Row=\"1\" x:Name=\"RootContentDialog\" />\n    </Grid>\n</ui:FluentWindow>\n"
  },
  {
    "path": "StabilityMatrix/MainWindow.xaml.cs",
    "content": "using System;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing System.Windows;\nusing System.Windows.Interop;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Helper;\nusing StabilityMatrix.Services;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls.Navigation;\nusing Wpf.Ui.Controls.Window;\nusing Application = System.Windows.Application;\nusing EventManager = StabilityMatrix.Core.Helper.EventManager;\nusing ISnackbarService = Wpf.Ui.Contracts.ISnackbarService;\n\nnamespace StabilityMatrix\n{\n    /// <summary>\n    /// Interaction logic for MainWindow.xaml\n    /// </summary>\n    public partial class MainWindow : FluentWindow\n    {\n        private readonly MainWindowViewModel mainWindowViewModel;\n        private readonly ISettingsManager settingsManager;\n\n        public MainWindow(IPageService pageService, IContentDialogService contentDialogService,\n            MainWindowViewModel mainWindowViewModel, ISettingsManager settingsManager,\n            ISnackbarService snackbarService, INotificationBarService notificationBarService)\n        {\n            InitializeComponent();\n\n            this.mainWindowViewModel = mainWindowViewModel;\n            this.settingsManager = settingsManager;\n\n            DataContext = mainWindowViewModel;\n\n            RootNavigation.Navigating += (_, _) => Debug.WriteLine(\"Navigating\");\n            RootNavigation.SetPageService(pageService);\n\n            snackbarService.SetSnackbarControl(RootSnackbar);\n            notificationBarService.SetSnackbarControl(NotificationSnackbar);\n            contentDialogService.SetContentPresenter(RootContentDialog);\n\n            EventManager.Instance.PageChangeRequested += InstanceOnPageChangeRequested;\n        }\n\n        private void InstanceOnPageChangeRequested(object? sender, Type e)\n        {\n            RootNavigation.Navigate(e);\n        }\n\n        private async void MainWindow_OnLoaded(object sender, RoutedEventArgs e)\n        {\n            RootNavigation.Navigate(typeof(LaunchPage));\n            RootNavigation.IsPaneOpen = settingsManager.Settings.IsNavExpanded;\n            await mainWindowViewModel.OnLoaded();\n            ObserveSizeChanged();\n        }\n\n        private void RootNavigation_OnPaneOpened(NavigationView sender, RoutedEventArgs args)\n        {\n            if (settingsManager.IsLibraryDirSet)\n            {\n                settingsManager.Transaction(s => s.IsNavExpanded = true);\n            }\n        }\n\n        private void RootNavigation_OnPaneClosed(NavigationView sender, RoutedEventArgs args)\n        {\n            if (settingsManager.IsLibraryDirSet)\n            {\n                settingsManager.Transaction(s => s.IsNavExpanded = false);\n            }\n        }\n\n        private void MainWindow_OnClosed(object? sender, EventArgs e)\n        {\n            Application.Current.Shutdown();\n        }\n\n        private void ObserveSizeChanged()\n        {\n            var observableSizeChanges = Observable\n                .FromEventPattern<SizeChangedEventHandler, SizeChangedEventArgs>(\n                    h => SizeChanged += h,\n                    h => SizeChanged -= h)\n                .Select(x => x.EventArgs)\n                .Throttle(TimeSpan.FromMilliseconds(150));\n\n            observableSizeChanges\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe(args =>\n                {\n                    if (args is {HeightChanged: false, WidthChanged: false}) return;\n                    \n                    var interopHelper = new WindowInteropHelper(this);\n                    var placement = ScreenExtensions.GetPlacement(interopHelper.Handle);\n                    settingsManager.Transaction(s => s.Placement = placement.ToString());\n                });\n        }\n\n        private void MainWindow_OnClosing(object? sender, CancelEventArgs e)\n        {\n            var interopHelper = new WindowInteropHelper(this);\n            var placement = ScreenExtensions.GetPlacement(interopHelper.Handle);\n\n            if (settingsManager.IsLibraryDirSet)\n            {\n                settingsManager.Transaction(s => s.Placement = placement.ToString());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Models/CheckpointFile.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing System.Windows.Media.Imaging;\nusing AsyncAwaitBestPractices;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NLog;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Helper;\n\nnamespace StabilityMatrix.Models;\n\npublic partial class CheckpointFile : ObservableObject\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private readonly IDialogFactory dialogFactory;\n    \n    // Event for when this file is deleted\n    public event EventHandler<CheckpointFile>? Deleted;\n\n    /// <summary>\n    /// Absolute path to the checkpoint file.\n    /// </summary>\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(FileName))]\n    private string filePath = string.Empty;\n\n    /// <summary>\n    /// Custom title for UI.\n    /// </summary>\n    [ObservableProperty]\n    private string title = string.Empty;\n    \n    public string? PreviewImagePath { get; set; }\n    public BitmapImage? PreviewImage { get; set; }\n    public bool IsPreviewImageLoaded => PreviewImage != null;\n\n    [ObservableProperty]\n    private ConnectedModelInfo? connectedModel;\n    public bool IsConnectedModel => ConnectedModel != null;\n\n    [ObservableProperty] private bool isLoading;\n    \n    public string FileName => Path.GetFileName(FilePath);\n\n    public ObservableCollection<string> Badges { get; set; } = new();\n\n    private static readonly string[] SupportedCheckpointExtensions = { \".safetensors\", \".pt\", \".ckpt\", \".pth\", \"bin\" };\n    private static readonly string[] SupportedImageExtensions = { \".png\", \".jpg\", \".jpeg\" };\n    private static readonly string[] SupportedMetadataExtensions = { \".json\" };\n    \n    public CheckpointFile(IDialogFactory dialogFactory)\n    {\n        this.dialogFactory = dialogFactory;\n    }\n\n    partial void OnConnectedModelChanged(ConnectedModelInfo? value)\n    {\n        // Update title, first check user defined, then connected model name\n        Title = value?.UserTitle ?? value?.ModelName ?? string.Empty;\n        // Update badges\n        Badges.Clear();\n        var fpType = value.FileMetadata.Fp?.GetStringValue().ToUpperInvariant();\n        if (fpType != null)\n        {\n            Badges.Add(fpType);\n        }\n        if (!string.IsNullOrWhiteSpace(value.BaseModel))\n        {\n            Badges.Add(value.BaseModel);\n        }\n    }\n\n    [RelayCommand]\n    private async Task DeleteAsync()\n    {\n        if (File.Exists(FilePath))\n        {\n            IsLoading = true;\n            try\n            {\n                await using var delay = new MinimumDelay(200, 500);\n                await Task.Run(() => File.Delete(FilePath));\n                if (PreviewImagePath != null && File.Exists(PreviewImagePath))\n                {\n                    await Task.Run(() => File.Delete(PreviewImagePath));\n                }\n            }\n            catch (IOException ex)\n            {\n                Logger.Warn($\"Failed to delete checkpoint file {FilePath}: {ex.Message}\");\n                return; // Don't delete from collection\n            }\n            finally\n            {\n                IsLoading = false;\n            }\n        }\n        Deleted?.Invoke(this, this);\n    }\n\n    [RelayCommand]\n    private async Task RenameAsync()\n    {\n        var responses = await dialogFactory.ShowTextEntryDialog(\"Rename Model\", new []\n        {\n            (\"File Name\", FileName)\n        });\n        var name = responses?.FirstOrDefault();\n        if (name == null) return;\n        \n        // Rename file in OS\n        try\n        {\n            var newFilePath = Path.Combine(Path.GetDirectoryName(FilePath) ?? \"\", name);\n            File.Move(FilePath, newFilePath);\n            FilePath = newFilePath;\n        }\n        catch (Exception e)\n        {\n            Console.WriteLine(e);\n            throw;\n        }\n    }\n\n    [RelayCommand]\n    private void OpenOnCivitAi()\n    {\n        ProcessRunner.OpenUrl($\"https://civitai.com/models/{ConnectedModel.ModelId}\");\n    }\n    \n    // Loads image from path\n    private async Task LoadPreviewImage()\n    {\n        if (PreviewImagePath == null) return;\n        var bytes = await File.ReadAllBytesAsync(PreviewImagePath);\n        await Application.Current.Dispatcher.InvokeAsync(() =>\n        {\n            var bitmap = new BitmapImage();\n            using var ms = new MemoryStream(bytes);\n            bitmap.BeginInit();\n            bitmap.StreamSource = ms;\n            bitmap.CacheOption = BitmapCacheOption.OnLoad;\n            bitmap.EndInit();\n            PreviewImage = bitmap;\n        });\n    }\n\n    /// <summary>\n    /// Indexes directory and yields all checkpoint files.\n    /// First we match all files with supported extensions.\n    /// If found, we also look for\n    /// - {filename}.preview.{image-extensions} (preview image)\n    /// - {filename}.cm-info.json (connected model info)\n    /// </summary>\n    public static IEnumerable<CheckpointFile> FromDirectoryIndex(IDialogFactory dialogFactory, string directory, SearchOption searchOption = SearchOption.TopDirectoryOnly)\n    {\n        // Get all files with supported extensions\n        var allExtensions = SupportedCheckpointExtensions\n            .Concat(SupportedImageExtensions)\n            .Concat(SupportedMetadataExtensions);\n\n        var files = allExtensions.AsParallel()\n            .SelectMany(pattern => Directory.EnumerateFiles(directory, $\"*{pattern}\", searchOption)).ToDictionary<string, string>(Path.GetFileName);\n\n        foreach (var file in files.Keys.Where(k => SupportedCheckpointExtensions.Contains(Path.GetExtension(k))))\n        {\n            var checkpointFile = new CheckpointFile(dialogFactory)\n            {\n                Title = Path.GetFileNameWithoutExtension(file),\n                FilePath = Path.Combine(directory, file),\n            };\n            \n            // Check for connected model info\n            var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file);\n            var cmInfoPath = $\"{fileNameWithoutExtension}.cm-info.json\";\n            if (files.TryGetValue(cmInfoPath, out var jsonPath))\n            {\n                try\n                {\n                    var jsonData = File.ReadAllText(jsonPath);\n                    checkpointFile.ConnectedModel = ConnectedModelInfo.FromJson(jsonData);\n                }\n                catch (IOException e)\n                {\n                    Debug.WriteLine($\"Failed to parse {cmInfoPath}: {e}\");\n                }\n            }\n\n            // Check for preview image\n            var previewImage = SupportedImageExtensions.Select(ext => $\"{fileNameWithoutExtension}.preview{ext}\").FirstOrDefault(files.ContainsKey);\n            if (previewImage != null)\n            {\n                checkpointFile.PreviewImagePath = files[previewImage];\n                checkpointFile.LoadPreviewImage().SafeFireAndForget();\n            }\n\n            yield return checkpointFile;\n        }\n    }\n\n    /// <summary>\n    /// Index with progress reporting.\n    /// </summary>\n    public static IEnumerable<CheckpointFile> FromDirectoryIndex(IDialogFactory dialogFactory, string directory, IProgress<ProgressReport> progress,\n        SearchOption searchOption = SearchOption.TopDirectoryOnly)\n    {\n        var current = 0ul;\n        foreach (var checkpointFile in FromDirectoryIndex(dialogFactory, directory, searchOption))\n        {\n            current++;\n            progress.Report(new ProgressReport(current, \"Indexing\", checkpointFile.FileName));\n            yield return checkpointFile;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Models/CheckpointFolder.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Collections.Specialized;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Helper;\nusing StabilityMatrix.ViewModels;\n\nnamespace StabilityMatrix.Models;\n\npublic partial class CheckpointFolder : ObservableObject\n{\n    private readonly IDialogFactory dialogFactory;\n    private readonly ISettingsManager settingsManager;\n    private readonly IDownloadService downloadService;\n    private readonly ModelFinder modelFinder;\n\n    // ReSharper disable once FieldCanBeMadeReadOnly.Local\n    private bool useCategoryVisibility;\n\n    /// <summary>\n    /// Absolute path to the folder.\n    /// </summary>\n    public string DirectoryPath { get; init; } = string.Empty;\n\n    /// <summary>\n    /// Custom title for UI.\n    /// </summary>\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(FolderType))]\n    [NotifyPropertyChangedFor(nameof(TitleWithFilesCount))]\n    private string title = string.Empty;\n\n    [ObservableProperty]\n    private SharedFolderType folderType;\n\n    /// <summary>\n    /// True if the category is enabled for the manager page.\n    /// </summary>\n    [ObservableProperty]\n    private bool isCategoryEnabled = true;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsDragBlurEnabled))]\n    private bool isCurrentDragTarget;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsDragBlurEnabled))]\n    private bool isImportInProgress;\n\n    public bool IsDragBlurEnabled => IsCurrentDragTarget || IsImportInProgress;\n    public string TitleWithFilesCount =>\n        CheckpointFiles.Any() ? $\"{Title} ({CheckpointFiles.Count})\" : Title;\n\n    public ProgressViewModel Progress { get; } = new();\n\n    public ObservableCollection<CheckpointFolder> SubFolders { get; init; } = new();\n    public ObservableCollection<CheckpointFile> CheckpointFiles { get; init; } = new();\n\n    public RelayCommand OnPreviewDragEnterCommand => new(() => IsCurrentDragTarget = true);\n    public RelayCommand OnPreviewDragLeaveCommand => new(() => IsCurrentDragTarget = false);\n\n    public CheckpointFolder(\n        IDialogFactory dialogFactory,\n        ISettingsManager settingsManager,\n        IDownloadService downloadService,\n        ModelFinder modelFinder,\n        bool useCategoryVisibility = true\n    )\n    {\n        this.dialogFactory = dialogFactory;\n        this.settingsManager = settingsManager;\n        this.downloadService = downloadService;\n        this.modelFinder = modelFinder;\n        this.useCategoryVisibility = useCategoryVisibility;\n\n        CheckpointFiles.CollectionChanged += OnCheckpointFilesChanged;\n    }\n\n    /// <summary>\n    /// When title is set, set the category enabled state from settings.\n    /// </summary>\n    // ReSharper disable once UnusedParameterInPartialMethod\n    partial void OnTitleChanged(string value)\n    {\n        if (!useCategoryVisibility)\n            return;\n\n        // Update folder type\n        var result = Enum.TryParse(Title, out SharedFolderType type);\n        FolderType = result ? type : new SharedFolderType();\n\n        IsCategoryEnabled = settingsManager.IsSharedFolderCategoryVisible(FolderType);\n    }\n\n    /// <summary>\n    /// When toggling the category enabled state, save it to settings.\n    /// </summary>\n    partial void OnIsCategoryEnabledChanged(bool value)\n    {\n        if (!useCategoryVisibility)\n            return;\n        if (value != settingsManager.IsSharedFolderCategoryVisible(FolderType))\n        {\n            settingsManager.SetSharedFolderCategoryVisible(FolderType, value);\n        }\n    }\n\n    // On collection changes\n    private void OnCheckpointFilesChanged(object? sender, NotifyCollectionChangedEventArgs e)\n    {\n        OnPropertyChanged(nameof(TitleWithFilesCount));\n        if (e.NewItems == null)\n            return;\n        // On new added items, add event handler for deletion\n        foreach (CheckpointFile item in e.NewItems)\n        {\n            item.Deleted += OnCheckpointFileDelete;\n        }\n    }\n\n    /// <summary>\n    /// Handler for CheckpointFile requesting to be deleted from the collection.\n    /// </summary>\n    /// <param name=\"sender\"></param>\n    /// <param name=\"file\"></param>\n    private void OnCheckpointFileDelete(object? sender, CheckpointFile file)\n    {\n        Application.Current.Dispatcher.Invoke(() => CheckpointFiles.Remove(file));\n    }\n\n    [RelayCommand]\n    private async Task OnPreviewDropAsync(DragEventArgs e)\n    {\n        IsImportInProgress = true;\n        IsCurrentDragTarget = false;\n\n        if (e.Data.GetData(DataFormats.FileDrop) is not string[] files || files.Length < 1)\n        {\n            IsImportInProgress = false;\n            return;\n        }\n\n        await ImportFilesAsync(files, settingsManager.Settings.IsImportAsConnected);\n    }\n\n    [RelayCommand]\n    private void ShowInExplorer(string path)\n    {\n        Process.Start(\"explorer.exe\", path);\n    }\n\n    /// <summary>\n    /// Imports files to the folder. Reports progress to instance properties.\n    /// </summary>\n    public async Task ImportFilesAsync(\n        IEnumerable<string> files,\n        bool convertToConnected = false,\n        bool copyFiles = true\n    )\n    {\n        Progress.IsIndeterminate = true;\n        Progress.IsProgressVisible = true;\n        var copyPaths = files.ToDictionary(\n            k => k,\n            v => Path.Combine(DirectoryPath, Path.GetFileName(v))\n        );\n\n        var progress = new Progress<ProgressReport>(report =>\n        {\n            Progress.IsIndeterminate = false;\n            Progress.Value = report.Percentage;\n            // For multiple files, add count\n            Progress.Text =\n                copyPaths.Count > 1\n                    ? $\"Importing {report.Title} ({report.Message})\"\n                    : $\"Importing {report.Title}\";\n        });\n\n        if (copyFiles)\n        {\n            await FileTransfers.CopyFiles(copyPaths, progress);\n        }\n\n        // Hash files and convert them to connected model if found\n        if (convertToConnected)\n        {\n            var modelFilesCount = copyPaths.Count;\n            var modelFiles = copyPaths.Values.Select(path => new FilePath(path));\n\n            // Holds tasks for model queries after hash\n            var modelQueryTasks = new List<Task<bool>>();\n\n            foreach (var (i, modelFile) in modelFiles.Enumerate())\n            {\n                var hashProgress = new Progress<ProgressReport>(report =>\n                {\n                    Progress.IsIndeterminate = false;\n                    Progress.Value = report.Percentage;\n                    Progress.Text =\n                        modelFilesCount > 1\n                            ? $\"Computing metadata for {modelFile.Info.Name} ({i}/{modelFilesCount})\"\n                            : $\"Computing metadata for {report.Title}\";\n                });\n\n                var hashBlake3 = await FileHash.GetBlake3Async(modelFile, hashProgress);\n\n                if (!string.IsNullOrWhiteSpace(hashBlake3))\n                {\n                    settingsManager.Transaction(s =>\n                    {\n                        s.InstalledModelHashes ??= new HashSet<string>();\n                        s.InstalledModelHashes.Add(hashBlake3);\n                    });\n                }\n\n                // Start a task to query the model in background\n                var queryTask = Task.Run(async () =>\n                {\n                    var result = await modelFinder.LocalFindModel(hashBlake3);\n                    result ??= await modelFinder.RemoteFindModel(hashBlake3);\n\n                    if (result is null)\n                        return false; // Not found\n\n                    var (model, version, file) = result.Value;\n\n                    // Save connected model info json\n                    var modelFileName = Path.GetFileNameWithoutExtension(modelFile.Info.Name);\n                    var modelInfo = new ConnectedModelInfo(\n                        model,\n                        version,\n                        file,\n                        DateTimeOffset.UtcNow\n                    );\n                    await modelInfo.SaveJsonToDirectory(DirectoryPath, modelFileName);\n\n                    // If available, save thumbnail\n                    var image = version.Images?.FirstOrDefault();\n                    if (image != null)\n                    {\n                        var imageExt = Path.GetExtension(image.Url).TrimStart('.');\n                        if (imageExt is \"jpg\" or \"jpeg\" or \"png\")\n                        {\n                            var imageDownloadPath = Path.GetFullPath(\n                                Path.Combine(DirectoryPath, $\"{modelFileName}.preview.{imageExt}\")\n                            );\n                            await downloadService.DownloadToFileAsync(image.Url, imageDownloadPath);\n                        }\n                    }\n\n                    return true;\n                });\n                modelQueryTasks.Add(queryTask);\n            }\n\n            // Set progress to indeterminate\n            Progress.IsIndeterminate = true;\n            Progress.Text = \"Checking connected model information\";\n\n            // Wait for all model queries to finish\n            var modelQueryResults = await Task.WhenAll(modelQueryTasks);\n\n            var successCount = modelQueryResults.Count(r => r);\n            var totalCount = modelQueryResults.Length;\n            var failCount = totalCount - successCount;\n\n            await IndexAsync();\n\n            Progress.Value = 100;\n            Progress.Text = successCount switch\n            {\n                0 when failCount > 0 => \"Import complete. No connected data found.\",\n                > 0 when failCount > 0\n                    => $\"Import complete. Found connected data for {successCount} of {totalCount} models.\",\n                _ => $\"Import complete. Found connected data for all {totalCount} models.\"\n            };\n\n            DelayedClearProgress(TimeSpan.FromSeconds(1));\n        }\n        else\n        {\n            await IndexAsync();\n            Progress.Value = 100;\n            Progress.Text = \"Import complete\";\n            DelayedClearProgress(TimeSpan.FromSeconds(1));\n        }\n    }\n\n    /// <summary>\n    /// Clears progress after a delay.\n    /// </summary>\n    private void DelayedClearProgress(TimeSpan delay)\n    {\n        Task.Delay(delay)\n            .ContinueWith(_ =>\n            {\n                IsImportInProgress = false;\n                Progress.IsProgressVisible = false;\n                Progress.Value = 0;\n                Progress.Text = string.Empty;\n            });\n    }\n\n    /// <summary>\n    /// Gets checkpoint files from folder index\n    /// </summary>\n    private async Task<List<CheckpointFile>> GetCheckpointFilesAsync(\n        IProgress<ProgressReport>? progress = default\n    )\n    {\n        if (!Directory.Exists(DirectoryPath))\n        {\n            return new List<CheckpointFile>();\n        }\n\n        return await (\n            progress switch\n            {\n                null\n                    => Task.Run(\n                        () =>\n                            CheckpointFile.FromDirectoryIndex(dialogFactory, DirectoryPath).ToList()\n                    ),\n\n                _\n                    => Task.Run(\n                        () =>\n                            CheckpointFile\n                                .FromDirectoryIndex(dialogFactory, DirectoryPath, progress)\n                                .ToList()\n                    )\n            }\n        );\n    }\n\n    /// <summary>\n    /// Indexes the folder for checkpoint files and refreshes the CheckPointFiles collection.\n    /// </summary>\n    public async Task IndexAsync(IProgress<ProgressReport>? progress = default)\n    {\n        SubFolders.Clear();\n        foreach (var folder in Directory.GetDirectories(DirectoryPath))\n        {\n            // Inherit our folder type\n            var subFolder = new CheckpointFolder(\n                dialogFactory,\n                settingsManager,\n                downloadService,\n                modelFinder,\n                useCategoryVisibility: false\n            )\n            {\n                Title = Path.GetFileName(folder),\n                DirectoryPath = folder,\n                FolderType = FolderType\n            };\n\n            await subFolder.IndexAsync(progress);\n            SubFolders.Add(subFolder);\n        }\n\n        var checkpointFiles = await GetCheckpointFilesAsync();\n        CheckpointFiles.Clear();\n        foreach (var checkpointFile in checkpointFiles)\n        {\n            CheckpointFiles.Add(checkpointFile);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/OneClickInstallDialog.xaml",
    "content": "﻿<ui:ContentDialog\n    CloseButtonText=\"Close\"\n    Loaded=\"OneClickInstallDialog_OnLoaded\"\n    Title=\"Stable Diffusion WebUI Installer\"\n    d:DataContext=\"{d:DesignInstance Type=viewModels:OneClickInstallViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"512\"\n    d:DesignWidth=\"640\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.OneClickInstallDialog\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:StabilityMatrix\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Controls\">\n\n    <ui:ContentDialog.Resources>\n        <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n        <Style BasedOn=\"{StaticResource {x:Type ui:ContentDialog}}\" TargetType=\"{x:Type local:OneClickInstallDialog}\" />\n        <BooleanToVisibilityConverter x:Key=\"BoolToVisConverter\" />\n    </ui:ContentDialog.Resources>\n\n    <Grid MaxHeight=\"900\" VerticalAlignment=\"Stretch\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"1*\" />\n            <RowDefinition Height=\"0.25*\" />\n        </Grid.RowDefinitions>\n        <TextBlock Text=\"{Binding HeaderText, FallbackValue=Installing Stable Diffusion WebUI...}\"\n                   TextWrapping=\"Wrap\"\n                   VerticalAlignment=\"Top\" HorizontalAlignment=\"Center\"\n                   FontSize=\"36\" FontWeight=\"Light\" Margin=\"8\" />\n        <TextBlock Grid.Row=\"1\"\n                   Text=\"{Binding SubHeaderText, FallbackValue=Installing git...}\"\n                   TextWrapping=\"Wrap\" TextAlignment=\"Center\"\n                   VerticalAlignment=\"Top\" HorizontalAlignment=\"Center\"\n                   FontSize=\"16\" FontWeight=\"Light\" Margin=\"8\"/>\n        \n        <TextBlock Grid.Row=\"2\"\n                   Text=\"{Binding SubSubHeaderText, FallbackValue=Installing git...}\"\n                   TextWrapping=\"Wrap\" TextAlignment=\"Center\"\n                   VerticalAlignment=\"Top\" HorizontalAlignment=\"Center\"\n                   FontSize=\"10\" Margin=\"8\"/>\n\n        <StackPanel Grid.Row=\"3\" VerticalAlignment=\"Center\">\n            <ProgressBar Maximum=\"100\" \n                         controls:ProgressBarSmoother.SmoothValue=\"{Binding OneClickInstallProgress}\"\n                         Visibility=\"{Binding ProgressBarVisibility}\"\n                         IsIndeterminate=\"{Binding IsIndeterminate}\"\n                         Margin=\"16\" />\n            <ui:Button\n                Visibility=\"{Binding ShowInstallButton, Converter={StaticResource BoolToVisConverter}}\"\n                Content=\"Install\"\n                Command=\"{Binding InstallCommand}\"\n                FontSize=\"32\"\n                HorizontalAlignment=\"Center\"\n                Appearance=\"Success\"\n                Margin=\"16\"\n                Padding=\"16, 8, 16, 8\" />\n        </StackPanel>\n\n        <ui:Button Grid.Row=\"4\"\n                Appearance=\"Transparent\"\n                Visibility=\"{Binding ShowInstallButton, Converter={StaticResource BoolToVisConverter}}\"\n                Command=\"{Binding ToggleAdvancedModeCommand}\"\n                Content=\"Skip first-time setup\"\n                FontSize=\"14\"\n                HorizontalAlignment=\"Center\"\n                Margin=\"16\"\n                VerticalAlignment=\"Bottom\" />\n    </Grid>\n\n</ui:ContentDialog>\n"
  },
  {
    "path": "StabilityMatrix/OneClickInstallDialog.xaml.cs",
    "content": "﻿using System.Windows;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls.ContentDialogControl;\n\nnamespace StabilityMatrix;\n\npublic partial class OneClickInstallDialog : ContentDialog\n{\n    private readonly OneClickInstallViewModel viewModel;\n\n    public OneClickInstallDialog(IContentDialogService dialogService, OneClickInstallViewModel viewModel) : base(\n        dialogService.GetContentPresenter())\n    {\n        this.viewModel = viewModel;\n        InitializeComponent();\n        DataContext = viewModel;\n    }\n\n    private void OneClickInstallDialog_OnLoaded(object sender, RoutedEventArgs e)\n    {\n        //viewModel.OnLoad(); \n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Package.appxmanifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<Package\n  xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10\"\n  xmlns:uap=\"http://schemas.microsoft.com/appx/manifest/uap/windows10\"\n  xmlns:rescap=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities\"\n  IgnorableNamespaces=\"uap rescap\">\n\n  <Identity\n    Name=\"f156679e-9c58-4ae7-9a58-0c380033bf72\"\n    Publisher=\"CN=ionite\"\n    Version=\"1.0.0.0\" />\n\n  <Properties>\n    <DisplayName>StabilityMatrix</DisplayName>\n    <PublisherDisplayName>ionite</PublisherDisplayName>\n    <Logo>Assets\\StoreLogo.png</Logo>\n  </Properties>\n\n  <Dependencies>\n    <TargetDeviceFamily Name=\"Windows.Universal\" MinVersion=\"10.0.17763.0\" MaxVersionTested=\"10.0.19041.0\" />\n    <TargetDeviceFamily Name=\"Windows.Desktop\" MinVersion=\"10.0.17763.0\" MaxVersionTested=\"10.0.19041.0\" />\n  </Dependencies>\n\n  <Resources>\n    <Resource Language=\"x-generate\"/>\n  </Resources>\n\n  <Applications>\n    <Application Id=\"App\"\n      Executable=\"$targetnametoken$.exe\"\n      EntryPoint=\"$targetentrypoint$\">\n      <uap:VisualElements\n        DisplayName=\"StabilityMatrix\"\n        Description=\"StabilityMatrix\"\n        BackgroundColor=\"transparent\"\n        Square150x150Logo=\"Assets\\Square150x150Logo.png\"\n        Square44x44Logo=\"Assets\\Square44x44Logo.png\">\n        <uap:DefaultTile Wide310x150Logo=\"Assets\\Wide310x150Logo.png\" />\n        <uap:SplashScreen Image=\"Assets\\SplashScreen.png\" />\n      </uap:VisualElements>\n    </Application>\n  </Applications>\n\n  <Capabilities>\n    <rescap:Capability Name=\"runFullTrust\" />\n  </Capabilities>\n</Package>\n"
  },
  {
    "path": "StabilityMatrix/PackageManagerPage.xaml",
    "content": "﻿<Page\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Loaded=\"InstallPage_OnLoaded\"\n    d:DataContext=\"{d:DesignInstance Type=viewModels:PackageManagerViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"700\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    mc:Ignorable=\"d\"\n    x:Class=\"StabilityMatrix.PackageManagerPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n    \n    <Page.Resources>\n        <BooleanToVisibilityConverter x:Key=\"BoolToVisConverter\"/>\n    </Page.Resources>\n\n    <Grid Margin=\"16\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n\n        <StackPanel\n            Grid.Row=\"0\"\n            Orientation=\"Vertical\"\n            Visibility=\"{Binding ProgressBarVisibility, FallbackValue=Visible}\">\n\n            <ProgressBar\n                IsIndeterminate=\"{Binding IsIndeterminate, FallbackValue=False}\"\n                Maximum=\"100\"\n                Value=\"{Binding ProgressValue, FallbackValue=10}\"\n                Width=\"500\" />\n            <TextBlock\n                HorizontalAlignment=\"Center\"\n                Padding=\"8\"\n                Text=\"{Binding ProgressText, FallbackValue=Installing...}\" />\n        </StackPanel>\n\n        <StackPanel\n            Grid.Row=\"1\"\n            HorizontalAlignment=\"Left\"\n            Margin=\"16\"\n            Orientation=\"Horizontal\">\n            <StackPanel Orientation=\"Vertical\">\n                <ui:Button\n                    Appearance=\"Success\"\n                    Command=\"{Binding ShowInstallWindowCommand}\"\n                    Foreground=\"{ui:ThemeResource TextFillColorPrimaryBrush}\"\n                    Height=\"40\"\n                    HorizontalAlignment=\"Stretch\"\n                    IsEnabled=\"{Binding InstallButtonEnabled, FallbackValue=true}\"\n                    Margin=\"0,0,0,16\"\n                    VerticalContentAlignment=\"Center\">\n                    <StackPanel Orientation=\"Horizontal\">\n                        <ui:SymbolIcon Symbol=\"Add24\" />\n                        <Label\n                            Content=\"Add Package\"\n                            Margin=\"8,4,0,0\"\n                            VerticalContentAlignment=\"Center\" />\n                    </StackPanel>\n                </ui:Button>\n\n                <ListView\n                    ItemsSource=\"{Binding Packages}\"\n                    SelectedItem=\"{Binding SelectedPackage, Mode=TwoWay}\"\n                    Visibility=\"{Binding InstallButtonVisibility, FallbackValue=Visible}\">\n\n                    <ListView.Style>\n                        <Style TargetType=\"ListView\">\n                            <Setter Property=\"Background\" Value=\"#191919\" />\n                        </Style>\n                    </ListView.Style>\n\n                    <ListView.Template>\n                        <ControlTemplate>\n                            <Border\n                                BorderBrush=\"{ui:ThemeResource KeyboardFocusBorderColorBrush}\"\n                                BorderThickness=\"1\"\n                                CornerRadius=\"5\">\n                                <ItemsPresenter />\n                            </Border>\n                        </ControlTemplate>\n                    </ListView.Template>\n\n                    <ListView.ItemTemplate>\n                        <DataTemplate DataType=\"{x:Type models:InstalledPackage}\">\n                            <StackPanel Margin=\"8\" VerticalAlignment=\"Top\">\n                                <TextBlock Margin=\"0,5,0,5\" Text=\"{Binding DisplayName}\" />\n                                <TextBlock Margin=\"0,0,0,5\" Text=\"{Binding DisplayVersion}\" />\n                            </StackPanel>\n                        </DataTemplate>\n                    </ListView.ItemTemplate>\n                </ListView>\n            </StackPanel>\n\n            <StackPanel Margin=\"32,0,0,0\" Orientation=\"Vertical\">\n                <TextBlock\n                    FontSize=\"24\"\n                    FontWeight=\"Bold\"\n                    Text=\"{Binding SelectedPackage.DisplayName, FallbackValue=Stable Diffusion Web UI}\" />\n                <TextBlock FontSize=\"12\" Margin=\"0,5,0,5\">\n                    <Run Text=\"{Binding SelectedPackage.PackageName, FallbackValue=stable-diffusion-webui}\" />\n                    <Run Text=\"{Binding SelectedPackage.DisplayVersion, FallbackValue=v1.3.37}\" />\n                </TextBlock>\n                <TextBlock\n                    FontSize=\"12\"\n                    Margin=\"0,5,0,5\" \n                    Text=\"{Binding SelectedPackage.FullPath, FallbackValue=Packages\\stable-diffusion}\" />\n                    <StackPanel Margin=\"0,16,0,0\" Orientation=\"Horizontal\">\n                    <ui:Button\n                        Appearance=\"Success\"\n                        Command=\"{Binding InstallCommand}\"\n                        Content=\"{Binding InstallButtonText, FallbackValue=Install}\"\n                        Height=\"50\"\n                        IsEnabled=\"{Binding InstallButtonEnabled, FallbackValue=true}\"\n                        VerticalAlignment=\"Top\"\n                        Visibility=\"{Binding InstallButtonVisibility, FallbackValue=Visible}\"\n                        Width=\"100\" />\n                    <ui:Button\n                        Appearance=\"Danger\"\n                        Command=\"{Binding UninstallCommand}\"\n                        Content=\"Uninstall\"\n                        Height=\"50\"\n                        Margin=\"16,0,0,0\"\n                        VerticalAlignment=\"Top\"\n                        Visibility=\"{Binding InstallButtonVisibility, FallbackValue=Visible}\"\n                        Width=\"100\" />\n                </StackPanel>\n                <StackPanel Orientation=\"Horizontal\" Visibility=\"{Binding IsUninstalling,\n                                                        Converter={StaticResource BoolToVisConverter}}\">\n                    <ui:ProgressRing Height=\"24\" Width=\"24\" Margin=\"8, 16, 8, 8\"\n                                     VerticalAlignment=\"Center\"\n                                     IsIndeterminate=\"True\" HorizontalAlignment=\"Left\"/>\n                    <TextBlock Text=\"Uninstalling...\" VerticalAlignment=\"Center\" Margin=\"0,8,0,0\"/>\n                </StackPanel>\n            </StackPanel>\n        </StackPanel>\n    </Grid>\n</Page>\n"
  },
  {
    "path": "StabilityMatrix/PackageManagerPage.xaml.cs",
    "content": "﻿using System.Windows;\nusing System.Windows.Controls;\nusing StabilityMatrix.ViewModels;\n\n// To learn more about WinUI, the WinUI project structure,\n// and more about our project templates, see: http://aka.ms/winui-project-info.\n\nnamespace StabilityMatrix\n{\n    /// <summary>\n    /// An empty page that can be used on its own or navigated to within a Frame.\n    /// </summary>\n    public sealed partial class PackageManagerPage : Page\n    {\n        private readonly PackageManagerViewModel viewModel;\n\n        public PackageManagerPage(PackageManagerViewModel viewModel)\n        {\n            this.viewModel = viewModel;\n            InitializeComponent();\n            DataContext = viewModel;\n        }\n\n        private async void InstallPage_OnLoaded(object sender, RoutedEventArgs e)\n        {\n            await viewModel.OnLoaded();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Properties/launchSettings.json",
    "content": "{\n  \"profiles\": {\n    \"StabilityMatrix (Package)\": {\n      \"commandName\": \"MsixPackage\",\n      \"environmentVariables\": {\n        \"DOTNET_ENVIRONMENT\": \"Production\"\n      }\n    },\n    \"StabilityMatrix (Unpackaged)\": {\n      \"commandName\": \"Project\",\n      \"environmentVariables\": {\n        \"DOTNET_ENVIRONMENT\": \"Development\"\n      }\n    },\n    \"StabilityMatrix (Unpackaged) with Exception Window\": {\n      \"commandName\": \"Project\",\n      \"environmentVariables\": {\n        \"DOTNET_ENVIRONMENT\": \"Development\",\n        \"DEBUG_EXCEPTION_WINDOW\": \"true\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "StabilityMatrix/SelectInstallLocationsDialog.xaml",
    "content": "﻿<ui:ContentDialog\n    CloseButtonText=\"Close\"\n    Loaded=\"SelectInstallLocationsDialog_OnLoaded\"\n    Title=\"Select Data Directory\"\n    d:DataContext=\"{d:DesignInstance Type=viewModels:SelectInstallLocationsViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"512\"\n    d:DesignWidth=\"640\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.SelectInstallLocationsDialog\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:StabilityMatrix\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n\n    <ui:ContentDialog.Resources>\n        <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n        <Style BasedOn=\"{StaticResource {x:Type ui:ContentDialog}}\" TargetType=\"{x:Type local:SelectInstallLocationsDialog}\" />\n        \n        <converters:BoolNegationConverter x:Key=\"BoolNegationConverter\" />\n        <BooleanToVisibilityConverter x:Key=\"BooleanToVisibilityConverter\" />\n        <converters:ValueConverterGroup x:Key=\"InvertAndVisibilitate\">\n            <converters:BoolNegationConverter />\n            <BooleanToVisibilityConverter />\n        </converters:ValueConverterGroup>\n    </ui:ContentDialog.Resources>\n\n    <Grid\n        Margin=\"16,0,16,16\"\n        MinHeight=\"450\"\n        MaxWidth=\"700\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"1*\" />\n            <RowDefinition Height=\"1*\" />\n        </Grid.RowDefinitions>\n\n        <StackPanel Grid.Row=\"0\" Margin=\"0,0,0,16\">\n            <ui:InfoBar\n                IsClosable=\"False\"\n                IsOpen=\"True\"\n                Margin=\"0,0,0,16\"\n                Visibility=\"{Binding HasOldData, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Visible}\"\n                Title=\"Welcome back! In this update, you can optionally choose a custom location to store all data. If you choose a new location, or opt to use Portable Mode, you'll be able to migrate your existing data on the next page.\"/>\n            \n            <Label\n                Content=\"Data Directory\"\n                FontSize=\"13\"\n                Margin=\"0,16,0,0\" />\n            <Grid>\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition Width=\"*\" />\n                    <ColumnDefinition Width=\"Auto\" />\n                </Grid.ColumnDefinitions>\n\n                <ui:TextBox\n                    Height=\"36\"\n                    IsEnabled=\"{Binding IsPortableMode, Converter={StaticResource BoolNegationConverter}}\"\n                    Margin=\"0,0,8,0\"\n                    PlaceholderEnabled=\"True\"\n                    PlaceholderText=\"{Binding DefaultInstallLocation}\"\n                    Text=\"{Binding DataDirectory, UpdateSourceTrigger=PropertyChanged}\"\n                    VerticalAlignment=\"Stretch\" />\n\n                <ui:Button\n                    Command=\"{Binding ShowFolderBrowserDialogCommand}\"\n                    Grid.Column=\"1\"\n                    Height=\"36\"\n                    HorizontalAlignment=\"Stretch\"\n                    IsEnabled=\"{Binding IsPortableMode, Converter={StaticResource BoolNegationConverter}}\">\n                    <ui:Button.Icon>\n                        <ui:SymbolIcon Symbol=\"FolderOpen24\" />\n                    </ui:Button.Icon>\n                </ui:Button>\n            </Grid>\n            <TextBlock\n                Text=\"This is where the model checkpoints, LORAs, web UIs, settings, etc. will be installed. If you were satisfied with the previous versions, you don't need to change anything here.\"\n                TextWrapping=\"Wrap\"\n                Foreground=\"LightGray\"\n                FontSize=\"12\"\n                Margin=\"0,8,0,0\" />\n\n            <CheckBox\n                Content=\"Portable Mode\"\n                IsChecked=\"{Binding IsPortableMode, Mode=TwoWay}\"\n                Margin=\"0,16,0,0\" />\n\n            <ui:InfoBar\n                IsClosable=\"False\"\n                IsOpen=\"True\"\n                FontSize=\"13\"\n                Margin=\"0,8,0,0\"\n                Padding=\"16\"\n                Title=\"In Portable Mode, all data and settings will be stored in the same directory as the application. You will be able to move the application with its 'Data' folder to a different location or computer.\" />\n            \n        </StackPanel>\n\n        <!--  Indicator of existing or new data directory  -->\n        <StackPanel\n            Grid.Row=\"1\"\n            HorizontalAlignment=\"Center\"\n            Margin=\"8,0,8,8\"\n            Orientation=\"Horizontal\"\n            Visibility=\"{Binding IsStatusBadgeVisible, Converter={StaticResource BooleanToVisibilityConverter}}\">\n            <controls:RefreshBadge DataContext=\"{Binding RefreshBadgeViewModel}\" />\n            <TextBlock\n                FontSize=\"14\"\n                Text=\"{Binding DirectoryStatusText}\"\n                VerticalAlignment=\"Center\" />\n        </StackPanel>\n\n        <ui:Button\n            Appearance=\"Success\"\n            Click=\"ContinueButton_OnClick\"\n            Content=\"Continue\"\n            FontSize=\"16\"\n            Grid.Row=\"2\"\n            HorizontalAlignment=\"Center\"\n            IsEnabled=\"{Binding IsDirectoryValid}\"\n            Padding=\"16,8\" />\n    </Grid>\n\n</ui:ContentDialog>\n"
  },
  {
    "path": "StabilityMatrix/SelectInstallLocationsDialog.xaml.cs",
    "content": "﻿using System.Windows;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls.ContentDialogControl;\n\nnamespace StabilityMatrix;\n\npublic partial class SelectInstallLocationsDialog : ContentDialog\n{\n    public SelectInstallLocationsDialog(IContentDialogService dialogService, SelectInstallLocationsViewModel viewModel) : base(\n        dialogService.GetContentPresenter())\n    {\n        InitializeComponent();\n        DataContext = viewModel;\n    }\n\n    private void ContinueButton_OnClick(object sender, RoutedEventArgs e)\n    {\n        Hide(ContentDialogResult.Primary);\n    }\n\n    private void SelectInstallLocationsDialog_OnLoaded(object sender, RoutedEventArgs e)\n    {\n        ((SelectInstallLocationsViewModel) DataContext).OnLoaded();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/SelectModelVersionDialog.xaml",
    "content": "﻿<ui:ContentDialog\n    CloseButtonText=\"Close\"\n    Title=\"{Binding CivitModel.Name}\"\n    d:DataContext=\"{d:DesignInstance Type=designData:MockModelVersionDialogViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"512\"\n    d:DesignWidth=\"700\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.SelectModelVersionDialog\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:StabilityMatrix\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.DesignData\"\n    xmlns:api=\"clr-namespace:StabilityMatrix.Core.Models.Api;assembly=StabilityMatrix.Core\">\n\n    <ui:ContentDialog.Resources>\n        <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n        <Style BasedOn=\"{StaticResource {x:Type ui:ContentDialog}}\"\n               TargetType=\"{x:Type local:SelectModelVersionDialog}\" />\n\n        <converters:BoolNegationConverter x:Key=\"BoolNegationConverter\" />\n        <BooleanToVisibilityConverter x:Key=\"BooleanToVisibilityConverter\" />\n        <converters:ValueConverterGroup x:Key=\"InvertAndVisibilitate\">\n            <converters:BoolNegationConverter />\n            <BooleanToVisibilityConverter />\n        </converters:ValueConverterGroup>\n    </ui:ContentDialog.Resources>\n\n    <Grid\n        Margin=\"8\"\n        MinHeight=\"450\"\n        MinWidth=\"700\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"*\" />\n            <RowDefinition Height=\"Auto\" />\n        </Grid.RowDefinitions>\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"*\" />\n            <ColumnDefinition Width=\"Auto\" />\n            <ColumnDefinition Width=\"*\" />\n        </Grid.ColumnDefinitions>\n\n        <ui:Image Grid.Column=\"0\" Grid.Row=\"0\" Margin=\"8\"\n                  VerticalAlignment=\"Top\"\n                  MaxHeight=\"400\"\n                  HorizontalAlignment=\"Left\"\n                  Source=\"{Binding PreviewImage}\" />\n\n\n        <ListView Grid.Row=\"0\" Grid.Column=\"1\"\n                  Margin=\"8\"\n                  MaxHeight=\"450\"\n                  ItemsSource=\"{Binding Versions}\"\n                  SelectedItem=\"{Binding SelectedVersion}\">\n            <ListView.ItemTemplate>\n                <DataTemplate DataType=\"{x:Type api:CivitModelVersion}\">\n                    <StackPanel Margin=\"4\" VerticalAlignment=\"Top\">\n                        <TextBlock Margin=\"0,5,0,5\"\n                                   MinWidth=\"128\"\n                                   Text=\"{Binding Name}\" />\n                    </StackPanel>\n                </DataTemplate>\n            </ListView.ItemTemplate>\n        </ListView>\n\n        <ListView Grid.Row=\"0\" Grid.Column=\"2\"\n                  Margin=\"8\"\n                  PreviewMouseWheel=\"ListView_PreviewMouseWheel\"\n                  ItemsSource=\"{Binding SelectedVersion.Files}\"\n                  SelectedItem=\"{Binding SelectedFile}\">\n            <ListView.ItemTemplate>\n                <DataTemplate DataType=\"{x:Type api:CivitFile}\">\n                    <StackPanel Margin=\"4\" VerticalAlignment=\"Top\">\n                        <TextBlock Margin=\"0,4,0,4\" Text=\"{Binding Name}\" />\n                        <TextBlock Margin=\"0,0,0,4\" FontSize=\"11\">\n                            <Run Text=\"{Binding Metadata.Size}\" />\n                            <Run Text=\"{Binding Metadata.Fp}\" />\n                            <Run Text=\"-\" />\n                            <Run Text=\"{Binding FullFilesSize, Mode=OneWay}\" />\n                        </TextBlock>\n                    </StackPanel>\n                </DataTemplate>\n            </ListView.ItemTemplate>\n        </ListView>\n\n        <StackPanel Grid.Column=\"0\" Grid.ColumnSpan=\"3\" Grid.Row=\"1\" \n                    Orientation=\"Horizontal\"\n                    HorizontalAlignment=\"Center\">\n            <ui:Button\n                Content=\"Cancel\"\n                HorizontalAlignment=\"Center\"\n                Margin=\"8, 0\"\n                Click=\"Cancel_Click\" />\n            \n            <ui:Button\n                Margin=\"8, 0\"\n                Content=\"Import\"\n                IsEnabled=\"{Binding IsImportEnabled}\"\n                Appearance=\"Primary\"\n                Click=\"Import_OnClick\"/>\n        </StackPanel>\n\n    </Grid>\n\n</ui:ContentDialog>\n"
  },
  {
    "path": "StabilityMatrix/SelectModelVersionDialog.xaml.cs",
    "content": "﻿using System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Input;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls.ContentDialogControl;\n\nnamespace StabilityMatrix;\n\npublic partial class SelectModelVersionDialog : ContentDialog\n{\n    private SelectModelVersionDialogViewModel viewModel;\n    \n    public SelectModelVersionDialog(IContentDialogService dialogService,\n        SelectModelVersionDialogViewModel viewModel) : base(dialogService.GetContentPresenter())\n    {\n        this.viewModel = viewModel;\n        InitializeComponent();\n        DataContext = viewModel;\n    }\n\n    private void ListView_PreviewMouseWheel(object sender, MouseWheelEventArgs e)\n    {\n        if (e.Handled) return;\n        \n        e.Handled = true;\n        var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)\n        {\n            RoutedEvent = MouseWheelEvent,\n            Source = sender\n        };\n        if (((Control)sender).Parent is UIElement parent)\n        {\n            parent.RaiseEvent(eventArg);\n        }\n    }\n\n    private void Cancel_Click(object sender, RoutedEventArgs e)\n    {\n        Hide(ContentDialogResult.Secondary);\n    }\n\n    private void Import_OnClick(object sender, RoutedEventArgs e)\n    {\n        Hide(ContentDialogResult.Primary);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Services/INotificationBarService.cs",
    "content": "﻿using Wpf.Ui.Contracts;\n\nnamespace StabilityMatrix.Services;\n\npublic interface INotificationBarService : ISnackbarService\n{\n    public void ShowStartupNotifications();\n}"
  },
  {
    "path": "StabilityMatrix/Services/InstallerWindowDialogService.cs",
    "content": "﻿using Wpf.Ui.Services;\n\nnamespace StabilityMatrix.Services;\n\npublic class InstallerWindowDialogService : ContentDialogService\n{\n    \n}\n"
  },
  {
    "path": "StabilityMatrix/Services/NotificationBarService.cs",
    "content": "﻿using AsyncAwaitBestPractices;\nusing StabilityMatrix.Core.Services;\nusing Wpf.Ui.Common;\nusing Wpf.Ui.Controls;\nusing Wpf.Ui.Controls.IconElements;\nusing SnackbarService = Wpf.Ui.Services.SnackbarService;\n\nnamespace StabilityMatrix.Services;\n\npublic class NotificationBarService : SnackbarService, INotificationBarService\n{\n    private readonly ISettingsManager settingsManager;\n\n    public NotificationBarService(ISettingsManager settingsManager)\n    {\n        this.settingsManager = settingsManager;\n    }\n    \n    public void ShowStartupNotifications()\n    {\n        if (settingsManager.Settings.HasSeenWelcomeNotification) \n            return;\n        \n        Timeout = 10000;\n        var linkIcon = new SymbolIcon(SymbolRegular.Link24);\n        var snackbar = ShowAsync(\n            \"Welcome to StabilityMatrix!\",\n            \"You can join our Discord server for support and feedback.\", linkIcon, ControlAppearance.Info);\n        snackbar.SafeFireAndForget();\n        \n        settingsManager.Transaction(s => s.HasSeenWelcomeNotification = true);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/Services/PageContentDialogService.cs",
    "content": "﻿using Wpf.Ui.Services;\n\nnamespace StabilityMatrix.Services;\n\npublic class PageContentDialogService : ContentDialogService\n{\n    \n}\n"
  },
  {
    "path": "StabilityMatrix/Services/PageService.cs",
    "content": "﻿using System;\nusing System.Windows;\nusing Wpf.Ui.Contracts;\n\nnamespace StabilityMatrix.Services;\n\npublic class PageService : IPageService\n{\n    /// <summary>\n    /// Service which provides the instances of pages.\n    /// </summary>\n    private readonly IServiceProvider serviceProvider;\n\n    /// <summary>\n    /// Creates new instance and attaches the <see cref=\"IServiceProvider\"/>.\n    /// </summary>\n    public PageService(IServiceProvider serviceProvider)\n    {\n        this.serviceProvider = serviceProvider;\n    }\n\n    /// <inheritdoc />\n    public T? GetPage<T>() where T : class\n    {\n        if (!typeof(FrameworkElement).IsAssignableFrom(typeof(T)))\n            throw new InvalidOperationException(\"The page should be a WPF control.\");\n\n        return (T?)serviceProvider.GetService(typeof(T));\n    }\n\n    /// <inheritdoc />\n    public FrameworkElement? GetPage(Type pageType)\n    {\n        if (!typeof(FrameworkElement).IsAssignableFrom(pageType))\n            throw new InvalidOperationException(\"The page should be a WPF control.\");\n\n        return serviceProvider.GetService(pageType) as FrameworkElement;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/SettingsPage.xaml",
    "content": "<Page\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Loaded=\"SettingsPage_OnLoaded\"\n    d:DataContext=\"{d:DesignInstance Type=viewModels:SettingsViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"1300\"\n    d:DesignWidth=\"1100\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.SettingsPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"using:StabilityMatrix\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:models=\"using:StabilityMatrix.Models\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:xaml=\"clr-namespace:MdXaml;assembly=MdXaml\">\n\n    <Page.Resources>\n        <BooleanToVisibilityConverter x:Key=\"BoolToVisibilityConverter\" />\n        <xaml:Markdown\n            AssetPathRoot=\"{x:Static system:Environment.CurrentDirectory}\"\n            DocumentStyle=\"{StaticResource DocumentStyle}\"\n            Heading1Style=\"{StaticResource H1Style}\"\n            Heading2Style=\"{StaticResource H2Style}\"\n            Heading3Style=\"{StaticResource H3Style}\"\n            Heading4Style=\"{StaticResource H4Style}\"\n            ImageStyle=\"{StaticResource ImageStyle}\"\n            LinkStyle=\"{StaticResource LinkStyle}\"\n            SeparatorStyle=\"{StaticResource SeparatorStyle}\"\n            x:Key=\"Markdown\" />\n        <xaml:TextToFlowDocumentConverter Markdown=\"{StaticResource Markdown}\" x:Key=\"TextToFlowDocumentConverter\" />\n\n        <Style\n            BasedOn=\"{StaticResource {x:Type ui:Flyout}}\"\n            TargetType=\"{x:Type ui:Flyout}\"\n            x:Key=\"VersionFlyoutStyle\">\n            <Setter Property=\"Popup.StaysOpen\" Value=\"True\" />\n        </Style>\n    </Page.Resources>\n\n    <!--  Scroll view  -->\n    <ScrollViewer HorizontalScrollBarVisibility=\"Auto\">\n        <Grid>\n            <StackPanel Orientation=\"Vertical\">\n                <ui:Card Margin=\"8,16,8,8\">\n                    <StackPanel HorizontalAlignment=\"Left\" Orientation=\"Vertical\">\n                        <TextBlock\n                            FontSize=\"16\"\n                            FontWeight=\"Bold\"\n                            Margin=\"0,8\"\n                            Text=\"Theme\" />\n                        <ComboBox\n                            ItemsSource=\"{Binding AvailableThemes}\"\n                            Margin=\"8\"\n                            SelectedItem=\"{Binding SelectedTheme, Mode=TwoWay}\"\n                            Width=\"500\" />\n                    </StackPanel>\n                </ui:Card>\n\n                <!--  Text2Image host port settings  -->\n                <ui:Card Margin=\"8\" Visibility=\"{Binding IsDebugModeEnabled, Converter={StaticResource BoolToVisibilityConverter}}\">\n                    <StackPanel HorizontalAlignment=\"Left\">\n                        <TextBlock\n                            FontSize=\"16\"\n                            FontWeight=\"Bold\"\n                            Margin=\"0,8\"\n                            Text=\"Text2Image\" />\n                        <TextBlock\n                            Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                            Margin=\"4,0,0,4\"\n                            Text=\"Uses the launch settings of the active package by default\" />\n                        <Grid>\n                            <Grid.ColumnDefinitions>\n                                <ColumnDefinition Width=\"*\" />\n                                <ColumnDefinition Width=\"0.2*\" />\n                            </Grid.ColumnDefinitions>\n                            <StackPanel>\n                                <Grid>\n                                    <Grid.ColumnDefinitions>\n                                        <ColumnDefinition Width=\"0.2*\" />\n                                        <ColumnDefinition Width=\"*\" />\n                                    </Grid.ColumnDefinitions>\n                                    <TextBlock\n                                        Margin=\"8\"\n                                        Text=\"Host\"\n                                        VerticalAlignment=\"Center\" />\n                                    <ui:TextBox\n                                        ClearButtonEnabled=\"True\"\n                                        Grid.Column=\"1\"\n                                        Margin=\"8\"\n                                        PlaceholderEnabled=\"True\"\n                                        PlaceholderText=\"{Binding WebApiActivePackageHost}\"\n                                        Width=\"280\" />\n                                </Grid>\n                                <Grid>\n                                    <Grid.ColumnDefinitions>\n                                        <ColumnDefinition Width=\"0.2*\" />\n                                        <ColumnDefinition Width=\"*\" />\n                                    </Grid.ColumnDefinitions>\n                                    <TextBlock\n                                        Margin=\"8\"\n                                        Text=\"Port\"\n                                        VerticalAlignment=\"Center\" />\n                                    <ui:TextBox\n                                        ClearButtonEnabled=\"True\"\n                                        Grid.Column=\"1\"\n                                        Margin=\"8\"\n                                        PlaceholderEnabled=\"True\"\n                                        PlaceholderText=\"{Binding WebApiActivePackagePort}\"\n                                        Width=\"280\" />\n                                </Grid>\n                            </StackPanel>\n                            <controls:RefreshBadge DataContext=\"{Binding Text2ImageRefreshBadge}\" Grid.Column=\"1\" />\n                        </Grid>\n                    </StackPanel>\n                </ui:Card>\n\n                <ui:Card Margin=\"8\">\n                    <StackPanel HorizontalAlignment=\"Left\" Orientation=\"Vertical\">\n                        <TextBlock\n                            FontSize=\"16\"\n                            FontWeight=\"Bold\"\n                            Margin=\"0,8\"\n                            Text=\"Checkpoint Manager Options\" />\n                        <CheckBox \n                            IsChecked=\"{Binding KeepFolderLinksOnShutdown}\"\n                            Margin=\"8\" \n                            ToolTip=\"Normally removed on shutdown to allow the data folder to be moved without potential file explorer issues.\"\n                            Content=\"Keep shared checkpoints directory symbolic links on shutdown\"/>\n                    </StackPanel>\n                </ui:Card>\n                \n                <ui:Card Margin=\"8\">\n                    <StackPanel HorizontalAlignment=\"Left\" Orientation=\"Vertical\">\n                        <TextBlock\n                            FontSize=\"16\"\n                            FontWeight=\"Bold\"\n                            Margin=\"0,8\"\n                            Text=\"Embedded Python\" />\n                        <StackPanel Orientation=\"Horizontal\">\n                            <ui:Button\n                                Command=\"{Binding PythonVersionCommand}\"\n                                Content=\"Check Version Info\"\n                                Margin=\"8\" />\n                            <!--  Progress for python install if needed  -->\n                            <StackPanel Orientation=\"Horizontal\" Visibility=\"{Binding IsPythonInstalling, Converter={StaticResource BoolToVisibilityConverter}, FallbackValue=Collapsed}\">\n                                <ui:ProgressRing\n                                    Height=\"24\"\n                                    IsEnabled=\"{Binding IsPythonInstalling}\"\n                                    IsIndeterminate=\"True\"\n                                    Margin=\"8\"\n                                    Width=\"24\" />\n                                <TextBlock\n                                    Margin=\"4\"\n                                    Text=\"Preparing Environment\"\n                                    VerticalAlignment=\"Center\" />\n                            </StackPanel>\n                        </StackPanel>\n                    </StackPanel>\n                </ui:Card>\n\n                <ui:Card Margin=\"8\" Visibility=\"{Binding IsDebugModeEnabled, Converter={StaticResource BoolToVisibilityConverter}}\">\n                    <StackPanel HorizontalAlignment=\"Left\" Orientation=\"Vertical\">\n                        <TextBlock\n                            FontSize=\"16\"\n                            FontWeight=\"Bold\"\n                            Margin=\"0,8\"\n                            Text=\"Debug\" />\n                        <TextBlock Margin=\"8\" Text=\"{Binding GpuInfo, FallbackValue=3dfx Voodoo 5 6000 - 128MB VRAM}\" />\n                        <TextBlock Margin=\"8\" Text=\"{Binding TestProperty, FallbackValue=TestProperty}\" />\n                        <ui:Flyout\n                            HorizontalAlignment=\"Right\"\n                            IsOpen=\"{Binding IsFileSearchFlyoutOpen, Mode=TwoWay}\"\n                            Placement=\"Top\"\n                            Style=\"{StaticResource VersionFlyoutStyle}\">\n                            <ProgressBar MinWidth=\"100\" Value=\"{Binding FileSearchProgress}\" />\n                        </ui:Flyout>\n                        <StackPanel Orientation=\"Horizontal\">\n                            <Button\n                                Command=\"{Binding AddInstallationCommand}\"\n                                Content=\"Add Installation\"\n                                Margin=\"8\" />\n                            <Button\n                                Command=\"{Binding PingWebApiCommand}\"\n                                Content=\"Ping Web API\"\n                                Margin=\"8\" />\n                            <Button\n                                Command=\"{Binding DebugTriggerExceptionCommand}\"\n                                Content=\"Trigger Exception\"\n                                Margin=\"8\" />\n                            <Button\n                                Command=\"{Binding ModelFileSearchCommand}\"\n                                Content=\"Model File Search\"\n                                Margin=\"8\" />\n                            <Button\n                                Command=\"{Binding WebViewDemoCommand}\"\n                                Content=\"Open WebView\"\n                                Margin=\"8\" />\n                        </StackPanel>\n                    </StackPanel>\n                </ui:Card>\n\n                <ui:Card Margin=\"8\">\n                    <StackPanel HorizontalAlignment=\"Left\" Orientation=\"Vertical\">\n                        <TextBlock\n                            FontSize=\"16\"\n                            FontWeight=\"Bold\"\n                            Margin=\"0,8\"\n                            Text=\"Directories\" />\n                        <StackPanel Orientation=\"Horizontal\">\n                            <ui:Button\n                                Command=\"{Binding OpenAppDataDirectoryCommand}\"\n                                Content=\"App Data\"\n                                Margin=\"8\">\n                                <ui:Button.Icon>\n                                    <ui:SymbolIcon Margin=\"4\" Symbol=\"Open32\" />\n                                </ui:Button.Icon>\n                            </ui:Button>\n                            <ui:Button\n                                Command=\"{Binding OpenLibraryDirectoryCommand}\"\n                                Content=\"Library\"\n                                Margin=\"8\">\n                                <ui:Button.Icon>\n                                    <ui:SymbolIcon Margin=\"4\" Symbol=\"Open32\" />\n                                </ui:Button.Icon>\n                            </ui:Button>\n                        </StackPanel>\n                    </StackPanel>\n                </ui:Card>\n\n                <ui:Card Margin=\"8\">\n                    <StackPanel HorizontalAlignment=\"Left\" Orientation=\"Vertical\">\n                        <TextBlock\n                            FontSize=\"16\"\n                            FontWeight=\"Bold\"\n                            Margin=\"0,8\"\n                            Text=\"About\" />\n                        <ui:Image\n                            Height=\"112\"\n                            HorizontalAlignment=\"Left\"\n                            HorizontalContentAlignment=\"Left\"\n                            Margin=\"8\"\n                            Source=\"pack://application:,,,/Assets/Icon.png\"\n                            Width=\"112\" />\n                        <TextBlock\n                            FontWeight=\"Medium\"\n                            Margin=\"8\"\n                            Text=\"Stability Matrix\" />\n                        <Grid>\n                            <ui:Flyout\n                                Content=\"{Binding VersionFlyoutText}\"\n                                IsOpen=\"{Binding IsVersionFlyoutOpen, Mode=TwoWay}\"\n                                Name=\"VersionFlyout\"\n                                Placement=\"Top\"\n                                Style=\"{StaticResource VersionFlyoutStyle}\"\n                                Visibility=\"Visible\" />\n                            <Button\n                                Background=\"Transparent\"\n                                BorderThickness=\"0\"\n                                Command=\"{Binding AppVersionClickCommand}\"\n                                Content=\"{Binding AppVersion, Mode=OneWay, FallbackValue=Version 0.0.0}\"\n                                Margin=\"8,0,8,8\"\n                                Padding=\"2,0,2,0\" />\n                        </Grid>\n\n\n                        <StackPanel HorizontalAlignment=\"Left\" Orientation=\"Horizontal\">\n                            <ui:Button\n                                Command=\"{Binding OpenLicenseDialogCommand}\"\n                                Content=\"License and Open Source Notices\"\n                                HorizontalAlignment=\"Left\"\n                                Margin=\"8\" />\n                        </StackPanel>\n                    </StackPanel>\n                </ui:Card>\n            </StackPanel>\n        </Grid>\n    </ScrollViewer>\n</Page>\n"
  },
  {
    "path": "StabilityMatrix/SettingsPage.xaml.cs",
    "content": "using System.Windows;\nusing MdXaml;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Controls.Navigation;\n\nnamespace StabilityMatrix\n{\n    /// <summary>\n    /// An empty page that can be used on its own or navigated to within a Frame.\n    /// </summary>\n    public partial class SettingsPage : INavigableView<SettingsViewModel>\n    {\n        public SettingsPage(SettingsViewModel viewModel)\n        {\n            ViewModel = viewModel;\n            DataContext = ViewModel;\n\n            InitializeComponent();\n            \n            var textToFlowDocumentConverter = Resources[\"TextToFlowDocumentConverter\"] as TextToFlowDocumentConverter;\n            ViewModel.TextToFlowDocumentConverter = textToFlowDocumentConverter;\n        }\n\n        private void SettingsPage_OnLoaded(object sender, RoutedEventArgs e)\n        {\n            ViewModel.OnLoaded();\n        }\n\n        public SettingsViewModel ViewModel { get; }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/StabilityMatrix.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <OutputType>WinExe</OutputType>\n    <TargetFramework>net7.0-windows10.0.17763.0</TargetFramework>\n    <Nullable>enable</Nullable>\n    <UseWPF>true</UseWPF>\n    <SelfContained>true</SelfContained>\n    <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>\n    <ApplicationIcon>Assets\\Icon.ico</ApplicationIcon>\n    <LangVersion>11</LangVersion>\n    <SatelliteResourceLanguages>en</SatelliteResourceLanguages>\n    <DefaultItemExcludes>Assets\\**</DefaultItemExcludes>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"7-Zip.CommandLine\" Version=\"18.1.0\" />\n    <PackageReference Include=\"AsyncAwaitBestPractices\" Version=\"6.0.6\" />\n    <PackageReference Include=\"Blake3\" Version=\"0.5.1\" />\n    <PackageReference Include=\"CommunityToolkit.Mvvm\" Version=\"8.2.1\" />\n    <PackageReference Include=\"FuzzySharp\" Version=\"2.0.2\" />\n    <PackageReference Include=\"LiteDB\" Version=\"5.0.16\" />\n    <PackageReference Include=\"LiteDB.Async\" Version=\"0.1.6\" />\n    <PackageReference Include=\"MdXaml\" Version=\"1.20.1\" />\n    <PackageReference Include=\"Microsoft.Extensions.Caching.Memory\" Version=\"7.0.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration.Json\" Version=\"7.0.0\" />\n      <PackageReference Include=\"Microsoft.Extensions.DependencyInjection\" Version=\"7.0.0\" />\n      <PackageReference Include=\"Microsoft.Extensions.Http.Polly\" Version=\"7.0.8\" />\n      <PackageReference Include=\"Microsoft.Extensions.Options.ConfigurationExtensions\" Version=\"7.0.0\" />\n      <PackageReference Include=\"Microsoft.Toolkit.Uwp.Notifications\" Version=\"7.1.3\" />\n      <PackageReference Include=\"Microsoft.Web.WebView2\" Version=\"1.0.1823.32\" />\n      <PackageReference Include=\"Microsoft.Xaml.Behaviors.Wpf\" Version=\"1.1.39\" />\n      <PackageReference Include=\"NLog\" Version=\"5.2.2\" />\n      <PackageReference Include=\"NLog.Extensions.Logging\" Version=\"5.3.2\" />\n      <PackageReference Include=\"NSec.Cryptography\" Version=\"22.4.0\" />\n      <PackageReference Include=\"Octokit\" Version=\"7.0.1\" />\n      <PackageReference Include=\"Ookii.Dialogs.Wpf\" Version=\"5.0.1\" />\n      <PackageReference Include=\"Polly\" Version=\"7.2.4\" />\n      <PackageReference Include=\"Polly.Contrib.WaitAndRetry\" Version=\"1.1.1\" />\n      <PackageReference Include=\"Refit\" Version=\"7.0.0\" />\n      <PackageReference Include=\"Refit.HttpClientFactory\" Version=\"7.0.0\" />\n      <PackageReference Include=\"Salaros.ConfigParser\" Version=\"0.3.8\" />\n      <PackageReference Include=\"Sentry\" Version=\"3.33.1\" />\n      <PackageReference Include=\"Sentry.NLog\" Version=\"3.33.1\" />\n      <PackageReference Include=\"SharpCompress\" Version=\"0.33.0\" />\n      <PackageReference Include=\"Simple.Wpf.Terminal\" Version=\"2.3.57\" />\n      <PackageReference Include=\"System.Reactive\" Version=\"6.0.0\" />\n      <PackageReference Include=\"WebView2.Runtime.AutoInstaller\" Version=\"1.0.0\" />\n      <PackageReference Include=\"WPF-UI\" Version=\"3.0.0-preview.2\" />\n      <PackageReference Include=\"pythonnet\" Version=\"3.0.1\" />\n  </ItemGroup>\n\n    <ItemGroup>\n        <InternalsVisibleTo Include=\"StabilityMatrix.Tests\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <Resource Include=\"Assets\\Icon.ico\" />\n        <None Update=\"appsettings.json\">\n            <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n        </None>\n        <None Update=\"appsettings.Development.json\">\n            <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n        </None>\n        <Resource Include=\"Assets\\Icon.png\" />\n        <Resource Include=\"Assets\\licenses.json\" />\n        <Resource Include=\"Assets\\noimage.png\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <EmbeddedResource Include=\"Assets\\7za.exe\" />\n        <EmbeddedResource Include=\"Assets\\7za - LICENSE.txt\" />\n        <EmbeddedResource Include=\"Assets\\get-pip.pyc\" />\n        <EmbeddedResource Include=\"Assets\\venv\\**\" />\n    </ItemGroup>\n\n  <ItemGroup>\n    <Page Update=\"InstallerWindow.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <XamlRuntime>Wpf</XamlRuntime>\n      <SubType>Designer</SubType>\n    </Page>\n    <Page Update=\"OneClickInstallDialog.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <XamlRuntime>Wpf</XamlRuntime>\n      <SubType>Designer</SubType>\n    </Page>\n    <Page Update=\"CheckpointBrowserPage.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <XamlRuntime>Wpf</XamlRuntime>\n      <SubType>Designer</SubType>\n    </Page>\n    <Page Update=\"SelectInstallLocationsDialog.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <XamlRuntime>Wpf</XamlRuntime>\n      <SubType>Designer</SubType>\n    </Page>\n    <Page Update=\"WebLoginDialog.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <XamlRuntime>Wpf</XamlRuntime>\n      <SubType>Designer</SubType>\n    </Page>\n    <Page Update=\"SelectModelVersionDialog.xaml\">\n      <Generator>MSBuild:Compile</Generator>\n      <XamlRuntime>Wpf</XamlRuntime>\n      <SubType>Designer</SubType>\n    </Page>\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\StabilityMatrix.Core\\StabilityMatrix.Core.csproj\" />\n  </ItemGroup>\n  \n</Project>\n"
  },
  {
    "path": "StabilityMatrix/Styles/Styles.xaml",
    "content": "﻿<ResourceDictionary xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n                    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n                    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n    <Style TargetType=\"FlowDocument\" x:Key=\"DocumentStyle\">\n        <Setter Property=\"FontFamily\"\n                Value=\"Calibri\" />\n        <Setter Property=\"TextAlignment\"\n                Value=\"Left\" />\n        </Style>\n\n        <Style x:Key=\"H1Style\"\n               TargetType=\"Paragraph\">\n            <Setter Property=\"FontSize\"\n                    Value=\"42\" />\n            <Setter Property=\"Foreground\"\n                    Value=\"{ui:ThemeResource TextFillColorPrimaryBrush}\" />\n            <Setter Property=\"FontWeight\"\n                    Value=\"Light\" />\n        </Style>\n\n        <Style x:Key=\"H2Style\"\n               TargetType=\"Paragraph\">\n            <Setter Property=\"FontSize\"\n                    Value=\"20\" />\n            <Setter Property=\"Foreground\"\n                    Value=\"{ui:ThemeResource TextFillColorPrimaryBrush}\" />\n            <Setter Property=\"FontWeight\"\n                    Value=\"Light\" />\n        </Style>\n\n        <Style x:Key=\"H3Style\"\n               TargetType=\"Paragraph\">\n            <Setter Property=\"FontSize\"\n                    Value=\"20\" />\n            <Setter Property=\"Foreground\"\n                    Value=\"{ui:ThemeResource TextFillColorPrimaryBrush}\" />\n            <Setter Property=\"FontWeight\"\n                    Value=\"Light\" />\n        </Style>\n\n        <Style x:Key=\"H4Style\"\n               TargetType=\"Paragraph\">\n            <Setter Property=\"FontSize\"\n                    Value=\"14\" />\n            <Setter Property=\"Foreground\"\n                    Value=\"{ui:ThemeResource TextFillColorPrimaryBrush}\" />\n            <Setter Property=\"FontWeight\"\n                    Value=\"Light\" />\n        </Style>\n\n        <Style x:Key=\"LinkStyle\"\n               TargetType=\"Hyperlink\">\n            <Setter Property=\"TextDecorations\"\n                    Value=\"None\" />\n        </Style>\n\n        <Style x:Key=\"ImageStyle\"\n               TargetType=\"Image\">\n          <Setter Property=\"RenderOptions.BitmapScalingMode\"\n                  Value=\"NearestNeighbor\" />\n          <Style.Triggers>\n              <Trigger Property=\"Tag\"\n                       Value=\"imageright\">\n                  <Setter Property=\"Margin\" \n                          Value=\"20,0,0,0\" />\n              </Trigger>\n          </Style.Triggers>\n      </Style>\n\n      <Style x:Key=\"SeparatorStyle\"\n             TargetType=\"Separator\">\n          <!--<Setter Property=\"X2\"\n                  Value=\"{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=FlowDocumentScrollViewer}}\" />\n          <Setter Property=\"Stroke\"\n                  Value=\"#99000000\" />\n          <Setter Property=\"StrokeThickness\"\n                  Value=\"2\" />-->\n      </Style>\n\n      <!-- \n         The Table's style don't seem to support border-collapse. \n         By making the ruled line width 0.5 and applying it to cell and table, \n         it looks like the ruled lines are not doubled. \n     --> \n     <Style x:Key=\"TableStyle\" TargetType=\"Table\"> \n         <Setter Property=\"CellSpacing\" Value=\"0\"/> \n         <Setter Property=\"BorderThickness\" Value=\"0.5\"/> \n         <Setter Property=\"BorderBrush\" Value=\"Gray\"/> \n         <Style.Resources> \n             <Style TargetType=\"TableCell\"> \n                 <Setter Property=\"BorderThickness\" Value=\"0.5\"/> \n                 <Setter Property=\"BorderBrush\" Value=\"Gray\"/> \n                 <Setter Property=\"Padding\" Value=\"2\"/> \n             </Style> \n         </Style.Resources> \n     </Style> \n     <Style x:Key=\"TableHeaderStyle\" TargetType=\"TableRowGroup\"> \n         <Setter Property=\"FontWeight\" Value=\"DemiBold\"/> \n         <Setter Property=\"Background\" Value=\"LightGray\"/> \n     </Style> \n\n      <DrawingImage x:Key=\"PatreonIconColored\">\n              <DrawingImage.Drawing>\n                      <DrawingGroup ClipGeometry=\"M0,-4.5 V251.5 H256 V0 H-4.5 Z\">\n                              <DrawingGroup.Transform>\n                                      <TranslateTransform X=\"0\" Y=\"4.5\" />\n                              </DrawingGroup.Transform>\n                              <DrawingGroup Opacity=\"1\">\n                                      <GeometryDrawing Brush=\"#FFFF424D\" Geometry=\"F1 M256,256z M0,0z M45.1355837,0L45.1355837,246.35001 0,246.35001 0,0 45.1355837,0z M163.657111,0C214.65668,0 256,41.3433196 256,92.3428889 256,143.342458 214.65668,184.685778 163.657111,184.685778 112.657542,184.685778 71.3142222,143.342458 71.3142222,92.3428889 71.3142222,41.3433196 112.657542,0 163.657111,0z\" />\n                              </DrawingGroup>\n                      </DrawingGroup>\n              </DrawingImage.Drawing>\n      </DrawingImage>\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix/TextToImagePage.xaml",
    "content": "﻿<Page\n    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    Loaded=\"TextToImagePage_OnLoaded\"\n    Title=\"TextToImagePage\"\n    d:DataContext=\"{d:DesignInstance Type=viewModels:TextToImageViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"700\"\n    d:DesignWidth=\"1100\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.TextToImagePage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:StabilityMatrix\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n\n    <Grid>\n        <Grid Margin=\"20\">\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"*\" />\n                <RowDefinition Height=\"Auto\" />\n            </Grid.RowDefinitions>\n\n            <!--  Controls  -->\n            <Grid>\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition Width=\"*\" />\n                    <ColumnDefinition Width=\"*\" />\n                </Grid.ColumnDefinitions>\n\n                <ScrollViewer\n                    HorizontalAlignment=\"Stretch\"\n                    Margin=\"8,8,8,16\"\n                    VerticalAlignment=\"Stretch\">\n                    <VirtualizingStackPanel>\n                        <!--  Checkpoint  -->\n                        <ui:Card\n                            HorizontalAlignment=\"Stretch\"\n                            Margin=\"0,4\"\n                            Padding=\"8\">\n                            <Grid>\n                                <Grid.ColumnDefinitions>\n                                    <ColumnDefinition Width=\"Auto\" />\n                                    <ColumnDefinition Width=\"*\" />\n                                </Grid.ColumnDefinitions>\n                                <TextBlock\n                                    FontSize=\"15\"\n                                    Grid.Column=\"0\"\n                                    Margin=\"8\"\n                                    Text=\"Model\"\n                                    VerticalAlignment=\"Center\" />\n                                <ComboBox\n                                    DisplayMemberPath=\"FileName\"\n                                    Grid.Column=\"1\"\n                                    ItemsSource=\"{Binding DiffusionCheckpointFolder.CheckpointFiles}\"\n                                    Margin=\"8\"\n                                    MinWidth=\"200\"\n                                    SelectedValuePath=\"FileName\" />\n                            </Grid>\n                        </ui:Card>\n                        <!--  Prompt card  -->\n                        <ui:Card\n                            HorizontalAlignment=\"Stretch\"\n                            Margin=\"0,4\"\n                            Padding=\"8\">\n                            <StackPanel Margin=\"8\">\n                                <Label\n                                    Content=\"Prompt\"\n                                    FontSize=\"14\"\n                                    Margin=\"2\" />\n                                <ui:AutoSuggestBox\n                                    Margin=\"8\"\n                                    Name=\"PositivePromptBox\"\n                                    PlaceholderText=\"Prompt\"\n                                    QuerySubmitted=\"PositivePromptBox_OnQuerySubmitted\"\n                                    SuggestionChosen=\"PositivePromptBox_OnSuggestionChosen\"\n                                    TextChanged=\"PositivePromptBox_OnTextChanged\" />\n                                <Label\n                                    Content=\"Negative Prompt\"\n                                    FontSize=\"14\"\n                                    Margin=\"2\" />\n                                <ui:AutoSuggestBox\n                                    Margin=\"8\"\n                                    Name=\"NegativePromptBox\"\n                                    PlaceholderText=\"Negative Prompt\"\n                                    QuerySubmitted=\"NegativePromptBox_OnQuerySubmitted\"\n                                    SuggestionChosen=\"NegativePromptBox_OnSuggestionChosen\"\n                                    TextChanged=\"NegativePromptBox_OnTextChanged\" />\n                            </StackPanel>\n                        </ui:Card>\n                        <!--  Samplers and steps  -->\n                        <ui:Card\n                            HorizontalAlignment=\"Stretch\"\n                            Margin=\"0,4\"\n                            Padding=\"8\">\n                            <VirtualizingStackPanel>\n                                <Grid Margin=\"8\">\n                                    <Grid.ColumnDefinitions>\n                                        <ColumnDefinition Width=\"Auto\" />\n                                        <ColumnDefinition Width=\"Auto\" />\n                                        <ColumnDefinition Width=\"*\" />\n                                        <ColumnDefinition Width=\"Auto\" />\n                                    </Grid.ColumnDefinitions>\n                                    <TextBlock\n                                        FontSize=\"15\"\n                                        Grid.Column=\"0\"\n                                        Margin=\"8\"\n                                        Text=\"Sampler\"\n                                        VerticalAlignment=\"Center\" />\n                                    <ComboBox\n                                        Grid.Column=\"1\"\n                                        ItemsSource=\"{Binding Samplers}\"\n                                        Margin=\"8\"\n                                        MinWidth=\"200\" />\n                                    <TextBlock\n                                        FontSize=\"14\"\n                                        Grid.Column=\"2\"\n                                        HorizontalAlignment=\"Right\"\n                                        Margin=\"8\"\n                                        Text=\"Steps\"\n                                        VerticalAlignment=\"Center\" />\n                                    <TextBox\n                                        FontSize=\"14\"\n                                        Grid.Column=\"3\"\n                                        Margin=\"8\"\n                                        Text=\"{Binding GenerationSteps}\"\n                                        VerticalAlignment=\"Bottom\"\n                                        Width=\"64\" />\n                                </Grid>\n\n                                <Slider\n                                    IsSnapToTickEnabled=\"True\"\n                                    Margin=\"8,0,8,8\"\n                                    Maximum=\"150\"\n                                    Minimum=\"0\"\n                                    TickFrequency=\"5\"\n                                    TickPlacement=\"BottomRight\"\n                                    Value=\"{Binding GenerationSteps}\" />\n                            </VirtualizingStackPanel>\n                        </ui:Card>\n                    </VirtualizingStackPanel>\n                </ScrollViewer>\n\n                <!--  Image Viewer  -->\n                <ui:Card\n                    Grid.Column=\"1\"\n                    HorizontalAlignment=\"Stretch\"\n                    Margin=\"8,8,8,16\"\n                    Padding=\"8\"\n                    VerticalAlignment=\"Top\">\n                    <Grid>\n                        <Grid.RowDefinitions>\n                            <RowDefinition Height=\"0.2*\" />\n                            <RowDefinition Height=\"0.7*\" />\n                        </Grid.RowDefinitions>\n                        <StackPanel\n                            Grid.Row=\"0\"\n                            Orientation=\"Vertical\"\n                            VerticalAlignment=\"Top\">\n                            <Label\n                                Content=\"Image\"\n                                FontSize=\"14\"\n                                Margin=\"2\" />\n                            <ProgressBar\n                                Maximum=\"100\"\n                                Value=\"{Binding ProgressValue, FallbackValue=10}\"\n                                Visibility=\"{Binding ProgressBarVisibility, FallbackValue=Visible}\"\n                                Width=\"500\" />\n                        </StackPanel>\n                        <Grid\n                            Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n                            Grid.Row=\"1\"\n                            MinHeight=\"512\"\n                            VerticalAlignment=\"Stretch\">\n                            <ui:ProgressRing\n                                Background=\"DarkRed\"\n                                Grid.Row=\"0\"\n                                Height=\"72\"\n                                HorizontalAlignment=\"Center\"\n                                IsIndeterminate=\"True\"\n                                Margin=\"8\"\n                                VerticalAlignment=\"Center\"\n                                Visibility=\"{Binding ProgressRingVisibility, FallbackValue=Collapsed}\"\n                                Width=\"72\"\n                                x:Name=\"ImageProgressRing\" />\n                            <Image\n                                HorizontalAlignment=\"Stretch\"\n                                Margin=\"8\"\n                                Source=\"{Binding ImagePreview}\"\n                                Stretch=\"Uniform\"\n                                VerticalAlignment=\"Stretch\"\n                                Visibility=\"{Binding ImagePreviewVisibility, FallbackValue=Visible}\" />\n                        </Grid>\n                    </Grid>\n                </ui:Card>\n            </Grid>\n\n            <ui:Button\n                Appearance=\"Primary\"\n                Command=\"{Binding TextToImageGenerateCommand}\"\n                Content=\"Generate Image\"\n                Grid.Row=\"1\"\n                HorizontalAlignment=\"Center\"\n                Name=\"TextToImageButton\" />\n\n        </Grid>\n        <ContentPresenter x:Name=\"PageContentDialog\" />\n    </Grid>\n</Page>\n"
  },
  {
    "path": "StabilityMatrix/TextToImagePage.xaml.cs",
    "content": "﻿using System.Windows;\nusing System.Windows.Controls;\nusing StabilityMatrix.Services;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Controls.AutoSuggestBoxControl;\n\nnamespace StabilityMatrix;\n\npublic sealed partial class TextToImagePage : Page\n{\n    private TextToImageViewModel ViewModel => (TextToImageViewModel) DataContext;\n    \n    public TextToImagePage(TextToImageViewModel viewModel, PageContentDialogService pageContentDialogService)\n    {\n        InitializeComponent();\n        DataContext = viewModel;\n        pageContentDialogService.SetContentPresenter(PageContentDialog);\n    }\n\n    private void PositivePromptBox_OnQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)\n    {\n    }\n\n    private void PositivePromptBox_OnSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)\n    {\n    }\n\n    private void PositivePromptBox_OnTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)\n    {\n        // Update the prompt text when the user types\n        if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)\n        {\n            var fullText = sender.Text;\n            ViewModel.PositivePromptText = fullText;\n        }\n    }\n\n    private void NegativePromptBox_OnQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)\n    {\n    }\n\n    private void NegativePromptBox_OnSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)\n    {\n    }\n\n    private void NegativePromptBox_OnTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)\n    {\n        // Update the prompt text when the user types\n        if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)\n        {\n            var fullText = sender.Text;\n            ViewModel.NegativePromptText = fullText;\n        }\n    }\n\n    private async void TextToImagePage_OnLoaded(object sender, RoutedEventArgs e)\n    {\n        await ViewModel.OnLoaded();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/UpdateWindow.xaml",
    "content": "﻿<ui:FluentWindow\n    ExtendsContentIntoTitleBar=\"True\"\n    Height=\"700\"\n    Icon=\"pack://application:,,,/Assets/Icon.ico\"\n    Loaded=\"UpdateWindow_OnLoaded\"\n    Title=\"Stability Matrix - Update\"\n    Width=\"700\"\n    WindowBackdropType=\"Mica\"\n    WindowStartupLocation=\"CenterOwner\"\n    d:DataContext=\"{d:DesignInstance Type=viewModels:UpdateWindowViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"700\"\n    d:DesignWidth=\"700\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.UpdateWindow\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:xaml=\"clr-namespace:MdXaml;assembly=MdXaml\">\n\n    <ui:FluentWindow.Resources>\n        <converters:ValueConverterGroup x:Key=\"InvertAndVisibilitate\">\n            <converters:BoolNegationConverter />\n            <BooleanToVisibilityConverter />\n        </converters:ValueConverterGroup>\n\n        <converters:BoolNegationConverter x:Key=\"BoolNegationConverter\" />\n        <BooleanToVisibilityConverter x:Key=\"BoolToVisConverter\" />\n        <converters:UriToBitmapConverter x:Key=\"UriToBitmapConverter\" />\n\n        <xaml:Markdown\n            AssetPathRoot=\"{x:Static system:Environment.CurrentDirectory}\"\n            DocumentStyle=\"{StaticResource DocumentStyle}\"\n            Heading1Style=\"{StaticResource H1Style}\"\n            Heading2Style=\"{StaticResource H2Style}\"\n            Heading3Style=\"{StaticResource H3Style}\"\n            Heading4Style=\"{StaticResource H4Style}\"\n            ImageStyle=\"{StaticResource ImageStyle}\"\n            LinkStyle=\"{StaticResource LinkStyle}\"\n            SeparatorStyle=\"{StaticResource SeparatorStyle}\"\n            x:Key=\"Markdown\" />\n        <xaml:TextToFlowDocumentConverter Markdown=\"{StaticResource Markdown}\" x:Key=\"TextToFlowDocumentConverter\" />\n    </ui:FluentWindow.Resources>\n\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n            <RowDefinition Height=\"Auto\" />\n        </Grid.RowDefinitions>\n        \n        <ui:TitleBar Background=\"{ui:ThemeResource ApplicationBackgroundBrush}\">\n            <ui:TitleBar.Header>\n                <TextBlock Margin=\"16,8\" Text=\"Stability Matrix - Update Available\" />\n            </ui:TitleBar.Header>\n        </ui:TitleBar>\n        \n        <TextBlock Grid.Row=\"1\" \n                   Text=\"A new version of Stability Matrix is available!\"\n                   HorizontalAlignment=\"Center\"\n                   FontWeight=\"Thin\"\n                   Margin=\"0,16,0,0\"\n                   FontSize=\"28\"/>\n        \n        <TextBlock Grid.Row=\"2\" \n                   HorizontalAlignment=\"Center\"\n                   FontSize=\"18\"\n                   TextWrapping=\"Wrap\"\n                   TextAlignment=\"Center\"\n                   Text=\"{Binding UpdateText, FallbackValue=Update available and stuff}\"\n                   Margin=\"16,32,16,0\"/>\n        \n        <TextBlock Grid.Row=\"3\" \n                   Text=\"Release Notes\" \n                   FontSize=\"16\"\n                   Visibility=\"{Binding ShowProgressBar, Converter={StaticResource InvertAndVisibilitate}}\"\n                   Margin=\"32,16,32,0\"/>\n        \n        <ProgressBar Grid.Row=\"4\" \n                     Height=\"200\"\n                     Value=\"{Binding ProgressValue}\"\n                     Visibility=\"{Binding ShowProgressBar, Converter={StaticResource BoolToVisConverter}}\"\n                     Margin=\"32\"/>\n        \n        <Grid Grid.Row=\"4\"\n              Visibility=\"{Binding ShowProgressBar, Converter={StaticResource InvertAndVisibilitate}}\">\n            <Border Margin=\"32, 16\"\n                    CornerRadius=\"16\"\n                    Background=\"#66000000\"/>\n            \n            <FlowDocumentScrollViewer\n                Margin=\"32,16\"\n                Document=\"{Binding ReleaseNotes, Converter={StaticResource TextToFlowDocumentConverter}}\"\n                HorizontalAlignment=\"Stretch\"\n                VerticalAlignment=\"Stretch\" />\n        </Grid>\n        <StackPanel Grid.Row=\"5\" Orientation=\"Horizontal\" HorizontalAlignment=\"Center\" Margin=\"0,0,0,16\">\n            <ui:Button Content=\"Remind Me Later\"\n                       Margin=\"0,0,8,0\"\n                       FontSize=\"18\"\n                       Click=\"RemindMeLaterButton_OnClick\"\n                       Appearance=\"Info\"/>\n            \n            <ui:Button Content=\"Install Now\"\n                       Margin=\"8,0,0,0\"\n                       FontSize=\"18\"\n                       Command=\"{Binding InstallUpdateCommand}\"\n                       Appearance=\"Success\"/>\n        </StackPanel>\n        \n    </Grid>\n</ui:FluentWindow>\n"
  },
  {
    "path": "StabilityMatrix/UpdateWindow.xaml.cs",
    "content": "﻿using System.Windows;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Controls.Window;\n\nnamespace StabilityMatrix;\n\npublic partial class UpdateWindow : FluentWindow\n{\n    private readonly UpdateWindowViewModel viewModel;\n\n    public UpdateWindow(UpdateWindowViewModel viewModel)\n    {\n        this.viewModel = viewModel;\n        InitializeComponent();\n        DataContext = viewModel;\n    }\n\n    private async void UpdateWindow_OnLoaded(object sender, RoutedEventArgs e)\n    {\n        await viewModel.OnLoaded();\n    }\n\n    private void RemindMeLaterButton_OnClick(object sender, RoutedEventArgs e)\n    {\n        Close();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/CheckpointBrowserCardViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing System.Windows.Media.Imaging;\nusing System.Windows.Threading;\nusing AsyncAwaitBestPractices;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NLog;\nusing Refit;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Helper;\nusing Wpf.Ui.Controls;\nusing Wpf.Ui.Controls.ContentDialogControl;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class CheckpointBrowserCardViewModel : ProgressViewModel\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private readonly IDownloadService downloadService;\n    private readonly ISnackbarService snackbarService;\n    private readonly ISettingsManager settingsManager;\n    private readonly IDialogFactory dialogFactory;\n    public CivitModel CivitModel { get; init; }\n    public BitmapImage CardImage { get; set; }\n\n    public override Visibility ProgressVisibility => Value > 0 ? Visibility.Visible : Visibility.Collapsed;\n    public override Visibility TextVisibility => Value > 0 ? Visibility.Visible : Visibility.Collapsed;\n\n    [ObservableProperty] private bool isImporting;\n\n    public CheckpointBrowserCardViewModel(\n        CivitModel civitModel, \n        IDownloadService downloadService, \n        ISnackbarService snackbarService, \n        ISettingsManager settingsManager,\n        IDialogFactory dialogFactory,\n        BitmapImage? fixedImage = null)\n    {\n        this.downloadService = downloadService;\n        this.snackbarService = snackbarService;\n        this.settingsManager = settingsManager;\n        this.dialogFactory = dialogFactory;\n        CivitModel = civitModel;\n\n        if (fixedImage != null)\n        {\n            CardImage = fixedImage;\n            return;\n        }\n        CardImage = GetImage();\n        \n        // Update image when nsfw setting changes\n        settingsManager.RegisterPropertyChangedHandler(\n            s => s.ModelBrowserNsfwEnabled,\n            _ => CardImage = GetImage());\n    }\n    \n    // Choose and load image based on nsfw setting\n    private BitmapImage GetImage()\n    {\n        var nsfwEnabled = settingsManager.Settings.ModelBrowserNsfwEnabled;\n        var version = CivitModel.ModelVersions?.FirstOrDefault();\n        var images = version?.Images;\n\n        var image = images?.FirstOrDefault(image => nsfwEnabled || image.Nsfw == \"None\");\n        if (image != null)\n        {\n            return new BitmapImage(new Uri(image.Url));\n        }\n        // Otherwise Default image\n        return new BitmapImage(\n            new Uri(\"pack://application:,,,/StabilityMatrix;component/Assets/noimage.png\"));\n    }\n\n    // On any mode changes, update the image\n    private void OnNsfwModeChanged(object? sender, bool value)\n    {\n        CardImage = GetImage();\n    }\n\n    [RelayCommand]\n    private void OpenModel()\n    {\n        ProcessRunner.OpenUrl($\"https://civitai.com/models/{CivitModel.Id}\");\n    }\n\n    [RelayCommand]\n    private async Task Import(CivitModel model)\n    {\n        await DoImport(model);\n    }\n\n    [RelayCommand]\n    private async Task ShowVersionDialog(CivitModel model)\n    {\n        var dialog = dialogFactory.CreateSelectModelVersionDialog(model);\n        var result = await dialog.ShowAsync();\n\n        if (result != ContentDialogResult.Primary)\n        {\n            return;\n        }\n\n        var viewModel = dialog.DataContext as SelectModelVersionDialogViewModel;\n        var selectedVersion = viewModel?.SelectedVersion;\n        var selectedFile = viewModel?.SelectedFile;\n        \n        await Task.Delay(100);\n        await DoImport(model, selectedVersion, selectedFile);\n    }\n\n    private async Task DoImport(CivitModel model, CivitModelVersion? selectedVersion = null, CivitFile? selectedFile = null)\n    {\n        IsImporting = true;\n        Text = \"Downloading...\";\n\n        // Holds files to be deleted on errors\n        var filesForCleanup = new HashSet<FilePath>();\n        \n        // Set Text when exiting, finally block will set 100 and delay clear progress\n        try\n        {\n            // Get latest version\n            var modelVersion = selectedVersion ?? model.ModelVersions?.FirstOrDefault();\n            if (modelVersion is null)\n            {\n                snackbarService.ShowSnackbarAsync(\n                    \"This model has no versions available for download\",\n                    \"Model has no versions available\", ControlAppearance.Caution).SafeFireAndForget();\n                Text = \"Unable to Download\";\n                return;\n            }\n            \n            // Get latest version file\n            var modelFile = selectedFile ?? modelVersion.Files?.FirstOrDefault();\n            if (modelFile is null)\n            {\n                snackbarService.ShowSnackbarAsync(\n                    \"This model has no files available for download\",\n                    \"Model has no files available\", ControlAppearance.Caution).SafeFireAndForget();\n                Text = \"Unable to Download\";\n                return;\n            }\n            \n            var downloadFolder = Path.Combine(settingsManager.ModelsDirectory,\n                model.Type.ConvertTo<SharedFolderType>().GetStringValue());\n            // Folders might be missing if user didn't install any packages yet\n            Directory.CreateDirectory(downloadFolder);\n            var downloadPath = Path.GetFullPath(Path.Combine(downloadFolder, modelFile.Name));\n            filesForCleanup.Add(downloadPath);\n            \n            // Do the download\n            var downloadTask = downloadService.DownloadToFileAsync(modelFile.DownloadUrl, downloadPath, \n                new Progress<ProgressReport>(report =>\n                {\n                    Application.Current.Dispatcher.Invoke(() =>\n                    {\n                        Value = report.Percentage;\n                        Text = $\"Downloading... {report.Percentage}%\";\n                    });\n                }));\n            \n            var downloadResult = await snackbarService.TryAsync(downloadTask, \"Could not download file\");\n            \n            // Failed download handling\n            if (downloadResult.Exception is not null)\n            {\n                // For exceptions other than ApiException or TaskCanceledException, log error\n                var logLevel = downloadResult.Exception switch\n                {\n                    HttpRequestException or ApiException or TaskCanceledException => LogLevel.Warn,\n                    _ => LogLevel.Error\n                };\n                Logger.Log(logLevel, downloadResult.Exception, \"Error during model download\");\n                \n                Text = \"Download Failed\";\n                return;\n            }\n            \n            // When sha256 is available, validate the downloaded file\n            var fileExpectedSha256 = modelFile.Hashes.SHA256;\n            if (!string.IsNullOrEmpty(fileExpectedSha256))\n            {\n                var hashProgress = new Progress<ProgressReport>(progress =>\n                {\n                    Value = progress.Percentage;\n                    Text = $\"Validating... {progress.Percentage}%\";\n                });\n                var sha256 = await FileHash.GetSha256Async(downloadPath, hashProgress);\n                if (sha256 != fileExpectedSha256.ToLowerInvariant())\n                {\n                    Text = \"Import Failed!\";\n                    DelayedClearProgress(TimeSpan.FromMilliseconds(800));\n                    snackbarService.ShowSnackbarAsync(\n                        \"This may be caused by network or server issues from CivitAI, please try again in a few minutes.\",\n                        \"Download failed hash validation\").SafeFireAndForget();\n                    Text = \"Download Failed\";\n                    return;\n                }\n                snackbarService.ShowSnackbarAsync($\"{model.Type} {model.Name} imported successfully!\",\n                    \"Import complete\", ControlAppearance.Info).SafeFireAndForget();\n            }\n            \n            IsIndeterminate = true;\n            \n            // Save connected model info\n            var modelFileName = Path.GetFileNameWithoutExtension(modelFile.Name);\n            var modelInfo = new ConnectedModelInfo(CivitModel, modelVersion, modelFile, DateTime.UtcNow);\n            var modelInfoPath = Path.GetFullPath(Path.Combine(\n                downloadFolder, modelFileName + ConnectedModelInfo.FileExtension));\n            filesForCleanup.Add(modelInfoPath);\n            await modelInfo.SaveJsonToDirectory(downloadFolder, modelFileName);\n            \n            // If available, save a model image\n            if (modelVersion.Images != null && modelVersion.Images.Any())\n            {\n                var image = modelVersion.Images[0];\n                var imageExtension = Path.GetExtension(image.Url).TrimStart('.');\n                if (imageExtension is \"jpg\" or \"jpeg\" or \"png\")\n                {\n                    var imageDownloadPath = Path.GetFullPath(Path.Combine(downloadFolder, $\"{modelFileName}.preview.{imageExtension}\"));\n                    filesForCleanup.Add(imageDownloadPath);\n                    var imageTask = downloadService.DownloadToFileAsync(image.Url, imageDownloadPath);\n                    await snackbarService.TryAsync(imageTask, \"Could not download preview image\");\n                }\n            }\n            \n            // Successful - clear cleanup list\n            filesForCleanup.Clear();\n                \n            Text = \"Import complete!\";\n        }\n        finally\n        {\n            foreach (var file in filesForCleanup.Where(file => file.Exists))\n            {\n                file.Delete();\n                Logger.Info($\"Download cleanup: Deleted file {file}\");\n            }\n            IsIndeterminate = false;\n            Value = 100;\n            DelayedClearProgress(TimeSpan.FromMilliseconds(800));\n        }\n    }\n    \n    private void DelayedClearProgress(TimeSpan delay)\n    {\n        Task.Delay(delay).ContinueWith(_ =>\n        {\n            Text = string.Empty;\n            Value = 0;\n            IsImporting = false;\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/CheckpointBrowserViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.ComponentModel;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NLog;\nusing System.Diagnostics;\nusing System.Net.Http;\nusing System.Windows.Data;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Helper;\nusing ApiException = Refit.ApiException;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class CheckpointBrowserViewModel : ObservableObject\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private readonly ICivitApi civitApi;\n    private readonly IDownloadService downloadService;\n    private readonly ISnackbarService snackbarService;\n    private readonly ISettingsManager settingsManager;\n    private readonly IDialogFactory dialogFactory;\n    private readonly ILiteDbContext liteDbContext;\n    private const int MaxModelsPerPage = 14;\n\n    [ObservableProperty] private ObservableCollection<CheckpointBrowserCardViewModel>? modelCards;\n    [ObservableProperty] private ICollectionView? modelCardsView;\n\n    [ObservableProperty] private string searchQuery = string.Empty;\n    [ObservableProperty] private bool showNsfw;\n    [ObservableProperty] private bool showMainLoadingSpinner;\n    [ObservableProperty] private CivitPeriod selectedPeriod = CivitPeriod.Month;\n    [ObservableProperty] private CivitSortMode sortMode = CivitSortMode.HighestRated;\n    [ObservableProperty] private CivitModelType selectedModelType = CivitModelType.Checkpoint;\n    [ObservableProperty] private int currentPageNumber;\n    [ObservableProperty] private int totalPages;\n    [ObservableProperty] private bool hasSearched;\n    [ObservableProperty] private bool canGoToNextPage;\n    [ObservableProperty] private bool canGoToPreviousPage;\n    [ObservableProperty] private bool isIndeterminate;\n    [ObservableProperty] private bool noResultsFound;\n    [ObservableProperty] private string noResultsText;\n    \n    public IEnumerable<CivitPeriod> AllCivitPeriods => Enum.GetValues(typeof(CivitPeriod)).Cast<CivitPeriod>();\n    public IEnumerable<CivitSortMode> AllSortModes => Enum.GetValues(typeof(CivitSortMode)).Cast<CivitSortMode>();\n\n    public IEnumerable<CivitModelType> AllModelTypes => Enum.GetValues(typeof(CivitModelType))\n        .Cast<CivitModelType>()\n        .Where(t => t == CivitModelType.All || t.ConvertTo<SharedFolderType>() > 0)\n        .OrderBy(t => t.ToString());\n\n    public CheckpointBrowserViewModel(\n        ICivitApi civitApi, \n        IDownloadService downloadService, \n        ISnackbarService snackbarService, \n        ISettingsManager settingsManager,\n        IDialogFactory dialogFactory,\n        ILiteDbContext liteDbContext)\n    {\n        this.civitApi = civitApi;\n        this.downloadService = downloadService;\n        this.snackbarService = snackbarService;\n        this.settingsManager = settingsManager;\n        this.dialogFactory = dialogFactory;\n        this.liteDbContext = liteDbContext;\n        \n        CurrentPageNumber = 1;\n        CanGoToNextPage = true;\n    }\n\n    public void OnLoaded()\n    {\n        var searchOptions = settingsManager.Settings.ModelSearchOptions;\n        \n        SelectedPeriod = searchOptions?.SelectedPeriod ?? CivitPeriod.Month;\n        SortMode = searchOptions?.SortMode ?? CivitSortMode.HighestRated;\n        SelectedModelType = searchOptions?.SelectedModelType ?? CivitModelType.Checkpoint;\n        \n        ShowNsfw = settingsManager.Settings.ModelBrowserNsfwEnabled;\n        \n        settingsManager.RelayPropertyFor(this, model => model.ShowNsfw,\n            settings => settings.ModelBrowserNsfwEnabled);\n    }\n\n    /// <summary>\n    /// Filter predicate for model cards\n    /// </summary>\n    private bool FilterModelCardsPredicate(object? item)\n    {\n        if (item is not CheckpointBrowserCardViewModel card) return false;\n        return !card.CivitModel.Nsfw || ShowNsfw;\n    }\n\n    /// <summary>\n    /// Background update task\n    /// </summary>\n    private async Task CivitModelQuery(CivitModelsRequest request)\n    {\n        var timer = Stopwatch.StartNew();\n        var queryText = request.Query;\n        try\n        {\n            var modelsResponse = await civitApi.GetModels(request);\n            var models = modelsResponse.Items;\n            if (models is null)\n            {\n                Logger.Debug(\"CivitAI Query {Text} returned no results (in {Elapsed:F1} s)\",\n                    queryText, timer.Elapsed.TotalSeconds);\n                return;\n            }\n\n            Logger.Debug(\"CivitAI Query {Text} returned {Results} results (in {Elapsed:F1} s)\",\n                queryText, models.Count, timer.Elapsed.TotalSeconds);\n\n            var unknown = models.Where(m => m.Type == CivitModelType.Unknown).ToList();\n            if (unknown.Any())\n            {\n                var names = unknown.Select(m => m.Name).ToList();\n                Logger.Warn(\"Excluded {Unknown} unknown model types: {Models}\", unknown.Count,\n                    names);\n            }\n\n            // Filter out unknown model types and archived/taken-down models\n            models = models.Where(m => m.Type.ConvertTo<SharedFolderType>() > 0)\n                .Where(m => m.Mode == null).ToList();\n\n            // Database update calls will invoke `OnModelsUpdated`\n            // Add to database\n            await liteDbContext.UpsertCivitModelAsync(models);\n            // Add as cache entry\n            var cacheNew = await liteDbContext.UpsertCivitModelQueryCacheEntryAsync(new()\n            {\n                Id = ObjectHash.GetMd5Guid(request),\n                InsertedAt = DateTimeOffset.UtcNow,\n                Request = request,\n                Items = models,\n                Metadata = modelsResponse.Metadata\n            });\n\n            if (cacheNew)\n            {\n                Logger.Debug(\"New cache entry, updating model cards\");\n                UpdateModelCards(models, modelsResponse.Metadata);\n            }\n            else\n            {\n                Logger.Debug(\"Cache entry already exists, not updating model cards\");\n            }\n        }\n        catch (OperationCanceledException)\n        {\n            snackbarService.ShowSnackbarAsync(\"Request to CivitAI timed out\",\n                \"Please try again in a few minutes\").SafeFireAndForget();\n            Logger.Warn($\"CivitAI query timed out ({request})\");\n        }\n        catch (HttpRequestException e)\n        {\n            snackbarService.ShowSnackbarAsync(\"CivitAI can't be reached right now\",\n                \"Please try again in a few minutes\").SafeFireAndForget();\n            Logger.Warn(e, $\"CivitAI query HttpRequestException ({request})\");\n        }\n        catch (ApiException e)\n        {\n            snackbarService.ShowSnackbarAsync(\"CivitAI can't be reached right now\",\n                \"Please try again in a few minutes\").SafeFireAndForget();\n            Logger.Warn(e, $\"CivitAI query ApiException ({request})\");\n        }\n        catch (Exception e)\n        {\n            snackbarService.ShowSnackbarAsync($\"Please try again in a few minutes\",\n                $\"Unknown exception during CivitAI query: {e.GetType().Name}\").SafeFireAndForget();\n            Logger.Error(e, $\"CivitAI query unknown exception ({request})\");\n        }\n        finally\n        {\n            ShowMainLoadingSpinner = false;\n            UpdateResultsText();\n        }\n    }\n    \n    /// <summary>\n    /// Updates model cards using api response object.\n    /// </summary>\n    private void UpdateModelCards(IEnumerable<CivitModel>? models, CivitMetadata? metadata) \n    {\n        if (models is null)\n        {\n            ModelCards?.Clear();\n        }\n        else\n        {\n            var updateCards = models\n                .Select(model => new CheckpointBrowserCardViewModel(model, \n                    downloadService, snackbarService, settingsManager, dialogFactory));\n            ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>(updateCards);\n        }\n        TotalPages = metadata?.TotalPages ?? 1;\n        CanGoToPreviousPage = CurrentPageNumber > 1;\n        CanGoToNextPage = CurrentPageNumber < TotalPages;\n        // Status update\n        ShowMainLoadingSpinner = false;\n        IsIndeterminate = false;\n        HasSearched = true;\n    }\n\n    private string previousSearchQuery = string.Empty;\n\n    [RelayCommand]\n    private async Task SearchModels()\n    {\n        var timer = Stopwatch.StartNew();\n        \n        if (SearchQuery != previousSearchQuery)\n        {\n            // Reset page number\n            CurrentPageNumber = 1;\n            previousSearchQuery = SearchQuery;\n        }\n        \n        // Build request\n        var modelRequest = new CivitModelsRequest\n        {\n            Limit = MaxModelsPerPage,\n            Nsfw = \"true\", // Handled by local view filter\n            Sort = SortMode,\n            Period = SelectedPeriod,\n            Page = CurrentPageNumber\n        };\n\n        if (SearchQuery.StartsWith(\"#\"))\n        {\n            modelRequest.Tag = SearchQuery[1..];\n        }\n        else if (SearchQuery.StartsWith(\"@\"))\n        {\n            modelRequest.Username = SearchQuery[1..];\n        }\n        else\n        {\n            modelRequest.Query = SearchQuery;\n        }\n        \n        if (SelectedModelType != CivitModelType.All)\n        {\n            modelRequest.Types = new[] {SelectedModelType};\n        }\n        \n        // See if query is cached\n        var cachedQuery = await liteDbContext.CivitModelQueryCache\n            .IncludeAll()\n            .FindByIdAsync(ObjectHash.GetMd5Guid(modelRequest));\n        \n        // If cached, update model cards\n        if (cachedQuery is not null)\n        {\n            var elapsed = timer.Elapsed;\n            Logger.Debug(\"Using cached query for {Text} [{RequestHash}] (in {Elapsed:F1} s)\", \n                SearchQuery, modelRequest.GetHashCode(), elapsed.TotalSeconds);\n            UpdateModelCards(cachedQuery.Items, cachedQuery.Metadata);\n\n            // Start remote query (background mode)\n            // Skip when last query was less than 2 min ago\n            var timeSinceCache = DateTimeOffset.UtcNow - cachedQuery.InsertedAt;\n            if (timeSinceCache?.TotalMinutes >= 2)\n            {\n                CivitModelQuery(modelRequest).SafeFireAndForget();\n                Logger.Debug(\n                    \"Cached query was more than 2 minutes ago ({Seconds:F0} s), updating cache with remote query\",\n                    timeSinceCache.Value.TotalSeconds);\n            }\n        }\n        else\n        {\n            // Not cached, wait for remote query\n            ShowMainLoadingSpinner = true;\n            await CivitModelQuery(modelRequest);\n        }\n        \n        UpdateResultsText();\n    }\n\n    [RelayCommand]\n    private async Task PreviousPage()\n    {\n        if (CurrentPageNumber == 1) return;\n\n        CurrentPageNumber--;\n        await TrySearchAgain(false);\n    }\n\n    [RelayCommand]\n    private async Task NextPage()\n    {\n        CurrentPageNumber++;\n        await TrySearchAgain(false);\n    }\n\n    // On changes to ModelCards, update the view source\n    partial void OnModelCardsChanged(ObservableCollection<CheckpointBrowserCardViewModel>? value)\n    {\n        if (value is null)\n        {\n            ModelCardsView = null;\n        }\n        // Create new view\n        var view = new ListCollectionView(value!)\n        {\n            Filter = FilterModelCardsPredicate,\n        };\n        ModelCardsView = view;\n    }\n    \n    partial void OnShowNsfwChanged(bool value)\n    {\n        settingsManager.Transaction(s => s.ModelBrowserNsfwEnabled = value);\n        ModelCardsView?.Refresh();\n        \n        if (!HasSearched) \n            return;\n        \n        UpdateResultsText();\n    }\n\n    partial void OnSelectedPeriodChanged(CivitPeriod oldValue, CivitPeriod newValue)\n    {\n        TrySearchAgain().SafeFireAndForget();\n        settingsManager.Transaction(s => s.ModelSearchOptions = new ModelSearchOptions(\n                newValue, SortMode, SelectedModelType));\n    }\n\n    partial void OnSortModeChanged(CivitSortMode oldValue, CivitSortMode newValue)\n    {\n        TrySearchAgain().SafeFireAndForget();\n        settingsManager.Transaction(s => s.ModelSearchOptions = new ModelSearchOptions(\n                SelectedPeriod, newValue, SelectedModelType));\n    }\n    \n    partial void OnSelectedModelTypeChanged(CivitModelType oldValue, CivitModelType newValue)\n    {\n        TrySearchAgain().SafeFireAndForget();\n        settingsManager.Transaction(s => s.ModelSearchOptions = new ModelSearchOptions(\n            SelectedPeriod, SortMode, newValue));\n    }\n\n    private async Task TrySearchAgain(bool shouldUpdatePageNumber = true)\n    {\n        if (!HasSearched) return;\n        ModelCards?.Clear();\n\n        if (shouldUpdatePageNumber)\n        {\n            CurrentPageNumber = 1;\n        }\n\n        // execute command instead of calling method directly so that the IsRunning property gets updated\n        await SearchModelsCommand.ExecuteAsync(null);\n    }\n\n    private void UpdateResultsText()\n    {\n        NoResultsFound = ModelCardsView?.IsEmpty ?? true;\n        NoResultsText = ModelCards?.Count > 0\n            ? $\"{ModelCards.Count} results hidden by filters\"\n            : \"No results found\";\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/CheckpointManagerViewModel.cs",
    "content": "﻿using System.Collections.Concurrent;\nusing System.Collections.ObjectModel;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NLog;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Helper;\nusing StabilityMatrix.Models;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class CheckpointManagerViewModel : ObservableObject\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private readonly ISharedFolders sharedFolders;\n    private readonly ISettingsManager settingsManager;\n    private readonly IDialogFactory dialogFactory;\n    private readonly ModelFinder modelFinder;\n    private readonly IDownloadService downloadService;\n\n    // Toggle button for auto hashing new drag-and-dropped files for connected upgrade\n    [ObservableProperty] private bool isImportAsConnected;\n    \n    partial void OnIsImportAsConnectedChanged(bool value)\n    {\n        if (settingsManager.IsLibraryDirSet && \n            value != settingsManager.Settings.IsImportAsConnected)\n        {\n            settingsManager.Transaction(s => s.IsImportAsConnected = value);\n        }\n    }\n    \n    public ObservableCollection<CheckpointFolder> CheckpointFolders { get; set; } = new();\n    \n    public CheckpointManagerViewModel(\n        ISharedFolders sharedFolders, \n        ISettingsManager settingsManager, \n        IDialogFactory dialogFactory,\n        IDownloadService downloadService,\n        ModelFinder modelFinder)\n    {\n        this.sharedFolders = sharedFolders;\n        this.settingsManager = settingsManager;\n        this.dialogFactory = dialogFactory;\n        this.downloadService = downloadService;\n        this.modelFinder = modelFinder;\n    }\n    \n    public async Task OnLoaded()\n    {\n        // Set UI states\n        IsImportAsConnected = settingsManager.Settings.IsImportAsConnected;\n        \n        var modelsDirectory = settingsManager.ModelsDirectory;\n        // Get all folders within the shared folder root\n        if (string.IsNullOrWhiteSpace(modelsDirectory))\n        {\n            return;\n        }\n        // Skip if the shared folder root doesn't exist\n        if (!Directory.Exists(modelsDirectory))\n        {\n            Logger.Debug($\"Skipped shared folder index - {modelsDirectory} doesn't exist\");\n            return;\n        }\n        var folders = Directory.GetDirectories(modelsDirectory);\n        \n        CheckpointFolders.Clear();\n\n        // Results\n        var indexedFolders = new ConcurrentBag<CheckpointFolder>();\n        // Index all folders\n        var tasks = folders.Select(f => Task.Run(async () =>\n            {\n                var checkpointFolder = new CheckpointFolder(dialogFactory, settingsManager, downloadService, modelFinder)\n                {\n                    Title = Path.GetFileName(f), \n                    DirectoryPath = f\n                };\n                await checkpointFolder.IndexAsync();\n                indexedFolders.Add(checkpointFolder);\n            })).ToList();\n        await Task.WhenAll(tasks);\n        // Add to observable collection by alphabetical order\n        foreach (var checkpointFolder in indexedFolders.OrderBy(f => f.Title))\n        {\n            CheckpointFolders.Add(checkpointFolder);\n        }\n    }\n\n    [RelayCommand]\n    private void OpenModelsFolder()\n    {\n        Process.Start(\"explorer.exe\", settingsManager.ModelsDirectory);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/DataDirectoryMigrationViewModel.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NLog;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class DataDirectoryMigrationViewModel : ObservableObject\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private readonly ISettingsManager settingsManager;\n    private readonly IPrerequisiteHelper prerequisiteHelper;\n    private readonly IPyRunner pyRunner;\n    private readonly ISharedFolders sharedFolders;\n    private readonly IPackageFactory packageFactory;\n\n    [ObservableProperty] \n    [NotifyPropertyChangedFor(nameof(AutoMigrateText))]\n    [NotifyPropertyChangedFor(nameof(MigrateProgressText))]\n    private int autoMigrateCount;\n    \n    [ObservableProperty] \n    [NotifyPropertyChangedFor(nameof(NeedsMoveMigrateText))]\n    [NotifyPropertyChangedFor(nameof(MigrateProgressText))]\n    private int needsMoveMigrateCount;\n    \n    [ObservableProperty] \n    [NotifyPropertyChangedFor(nameof(MigrateProgressText))]\n    private int migrateProgressCount;\n    \n    [ObservableProperty] private bool isMigrating;\n    [ObservableProperty] private bool canShowNoThanksButton;\n    \n    [ObservableProperty] \n    [NotifyPropertyChangedFor(nameof(NeedsMoveMigrateText))]\n    [NotifyPropertyChangedFor(nameof(MigrateProgressText))]\n    private bool hasFreeSpaceError;\n\n    public string AutoMigrateText => AutoMigrateCount == 0 ? string.Empty :\n        $\"{AutoMigrateCount} Packages will be automatically migrated to the new format\";\n\n    public string NeedsMoveMigrateText => NeedsMoveMigrateCount == 0 || HasFreeSpaceError\n        ? string.Empty\n        : $\"{NeedsMoveMigrateCount} Package{(NeedsMoveMigrateCount > 1 ? \"s\" : \"\")} {(NeedsMoveMigrateCount > 1 ? \"are\" : \"is\")} \" +\n          \"not relative to the Data Directory and will be moved, this may take a few minutes\";\n\n    [ObservableProperty]\n    private string migrateProgressText = \"\";\n\n    partial void OnMigrateProgressCountChanged(int value)\n    {\n        MigrateProgressText = value > 0 ? $\"Migrating {value} of {AutoMigrateCount + NeedsMoveMigrateCount} Packages\" : string.Empty;\n    }\n\n    public DataDirectoryMigrationViewModel(ISettingsManager settingsManager,\n        IPrerequisiteHelper prerequisiteHelper, IPyRunner pyRunner, ISharedFolders sharedFolders,\n        IPackageFactory packageFactory)\n    {\n        this.settingsManager = settingsManager;\n        this.prerequisiteHelper = prerequisiteHelper;\n        this.pyRunner = pyRunner;\n        this.sharedFolders = sharedFolders;\n        this.packageFactory = packageFactory;\n    }\n\n    public async Task OnLoaded()\n    {\n        AutoMigrateCount = 0;\n        NeedsMoveMigrateCount = 0;\n        HasFreeSpaceError = false;\n        \n        // Get all old packages\n        var oldPackages = settingsManager.GetOldInstalledPackages().ToArray();\n        // Attempt to migrate with pure, and count successful migrations\n        AutoMigrateCount = oldPackages.Count(p => p.CanPureMigratePath());\n        // Any remaining packages need to be moved\n        NeedsMoveMigrateCount = oldPackages.Length - AutoMigrateCount;\n\n        var oldLibraryDir =\n            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),\n                \"StabilityMatrix\");\n        CanShowNoThanksButton = settingsManager.LibraryDir != oldLibraryDir;\n        \n\n        if (settingsManager.LibraryDir != oldLibraryDir)\n        {\n            var oldDir = new DirectoryPath(oldLibraryDir);\n            var size = await oldDir.GetSizeAsync(includeSymbolicLinks: false);\n\n            // If there's not enough space in the new DataDirectory, show warning\n            if (size > new DriveInfo(settingsManager.LibraryDir).AvailableFreeSpace)\n            {\n                HasFreeSpaceError = true;\n            }\n        }\n    }\n    \n    public void CleanupOldInstall()\n    {\n        var oldLibraryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), \"StabilityMatrix\");\n        if (settingsManager.LibraryDir == oldLibraryPath) return;\n        \n        // rename old settings.json to settings.json.old\n        var oldSettingsPath = Path.Combine(oldLibraryPath, \"settings.json\");\n        File.Move(oldSettingsPath, oldSettingsPath + \".old\", true);\n            \n        // delete old PortableGit dir\n        var oldPortableGitDir = Path.Combine(oldLibraryPath, \"PortableGit\");\n        if (Directory.Exists(oldPortableGitDir))\n        {\n            Directory.Delete(oldPortableGitDir, true);\n        }\n            \n        // delete old Assets dir\n        var oldAssetsDir = Path.Combine(oldLibraryPath, \"Assets\");\n        if (Directory.Exists(oldAssetsDir))\n        {\n            Directory.Delete(oldAssetsDir, true);\n        }\n    }\n    \n    [RelayCommand]\n    private async Task MigrateAsync()\n    {\n        await using var delay = new MinimumDelay(200, 300);\n\n        IsMigrating = true;\n        \n        // Since we are going to recreate venvs, need python to be installed\n        if (!prerequisiteHelper.IsPythonInstalled)\n        {\n            MigrateProgressText = \"Preparing Environment...\";\n            await prerequisiteHelper.InstallPythonIfNecessary();\n        }\n        \n        if (!PyRunner.PipInstalled)\n        {\n            await pyRunner.SetupPip();\n        }\n            \n        if (!PyRunner.VenvInstalled)\n        {\n            await pyRunner.InstallPackage(\"virtualenv\");\n        }\n\n        var libraryPath = settingsManager.LibraryDir;\n        var oldLibraryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), \"StabilityMatrix\");\n        var oldPackages = settingsManager.GetOldInstalledPackages().ToArray();\n\n        MigrateProgressText = \"Migrating Packages...\";\n        \n        await using var st = settingsManager.BeginTransaction();\n        \n        foreach (var package in oldPackages)\n        {\n            st.Settings.RemoveInstalledPackageAndUpdateActive(package);\n\n#pragma warning disable CS0618\n            Logger.Info($\"Migrating package {MigrateProgressCount} of {oldPackages.Length} at path {package.Path}\");\n#pragma warning restore CS0618\n            \n            await package.MigratePath();\n            MigrateProgressCount++;\n            st.Settings.InstalledPackages.Add(package);\n            st.Settings.ActiveInstalledPackageId = package.Id;\n\n            if (oldLibraryPath != libraryPath)\n            {\n                // setup model links again\n                if (!string.IsNullOrWhiteSpace(package.PackageName))\n                {\n                    var basePackage = packageFactory.FindPackageByName(package.PackageName);\n                    if (basePackage != default)\n                    {\n                        sharedFolders.SetupLinksForPackage(basePackage, package.FullPath!);\n                    }\n                }\n            }\n\n            // Also recreate the venv\n            var venvPath = Path.Combine(libraryPath, package.FullPath!);\n            var venv = new PyVenvRunner(venvPath);\n            await venv.Setup(existsOk: true);\n        }\n\n        // Copy models directory\n        if (oldLibraryPath != libraryPath)\n        {\n            MigrateProgressText = \"Copying models...\";\n            var oldModelsDir = Path.Combine(oldLibraryPath, \"Models\");\n            var newModelsDir = Path.Combine(libraryPath, \"Models\");\n            await Task.Run(() => Utilities.CopyDirectory(oldModelsDir, newModelsDir, true));\n\n            MigrateProgressText = \"Cleaning up...\";\n            await Task.Run(CleanupOldInstall);\n        }\n\n        IsMigrating = false;\n        EventManager.Instance.OnInstalledPackagesChanged();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/ExceptionWindowViewModel.cs",
    "content": "﻿using System;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic class ExceptionWindowViewModel\n{\n    public Exception Exception { get; set; }\n    \n    public string ExceptionType => Exception.GetType().Name;\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/FirstLaunchSetupViewModel.cs",
    "content": "﻿using System.Linq;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing StabilityMatrix.Controls;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class FirstLaunchSetupViewModel : ObservableObject\n{\n    [ObservableProperty]\n    private bool eulaAccepted;\n\n    [ObservableProperty]\n    private string gpuInfoText = string.Empty;\n\n    [ObservableProperty]\n    private RefreshBadgeViewModel checkHardwareBadge = new()\n    {\n        WorkingToolTipText = \"We're checking some hardware specifications to determine compatibility.\",\n        SuccessToolTipText = \"Everything looks good!\",\n        FailToolTipText = \"We recommend a GPU with CUDA support for the best experience. \" +\n                          \"You can continue without one, but some packages may not work, and inference may be slower.\",\n        FailColorBrush = AppBrushes.WarningYellow,\n    };\n\n    public FirstLaunchSetupViewModel()\n    {\n        CheckHardwareBadge.RefreshFunc = SetGpuInfo;\n    }\n\n    private async Task<bool> SetGpuInfo()\n    {\n        GpuInfo[] gpuInfo;\n        await using (new MinimumDelay(800, 1200))\n        {\n            // Query GPU info\n            gpuInfo = await Task.Run(() => HardwareHelper.IterGpuInfo().ToArray());\n        }\n        // First Nvidia GPU\n        var activeGpu = gpuInfo.FirstOrDefault(gpu => gpu.Name?.ToLowerInvariant().Contains(\"nvidia\") ?? false);\n        var isNvidia = activeGpu is not null;\n        // Otherwise first GPU\n        activeGpu ??= gpuInfo.FirstOrDefault();\n        GpuInfoText = activeGpu is null\n            ? \"No GPU detected\"\n            : $\"{activeGpu.Name} ({Size.FormatBytes(activeGpu.MemoryBytes)})\";\n        \n        return isNvidia;\n    }\n\n    public void OnLoaded()\n    {\n        CheckHardwareBadge.RefreshCommand.ExecuteAsync(null).SafeFireAndForget();\n    }\n\n    [RelayCommand]\n    private void OpenLicenseLink()\n    {\n        ProcessRunner.OpenUrl(\"https://lykos.ai/matrix/license\");\n    }\n\n\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/InstallerViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.ObjectModel;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing System.Windows.Media;\nusing System.Windows.Media.Imaging;\nusing System.Windows.Threading;\nusing AsyncAwaitBestPractices;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Microsoft.Extensions.Logging;\nusing Octokit;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Services;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls;\nusing Wpf.Ui.Controls.ContentDialogControl;\nusing Application = System.Windows.Application;\nusing EventManager = StabilityMatrix.Core.Helper.EventManager;\nusing ISnackbarService = StabilityMatrix.Helper.ISnackbarService;\nusing PackageVersion = StabilityMatrix.Core.Models.PackageVersion;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class InstallerViewModel : ObservableObject\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly ILogger<InstallerViewModel> logger;\n    private readonly IPyRunner pyRunner;\n    private readonly ISharedFolders sharedFolders;\n    private readonly IPrerequisiteHelper prerequisiteHelper;\n    private readonly IContentDialogService contentDialogService;\n    private readonly ISnackbarService snackbarService;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ProgressBarVisibility))]\n    private int progressValue;\n    \n    [ObservableProperty]\n    private BasePackage selectedPackage;\n    \n    [ObservableProperty]\n    private string progressText;\n\n    [ObservableProperty] \n    private string secondaryProgressText;\n    \n    [ObservableProperty]\n    private bool isIndeterminate;\n    \n    [ObservableProperty]\n    private Visibility packageInstalledVisibility;\n    \n    [ObservableProperty]\n    private string installButtonText;\n\n    [ObservableProperty] \n    private string installName;\n    \n    [ObservableProperty]\n    private ObservableCollection<PackageVersion> availableVersions;\n\n    [ObservableProperty] \n    private PackageVersion selectedVersion;\n\n    [ObservableProperty] \n    private ObservableCollection<BasePackage> availablePackages;\n    \n    [ObservableProperty]\n    private ObservableCollection<GitHubCommit> availableCommits;\n    \n    [ObservableProperty]\n    private GitHubCommit selectedCommit;\n\n    [ObservableProperty] \n    private string releaseNotes;\n\n    [ObservableProperty]\n    private bool isReleaseMode;\n\n    [ObservableProperty] \n    private bool isReleaseModeEnabled;\n\n    [ObservableProperty]\n    private bool showDuplicateWarning;\n\n\n    public Visibility ProgressBarVisibility => ProgressValue > 0 || IsIndeterminate ? Visibility.Visible : Visibility.Collapsed;\n\n    public string ReleaseLabelText => IsReleaseMode ? \"Version\" : \"Branch\";\n\n    internal event EventHandler? PackageInstalled;\n\n\n    public InstallerViewModel(ISettingsManager settingsManager, ILogger<InstallerViewModel> logger, IPyRunner pyRunner,\n        IPackageFactory packageFactory, ISnackbarService snackbarService, ISharedFolders sharedFolders,\n        IPrerequisiteHelper prerequisiteHelper, InstallerWindowDialogService contentDialogService)\n    {\n        this.settingsManager = settingsManager;\n        this.logger = logger;\n        this.pyRunner = pyRunner;\n        this.sharedFolders = sharedFolders;\n        this.prerequisiteHelper = prerequisiteHelper;\n        this.contentDialogService = contentDialogService;\n        this.snackbarService = snackbarService;\n\n        ProgressText = \"\";\n        SecondaryProgressText = \"\";\n        InstallButtonText = \"Install\";\n        ProgressValue = 0;\n        IsReleaseMode = true;\n        IsReleaseModeEnabled = true;\n\n        AvailablePackages = new ObservableCollection<BasePackage>(packageFactory.GetAllAvailablePackages());\n        if (!AvailablePackages.Any()) return;\n\n        SelectedPackage = AvailablePackages[0];\n        InstallName = SelectedPackage.DisplayName;\n    }\n\n    public async Task OnLoaded()\n    {\n        if (SelectedPackage == null)\n            return;\n        \n        if (SelectedPackage.ShouldIgnoreReleases)\n        {\n            IsReleaseMode = false;\n        }\n\n        if (IsReleaseMode)\n        {\n            var versions = (await SelectedPackage.GetAllVersions()).ToList();\n            AvailableVersions = new ObservableCollection<PackageVersion>(versions);\n            if (!AvailableVersions.Any())\n                return;\n            SelectedVersion = AvailableVersions[0];\n        }\n        else\n        {\n            var branches = (await SelectedPackage.GetAllBranches()).ToList();\n            AvailableVersions = new ObservableCollection<PackageVersion>(branches.Select(b => new PackageVersion\n            {\n                TagName = b.Name,\n                ReleaseNotesMarkdown = b.Commit.Label\n            }));\n            SelectedVersion = AvailableVersions.FirstOrDefault(x => x.TagName == \"master\") ?? AvailableVersions[0];\n        }\n\n        ReleaseNotes = SelectedVersion.ReleaseNotesMarkdown;\n        \n        ShowDuplicateWarning =\n            settingsManager.Settings.InstalledPackages.Any(p =>\n                p.LibraryPath.Equals($\"Packages\\\\{InstallName}\"));\n    }\n    \n    [RelayCommand]\n    private async Task Install()\n    {\n        await ActuallyInstall();\n        snackbarService.ShowSnackbarAsync($\"Package {SelectedPackage.Name} installed successfully!\",\n            \"Success\", ControlAppearance.Success).SafeFireAndForget();\n        OnPackageInstalled();\n    }\n\n    [RelayCommand]\n    private async Task ShowPreview()\n    {\n        var bitmap = new BitmapImage(SelectedPackage.PreviewImageUri);\n        var dialog = contentDialogService.CreateDialog();\n        dialog.Content = new Image\n        {\n            Source = bitmap, \n            Stretch = Stretch.Uniform, \n            MaxHeight = 500,\n            HorizontalAlignment = HorizontalAlignment.Center\n        };\n        dialog.PrimaryButtonText = \"Open in Browser\";\n        \n        var result = await dialog.ShowAsync();\n        if (result == ContentDialogResult.Primary)\n        {\n            Process.Start(new ProcessStartInfo\n            {\n                FileName = SelectedPackage.PreviewImageUri.ToString(),\n                UseShellExecute = true\n            });\n        }\n    }\n\n    partial void OnSelectedPackageChanged(BasePackage? value)\n    {\n        if (value == null) return;\n        \n        InstallName = value.DisplayName;\n        ReleaseNotes = string.Empty;\n        AvailableVersions?.Clear();\n        AvailableCommits?.Clear();\n\n        // This can swallow exceptions if you don't explicitly try/catch\n        // Idk how to make it better tho\n        Task.Run(async () =>\n        {\n            if (SelectedPackage.ShouldIgnoreReleases)\n            {\n                Application.Current.Dispatcher.Invoke(() =>\n                {\n                    IsReleaseMode = false;\n                    IsReleaseModeEnabled = false;\n                });\n            }\n            else\n            {\n                Application.Current.Dispatcher.Invoke(() => { IsReleaseModeEnabled = true; });\n            }\n            \n            var versions = (await value.GetAllVersions(IsReleaseMode)).ToList();\n            if (!versions.Any())\n                return;\n\n            Application.Current.Dispatcher.Invoke(() =>\n            {\n                AvailableVersions = new ObservableCollection<PackageVersion>(versions);\n                SelectedVersion = AvailableVersions[0];\n                ReleaseNotes = versions.First().ReleaseNotesMarkdown;\n            });\n            \n            if (!IsReleaseMode)\n            {\n                var commits = await value.GetAllCommits(SelectedVersion.TagName);\n                Application.Current.Dispatcher.Invoke(() =>\n                {\n                    AvailableCommits = new ObservableCollection<GitHubCommit>(commits);\n                    SelectedCommit = AvailableCommits[0];\n                    SelectedVersion = AvailableVersions.FirstOrDefault(x => x.TagName == \"master\");\n                });\n            }\n        });\n\n    }\n\n    partial void OnIsReleaseModeChanged(bool oldValue, bool newValue)\n    {\n        OnSelectedPackageChanged(SelectedPackage);\n    }\n\n    partial void OnInstallNameChanged(string? oldValue, string newValue)\n    {\n        ShowDuplicateWarning =\n            settingsManager.Settings.InstalledPackages.Any(p =>\n                p.LibraryPath.Equals($\"Packages\\\\{newValue}\"));\n    }\n\n    partial void OnSelectedVersionChanged(PackageVersion? value)\n    {\n        ReleaseNotes = value?.ReleaseNotesMarkdown ?? string.Empty;\n        if (value == null) return;\n        \n        SelectedCommit = null;\n        AvailableCommits?.Clear();\n        \n        if (!IsReleaseMode)\n        {\n            Task.Run(async () =>\n            {\n                try\n                {\n                    var hashes = await SelectedPackage.GetAllCommits(value.TagName);\n\n                    Application.Current.Dispatcher.Invoke(() =>\n                    {\n                        AvailableCommits = new ObservableCollection<GitHubCommit>(hashes);\n                        SelectedCommit = AvailableCommits[0];\n                    });\n                }\n                catch (Exception e)\n                {\n                    logger.LogError(e, \"Error getting commits\");\n                }\n            });\n        }\n    }\n\n    private async Task ActuallyInstall()\n    {\n        var isCurrentlyReleaseMode = IsReleaseMode;\n        \n        await InstallGitIfNecessary();\n\n        SelectedPackage.InstallLocation = $\"{settingsManager.LibraryDir}\\\\Packages\\\\{InstallName}\";\n        SelectedPackage.DisplayName = InstallName;\n\n        if (!PyRunner.PipInstalled || !PyRunner.VenvInstalled)\n        {\n            ProgressText = \"Installing dependencies...\";\n            await pyRunner.Initialize();\n            if (!PyRunner.PipInstalled)\n            {\n                await pyRunner.SetupPip();\n            }\n            if (!PyRunner.VenvInstalled)\n            {\n                await pyRunner.InstallPackage(\"virtualenv\");\n            }\n        }\n\n        var version = isCurrentlyReleaseMode\n            ? await DownloadPackage(SelectedVersion.TagName, false)\n            : await DownloadPackage(SelectedCommit.Sha, true);\n        \n        await InstallPackage();\n\n        ProgressText = \"Setting up shared folder links...\";\n        sharedFolders.SetupLinksForPackage(SelectedPackage, SelectedPackage.InstallLocation);\n        \n        ProgressText = \"Done\";\n        IsIndeterminate = false;\n        ProgressValue = 100;\n        EventManager.Instance.OnGlobalProgressChanged(100);\n\n        var branch = isCurrentlyReleaseMode ? null : SelectedVersion.TagName;\n\n        var package = new InstalledPackage\n        {\n            DisplayName = SelectedPackage.DisplayName,\n            LibraryPath = $\"Packages\\\\{InstallName}\",\n            Id = Guid.NewGuid(),\n            PackageName = SelectedPackage.Name,\n            PackageVersion = version,\n            DisplayVersion = GetDisplayVersion(version, branch),\n            InstalledBranch = branch,\n            LaunchCommand = SelectedPackage.LaunchCommand,\n            LastUpdateCheck = DateTimeOffset.Now\n        };\n        await using var st = settingsManager.BeginTransaction();\n        st.Settings.InstalledPackages.Add(package);\n        st.Settings.ActiveInstalledPackageId = package.Id;\n        \n        ProgressValue = 0;\n    }\n\n    private string GetDisplayVersion(string version, string? branch)\n    {\n        return branch == null ? version : $\"{branch}@{version[..7]}\";\n    }\n    \n    private Task<string?> DownloadPackage(string version, bool isCommitHash)\n    {\n        ProgressText = \"Downloading package...\";\n        \n        var progress = new Progress<ProgressReport>(progress =>\n        {\n            IsIndeterminate = progress.IsIndeterminate;\n            ProgressValue = Convert.ToInt32(progress.Percentage);\n            EventManager.Instance.OnGlobalProgressChanged(ProgressValue);\n        });\n        \n        return SelectedPackage.DownloadPackage(version, isCommitHash, progress);\n    }\n\n    private async Task InstallPackage()\n    {\n        SelectedPackage.ConsoleOutput += SelectedPackageOnConsoleOutput;\n        ProgressText = \"Installing package...\";\n        \n        var progress = new Progress<ProgressReport>(progress =>\n        {\n            IsIndeterminate = progress.IsIndeterminate;\n            ProgressValue = Convert.ToInt32(progress.Percentage);\n            EventManager.Instance.OnGlobalProgressChanged(ProgressValue);\n        });\n        \n        await SelectedPackage.InstallPackage(progress);\n    }\n\n    private void SelectedPackageOnConsoleOutput(object? sender, ProcessOutput e)\n    {\n        SecondaryProgressText = e.Text;\n    }\n\n    private async Task InstallGitIfNecessary()\n    {\n        var progressHandler = new Progress<ProgressReport>(progress =>\n        {\n            if (progress.Message != null && progress.Message.Contains(\"Downloading\"))\n            {\n                ProgressText = $\"Downloading prerequisites... {progress.Percentage:N0}%\";\n            }\n            else if (progress.Type == ProgressType.Extract)\n            {\n                ProgressText = $\"Installing git... {progress.Percentage:N0}%\";\n            }\n            else if (progress.Title != null && progress.Title.Contains(\"Unpacking\"))\n            {\n                ProgressText = $\"Unpacking resources... {progress.Percentage:N0}%\";\n            }\n            else\n            {\n                ProgressText = progress.Message;\n            }\n\n            IsIndeterminate = progress.IsIndeterminate;\n            ProgressValue = Convert.ToInt32(progress.Percentage);\n        });\n\n        await prerequisiteHelper.InstallAllIfNecessary(progressHandler);\n    }\n\n    private void OnPackageInstalled() => PackageInstalled?.Invoke(this, EventArgs.Empty);\n\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/LaunchOptionsDialogViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Collections.ObjectModel;\nusing System.Diagnostics;\nusing System.Linq;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing NLog;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class LaunchOptionsDialogViewModel : ObservableObject\n{\n    public static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    public ObservableCollection<LaunchOptionCard> Cards { get; set; } = new();\n\n    [ObservableProperty]\n    private string title = \"Launch Options\";\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(FilteredCards))]\n    private string searchText = string.Empty;\n    \n    [ObservableProperty]\n    private bool isSearchBoxEnabled = true;\n    \n    private LRUCache<string, ImmutableList<LaunchOptionCard>> cache = new(100);\n    \n    /// <summary>\n    /// Return cards that match the search text\n    /// </summary>\n    public IEnumerable<LaunchOptionCard> FilteredCards\n    {\n        get\n        {\n            var text = SearchText;\n            if (string.IsNullOrWhiteSpace(text) || text.Length < 2)\n            {\n                return Cards;\n            }\n            // Try cache\n            if (cache.Get(text, out var cachedCards))\n            {\n                return cachedCards!;\n            }\n            var searchCard = new LaunchOptionCard(text.ToLowerInvariant());\n            var extracted = FuzzySharp.Process\n                .ExtractTop(searchCard, Cards, c => c.Title.ToLowerInvariant());\n            var results = extracted\n                .Where(r => r.Score > 40)\n                .Select(r => r.Value)\n                .ToImmutableList();\n            cache.Add(text, results);\n            return results;\n        }\n    }\n\n    /// <summary>\n    /// Export the current cards options to a list of strings\n    /// </summary>\n    public List<LaunchOption> AsLaunchArgs()\n    {\n        var launchArgs = new List<LaunchOption>();\n        foreach (var card in Cards)\n        {\n            launchArgs.AddRange(card.Options);\n        }\n        return launchArgs;\n    }\n\n    public void Initialize(IEnumerable<LaunchOptionDefinition> definitions, IEnumerable<LaunchOption> launchArgs)\n    {\n        Clear();\n        // During card creation, store dict of options with initial values\n        var initialOptions = new Dictionary<string, object>();\n        // Create cards\n        foreach (var definition in definitions)\n        {\n            // Check that non-bool types have exactly one option\n            if (definition.Type != LaunchOptionType.Bool && definition.Options.Count != 1)\n            {\n                throw new InvalidOperationException(\n                    $\"Definition: '{definition.Name}' has {definition.Options.Count} options,\" +\n                    $\" it must have exactly 1 option for non-bool types\");\n            }\n            // Store initial values\n            if (definition.InitialValue != null)\n            {\n                // For bool types, initial value can be string (single/multiple options) or bool (single option)\n                if (definition.Type == LaunchOptionType.Bool)\n                {\n                    // For single option, check bool\n                    if (definition.Options.Count == 1 && definition.InitialValue is bool boolValue)\n                    {\n                        initialOptions[definition.Options.First()] = boolValue;\n                    }\n                    else\n                    {\n                        // For single/multiple options (string only)\n                        var option = definition.Options.FirstOrDefault(opt => opt.Equals(definition.InitialValue));\n                        if (option == null)\n                        {\n                            throw new InvalidOperationException(\n                                $\"Definition '{definition.Name}' has InitialValue of '{definition.InitialValue}', but it was not found in options:\" +\n                                $\" '{string.Join(\",\", definition.Options)}'\");\n                        }\n                        initialOptions[option] = true;\n                    }\n                }\n                else\n                {\n                    // Otherwise store initial value for first option\n                    initialOptions[definition.Options.First()] = definition.InitialValue;\n                }\n            }\n            Cards.Add(new LaunchOptionCard(definition));\n        }\n        // Load launch args\n        var launchArgsDict = launchArgs.ToDictionary(launchArg => launchArg.Name);\n        foreach (var card in Cards)\n        {\n            foreach (var option in card.Options)\n            {\n                var userOption = launchArgsDict.GetValueOrDefault(option.Name);\n                var userValue = userOption?.OptionValue?.ToString();\n                // If no user value, check for initial value\n                if (userValue == null)\n                {\n                    var initialValue = initialOptions.GetValueOrDefault(option.Name);\n                    if (initialValue != null)\n                    {\n                        userValue = initialValue.ToString();\n                        Logger.Info(\"Using initial value '{InitialValue}' for option '{OptionName}'\",\n                            initialValue, option.Name);\n                    }\n                }\n                option.SetValueFromString(userValue);\n            }\n        }\n    }\n    \n    /// <summary>\n    /// Clear Cards and cache\n    /// </summary>\n    public void Clear()\n    {\n        cache = new LRUCache<string, ImmutableList<LaunchOptionCard>>(100);\n        Cards.Clear();\n    }\n    \n    public void OnLoad()\n    {\n        Debug.WriteLine(\"In LaunchOptions OnLoad\");\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/LaunchViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing AsyncAwaitBestPractices;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Toolkit.Uwp.Notifications;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Helper;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls.ContentDialogControl;\nusing EventManager = StabilityMatrix.Core.Helper.EventManager;\nusing ISnackbarService = StabilityMatrix.Helper.ISnackbarService;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class LaunchViewModel : ObservableObject\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly IPackageFactory packageFactory;\n    private readonly IContentDialogService contentDialogService;\n    private readonly LaunchOptionsDialogViewModel launchOptionsDialogViewModel;\n    private readonly ILogger<LaunchViewModel> logger;\n    private readonly IPyRunner pyRunner;\n    private readonly IDialogFactory dialogFactory;\n    private readonly ISnackbarService snackbarService;\n    private readonly ISharedFolders sharedFolders;\n\n    private BasePackage? runningPackage;\n    private bool clearingPackages;\n    private string webUiUrl = string.Empty;\n\n    [ObservableProperty] private string consoleInput = \"\";\n    [ObservableProperty] private Visibility launchButtonVisibility;\n    [ObservableProperty] private Visibility stopButtonVisibility;\n    [ObservableProperty] private bool isLaunchTeachingTipsOpen = false;\n    [ObservableProperty] private bool showWebUiButton;\n    [ObservableProperty] private ObservableCollection<string>? consoleHistory;\n    \n    [ObservableProperty] private InstalledPackage? selectedPackage;\n    [ObservableProperty] private ObservableCollection<InstalledPackage> installedPackages = new();\n    \n    public LaunchViewModel(ISettingsManager settingsManager,\n        IPackageFactory packageFactory,\n        IContentDialogService contentDialogService,\n        LaunchOptionsDialogViewModel launchOptionsDialogViewModel,\n        ILogger<LaunchViewModel> logger,\n        IPyRunner pyRunner,\n        IDialogFactory dialogFactory,\n        ISnackbarService snackbarService,\n        ISharedFolders sharedFolders)\n    {\n        this.pyRunner = pyRunner;\n        this.dialogFactory = dialogFactory;\n        this.contentDialogService = contentDialogService;\n        this.launchOptionsDialogViewModel = launchOptionsDialogViewModel;\n        this.logger = logger;\n        this.settingsManager = settingsManager;\n        this.packageFactory = packageFactory;\n        this.snackbarService = snackbarService;\n        this.sharedFolders = sharedFolders;\n        SetProcessRunning(false);\n\n        EventManager.Instance.InstalledPackagesChanged += OnInstalledPackagesChanged;\n        EventManager.Instance.TeachingTooltipNeeded += OnTeachingTooltipNeeded;\n\n        ToastNotificationManagerCompat.OnActivated += ToastNotificationManagerCompatOnOnActivated;\n    }\n    \n    partial void OnSelectedPackageChanged(InstalledPackage? value)\n    {\n        if (clearingPackages) return;\n        settingsManager.Transaction(s => s.ActiveInstalledPackageId = value?.Id);\n    }\n\n    private void OnTeachingTooltipNeeded(object? sender, EventArgs e)\n    {\n        IsLaunchTeachingTipsOpen = true;\n    }\n\n    private void OnInstalledPackagesChanged(object? sender, EventArgs e)\n    {\n        OnLoaded();\n    }\n\n    private void ToastNotificationManagerCompatOnOnActivated(\n        ToastNotificationActivatedEventArgsCompat e)\n    {\n        if (!e.Argument.StartsWith(\"http\"))\n            return;\n\n        if (string.IsNullOrWhiteSpace(webUiUrl))\n        {\n            webUiUrl = e.Argument;\n        }\n\n        LaunchWebUi();\n    }\n\n    public AsyncRelayCommand LaunchCommand => new(async () =>\n    {\n        var activeInstall = SelectedPackage;\n\n        if (activeInstall == null)\n        {\n            // No selected package: error snackbar\n            snackbarService.ShowSnackbarAsync(\n                \"You must install and select a package before launching\",\n                \"No package selected\").SafeFireAndForget();\n            return;\n        }\n\n        var activeInstallName = activeInstall.PackageName;\n        var basePackage = string.IsNullOrWhiteSpace(activeInstallName)\n            ? null\n            : packageFactory.FindPackageByName(activeInstallName);\n\n        if (basePackage == null)\n        {\n            logger.LogWarning(\n                \"During launch, package name '{PackageName}' did not match a definition\",\n                activeInstallName);\n            snackbarService.ShowSnackbarAsync(\n                \"Install package name did not match a definition. Please reinstall and let us know about this issue.\",\n                \"Package name invalid\").SafeFireAndForget();\n            return;\n        }\n\n        // If this is the first launch (LaunchArgs is null),\n        // load and save a launch options dialog in background\n        // so that dynamic initial values are saved.\n        if (activeInstall.LaunchArgs == null)\n        {\n            var definitions = basePackage.LaunchOptions;\n            // Open a config page and save it\n            var dialog = dialogFactory.CreateLaunchOptionsDialog(definitions, activeInstall);\n            var args = dialog.AsLaunchArgs();\n            settingsManager.SaveLaunchArgs(activeInstall.Id, args);\n        }\n\n        // Clear console\n        ConsoleHistory?.Clear();\n\n        await pyRunner.Initialize();\n\n        // Get path from package\n        var packagePath = $\"{settingsManager.LibraryDir}\\\\{activeInstall.LibraryPath!}\";\n\n        basePackage.ConsoleOutput += OnConsoleOutput;\n        basePackage.Exited += OnExit;\n        basePackage.StartupComplete += RunningPackageOnStartupComplete;\n\n        // Update shared folder links (in case library paths changed)\n        await sharedFolders.UpdateLinksForPackage(basePackage, packagePath);\n\n        // Load user launch args from settings and convert to string\n        var userArgs = settingsManager.GetLaunchArgs(activeInstall.Id);\n        var userArgsString = string.Join(\" \", userArgs.Select(opt => opt.ToArgString()));\n\n        // Join with extras, if any\n        userArgsString = string.Join(\" \", userArgsString, basePackage.ExtraLaunchArguments);\n        await basePackage.RunPackage(packagePath, userArgsString);\n        runningPackage = basePackage;\n        SetProcessRunning(true);\n    });\n\n    [RelayCommand]\n    private async Task ConfigAsync()\n    {\n        var activeInstall = SelectedPackage;\n        var name = activeInstall?.PackageName;\n        if (name == null || activeInstall == null)\n        {\n            logger.LogWarning($\"Selected package is null\");\n            return;\n        }\n\n        var package = packageFactory.FindPackageByName(name);\n        if (package == null)\n        {\n            logger.LogWarning(\"Package {Name} not found\", name);\n            return;\n        }\n\n        var definitions = package.LaunchOptions;\n        // Check if package supports IArgParsable\n        // Use dynamic parsed args over static\n        if (package is IArgParsable parsable)\n        {\n            var rootPath = activeInstall.FullPath!;\n            var moduleName = parsable.RelativeArgsDefinitionScriptPath;\n            var parser = new ArgParser(pyRunner, rootPath, moduleName);\n            definitions = await parser.GetArgsAsync();\n        }\n\n        // Open a config page\n        var dialog = dialogFactory.CreateLaunchOptionsDialog(definitions, activeInstall);\n        dialog.IsPrimaryButtonEnabled = true;\n        dialog.PrimaryButtonText = \"Save\";\n        dialog.CloseButtonText = \"Cancel\";\n        var result = await dialog.ShowAsync();\n\n        if (result == ContentDialogResult.Primary)\n        {\n            // Save config\n            var args = dialog.AsLaunchArgs();\n            settingsManager.SaveLaunchArgs(activeInstall.Id, args);\n        }\n    }\n\n    [RelayCommand]\n    private void LaunchWebUi()\n    {\n        ProcessRunner.OpenUrl(webUiUrl);\n    }\n\n    private void RunningPackageOnStartupComplete(object? sender, string url)\n    {\n        webUiUrl = url;\n        ShowWebUiButton = true;\n        // Uwp toasts has upstream issues on some machines\n        // https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/4858\n        try\n        {\n            new ToastContentBuilder()\n                .AddText(\"Stable Diffusion Web UI ready to go!\")\n                .AddButton(\"Open Web UI\", ToastActivationType.Foreground, url)\n                .Show();\n        }\n        catch (Exception e)\n        {\n            logger.LogWarning(\"Failed to show Windows notification: {Message}\", e.Message);\n        }\n    }\n\n    public void OnLoaded()\n    {\n        LoadPackages();\n        lock (InstalledPackages)\n        {\n            // Skip if no packages\n            if (!InstalledPackages.Any())\n            {\n                logger.LogTrace($\"No packages for {nameof(LaunchViewModel)}\");\n                return;\n            }\n\n            var activePackageId = settingsManager.Settings.ActiveInstalledPackageId;\n            if (activePackageId != null)\n            {\n                SelectedPackage = InstalledPackages.FirstOrDefault(\n                    x => x.Id == activePackageId) ?? InstalledPackages[0];\n            }\n        }\n    }\n\n    public void OnShutdown()\n    {\n        Stop();\n    }\n\n    [RelayCommand]\n    private Task Stop()\n    {\n        if (runningPackage != null)\n        {\n            runningPackage.StartupComplete -= RunningPackageOnStartupComplete;\n            runningPackage.ConsoleOutput -= OnConsoleOutput;\n            runningPackage.Exited -= OnExit;\n        }\n\n        runningPackage?.Shutdown();\n        runningPackage = null;\n        SetProcessRunning(false);\n        ConsoleHistory?.Add(\n            $\"Stopped process at {DateTimeOffset.Now}\");\n        ShowWebUiButton = false;\n        return Task.CompletedTask;\n    }\n\n    private void LoadPackages()\n    {\n        var packages = settingsManager.Settings.InstalledPackages;\n        if (!packages?.Any() ?? true)\n        {\n            InstalledPackages.Clear();\n            return;\n        }\n\n        clearingPackages = true;\n        InstalledPackages.Clear();\n\n        foreach (var package in packages)\n        {\n            InstalledPackages.Add(package);\n        }\n\n        clearingPackages = false;\n    }\n\n    private void SetProcessRunning(bool running)\n    {\n        if (running)\n        {\n            LaunchButtonVisibility = Visibility.Collapsed;\n            StopButtonVisibility = Visibility.Visible;\n        }\n        else\n        {\n            LaunchButtonVisibility = Visibility.Visible;\n            StopButtonVisibility = Visibility.Collapsed;\n        }\n    }\n\n    private void OnConsoleOutput(object? sender, ProcessOutput output)\n    {\n        if (string.IsNullOrWhiteSpace(output.Text)) return;\n        Application.Current.Dispatcher.Invoke(() =>\n        {\n            ConsoleHistory ??= new ObservableCollection<string>();\n\n            if (output.Text.Contains(\"Total progress\") && !output.Text.Contains(\" 0%\"))\n            {\n                ConsoleHistory[^2] = output.Text.TrimEnd('\\n');\n            }\n            else if ((output.Text.Contains(\"it/s\") || output.Text.Contains(\"s/it\") || output.Text.Contains(\"B/s\")) &&\n                     !output.Text.Contains(\"Total progress\") && !output.Text.Contains(\" 0%\"))\n            {\n                ConsoleHistory[^1] = output.Text.TrimEnd('\\n');\n            }\n            else\n            {\n                if (!string.IsNullOrWhiteSpace(output.Text.TrimEnd('\\n')))\n                {\n                    ConsoleHistory.Add(output.Text.TrimEnd('\\n'));\n                }\n            }\n            \n            ConsoleHistory =\n                new ObservableCollection<string>(\n                    ConsoleHistory.Where(str => !string.IsNullOrWhiteSpace(str)));\n        });\n    }\n\n    private void OnExit(object? sender, int exitCode)\n    {\n        var basePackage = packageFactory.FindPackageByName(SelectedPackage.PackageName);\n        basePackage.ConsoleOutput -= OnConsoleOutput;\n        basePackage.Exited -= OnExit;\n        basePackage.StartupComplete -= RunningPackageOnStartupComplete;\n\n        Application.Current.Dispatcher.Invoke(() =>\n        {\n            ConsoleHistory?.Add($\"Venv process exited with code {exitCode}\");\n            SetProcessRunning(false);\n            ShowWebUiButton = false;\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/MainWindowViewModel.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing System.Windows.Interop;\nusing System.Windows.Shell;\nusing System.Windows.Threading;\nusing AsyncAwaitBestPractices;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Microsoft.Extensions.Options;\nusing StabilityMatrix.Core.Models.Configs;\nusing StabilityMatrix.Core.Models.Update;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Core.Updater;\nusing StabilityMatrix.Helper;\nusing StabilityMatrix.Services;\nusing Wpf.Ui.Appearance;\nusing Wpf.Ui.Controls.ContentDialogControl;\nusing Wpf.Ui.Controls.Window;\nusing EventManager = StabilityMatrix.Core.Helper.EventManager;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class  MainWindowViewModel : ObservableObject\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly IDialogFactory dialogFactory;\n    private readonly INotificationBarService notificationBarService;\n    private readonly UpdateWindowViewModel updateWindowViewModel;\n    private readonly IUpdateHelper updateHelper;\n    private readonly DebugOptions debugOptions;\n\n    private UpdateInfo? updateInfo;\n\n    public MainWindowViewModel(\n        ISettingsManager settingsManager, \n        IDialogFactory dialogFactory, \n        INotificationBarService notificationBarService,\n        UpdateWindowViewModel updateWindowViewModel,\n        IUpdateHelper updateHelper,\n        IOptions<DebugOptions> debugOptions)\n    {\n        this.settingsManager = settingsManager;\n        this.dialogFactory = dialogFactory;\n        this.notificationBarService = notificationBarService;\n        this.updateWindowViewModel = updateWindowViewModel;\n        this.updateHelper = updateHelper;\n        this.debugOptions = debugOptions.Value;\n\n        // Listen to dev mode event\n        EventManager.Instance.DevModeSettingChanged += (_, value) => IsTextToImagePageEnabled = value;\n    }\n\n    [ObservableProperty]\n    private float progressValue;\n\n    [ObservableProperty] \n    private bool isIndeterminate;\n\n    [ObservableProperty]   \n    private TaskbarItemProgressState progressState;\n\n    [ObservableProperty]\n    private bool isTextToImagePageEnabled;\n\n    [ObservableProperty] \n    private bool isUpdateAvailable;\n\n    public async Task OnLoaded()\n    {\n        SetTheme();\n        EventManager.Instance.GlobalProgressChanged += OnGlobalProgressChanged;\n        EventManager.Instance.UpdateAvailable += (_, args) =>\n        {\n            IsUpdateAvailable = true;\n            updateInfo = args;\n        };\n\n        updateHelper.StartCheckingForUpdates().SafeFireAndForget();\n        \n        // show path selection window if no paths are set\n        await DoSettingsCheck();\n        \n        // Insert path extensions\n        settingsManager.InsertPathExtensions();\n        \n        ResizeWindow();\n\n        if (debugOptions.ShowOneClickInstall || !settingsManager.Settings.InstalledPackages.Any())\n        {\n            var dialog = dialogFactory.CreateOneClickInstallDialog();\n            dialog.IsPrimaryButtonEnabled = false;\n            dialog.IsSecondaryButtonEnabled = false;\n            dialog.IsFooterVisible = false;\n\n            EventManager.Instance.OneClickInstallFinished += (_, skipped) =>\n            {\n                dialog.Hide();\n                if (skipped) return;\n                \n                EventManager.Instance.OnTeachingTooltipNeeded();\n            };\n\n            await dialog.ShowAsync();\n        }\n\n        notificationBarService.ShowStartupNotifications();\n    }\n    \n    [RelayCommand]\n    private void OpenLinkPatreon()\n    {\n        ProcessRunner.OpenUrl(\"https://www.patreon.com/StabilityMatrix\");\n    }\n    \n    [RelayCommand]\n    private void OpenLinkDiscord()\n    {\n        ProcessRunner.OpenUrl(\"https://discord.gg/TUrgfECxHz\");\n    }\n\n    [RelayCommand]\n    private void DoUpdate()\n    {\n        updateWindowViewModel.UpdateInfo = updateInfo;\n        var updateWindow = new UpdateWindow(updateWindowViewModel)\n        {\n            Owner = Application.Current.MainWindow\n        };\n        updateWindow.ShowDialog();\n    }\n    \n    private async Task DoSettingsCheck()\n    {\n        // Check if library path is set\n        if (!settingsManager.TryFindLibrary())\n        {\n            await ShowPathSelectionDialog();            \n        }\n        \n        // Try to find library again, should be found now\n        if (!settingsManager.TryFindLibrary())\n        {\n            throw new Exception(\"Could not find library after setting path\");\n        }\n        \n        // Check if there are old packages, if so show migration dialog\n        if (settingsManager.GetOldInstalledPackages().Any())\n        {\n            var dialog = dialogFactory.CreateDataDirectoryMigrationDialog();\n            var result = await dialog.ShowAsync();\n            if (result == ContentDialogResult.None)\n            {\n                Application.Current.Shutdown();\n            }\n            else if (result == ContentDialogResult.Secondary)\n            {\n                var portableMarkerFile = Path.Combine(\"Data\", \".sm-portable\");\n                if (File.Exists(portableMarkerFile))\n                {\n                    File.Delete(portableMarkerFile);\n                }\n                await ShowPathSelectionDialog();\n                await DoSettingsCheck();\n            }\n        }\n    }\n    \n    private async Task ShowPathSelectionDialog()\n    {\n        // See if this is an existing user for message variation\n        var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);\n        var settingsPath = Path.Combine(appDataPath, \"StabilityMatrix\", \"settings.json\");\n        var isExistingUser = File.Exists(settingsPath);\n            \n        // need to show dialog to choose library location\n        var dialog = dialogFactory.CreateInstallLocationsDialog();\n        var result = await dialog.ShowAsync();\n        if (result != ContentDialogResult.Primary)\n        {\n            Application.Current.Shutdown();\n        }\n\n        // 1. For portable mode, call settings.SetPortableMode()\n        var viewModel = (dialog.DataContext as SelectInstallLocationsViewModel)!;\n        if (viewModel.IsPortableMode)\n        {\n            settingsManager.SetPortableMode();\n        }\n        // 2. For custom path, call settings.SetLibraryPath(path)\n        else\n        {\n            settingsManager.SetLibraryPath(viewModel.DataDirectory);\n        }\n    }\n\n    /// <summary>\n    ///   Updates the taskbar progress bar value and state.\n    /// </summary>\n    /// <param name=\"progress\">Progress value from 0 to 100</param>\n    private void OnGlobalProgressChanged(object? sender, int progress)\n    {\n        if (progress == -1)\n        {\n            ProgressState = TaskbarItemProgressState.Indeterminate;\n            ProgressValue = 0;\n        }\n        else\n        {\n            ProgressState = TaskbarItemProgressState.Normal;\n            ProgressValue = progress / 100f;\n        }\n\n        if (Math.Abs(ProgressValue - 1) < 0.01f)\n        {\n            Task.Run(async () =>\n            {\n                await Task.Delay(5000);\n                Application.Current.Dispatcher.Invoke(() =>\n                {\n                    ProgressState = TaskbarItemProgressState.None;\n                    ProgressValue = 0;\n                });\n            });\n        }\n    }\n    \n    private void ResizeWindow()\n    {\n        if (Application.Current.MainWindow == null) return;\n        \n        var interopHelper = new WindowInteropHelper(Application.Current.MainWindow);\n\n        if (string.IsNullOrWhiteSpace(settingsManager.Settings.Placement))\n            return;\n        var placement = new ScreenExtensions.WindowPlacement();\n        placement.ReadFromBase64String(settingsManager.Settings.Placement);\n\n        var primaryMonitorScaling = ScreenExtensions.GetScalingForPoint(new System.Drawing.Point(1, 1));\n        var currentMonitorScaling = ScreenExtensions.GetScalingForPoint(new System.Drawing.Point(placement.rcNormalPosition.left, placement.rcNormalPosition.top));\n        var rescaleFactor = currentMonitorScaling / primaryMonitorScaling;\n        double width = placement.rcNormalPosition.right - placement.rcNormalPosition.left;\n        double height = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top;\n        placement.rcNormalPosition.right = placement.rcNormalPosition.left + (int)(width / rescaleFactor + 0.5);\n        placement.rcNormalPosition.bottom = placement.rcNormalPosition.top + (int)(height / rescaleFactor + 0.5);\n        ScreenExtensions.SetPlacement(interopHelper.Handle, placement);\n    }\n\n    private void SetTheme()\n    {\n        Application.Current.Dispatcher.BeginInvoke(() =>\n        {\n            if (Application.Current.MainWindow != null)\n            {\n                WindowBackdrop.ApplyBackdrop(Application.Current.MainWindow, WindowBackdropType.Mica);\n            }\n\n            var theme = settingsManager.Settings.Theme;\n            switch (theme)\n            {\n                case \"Dark\":\n                    Theme.Apply(ThemeType.Dark);\n                    break;\n                case \"Light\":\n                    Theme.Apply(ThemeType.Light);\n                    break;\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/OneClickInstallViewModel.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\nusing EventManager = StabilityMatrix.Core.Helper.EventManager;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class OneClickInstallViewModel : ObservableObject\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly IPackageFactory packageFactory;\n    private readonly IPrerequisiteHelper prerequisiteHelper;\n    private readonly ILogger<MainWindowViewModel> logger;\n    private readonly IPyRunner pyRunner;\n    private readonly ISharedFolders sharedFolders;\n    private const string DefaultPackageName = \"stable-diffusion-webui\";\n    \n    [ObservableProperty] private string headerText;\n    [ObservableProperty] private string subHeaderText;\n    [ObservableProperty] private string subSubHeaderText;\n    [ObservableProperty] private bool showInstallButton;\n    [ObservableProperty] private bool isIndeterminate;\n    \n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ProgressBarVisibility))]\n    private int oneClickInstallProgress;\n\n\n    public Visibility ProgressBarVisibility =>\n        OneClickInstallProgress > 0 || IsIndeterminate ? Visibility.Visible : Visibility.Collapsed;\n\n    public OneClickInstallViewModel(ISettingsManager settingsManager, IPackageFactory packageFactory,\n        IPrerequisiteHelper prerequisiteHelper, ILogger<MainWindowViewModel> logger, IPyRunner pyRunner,\n        ISharedFolders sharedFolders)\n    {\n        this.settingsManager = settingsManager;\n        this.packageFactory = packageFactory;\n        this.prerequisiteHelper = prerequisiteHelper;\n        this.logger = logger;\n        this.pyRunner = pyRunner;\n        this.sharedFolders = sharedFolders;\n\n        HeaderText = \"Welcome to Stability Matrix!\";\n        SubHeaderText =\n            \"Click the Install button below to get started!\\n\" +\n            \"This will install the latest version of Stable Diffusion WebUI by Automatic1111.\\n\" +\n            \"If you don't know what this means, don't worry, you'll be generating images soon!\";\n        ShowInstallButton = true;\n    }\n\n    [RelayCommand]\n    private async Task Install()\n    {\n        ShowInstallButton = false;\n        await DoInstall();\n        ShowInstallButton = true;\n    }\n\n    [RelayCommand]\n    private Task ToggleAdvancedMode()\n    {\n        EventManager.Instance.OnOneClickInstallFinished(true);\n        return Task.CompletedTask;\n    }\n    \n    private async Task DoInstall()\n    {\n        var a1111 = packageFactory.FindPackageByName(DefaultPackageName)!;\n        HeaderText = \"Installing Stable Diffusion WebUI\";\n\n        var progressHandler = new Progress<ProgressReport>(progress =>\n        {\n            if (progress.Message != null && progress.Message.Contains(\"Downloading\"))\n            {\n                SubHeaderText = $\"Downloading prerequisites... {progress.Percentage:N0}%\";\n            }\n            else if (progress.Type == ProgressType.Extract)\n            {\n                SubHeaderText = $\"Installing git... {progress.Percentage:N0}%\";\n            }\n            else if (progress.Title != null && progress.Title.Contains(\"Unpacking\"))\n            {\n                SubHeaderText = $\"Unpacking resources... {progress.Percentage:N0}%\";\n            }\n            else if (progress.Message != null)\n            {\n                SubHeaderText = progress.Message;\n            }\n            \n            IsIndeterminate = progress.IsIndeterminate;\n            OneClickInstallProgress = Convert.ToInt32(progress.Percentage);\n        });\n        \n        await prerequisiteHelper.InstallAllIfNecessary(progressHandler);\n\n        SubHeaderText = \"Installing prerequisites...\";\n        IsIndeterminate = true;\n        if (!PyRunner.PipInstalled)\n        {\n            await pyRunner.SetupPip();\n        }\n\n        if (!PyRunner.VenvInstalled)\n        {\n            await pyRunner.InstallPackage(\"virtualenv\");\n        }\n        IsIndeterminate = false;\n\n        // get latest version & download & install\n        SubHeaderText = \"Getting latest version...\";\n        var latestVersion = await a1111.GetLatestVersion();\n        a1111.InstallLocation = $\"{settingsManager.LibraryDir}\\\\Packages\\\\stable-diffusion-webui\";\n        a1111.ConsoleOutput += (_, output) => SubSubHeaderText = output.Text;\n        \n        await DownloadPackage(a1111, latestVersion);\n        await InstallPackage(a1111);\n\n        SubHeaderText = \"Setting up shared folder links...\";\n        sharedFolders.SetupLinksForPackage(a1111, a1111.InstallLocation);\n        \n        var package = new InstalledPackage\n        {\n            DisplayName = a1111.DisplayName,\n            LibraryPath = \"Packages\\\\stable-diffusion-webui\",\n            Id = Guid.NewGuid(),\n            PackageName = a1111.Name,\n            PackageVersion = latestVersion,\n            DisplayVersion = latestVersion,\n            LaunchCommand = a1111.LaunchCommand,\n            LastUpdateCheck = DateTimeOffset.Now\n        };\n        await using var st = settingsManager.BeginTransaction();\n        st.Settings.InstalledPackages.Add(package);\n        st.Settings.ActiveInstalledPackageId = package.Id;\n        EventManager.Instance.OnInstalledPackagesChanged();\n        \n        HeaderText = \"Installation complete!\";\n        OneClickInstallProgress = 100;\n        SubHeaderText = \"Proceeding to Launch page in 3 seconds...\";\n        await Task.Delay(1000);\n        SubHeaderText = \"Proceeding to Launch page in 2 seconds...\";\n        await Task.Delay(1000);\n        SubHeaderText = \"Proceeding to Launch page in 1 second...\";\n        await Task.Delay(1000);\n        \n        // should close dialog\n        EventManager.Instance.OnOneClickInstallFinished(false);\n    }\n\n    private async Task DownloadPackage(BasePackage selectedPackage, string version)\n    {\n        SubHeaderText = \"Downloading package...\";\n        \n        var progress = new Progress<ProgressReport>(progress =>\n        {\n            IsIndeterminate = progress.IsIndeterminate;\n            OneClickInstallProgress = Convert.ToInt32(progress.Percentage);\n            EventManager.Instance.OnGlobalProgressChanged(OneClickInstallProgress);\n        });\n        \n        await selectedPackage.DownloadPackage(version, false, progress);\n        SubHeaderText = \"Download Complete\";\n        OneClickInstallProgress = 100;\n    }\n\n    private async Task InstallPackage(BasePackage selectedPackage)\n    {\n        selectedPackage.ConsoleOutput += (_, output) => SubSubHeaderText = output.Text;\n        SubHeaderText = \"Downloading and installing package requirements...\";\n        \n        var progress = new Progress<ProgressReport>(progress =>\n        {\n            IsIndeterminate = progress.IsIndeterminate;\n            OneClickInstallProgress = Convert.ToInt32(progress.Percentage);\n            EventManager.Instance.OnGlobalProgressChanged(OneClickInstallProgress);\n        });\n        \n        await selectedPackage.InstallPackage(progress);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/PackageManagerViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.ObjectModel;\nusing System.IO;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Helper;\nusing System.Linq;\nusing System.Windows;\nusing AsyncAwaitBestPractices;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Microsoft.Extensions.Logging;\nusing Polly;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls;\nusing Wpf.Ui.Controls.ContentDialogControl;\nusing EventManager = StabilityMatrix.Core.Helper.EventManager;\nusing ISnackbarService = StabilityMatrix.Helper.ISnackbarService;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class PackageManagerViewModel : ObservableObject\n{\n    private readonly ILogger<PackageManagerViewModel> logger;\n    private readonly ISettingsManager settingsManager;\n    private readonly IPackageFactory packageFactory;\n    private readonly IDialogFactory dialogFactory;\n    private readonly IContentDialogService contentDialogService;\n    private readonly ISnackbarService snackbarService;\n    private const int MinutesToWaitForUpdateCheck = 60;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ProgressBarVisibility))]\n    private int progressValue;\n    \n    [ObservableProperty]\n    private InstalledPackage selectedPackage;\n    \n    [ObservableProperty]\n    private string progressText;\n    \n    [ObservableProperty]\n    private bool isIndeterminate;\n\n    [ObservableProperty]\n    private string installButtonText;\n\n    [ObservableProperty] \n    private bool installButtonEnabled;\n\n    [ObservableProperty] \n    private Visibility installButtonVisibility;\n\n    [ObservableProperty] \n    private bool isUninstalling;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(SelectedPackage))]\n    private bool updateAvailable;\n\n    public PackageManagerViewModel(ILogger<PackageManagerViewModel> logger, ISettingsManager settingsManager,\n        IPackageFactory packageFactory, IDialogFactory dialogFactory, IContentDialogService contentDialogService, ISnackbarService snackbarService)\n    {\n        this.logger = logger;\n        this.settingsManager = settingsManager;\n        this.packageFactory = packageFactory;\n        this.dialogFactory = dialogFactory;\n        this.contentDialogService = contentDialogService;\n        this.snackbarService = snackbarService;\n\n        ProgressText = \"shrug\";\n        InstallButtonText = \"Install\";\n        installButtonEnabled = true;\n        ProgressValue = 0;\n        Packages = new ObservableCollection<InstalledPackage>(settingsManager.Settings.InstalledPackages);\n\n        if (Packages.Any())\n        {\n            SelectedPackage = Packages[0];\n            InstallButtonVisibility = Visibility.Visible;\n        }\n        else\n        {\n            SelectedPackage = new InstalledPackage\n            {\n                DisplayName = \"Click \\\"Add Package\\\" to install a package\"\n            };\n            InstallButtonVisibility = Visibility.Collapsed;\n        }\n    }\n\n    public async Task OnLoaded()\n    {\n        Packages.Clear();\n        var installedPackages = settingsManager.Settings.InstalledPackages;\n        if (installedPackages.Count == 0)\n        {\n            SelectedPackage = new InstalledPackage\n            {\n                DisplayName = \"Click \\\"Add Package\\\" to install a package\"\n            };\n            InstallButtonVisibility = Visibility.Collapsed;\n            \n            return;\n        }\n        \n        \n        foreach (var packageToUpdate in installedPackages)\n        {\n            var basePackage = packageFactory.FindPackageByName(packageToUpdate.PackageName);\n            if (basePackage == null) continue;\n            \n            var canCheckUpdate = packageToUpdate.LastUpdateCheck == null ||\n                            packageToUpdate.LastUpdateCheck.Value.AddMinutes(MinutesToWaitForUpdateCheck) <\n                            DateTimeOffset.Now;\n            if (canCheckUpdate)\n            {\n                var hasUpdate = await basePackage.CheckForUpdates(packageToUpdate.DisplayName);\n                packageToUpdate.UpdateAvailable = hasUpdate;\n                packageToUpdate.LastUpdateCheck = DateTimeOffset.Now;\n                settingsManager.SetLastUpdateCheck(packageToUpdate);\n            }\n\n            Packages.Add(packageToUpdate);\n        }\n\n        SelectedPackage =\n            installedPackages.FirstOrDefault(x => x.Id == settingsManager.Settings.ActiveInstalledPackageId) ??\n            Packages[0];\n    }\n\n    public ObservableCollection<InstalledPackage> Packages { get; }\n\n    partial void OnSelectedPackageChanged(InstalledPackage? value)\n    {\n        if (value == null) return;\n        \n        UpdateAvailable = value.UpdateAvailable;\n        InstallButtonText = value.UpdateAvailable ? \"Update\" : \"Launch\";\n        InstallButtonVisibility = Visibility.Visible;\n    }\n\n    public Visibility ProgressBarVisibility => ProgressValue > 0 || IsIndeterminate ? Visibility.Visible : Visibility.Collapsed;\n\n    [RelayCommand]\n    private async Task Install()\n    {\n        switch (InstallButtonText.ToLower())\n        {\n            case \"update\":\n                await UpdateSelectedPackage();\n                break;\n            case \"launch\":\n                EventManager.Instance.RequestPageChange(typeof(LaunchPage));\n                break;\n        }\n    }\n\n    [RelayCommand]\n    private async Task Uninstall()\n    {\n        if (SelectedPackage?.LibraryPath == null)\n        {\n            logger.LogError(\"No package selected to uninstall\");\n            return;\n        }\n        \n        var dialog = contentDialogService.CreateDialog();\n        dialog.Title = \"Are you sure?\";\n        dialog.Content = \"This will delete all folders in the package directory, including any generated images in that directory as well as any files you may have added.\";\n        dialog.PrimaryButtonText = \"Yes, delete it\";\n        dialog.CloseButtonText = \"No, keep it\";\n        var result = await dialog.ShowAsync();\n\n        if (result == ContentDialogResult.Primary)\n        {\n            IsUninstalling = true;\n            InstallButtonEnabled = false;\n            var deleteTask = DeleteDirectoryAsync(Path.Combine(settingsManager.LibraryDir,\n                SelectedPackage.LibraryPath));\n            var taskResult = await snackbarService.TryAsync(deleteTask,\n                \"Some files could not be deleted. Please close any open files in the package directory and try again.\");\n            if (taskResult.IsSuccessful)\n            {\n                snackbarService.ShowSnackbarAsync($\"Package {SelectedPackage.DisplayName} uninstalled\", \"Success\",\n                    ControlAppearance.Success).SafeFireAndForget();\n\n                settingsManager.Transaction(settings =>\n                {\n                    settings.RemoveInstalledPackageAndUpdateActive(SelectedPackage);\n                });\n            }\n            await OnLoaded();\n            IsUninstalling = false;\n            InstallButtonEnabled = true;\n        }\n    }\n    \n    /// <summary>\n    /// Deletes a directory and all of its contents recursively.\n    /// Uses Polly to retry the deletion if it fails, up to 5 times with an exponential backoff.\n    /// </summary>\n    /// <param name=\"targetDirectory\"></param>\n    private Task DeleteDirectoryAsync(string targetDirectory)\n    {\n        var policy = Policy.Handle<IOException>()\n            .WaitAndRetryAsync(3, attempt => TimeSpan.FromMilliseconds(50 * Math.Pow(2, attempt)),\n                onRetry: (exception, calculatedWaitDuration) =>\n                {\n                    logger.LogWarning(\n                        exception, \n                        \"Deletion of {TargetDirectory} failed. Retrying in {CalculatedWaitDuration}\", \n                        targetDirectory, calculatedWaitDuration);\n                });\n\n        return policy.ExecuteAsync(async () =>\n        {\n            await Task.Run(() =>\n            {\n                DeleteDirectory(targetDirectory);\n            });\n        });\n    }\n    \n    private void DeleteDirectory(string targetDirectory)\n    {\n        // Skip if directory does not exist\n        if (!Directory.Exists(targetDirectory))\n        {\n            return;\n        }\n        // For junction points, delete with recursive false\n        if (new DirectoryInfo(targetDirectory).LinkTarget != null)\n        {\n            logger.LogInformation(\"Removing junction point {TargetDirectory}\", targetDirectory);\n            try\n            {\n                Directory.Delete(targetDirectory, false);\n                return;\n            }\n            catch (IOException ex)\n            {\n                throw new IOException($\"Failed to delete junction point {targetDirectory}\", ex);\n            }\n        }\n        // Recursively delete all subdirectories\n        var subdirectoryEntries = Directory.GetDirectories(targetDirectory);\n        foreach (var subdirectoryPath in subdirectoryEntries)\n        {\n            DeleteDirectory(subdirectoryPath);\n        }\n        // Delete all files in the directory\n        var fileEntries = Directory.GetFiles(targetDirectory);\n        foreach (var filePath in fileEntries)\n        {\n            try\n            {\n                File.SetAttributes(filePath, FileAttributes.Normal);\n                File.Delete(filePath);\n            }\n            catch (IOException ex)\n            {\n                throw new IOException($\"Failed to delete file {filePath}\", ex);\n            }\n        }\n        // Delete the target directory itself\n        try\n        {\n            Directory.Delete(targetDirectory, false);\n        }\n        catch (IOException ex)\n        {\n            throw new IOException($\"Failed to delete directory {targetDirectory}\", ex);\n        }\n    }\n\n    private async Task UpdateSelectedPackage()\n    {\n        var package = packageFactory.FindPackageByName(SelectedPackage?.PackageName ?? string.Empty);\n        if (package == null)\n        {\n            logger.LogError($\"Could not find package {SelectedPackage.PackageName}\");\n            return;\n        }\n\n        ProgressText = $\"Updating {SelectedPackage.DisplayName} to latest version...\";\n        package.InstallLocation = SelectedPackage.FullPath!;\n        var progress = new Progress<ProgressReport>(progress =>\n        {\n            var percent = Convert.ToInt32(progress.Percentage);\n            if (progress.IsIndeterminate || progress.Progress == -1)\n            {\n                IsIndeterminate = true;\n            }\n            else\n            {\n                IsIndeterminate = false;\n                ProgressValue = percent;\n            }\n\n            ProgressText = $\"Updating {SelectedPackage.DisplayName} to latest version... {percent:N0}%\";\n            EventManager.Instance.OnGlobalProgressChanged(percent);\n        });\n        var updateResult = await package.Update(SelectedPackage, progress);\n        \n        ProgressText = \"Update complete\";\n        SelectedPackage.UpdateAvailable = false;\n        UpdateAvailable = false;\n        \n        settingsManager.UpdatePackageVersionNumber(SelectedPackage.Id, updateResult);\n        await OnLoaded();\n    }\n\n    [RelayCommand]\n    private async Task ShowInstallWindow()\n    {\n        var installWindow = dialogFactory.CreateInstallerWindow();\n        if (Application.Current.MainWindow != null)\n        {\n            installWindow.Owner = Application.Current.MainWindow;\n        }\n        installWindow.ShowDialog();\n        await OnLoaded();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/ProgressViewModel.cs",
    "content": "﻿using System.Windows;\nusing CommunityToolkit.Mvvm.ComponentModel;\n\nnamespace StabilityMatrix.ViewModels;\n\n/// <summary>\n/// Generic view model for progress reporting.\n/// </summary>\npublic partial class ProgressViewModel : ObservableObject\n{\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(TextVisibility))]\n    private string text;\n    \n    [ObservableProperty]\n    private double value;\n    \n    [ObservableProperty]\n    private bool isIndeterminate;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ProgressVisibility))]\n    private bool isProgressVisible;\n    \n    public virtual Visibility ProgressVisibility => IsProgressVisible ? Visibility.Visible : Visibility.Collapsed;\n    \n    public virtual Visibility TextVisibility => string.IsNullOrEmpty(Text) ? Visibility.Collapsed : Visibility.Visible;\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/RefreshBadgeViewModel.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing System.Windows.Forms;\nusing System.Windows.Media;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NLog;\nusing StabilityMatrix.Controls;\nusing StabilityMatrix.Core.Models.Progress;\nusing Wpf.Ui.Common;\nusing Wpf.Ui.Controls;\nusing Wpf.Ui.Controls.IconElements;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class RefreshBadgeViewModel : ObservableObject\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    \n    public string WorkingToolTipText = \"Loading...\";\n    public string SuccessToolTipText = \"Success\";\n    public string InactiveToolTipText = \"\";\n    public string FailToolTipText = \"Failed\";\n    \n    public SymbolIcon InactiveIcon = new(SymbolRegular.Circle12);\n    public SymbolIcon SuccessIcon = new(SymbolRegular.CheckmarkCircle12, true);\n    public SymbolIcon FailIcon = new(SymbolRegular.ErrorCircle12);\n    \n    public SolidColorBrush SuccessColorBrush = AppBrushes.SuccessGreen;\n    public SolidColorBrush InactiveColorBrush = AppBrushes.WarningYellow;\n    public SolidColorBrush FailColorBrush = AppBrushes.FailedRed;\n    \n    public Func<Task<bool>>? RefreshFunc { get; set; }\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsWorking))]\n    [NotifyPropertyChangedFor(nameof(Appearance))]\n    [NotifyPropertyChangedFor(nameof(ColorBrush))]\n    [NotifyPropertyChangedFor(nameof(CurrentToolTip))]\n    [NotifyPropertyChangedFor(nameof(Icon))]\n    private ProgressState state;\n    \n    public bool IsWorking => State == ProgressState.Working;\n\n    public ControlAppearance Appearance => State switch\n    {\n        ProgressState.Working => ControlAppearance.Info,\n        ProgressState.Success => ControlAppearance.Success,\n        ProgressState.Failed => ControlAppearance.Danger,\n        _ => ControlAppearance.Secondary,\n    };\n    \n    public SolidColorBrush ColorBrush => State switch\n    {\n        ProgressState.Success => SuccessColorBrush,\n        ProgressState.Inactive => InactiveColorBrush,\n        ProgressState.Failed => FailColorBrush,\n        _ => Brushes.Gray,\n    };\n\n    public string CurrentToolTip => State switch\n    {\n        ProgressState.Working => WorkingToolTipText,\n        ProgressState.Success => SuccessToolTipText,\n        ProgressState.Inactive => InactiveToolTipText,\n        ProgressState.Failed => FailToolTipText,\n        _ => \"\"\n    };\n    \n    public SymbolIcon Icon => State switch\n    {\n        ProgressState.Success => SuccessIcon,\n        ProgressState.Failed => FailIcon,\n        _ => InactiveIcon\n    };\n\n    public RefreshBadgeViewModel()\n    {\n        Logger.Info(\"New RefreshVM instance!\");\n    }\n\n    [RelayCommand]\n    private async Task Refresh()\n    {\n        Logger.Info(\"Running refresh command...\");\n        if (RefreshFunc == null) return;\n        \n        State = ProgressState.Working;\n        try\n        {\n            var result = await RefreshFunc.Invoke();\n            State = result ? ProgressState.Success : ProgressState.Failed;\n        }\n        catch (Exception ex)\n        {\n            State = ProgressState.Failed;\n            Logger.Error(ex, \"Refresh command failed: {Ex}\", ex.Message);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/SelectInstallLocationsViewModel.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Linq;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NLog;\nusing Ookii.Dialogs.Wpf;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class SelectInstallLocationsViewModel : ObservableObject\n{\n    private readonly ISettingsManager settingsManager;\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    \n    private const string ValidExistingDirectoryText = \"Valid existing data directory found\";\n    private const string InvalidDirectoryText =\n        \"Directory must be empty or have a valid settings.json file\";\n    private const string NotEnoughFreeSpaceText = \"Not enough free space on the selected drive\";\n    \n    [ObservableProperty] private string dataDirectory;\n    [ObservableProperty] private bool isPortableMode;\n    \n    [ObservableProperty] private string directoryStatusText = string.Empty;\n    [ObservableProperty] private bool isStatusBadgeVisible;\n    [ObservableProperty] private bool isDirectoryValid;\n\n    public RefreshBadgeViewModel RefreshBadgeViewModel { get; } = new()\n    {\n        State = ProgressState.Inactive,\n        SuccessToolTipText = ValidExistingDirectoryText,\n        FailToolTipText = InvalidDirectoryText\n    };\n    \n    public string DefaultInstallLocation => Path.Combine(\n        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), \"StabilityMatrix\");\n\n    public bool HasOldData => settingsManager.GetOldInstalledPackages().Any();\n    \n    public SelectInstallLocationsViewModel(ISettingsManager settingsManager)\n    {\n        this.settingsManager = settingsManager;\n        DataDirectory = DefaultInstallLocation;\n        RefreshBadgeViewModel.RefreshFunc = ValidateDataDirectory;\n    }\n\n    public void OnLoaded()\n    {\n        RefreshBadgeViewModel.RefreshCommand.ExecuteAsync(null).SafeFireAndForget();\n    }\n    \n    // Revalidate on data directory change\n    partial void OnDataDirectoryChanged(string value)\n    {\n        RefreshBadgeViewModel.RefreshCommand.ExecuteAsync(null).SafeFireAndForget();\n    }\n    \n    private async Task<bool> ValidateDataDirectory()\n    {\n        await using var delay = new MinimumDelay(100, 200);\n\n        // Doesn't exist, this is fine as a new install, hide badge\n        if (!Directory.Exists(DataDirectory))\n        {\n            IsStatusBadgeVisible = false;\n            IsDirectoryValid = true;\n            return true;\n        }\n        // Otherwise check that a settings.json exists\n        var settingsPath = Path.Combine(DataDirectory, \"settings.json\");\n        \n        // settings.json exists: Try deserializing it\n        if (File.Exists(settingsPath))\n        {\n            try\n            {\n                var jsonText = await File.ReadAllTextAsync(settingsPath);\n                var _ = JsonSerializer.Deserialize<Settings>(jsonText, new JsonSerializerOptions\n                {\n                    Converters = { new JsonStringEnumConverter() }\n                });\n                // If successful, show existing badge\n                IsStatusBadgeVisible = true;\n                IsDirectoryValid = true;\n                DirectoryStatusText = ValidExistingDirectoryText;\n                return true;\n            }\n            catch (Exception e)\n            {\n                Logger.Info(\"Failed to deserialize settings.json: {Msg}\", e.Message);\n                // If not, show error badge, and set directory to invalid to prevent continuing\n                IsStatusBadgeVisible = true;\n                IsDirectoryValid = false;\n                DirectoryStatusText = InvalidDirectoryText;\n                return false;\n            }\n        }\n        \n        // No settings.json\n        \n        // Check if the directory is %APPDATA%\\StabilityMatrix: hide badge and set directory valid\n        if (DataDirectory == DefaultInstallLocation)\n        {\n            IsStatusBadgeVisible = false;\n            IsDirectoryValid = true;\n            return true;\n        }\n        \n        // Check if the directory is empty: hide badge and set directory to valid\n        var isEmpty = !Directory.EnumerateFileSystemEntries(DataDirectory).Any();\n        if (isEmpty)\n        {\n            IsStatusBadgeVisible = false;\n            IsDirectoryValid = true;\n            return true;\n        }\n\n        // Not empty and not appdata: show error badge, and set directory to invalid\n        IsStatusBadgeVisible = true;\n        IsDirectoryValid = false;\n        DirectoryStatusText = InvalidDirectoryText;\n        return false;\n    }\n\n    [RelayCommand]\n    private void ShowFolderBrowserDialog()\n    {\n        var dialog = new VistaFolderBrowserDialog\n        {\n            Description = \"Select a folder\",\n            UseDescriptionForTitle = true\n        };\n        if (dialog.ShowDialog() != true) return;\n        var path = dialog.SelectedPath;\n        if (path == null) return;\n        \n        DataDirectory = path;\n    }\n\n    partial void OnIsPortableModeChanged(bool value)\n    {\n        DataDirectory = value\n                ? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, \"Data\")\n                : DefaultInstallLocation;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/SelectModelVersionDialogViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing System.Windows.Media.Imaging;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class SelectModelVersionDialogViewModel : ObservableObject\n{\n    private readonly ISettingsManager settingsManager;\n    [ObservableProperty] private CivitModel civitModel;\n    [ObservableProperty] private BitmapImage previewImage;\n    [ObservableProperty] private ObservableCollection<CivitModelVersion> versions;\n    [ObservableProperty] private CivitModelVersion selectedVersion;\n    [ObservableProperty] private CivitFile selectedFile;\n    [ObservableProperty] private bool isImportEnabled;\n\n    public SelectModelVersionDialogViewModel(CivitModel civitModel, ISettingsManager settingsManager)\n    {\n        this.settingsManager = settingsManager;\n        CivitModel = civitModel;\n        Versions = new ObservableCollection<CivitModelVersion>(CivitModel.ModelVersions);\n        SelectedVersion = Versions.First();\n\n        var nsfwEnabled = settingsManager.Settings.ModelBrowserNsfwEnabled;\n        var firstImageUrl = Versions.FirstOrDefault()?.Images\n            ?.FirstOrDefault(img => nsfwEnabled || img.Nsfw == \"None\")?.Url;\n        \n        PreviewImage = firstImageUrl != null\n            ? new BitmapImage(new Uri(firstImageUrl))\n            : new BitmapImage(\n                new Uri(\"pack://application:,,,/StabilityMatrix;component/Assets/noimage.png\"));\n    }\n\n    partial void OnSelectedVersionChanged(CivitModelVersion value)\n    {\n        var nsfwEnabled = settingsManager.Settings.ModelBrowserNsfwEnabled;\n        var firstImageUrl = value.Images?.FirstOrDefault(img => nsfwEnabled || img.Nsfw == \"None\")\n            ?.Url;\n        \n        PreviewImage = firstImageUrl != null\n            ? new BitmapImage(new Uri(firstImageUrl))\n            : new BitmapImage(\n                new Uri(\"pack://application:,,,/StabilityMatrix;component/Assets/noimage.png\"));\n    }\n\n    partial void OnSelectedFileChanged(CivitFile value)\n    {\n        IsImportEnabled = value != null;\n    }\n    \n    \n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/SettingsViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Globalization;\nusing System.IO;\nusing System.Linq;\nusing System.Text.Json;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Documents;\nusing AsyncAwaitBestPractices;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing MdXaml;\nusing Microsoft.Extensions.Logging;\nusing Ookii.Dialogs.Wpf;\nusing Polly.Timeout;\nusing Refit;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Helper;\nusing Wpf.Ui.Appearance;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls;\nusing Wpf.Ui.Controls.Window;\nusing EventManager = StabilityMatrix.Core.Helper.EventManager;\nusing ISnackbarService = StabilityMatrix.Helper.ISnackbarService;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class SettingsViewModel : ObservableObject\n{\n    private readonly ILogger<SettingsViewModel> logger;\n    private readonly ISettingsManager settingsManager;\n    private readonly IDialogFactory dialogFactory;\n    private readonly IPackageFactory packageFactory;\n    private readonly IPyRunner pyRunner;\n    private readonly ISnackbarService snackbarService;\n    private readonly ILiteDbContext liteDbContext;\n    private readonly IPrerequisiteHelper prerequisiteHelper;\n    private static string LicensesPath => \"pack://application:,,,/Assets/licenses.json\";\n    public TextToFlowDocumentConverter? TextToFlowDocumentConverter { get; set; }\n\n    public ObservableCollection<string> AvailableThemes => new()\n    {\n        \"Light\",\n        \"Dark\",\n        \"System\",\n    };\n\n    public ObservableCollection<WindowBackdropType> AvailableBackdrops => new()\n    {\n        WindowBackdropType.Mica,\n        WindowBackdropType.Tabbed\n    };\n    private readonly IContentDialogService contentDialogService;\n    private readonly IA3WebApiManager a3WebApiManager;\n\n    [ObservableProperty] private bool isFileSearchFlyoutOpen;\n    [ObservableProperty] private double fileSearchProgress;\n\n    [ObservableProperty] private bool isPythonInstalling;\n\n    [ObservableProperty] private string? webApiHost;\n    [ObservableProperty] private string? webApiPort;\n    [ObservableProperty] private string? webApiActivePackageHost;\n    [ObservableProperty] private string? webApiActivePackagePort;\n    \n    partial void OnWebApiHostChanged(string? value)\n    {\n        settingsManager.Transaction(s => s.WebApiHost = value);\n        a3WebApiManager.ResetClient();\n    }\n    \n    partial void OnWebApiPortChanged(string? value)\n    {\n        settingsManager.Transaction(s => s.WebApiPort = value);\n        a3WebApiManager.ResetClient();\n    }\n    \n    [ObservableProperty] private bool keepFolderLinksOnShutdown;\n    \n    partial void OnKeepFolderLinksOnShutdownChanged(bool value)\n    {\n        if (value != settingsManager.Settings.RemoveFolderLinksOnShutdown)\n        {\n            settingsManager.Transaction(s => s.RemoveFolderLinksOnShutdown = value);\n        }\n    }\n\n    public RefreshBadgeViewModel Text2ImageRefreshBadge { get; } = new()\n    {\n        SuccessToolTipText = \"Connected\",\n        WorkingToolTipText = \"Trying to connect...\",\n        FailToolTipText = \"Failed to connect\",\n    };\n\n    public SettingsViewModel(\n        ISettingsManager settingsManager, \n        IContentDialogService contentDialogService,\n        IDialogFactory dialogFactory,\n        IA3WebApiManager a3WebApiManager, \n        IPyRunner pyRunner, \n        ISnackbarService snackbarService, \n        ILogger<SettingsViewModel> logger, \n        IPackageFactory packageFactory,\n        ILiteDbContext liteDbContext,\n        IPrerequisiteHelper prerequisiteHelper)\n    {\n        this.logger = logger;\n        this.settingsManager = settingsManager;\n        this.packageFactory = packageFactory;\n        this.contentDialogService = contentDialogService;\n        this.dialogFactory = dialogFactory;\n        this.snackbarService = snackbarService;\n        this.a3WebApiManager = a3WebApiManager;\n        this.pyRunner = pyRunner;\n        this.liteDbContext = liteDbContext;\n        this.prerequisiteHelper = prerequisiteHelper;\n        SelectedTheme = settingsManager.Settings.Theme ?? \"Dark\";\n        KeepFolderLinksOnShutdown = settingsManager.Settings.RemoveFolderLinksOnShutdown;\n    }\n\n    [ObservableProperty]\n    private bool isDebugModeEnabled;\n    partial void OnIsDebugModeEnabledChanged(bool value) => EventManager.Instance.OnDevModeSettingChanged(value);\n    \n    [ObservableProperty]\n    private string selectedTheme;\n\n    public string AppVersion => $\"Version {Utilities.GetAppVersion()}\";\n\n    partial void OnSelectedThemeChanged(string value)\n    {\n        using var st = settingsManager.BeginTransaction();\n        st.Settings.Theme = value;\n        ApplyTheme(value);\n    }\n\n    [ObservableProperty]\n    private string gpuInfo = $\"{HardwareHelper.IterGpuInfo().FirstOrDefault()}\";\n\n    [ObservableProperty] private string? testProperty;\n\n    [ObservableProperty] private bool isVersionFlyoutOpen;\n\n    private const int AppVersionClickCountThreshold = 7;\n    \n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(VersionFlyoutText))]\n    private int appVersionClickCount;\n    \n    public string VersionFlyoutText => $\"You are {AppVersionClickCountThreshold - AppVersionClickCount} clicks away from enabling Debug options.\";\n\n    partial void OnIsVersionFlyoutOpenChanged(bool value)\n    {\n        // If set to false (from timeout) clear click count\n        if (!value)\n        {\n            AppVersionClickCount = 0;\n        }\n    }\n\n    public AsyncRelayCommand PythonVersionCommand => new(async () =>\n    {\n        // Ensure python installed\n        if (!prerequisiteHelper.IsPythonInstalled)\n        {\n            IsPythonInstalling = true;\n            // Need 7z as well for site packages repack\n            await prerequisiteHelper.UnpackResourcesIfNecessary();\n            await prerequisiteHelper.InstallPythonIfNecessary();\n            IsPythonInstalling = false;\n        }\n\n        // Get python version\n        await pyRunner.Initialize();\n        var result = await pyRunner.GetVersionInfo();\n        // Show dialog box\n        var dialog = contentDialogService.CreateDialog();\n        dialog.Title = \"Python version info\";\n        dialog.Content = result;\n        dialog.PrimaryButtonText = \"Ok\";\n        await dialog.ShowAsync();\n    });\n\n    // Debug card commands\n    public RelayCommand AddInstallationCommand => new(() =>\n    {\n        // Show dialog box to choose a folder\n        var dialog = new VistaFolderBrowserDialog\n        {\n            Description = \"Select a folder\",\n            UseDescriptionForTitle = true\n        };\n        if (dialog.ShowDialog() != true) return;\n        var path = dialog.SelectedPath;\n        if (path == null) return;\n\n        // Create package\n        var package = new InstalledPackage\n        {\n            Id = Guid.NewGuid(),\n            DisplayName = Path.GetFileName(path),\n#pragma warning disable CS0618 // Type or member is obsolete\n            Path = path,\n#pragma warning restore CS0618 // Type or member is obsolete\n            PackageName = \"dank-diffusion\",\n            PackageVersion = \"v1.0.0\",\n        };\n\n        // Add package to settings\n        settingsManager.Transaction(s => s.InstalledPackages.Add(package));\n    });\n\n    // Debug card commands\n    [RelayCommand]\n    private async Task ModelFileSearchAsync()\n    {\n        // Show dialog box to choose a file\n        var fileDialog = new VistaOpenFileDialog\n        {\n            CheckFileExists = true,\n        };\n        if (fileDialog.ShowDialog() != true) return;\n        var path = fileDialog.FileName;\n        // Hash file\n        var timer = Stopwatch.StartNew();\n        IsFileSearchFlyoutOpen = true;\n        var progress = new Progress<ProgressReport>(report => FileSearchProgress = report.Percentage);\n        var fileHash = await FileHash.GetBlake3Async(path, progress);\n        var timeTakenHash = timer.Elapsed.TotalSeconds;\n        IsFileSearchFlyoutOpen = false;\n\n        // Search for file\n        timer.Restart();\n        var (model, version) = \n            await liteDbContext.FindCivitModelFromFileHashAsync(fileHash);\n        \n        timer.Stop();\n        var timeTakenSearch = timer.Elapsed.TotalMilliseconds;\n\n        var generalText =\n            $\"Time taken to hash: {timeTakenHash:F2} s\\n\" +\n            $\"Time taken to search: {timeTakenSearch:F1} ms\\n\";\n        \n        // Not found\n        if (model == null)\n        {\n            var dialog = contentDialogService.CreateDialog();\n            dialog.Title = \"Model not found :(\";\n            dialog.Content = $\"File not found in database. Hash: {fileHash}\\n\" + generalText;\n            await dialog.ShowAsync();\n        }\n        else\n        {\n            // Found\n            var dialog = contentDialogService.CreateDialog();\n            dialog.Title = \"Model found!\";\n            dialog.Content = $\"File found in database. Hash: {fileHash}\\n\" +\n                             $\"Model: {model.Name}\\n\" +\n                             $\"Version: {version!.Name}\\n\" + generalText; \n            await dialog.ShowAsync();\n        }\n    }\n\n    [RelayCommand]\n    private async Task WebViewDemo()\n    {\n        var enterUri = await dialogFactory.ShowTextEntryDialog(\"Enter a URI\", \n            new[] {\n                (\"Enter URI\", \"https://lykos.ai\")\n        });\n        if (enterUri == null) return;\n        \n        var uri = new Uri(enterUri.First());\n        var dialog = dialogFactory.CreateWebLoginDialog();\n        var loginViewModel = dialog.ViewModel;\n        loginViewModel.CurrentUri = uri;\n\n        var dialogResult = await dialog.ShowAsync();\n        logger.LogInformation(\"LoginDialog result: {Result}\", dialogResult);\n    }\n\n    [RelayCommand]\n    private async Task PingWebApi()\n    {\n        var result = await snackbarService.TryAsync(a3WebApiManager.Client.GetPing(), \"Failed to ping web api\");\n\n        if (result.IsSuccessful)\n        {\n            var dialog = contentDialogService.CreateDialog();\n            dialog.Title = \"Web API ping\";\n            dialog.Content = result;\n            dialog.PrimaryButtonText = \"Ok\";\n            await dialog.ShowAsync();\n        }\n    }\n\n    private async Task<bool> TryPingWebApi()\n    {\n        await using var minimumDelay = new MinimumDelay(100, 200);\n        try\n        {\n            await a3WebApiManager.Client.GetPing();\n            return true;\n        }\n        catch (TimeoutRejectedException)\n        {\n            logger.LogInformation(\"Ping timed out\");\n            return false;\n        }\n        catch (ApiException ex)\n        {\n            logger.LogInformation(\"Ping failed with status [{StatusCode}]: {Content}\", ex.StatusCode, ex.ReasonPhrase);\n            return false;\n        }\n    }\n    \n    [RelayCommand]\n    private void OpenAppDataDirectory()\n    {\n        // Open app data in file explorer\n        var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);\n        var appPath = Path.Combine(appDataPath, \"StabilityMatrix\");\n        Process.Start(\"explorer.exe\", appPath);\n    }\n    \n    [RelayCommand]\n    private void OpenLibraryDirectory()\n    {\n        Process.Start(\"explorer.exe\", settingsManager.LibraryDir);\n    }\n\n    [RelayCommand]\n    private async Task OpenLicenseDialog()\n    {\n        IEnumerable<LicenseInfo> licenses;\n        // Read json\n        try\n        {\n            var stream = Application.GetResourceStream(new Uri(LicensesPath));\n            using var reader = new StreamReader(stream!.Stream);\n            var licenseText = await reader.ReadToEndAsync();\n            licenses = JsonSerializer.Deserialize<IEnumerable<LicenseInfo>>(licenseText)\n                ?? throw new Exception(\"Failed to deserialize licenses\");\n        }\n        catch (Exception ex)\n        {\n            logger.LogError(ex, \"Failed to read licenses\");\n            snackbarService.ShowSnackbarAsync(\n                \"An embedded resource could not be read. Please try reinstalling the application.\", \n                \"Failed to read 'licenses.json'\").SafeFireAndForget();\n            return;\n        }\n\n        var flowViewer = new FlowDocumentScrollViewer\n        {\n            MaxHeight = 400,\n            MaxWidth = 600,\n        };\n        var markdownText = \"\";\n        foreach (var license in licenses)\n        {\n            markdownText += $\"## [**{license.PackageName}**]({license.PackageUrl}) by {string.Join(\", \", license.Authors)}\\n\\n\";\n            markdownText += $\"{license.Copyright}\\n\\n\";\n            markdownText += $\"[{license.LicenseUrl}]({license.LicenseUrl})\\n\\n\";\n        }\n        flowViewer.Document = TextToFlowDocumentConverter!.Convert(markdownText, typeof(FlowDocument), null!, CultureInfo.CurrentCulture) as FlowDocument;\n\n        var dialog = contentDialogService.CreateDialog();\n        dialog.Title = \"License and Open Source Notices\";\n        dialog.Content = flowViewer;\n        dialog.DialogMaxHeight = 1000;\n        dialog.DialogMaxWidth = 900;\n        dialog.IsPrimaryButtonEnabled = false;\n        await dialog.ShowAsync();\n    }\n\n    /// <summary>\n    /// Click app version 7 times to enable debug options\n    /// </summary>\n    [RelayCommand]\n    private async Task AppVersionClickAsync()\n    {\n        // Ignore if already enabled\n        if (IsDebugModeEnabled) return;\n        switch (AppVersionClickCount)\n        {\n            // Open flyout on 3rd click\n            case 2:\n                // Show the flyout\n                IsVersionFlyoutOpen = true;\n                AppVersionClickCount++;\n                break;\n            // Reached threshold\n            case AppVersionClickCountThreshold - 1:\n            {\n                // Close flyout\n                IsVersionFlyoutOpen = false;\n                // Enable debug options\n                IsDebugModeEnabled = true;\n                const string msg = \"Warning: Improper use may corrupt application state or cause loss of data.\";\n                var dialog = snackbarService.ShowSnackbarAsync(msg, \"Debug options enabled\",\n                    ControlAppearance.Info);\n                await dialog;\n                break;\n            }\n            default:\n                // Otherwise, increment click count\n                AppVersionClickCount++;\n                break;\n        }\n    }\n\n    [RelayCommand]\n    [DoesNotReturn]\n    private void DebugTriggerException()\n    {\n        throw new Exception(\"Test exception\");\n    }\n\n    private void ApplyTheme(string value)\n    {\n        switch (value)\n        {\n            case \"Light\":\n                Theme.Apply(ThemeType.Light);\n                break;\n            case \"Dark\":\n                Theme.Apply(ThemeType.Dark);\n                break;\n            case \"System\":\n                Theme.Apply(SystemInfo.ShouldUseDarkMode() ? ThemeType.Dark : ThemeType.Light);\n                break;\n        }\n    }\n\n    // Sets default port and host for web api fields\n    public void SetWebApiDefaults()\n    {\n        // Set from launch options\n        WebApiActivePackageHost = settingsManager.GetActivePackageHost();\n        WebApiActivePackagePort = settingsManager.GetActivePackagePort();\n        // Okay if both not empty\n        if (!string.IsNullOrWhiteSpace(WebApiActivePackageHost) && \n            !string.IsNullOrWhiteSpace(WebApiActivePackagePort)) return;\n        \n        // Also check default values\n        var currentInstall = settingsManager.Settings.GetActiveInstalledPackage();\n        if (currentInstall?.PackageName == null) return;\n        var currentPackage = packageFactory.FindPackageByName(currentInstall.PackageName);\n        if (currentPackage == null) return;\n        // Set default port and host\n        WebApiActivePackageHost ??= currentPackage.LaunchOptions\n            .FirstOrDefault(x => x.Name.ToLowerInvariant() == \"host\")?.DefaultValue as string;\n        WebApiActivePackagePort ??= currentPackage.LaunchOptions\n            .FirstOrDefault(x => x.Name.ToLowerInvariant() == \"port\")?.DefaultValue as string;\n    }\n\n    public void OnLoaded()\n    {\n        SelectedTheme = string.IsNullOrWhiteSpace(settingsManager.Settings.Theme)\n            ? \"Dark\"\n            : settingsManager.Settings.Theme;\n\n        TestProperty = $\"{SystemParameters.PrimaryScreenHeight} x {SystemParameters.PrimaryScreenWidth}\";\n        \n        // Set defaults\n        SetWebApiDefaults();\n        // Refresh text2image connection badge\n        Text2ImageRefreshBadge.RefreshFunc = TryPingWebApi;\n        Text2ImageRefreshBadge.RefreshCommand.ExecuteAsync(null).SafeFireAndForget();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/SnackbarViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Wpf.Ui.Common;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls;\nusing SymbolIcon = Wpf.Ui.Controls.IconElements.SymbolIcon;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class SnackbarViewModel : ObservableObject\n{\n    private readonly ISnackbarService snackbarService;\n\n    [ObservableProperty]\n    private ControlAppearance snackbarAppearance = ControlAppearance.Secondary;\n\n    [ObservableProperty]\n    private int snackbarTimeout = 2000;\n\n    public SnackbarViewModel(ISnackbarService snackbarService)\n    {\n        this.snackbarService = snackbarService;\n    }\n\n    [RelayCommand]\n    private void OnOpenSnackbar(object sender)\n    {\n        snackbarService.Timeout = SnackbarTimeout;\n        snackbarService.Show(\"Some title.\", \"Some message.\", new SymbolIcon(SymbolRegular.Fluent24), SnackbarAppearance);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/TextToImageViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing System.Windows.Media.Imaging;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Helper;\nusing StabilityMatrix.Models;\nusing StabilityMatrix.Services;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class TextToImageViewModel : ObservableObject\n{\n    private readonly ILogger<TextToImageViewModel> logger;\n    private readonly IA3WebApiManager a3WebApiManager;\n    private readonly ISnackbarService snackbarService;\n    private readonly PageContentDialogService pageContentDialogService;\n    private readonly ISettingsManager settingsManager;\n    private AsyncDispatcherTimer? progressQueryTimer;\n\n    [ObservableProperty]\n    private bool isGenerating;\n\n    [ObservableProperty]\n    private bool connectionFailed;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ProgressRingVisibility))]\n    [NotifyPropertyChangedFor(nameof(ImagePreviewVisibility))]\n    private bool isProgressRingActive;\n    \n    public Visibility ProgressRingVisibility => IsProgressRingActive ? Visibility.Visible : Visibility.Collapsed;\n    \n    public Visibility ImagePreviewVisibility => IsProgressRingActive ? Visibility.Collapsed : Visibility.Visible;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ProgressBarVisibility))]\n    private int progressValue;\n\n    [ObservableProperty]\n    private string positivePromptText;\n    \n    [ObservableProperty]\n    private string negativePromptText;\n    \n    [ObservableProperty]\n    private int generationSteps;\n    \n    [ObservableProperty]\n    private BitmapImage? imagePreview;\n\n    [ObservableProperty]\n    private CheckpointFolder? diffusionCheckpointFolder;\n    \n    [ObservableProperty]\n    private CheckpointFile? selectedCheckpointFile;\n    \n    public Visibility ProgressBarVisibility => ProgressValue > 0 ? Visibility.Visible : Visibility.Collapsed;\n    \n    public List<string> Samplers { get; } = new()\n    {\n        \"Euler a\", \n        \"Euler\", \n        \"DPM++ 2M Karras\"\n    };\n\n    public TextToImageViewModel(IA3WebApiManager a3WebApiManager, ILogger<TextToImageViewModel> logger, ISnackbarService snackbarService, PageContentDialogService pageContentDialogService, ISettingsManager settingsManager)\n    {\n        this.logger = logger;\n        this.a3WebApiManager = a3WebApiManager;\n        this.snackbarService = snackbarService;\n        this.pageContentDialogService = pageContentDialogService;\n        this.settingsManager = settingsManager;\n        positivePromptText = \"Positive\";\n        negativePromptText = \"Negative\";\n        generationSteps = 10;\n    }\n    \n    public async Task OnLoaded()\n    {\n        if (ConnectionFailed)\n        {\n            await PromptRetryConnection();\n        }\n        else\n        {\n            await CheckConnection();\n        }\n        \n        // Set the diffusion checkpoint folder\n        var sdModelsDir = Path.Join(settingsManager.ModelsDirectory, SharedFolderType.StableDiffusion.GetStringValue());\n        if (!Directory.Exists(sdModelsDir))\n        {\n            logger.LogWarning(\"Skipped model folder index - {SdModelsDir} does not exist\", sdModelsDir);\n            return;\n        }\n        DiffusionCheckpointFolder = new CheckpointFolder(null!, null!, null!, null!) // TODO: refactor to not use view models\n        {\n            Title = Path.GetFileName(sdModelsDir),\n            DirectoryPath = sdModelsDir\n        };\n        // Index the folder\n        await DiffusionCheckpointFolder.IndexAsync();\n        // Set the active model from the api\n        await SetActiveModelFromApi();\n    }\n\n    private async Task SetActiveModelFromApi()\n    {\n        var task = a3WebApiManager.Client.GetOptions();\n        var responseResult = await snackbarService.TryAsync(task, \"Failed to get options\");\n        if (responseResult is {IsSuccessful: true, Result: not null})\n        {\n            // Find file\n            var options = responseResult.Result;\n            var checkpointFile = DiffusionCheckpointFolder?\n                .CheckpointFiles.FirstOrDefault(f => f.FileName == options.SdModelCheckpoint);\n            logger.LogInformation(\"Set active checkpoint from api {CheckpointFile}\", checkpointFile?.FileName);\n            SelectedCheckpointFile = checkpointFile;\n        }\n    }\n\n    // Checks connection, if unsuccessful, shows a content dialog to retry\n    private async Task CheckConnection()\n    {\n        try\n        { \n            await a3WebApiManager.Client.GetPing();\n            ConnectionFailed = false;\n        }\n        catch (Exception e)\n        {\n            // On error, show a content dialog to retry\n            ConnectionFailed = true;\n            logger.LogWarning(\"Ping response failed: {EMessage}\", e.Message);\n            var dialog = pageContentDialogService.CreateDialog();\n            dialog.Title = \"Connection failed\";\n            dialog.Content = \"Please check the server is running with the --api launch option enabled.\";\n            dialog.CloseButtonText = \"Retry\";\n            dialog.IsPrimaryButtonEnabled = false;\n            dialog.IsSecondaryButtonEnabled = false;\n            await dialog.ShowAsync();\n            // Retry\n            await CheckConnection();\n        }\n    }\n\n    private async Task PromptRetryConnection()\n    {\n        var dialog = pageContentDialogService.CreateDialog();\n        dialog.Title = \"Connection failed\";\n        dialog.Content = \"Please check the server is running with the --api launch option enabled.\";\n        dialog.CloseButtonText = \"Retry\";\n        dialog.IsPrimaryButtonEnabled = false;\n        dialog.IsSecondaryButtonEnabled = false;\n        await dialog.ShowAsync();\n        // Retry\n        await CheckConnection();\n    }\n\n    private void StartProgressTracking(TimeSpan? interval = null)\n    {\n        progressQueryTimer = new AsyncDispatcherTimer\n        {\n            Interval = interval ?? TimeSpan.FromMilliseconds(150),\n            IsReentrant = false,\n            TickTask = OnProgressTrackingTick,\n        };\n        progressQueryTimer.Start();\n    }\n    \n    private void StopProgressTracking()\n    {\n        IsProgressRingActive = false;\n        ProgressValue = 0;\n        progressQueryTimer?.Stop();\n    }\n    \n    private async Task OnProgressTrackingTick()\n    {\n        var request = new ProgressRequest();\n        var task = a3WebApiManager.Client.GetProgress(request);\n        var responseResult = await snackbarService.TryAsync(task, \"Failed to get progress\");\n        if (!responseResult.IsSuccessful || responseResult.Result == null)\n        {\n            StopProgressTracking();\n            return;\n        }\n\n        var response = responseResult.Result;\n        var progress = response.Progress;\n        logger.LogInformation(\"Image Progress: {ResponseProgress}, ETA: {ResponseEtaRelative} s\", response.Progress, response.EtaRelative);\n        if (Math.Abs(progress - 1.0) < 0.01)\n        {\n            ProgressValue = 100;\n            progressQueryTimer?.Stop();\n        }\n        else\n        {\n            // Update progress\n            ProgressValue = (int) Math.Clamp(Math.Ceiling(progress * 100), 0, 100);\n            // Update preview image\n            var result = response.CurrentImage;\n            if (result != null)\n            {\n                // Stop indeterminate progress ring\n                IsProgressRingActive = false;\n                // Set preview image\n                var bitmap = Base64ToBitmap(result);\n                ImagePreview = bitmap;\n            }\n        }\n    }\n    \n    private static BitmapImage Base64ToBitmap(string base64String)\n    {\n        var imageBytes = Convert.FromBase64String(base64String);\n        \n        var bitmapImage = new BitmapImage();\n        bitmapImage.BeginInit();\n        \n        using var ms = new MemoryStream(imageBytes, 0, imageBytes.Length);\n        bitmapImage.StreamSource = ms;\n        bitmapImage.CacheOption = BitmapCacheOption.OnLoad;\n        bitmapImage.EndInit();\n        return bitmapImage;\n    }\n\n    [RelayCommand]\n    private async void TextToImageGenerate()\n    {\n        // Start indeterminate progress ring\n        IsProgressRingActive = true;\n        \n        var request = new TextToImageRequest\n        {\n            Prompt = PositivePromptText,\n            NegativePrompt = NegativePromptText,\n            Steps = GenerationSteps,\n        };\n        var task = a3WebApiManager.Client.TextToImage(request);\n        \n        // Progress track while waiting for response\n        StartProgressTracking();\n        var response = await snackbarService.TryAsync(task, \"Failed to get a response from the server\");\n        StopProgressTracking();\n\n        if (!response.IsSuccessful || response.Result == null) return;\n        \n        // Decode base64 image\n        var result = response.Result.Images[0];\n        var bitmap = Base64ToBitmap(result);\n\n        ImagePreview = bitmap;\n    }\n\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/UpdateWindowViewModel.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.Net.Http;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Models.Update;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Core.Updater;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic partial class UpdateWindowViewModel : ObservableObject\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly IHttpClientFactory httpClientFactory;\n    private readonly IUpdateHelper updateHelper;\n\n    public UpdateWindowViewModel(\n        ISettingsManager settingsManager,\n        IHttpClientFactory httpClientFactory,\n        IUpdateHelper updateHelper\n    )\n    {\n        this.settingsManager = settingsManager;\n        this.httpClientFactory = httpClientFactory;\n        this.updateHelper = updateHelper;\n    }\n\n    [ObservableProperty]\n    private string? releaseNotes;\n\n    [ObservableProperty]\n    private string? updateText;\n\n    [ObservableProperty]\n    private int progressValue;\n\n    [ObservableProperty]\n    private bool showProgressBar;\n\n    public UpdateInfo? UpdateInfo { get; set; }\n\n    public async Task OnLoaded()\n    {\n        UpdateText =\n            $\"Stability Matrix v{UpdateInfo?.Version} is now available! You currently have v{Utilities.GetAppVersion()}. Would you like to update now?\";\n\n        var client = httpClientFactory.CreateClient();\n        var response = await client.GetAsync(UpdateInfo?.Changelog);\n        if (response.IsSuccessStatusCode)\n        {\n            ReleaseNotes = await response.Content.ReadAsStringAsync();\n        }\n        else\n        {\n            ReleaseNotes = \"## Unable to load release notes\";\n        }\n    }\n\n    [RelayCommand]\n    private async Task InstallUpdate()\n    {\n        if (UpdateInfo == null)\n        {\n            return;\n        }\n\n        ShowProgressBar = true;\n        UpdateText = $\"Downloading update v{UpdateInfo.Version}...\";\n        await updateHelper.DownloadUpdate(\n            UpdateInfo,\n            new Progress<ProgressReport>(report =>\n            {\n                ProgressValue = Convert.ToInt32(report.Percentage);\n            })\n        );\n\n        UpdateText = \"Update complete. Restarting Stability Matrix in 3 seconds...\";\n        await Task.Delay(1000);\n        UpdateText = \"Update complete. Restarting Stability Matrix in 2 seconds...\";\n        await Task.Delay(1000);\n        UpdateText = \"Update complete. Restarting Stability Matrix in 1 second...\";\n        await Task.Delay(1000);\n\n        Process.Start(UpdateHelper.ExecutablePath);\n        Application.Current.Shutdown();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/ViewModels/WebLoginViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Microsoft.Web.WebView2.Core;\nusing NLog;\n\nnamespace StabilityMatrix.ViewModels;\n\npublic record struct NavigationResult(Uri? Uri, List<CoreWebView2Cookie>? Cookies);\n\npublic partial class WebLoginViewModel : ObservableObject\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    // Login Url, set externally on dialog creation\n    [ObservableProperty] private string? loginUrl;\n    // Bound current url source\n    [ObservableProperty] private Uri? currentUri;\n    // Always true after first navigation completed\n    [ObservableProperty] private bool isContentLoaded;\n    \n    // Events\n    public event EventHandler<NavigationResult> NavigationCompleted = delegate { };\n    public event EventHandler<NavigationResult> SourceChanged = delegate { };\n\n    public void OnLoaded()\n    {\n    }\n\n    /// <summary>\n    /// Called on navigation source changes.\n    /// </summary>\n    public void OnSourceChanged(Uri? source, List<CoreWebView2Cookie>? cookies)\n    {\n        Logger.Debug($\"WebView source changed to {source} ({cookies?.Count} cookies)\");\n        SourceChanged.Invoke(this, new NavigationResult(source, cookies));\n    }\n    \n    /// <summary>\n    /// Called on navigation completed. (After scrollbar patch)\n    /// </summary>\n    public void OnNavigationCompleted(Uri? uri)\n    {\n        Logger.Debug($\"WebView loaded: {uri}\");\n        NavigationCompleted.Invoke(this, new NavigationResult(uri, null));\n        IsContentLoaded = true;\n    }\n    \n    /// <summary>\n    /// Waits for navigation to a specific uri\n    /// </summary>\n    public async Task WaitForNavigation(Uri uri, CancellationToken ct = default)\n    {\n        Logger.Debug($\"Waiting for navigation to {uri}\");\n        \n        var navigationTask = new TaskCompletionSource<bool>();\n        \n        var handler = new EventHandler<NavigationResult>((_, result) =>\n        {\n            navigationTask.TrySetResult(true);\n        });\n        \n        NavigationCompleted += handler;\n        try\n        {\n            await using (ct.Register(() => navigationTask.TrySetCanceled()))\n            {\n                CurrentUri = uri;\n                await navigationTask.Task;\n            }\n        }\n        finally\n        {\n            NavigationCompleted -= handler;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/WebLoginDialog.xaml",
    "content": "﻿<ui:ContentDialog\n    CloseButtonText=\"Close\"\n    DialogMaxHeight=\"750\"\n    DialogMaxWidth=\"600\"\n    Loaded=\"WebLoginDialog_OnLoaded\"\n    Title=\"Login\"\n    d:DataContext=\"{d:DesignInstance Type=viewModels:WebLoginViewModel,\n                                     IsDesignTimeCreatable=True}\"\n    d:DesignHeight=\"650\"\n    d:DesignWidth=\"600\"\n    mc:Ignorable=\"d\"\n    ui:Design.Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n    ui:Design.Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n    x:Class=\"StabilityMatrix.WebLoginDialog\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:local=\"clr-namespace:StabilityMatrix\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.ViewModels\"\n    xmlns:wv2=\"clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n\n    <ui:ContentDialog.Resources>\n        <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n        <Style BasedOn=\"{StaticResource {x:Type ui:ContentDialog}}\" TargetType=\"{x:Type local:WebLoginDialog}\">\n            <!--  Disable scrollbars on the dialog  -->\n            <Setter Property=\"ScrollViewer.HorizontalScrollBarVisibility\" Value=\"Auto\" />\n            <Setter Property=\"ScrollViewer.VerticalScrollBarVisibility\" Value=\"Disabled\" />\n        </Style>\n\n        <BooleanToVisibilityConverter x:Key=\"BooleanToVisibilityConverter\" />\n        <converters:ValueConverterGroup x:Key=\"InvertIsStringNullOrWhitespaceConverter\">\n            <converters:IsStringNullOrWhitespaceConverter />\n            <converters:BoolNegationConverter />\n        </converters:ValueConverterGroup>\n    </ui:ContentDialog.Resources>\n\n    <Grid Margin=\"0,0,0,12\">\n        <Border\n            BorderBrush=\"Transparent\"\n            BorderThickness=\"8\"\n            CornerRadius=\"4\"\n            Padding=\"8\">\n\n            <DockPanel\n                Background=\"{DynamicResource ApplicationBackgroundBrush}\"\n                MaxHeight=\"600\"\n                MinHeight=\"500\"\n                MinWidth=\"400\">\n                <!--  Content  -->\n                <wv2:WebView2\n                    DefaultBackgroundColor=\"Transparent\"\n                    MinHeight=\"500\"\n                    Name=\"LoginWebView\"\n                    NavigationCompleted=\"LoginWebView_OnNavigationCompleted\"\n                    Source=\"{Binding CurrentUri, Mode=TwoWay}\"\n                    SourceChanged=\"LoginWebView_OnSourceChanged\" />\n            </DockPanel>\n\n        </Border>\n    </Grid>\n\n</ui:ContentDialog>\n"
  },
  {
    "path": "StabilityMatrix/WebLoginDialog.xaml.cs",
    "content": "﻿using System.Windows;\nusing Microsoft.Web.WebView2.Core;\nusing StabilityMatrix.ViewModels;\nusing Wpf.Ui.Contracts;\nusing Wpf.Ui.Controls.ContentDialogControl;\n\nnamespace StabilityMatrix;\n\npublic partial class WebLoginDialog : ContentDialog\n{\n    private const string DisableScrollbarJs =\n        @\"document.querySelector('body').style.overflow='scroll';\n        var style=document.createElement('style');style.type='text/css';\n        style.innerHTML='::-webkit-scrollbar{display:none}';\n        document.getElementsByTagName('body')[0].appendChild(style)\";\n\n    private readonly Microsoft.Web.WebView2.Wpf.WebView2 currentWebView;\n    \n    public WebLoginViewModel ViewModel { get; set; }\n    \n    public WebLoginDialog(IContentDialogService dialogService, WebLoginViewModel viewModel) : base(\n        dialogService.GetContentPresenter())\n    {\n        InitializeComponent();\n        DataContext = viewModel;\n        ViewModel = viewModel;\n        currentWebView = LoginWebView;\n    }\n\n    // Pass through OnLoaded to ViewModel\n    private void WebLoginDialog_OnLoaded(object sender, RoutedEventArgs e) => ViewModel.OnLoaded();\n\n    // On nav complete we run js to hide scrollbar while allowing scrolling\n    private async void LoginWebView_OnNavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs e)\n    {\n        if (e.IsSuccess)\n        {\n            await currentWebView.ExecuteScriptAsync(DisableScrollbarJs);\n        }\n        // Pass to ViewModel event\n        ViewModel.OnNavigationCompleted(currentWebView.Source);\n    }\n    \n    // This happens before OnNavigationCompleted\n    private async void LoginWebView_OnSourceChanged(object? sender, CoreWebView2SourceChangedEventArgs e)\n    {\n        var url = currentWebView.Source;\n        var cookies = url is null ? null :\n            await currentWebView.CoreWebView2.CookieManager.GetCookiesAsync(url.AbsoluteUri);\n        ViewModel.OnSourceChanged(currentWebView.Source, cookies);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/app.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n  <assemblyIdentity version=\"1.0.0.0\" name=\"StabilityMatrix.app\"/>\n\n  <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n      <!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8. \n      For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 \n      \n      It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->\n      <supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\" />\n    </application>\n  </compatibility>\n  \n  <application xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n    <windowsSettings>\n      <!-- The combination of below two tags have the following effect:\n           1) Per-Monitor for >= Windows 10 Anniversary Update\n           2) System < Windows 10 Anniversary Update\n      -->\n      <dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true/PM</dpiAware>\n      <dpiAwareness xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">PerMonitorV2, PerMonitor</dpiAwareness>\n    </windowsSettings>\n  </application>\n</assembly>"
  },
  {
    "path": "StabilityMatrix/appsettings.Development.json",
    "content": "﻿{\n    \"GithubApiKey\": \"\",\n    \"DebugOptions\": {\n        \"TempDatabase\": false,\n        \"ShowOneClickInstall\": false\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix/appsettings.json",
    "content": "﻿{\n  \"GithubApiKey\": \"\"\n}\n"
  },
  {
    "path": "StabilityMatrix.Analyzers.CodeFixes/StabilityMatrix.Analyzers.CodeFixes.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>net9.0</TargetFramework>\n        <ImplicitUsings>enable</ImplicitUsings>\n        <Nullable>enable</Nullable>\n    </PropertyGroup>\n\n</Project>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Animations/BaseTransitionInfo.cs",
    "content": "﻿using System;\nusing FluentAvalonia.UI.Media.Animation;\n\nnamespace StabilityMatrix.Avalonia.Animations;\n\npublic abstract class BaseTransitionInfo : NavigationTransitionInfo\n{\n    /// <summary>\n    /// The duration of the animation at 1x animation scale\n    /// </summary>\n    public abstract TimeSpan Duration { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Animations/BetterDrillInNavigationTransition.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing Avalonia;\nusing Avalonia.Animation;\nusing Avalonia.Animation.Easings;\nusing Avalonia.Media;\nusing Avalonia.Styling;\n\nnamespace StabilityMatrix.Avalonia.Animations;\n\npublic class BetterDrillInNavigationTransition : BaseTransitionInfo\n{\n    /// <summary>\n    /// Gets or sets whether the animation should drill in (false) or drill out (true)\n    /// </summary>\n    public bool IsReversed { get; set; } = false; //Zoom out if true\n\n    public override TimeSpan Duration { get; set; } = TimeSpan.FromMilliseconds(400);\n\n    public override async void RunAnimation(Animatable ctrl, CancellationToken cancellationToken)\n    {\n        var animation = new Animation\n        {\n            Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0),\n            Children =\n            {\n                new KeyFrame\n                {\n                    Setters =\n                    {\n                        new Setter(Visual.OpacityProperty, 0.0),\n                        new Setter(ScaleTransform.ScaleXProperty, IsReversed ? 1.5 : 0.0),\n                        new Setter(ScaleTransform.ScaleYProperty, IsReversed ? 1.5 : 0.0)\n                    },\n                    Cue = new Cue(0d)\n                },\n                new KeyFrame\n                {\n                    Setters =\n                    {\n                        new Setter(Visual.OpacityProperty, 1.0),\n                        new Setter(ScaleTransform.ScaleXProperty, IsReversed ? 1.0 : 1.0),\n                        new Setter(ScaleTransform.ScaleYProperty, IsReversed ? 1.0 : 1.0)\n                    },\n                    Cue = new Cue(1d)\n                }\n            },\n            Duration = Duration,\n            FillMode = FillMode.Forward\n        };\n\n        await animation.RunAsync(ctrl, cancellationToken);\n\n        if (ctrl is Visual visualCtrl)\n        {\n            visualCtrl.Opacity = 1;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Animations/BetterEntranceNavigationTransition.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing Avalonia;\nusing Avalonia.Animation;\nusing Avalonia.Animation.Easings;\nusing Avalonia.Media;\nusing Avalonia.Styling;\n\nnamespace StabilityMatrix.Avalonia.Animations;\n\npublic class BetterEntranceNavigationTransition : BaseTransitionInfo\n{\n    public override TimeSpan Duration { get; set; } = TimeSpan.FromMilliseconds(500);\n\n    /// <summary>\n    /// Gets or sets the Horizontal Offset used when animating\n    /// </summary>\n    public double FromHorizontalOffset { get; set; } = 0;\n\n    /// <summary>\n    /// Gets or sets the Vertical Offset used when animating\n    /// </summary>\n    public double FromVerticalOffset { get; set; } = 100;\n\n    public override async void RunAnimation(Animatable ctrl, CancellationToken cancellationToken)\n    {\n        var animation = new Animation\n        {\n            Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0),\n            Children =\n            {\n                new KeyFrame\n                {\n                    Setters =\n                    {\n                        new Setter(Visual.OpacityProperty, 0.0),\n                        new Setter(TranslateTransform.XProperty, FromHorizontalOffset),\n                        new Setter(TranslateTransform.YProperty, FromVerticalOffset)\n                    },\n                    Cue = new Cue(0d)\n                },\n                new KeyFrame\n                {\n                    Setters =\n                    {\n                        new Setter(Visual.OpacityProperty, 1d),\n                        new Setter(TranslateTransform.XProperty, 0.0),\n                        new Setter(TranslateTransform.YProperty, 0.0)\n                    },\n                    Cue = new Cue(1d)\n                }\n            },\n            Duration = Duration,\n            FillMode = FillMode.Forward\n        };\n\n        await animation.RunAsync(ctrl, cancellationToken);\n\n        if (ctrl is Visual visualCtrl)\n        {\n            visualCtrl.Opacity = 1;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing Avalonia;\nusing Avalonia.Animation;\nusing Avalonia.Animation.Easings;\nusing Avalonia.Media;\nusing Avalonia.Styling;\nusing FluentAvalonia.UI.Media.Animation;\n\nnamespace StabilityMatrix.Avalonia.Animations;\n\npublic class BetterSlideNavigationTransition : BaseTransitionInfo\n{\n    public override TimeSpan Duration { get; set; } = TimeSpan.FromMilliseconds(167);\n\n    /// <summary>\n    /// Gets or sets the type of animation effect to play during the slide transition.\n    /// </summary>\n    public SlideNavigationTransitionEffect Effect { get; set; } = SlideNavigationTransitionEffect.FromRight;\n\n    /// <summary>\n    /// Gets or sets the HorizontalOffset used when animating from the Left or Right\n    /// </summary>\n    public double FromHorizontalOffset { get; set; } = 56;\n\n    /// <summary>\n    /// Gets or sets the VerticalOffset used when animating from the Top or Bottom\n    /// </summary>\n    public double FromVerticalOffset { get; set; } = 56;\n\n    /// <summary>\n    /// Gets or sets the easing function applied to the slide transition.\n    /// </summary>\n    public Easing Easing { get; set; } = new SplineEasing(0.1, 0.9, 0.2, 1.0);\n\n    public override async void RunAnimation(Animatable ctrl, CancellationToken cancellationToken)\n    {\n        double length = 0;\n        bool isVertical = false;\n        switch (Effect)\n        {\n            case SlideNavigationTransitionEffect.FromLeft:\n                length = -FromHorizontalOffset;\n                break;\n            case SlideNavigationTransitionEffect.FromRight:\n                length = FromHorizontalOffset;\n                break;\n            case SlideNavigationTransitionEffect.FromTop:\n                length = -FromVerticalOffset;\n                isVertical = true;\n                break;\n            case SlideNavigationTransitionEffect.FromBottom:\n                length = FromVerticalOffset;\n                isVertical = true;\n                break;\n        }\n\n        var animation = new Animation\n        {\n            Easing = Easing,\n            Children =\n            {\n                new KeyFrame\n                {\n                    Setters =\n                    {\n                        new Setter(\n                            isVertical ? TranslateTransform.YProperty : TranslateTransform.XProperty,\n                            length\n                        ),\n                        new Setter(Visual.OpacityProperty, 0d)\n                    },\n                    Cue = new Cue(0d)\n                },\n                new KeyFrame { Setters = { new Setter(Visual.OpacityProperty, 1d) }, Cue = new Cue(0.05d) },\n                new KeyFrame\n                {\n                    Setters =\n                    {\n                        new Setter(Visual.OpacityProperty, 1d),\n                        new Setter(\n                            isVertical ? TranslateTransform.YProperty : TranslateTransform.XProperty,\n                            0.0\n                        )\n                    },\n                    Cue = new Cue(1d)\n                }\n            },\n            Duration = Duration,\n            FillMode = FillMode.Forward\n        };\n\n        await animation.RunAsync(ctrl, cancellationToken);\n\n        if (ctrl is Visual visual)\n        {\n            visual.Opacity = 1;\n        }\n    }\n\n    public static BetterSlideNavigationTransition PageSlideFromLeft =>\n        new()\n        {\n            Duration = TimeSpan.FromMilliseconds(300),\n            Effect = SlideNavigationTransitionEffect.FromLeft,\n            FromHorizontalOffset = 150,\n            Easing = new SplineEasing(0.6, 0.4, 0.1, 0.1)\n        };\n\n    public static BetterSlideNavigationTransition PageSlideFromRight =>\n        new()\n        {\n            Duration = TimeSpan.FromMilliseconds(300),\n            Effect = SlideNavigationTransitionEffect.FromRight,\n            FromHorizontalOffset = 150,\n            Easing = new SplineEasing(0.6, 0.4, 0.1, 0.1)\n        };\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Animations/ItemsRepeaterArrangeAnimation.cs",
    "content": "﻿using System;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Rendering.Composition;\n\nnamespace StabilityMatrix.Avalonia.Animations;\n\npublic class ItemsRepeaterArrangeAnimation : AvaloniaObject\n{\n    public static readonly AttachedProperty<bool> EnableItemsArrangeAnimationProperty =\n        AvaloniaProperty.RegisterAttached<ItemsRepeater, bool>(\n            \"EnableItemsArrangeAnimation\",\n            typeof(ItemsRepeaterArrangeAnimation)\n        );\n\n    static ItemsRepeaterArrangeAnimation()\n    {\n        EnableItemsArrangeAnimationProperty.Changed.AddClassHandler<ItemsRepeater>(\n            OnEnableItemsArrangeAnimationChanged\n        );\n    }\n\n    private static void OnEnableItemsArrangeAnimationChanged(\n        ItemsRepeater itemsRepeater,\n        AvaloniaPropertyChangedEventArgs eventArgs\n    )\n    {\n        if (eventArgs.NewValue is true)\n        {\n            itemsRepeater.ElementPrepared += OnElementPrepared;\n            itemsRepeater.ElementIndexChanged += OnElementIndexChanged;\n        }\n        else\n        {\n            // itemsRepeater.Opened -= OnOpened;\n        }\n    }\n\n    private static void CreateAnimation(Visual item)\n    {\n        var compositionVisual =\n            ElementComposition.GetElementVisual(item) ?? throw new NullReferenceException();\n\n        if (compositionVisual.ImplicitAnimations is { } animations && animations.HasKey(\"Offset\"))\n        {\n            return;\n        }\n\n        var compositor = compositionVisual.Compositor;\n\n        var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();\n        offsetAnimation.Target = \"Offset\";\n        // Using the \"this.FinalValue\" to indicate the last value of the Offset property\n        offsetAnimation.InsertExpressionKeyFrame(1.0f, \"this.FinalValue\");\n        offsetAnimation.Duration = TimeSpan.FromMilliseconds(150);\n\n        // Create a new implicit animation collection and bind the offset animation\n        var implicitAnimationCollection = compositor.CreateImplicitAnimationCollection();\n        implicitAnimationCollection[\"Offset\"] = offsetAnimation;\n        compositionVisual.ImplicitAnimations = implicitAnimationCollection;\n    }\n\n    private static void OnElementPrepared(object? sender, ItemsRepeaterElementPreparedEventArgs e)\n    {\n        if (\n            sender is not ItemsRepeater itemsRepeater\n            || !GetEnableItemsArrangeAnimation(itemsRepeater)\n        )\n            return;\n\n        CreateAnimation(itemsRepeater);\n    }\n\n    private static void OnElementIndexChanged(\n        object? sender,\n        ItemsRepeaterElementIndexChangedEventArgs e\n    )\n    {\n        if (\n            sender is not ItemsRepeater itemsRepeater\n            || !GetEnableItemsArrangeAnimation(itemsRepeater)\n        )\n            return;\n\n        CreateAnimation(itemsRepeater);\n    }\n\n    /*private static void OnOpened(object sender, EventArgs e)\n    {\n        if (sender is not WindowBase windowBase || !GetEnableScaleShowAnimation(windowBase))\n            return;\n\n        // Here we explicitly animate the \"Scale\" property\n        // The implementation is the same as `Offset` at the beginning, but just with the Scale property\n        windowBase.StartWindowScaleAnimation();\n    }*/\n\n    public static bool GetEnableItemsArrangeAnimation(ItemsRepeater element)\n    {\n        return element.GetValue(EnableItemsArrangeAnimationProperty);\n    }\n\n    public static void SetEnableItemsArrangeAnimation(ItemsRepeater element, bool value)\n    {\n        element.SetValue(EnableItemsArrangeAnimationProperty, value);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/App.axaml",
    "content": "﻿<Application\n    x:Class=\"StabilityMatrix.Avalonia.App\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:labs=\"clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls\"\n    xmlns:local=\"using:StabilityMatrix.Avalonia\"\n    xmlns:styles=\"clr-namespace:StabilityMatrix.Avalonia.Styles\"\n    xmlns:styling=\"clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia\"\n    Name=\"Stability Matrix\"\n    RequestedThemeVariant=\"Dark\">\n    <!--  \"Default\" ThemeVariant follows system theme variant. \"Dark\" or \"Light\" are other available options.  -->\n\n    <Application.DataTemplates>\n        <local:ViewLocator />\n    </Application.DataTemplates>\n\n    <Application.Resources>\n        <ResourceDictionary>\n            <ResourceDictionary.MergedDictionaries>\n                <ResourceInclude Source=\"Styles/ThemeColors.axaml\" />\n                <ResourceInclude Source=\"Styles/ThemeMaterials.axaml\" />\n                <ResourceInclude Source=\"Styles/Card.axaml\" />\n                <ResourceInclude Source=\"Controls/CodeCompletion/CompletionListThemes.axaml\" />\n                <ResourceInclude Source=\"Styles/ContextMenuStyles.axaml\" />\n                <ResourceInclude Source=\"Controls/EditorFlyouts.axaml\" />\n                <ResourceInclude Source=\"Controls/Scroll/BetterScrollViewer.axaml\" />\n                <ResourceInclude Source=\"Styles/ControlThemes/_index.axaml\" />\n                <ResourceInclude Source=\"Styles/ListBoxStyles.axaml\" />\n                <ResourceInclude Source=\"Styles/FAComboBoxStyles.axaml\" />\n                <ResourceInclude Source=\"Controls/Inference/ImageFolderCard.axaml\" />\n                <ResourceInclude Source=\"Controls/VendorLabs/Themes/BetterAsyncImage.axaml\" />\n                <ResourceInclude Source=\"Styles/ControlThemes/ButtonStyles.Accelerator.axaml\" />\n            </ResourceDictionary.MergedDictionaries>\n\n            <ControlRecycling x:Key=\"ControlRecyclingKey\" />\n            <x:Double x:Key=\"ContentDialogMaxWidth\">700</x:Double>\n\n            <SolidColorBrush x:Key=\"CoreSplashScreenBackground\" Color=\"#141414\" />\n            <SolidColorBrush x:Key=\"CoreSplashScreenForeground\" Color=\"#9FBDC3\" />\n            <SolidColorBrush x:Key=\"ToolTipBackground\" Color=\"#1E1F22\" />\n            <SolidColorBrush x:Key=\"ToolTipForeground\" Color=\"#9FBDC3\" />\n            <FontFamily x:Key=\"NotoSansJP\">avares://StabilityMatrix.Avalonia/Assets/Fonts/NotoSansJP#Noto Sans JP</FontFamily>\n        </ResourceDictionary>\n    </Application.Resources>\n\n    <Application.Styles>\n        <styles:SemiStyles />\n        <styling:FluentAvaloniaTheme\n            PreferUserAccentColor=\"True\"\n            TextVerticalAlignmentOverrideBehavior=\"Disabled\"\n            UseSystemFontOnWindows=\"True\" />\n        <StyleInclude Source=\"avares://Dock.Avalonia/Themes/DockSimpleTheme.axaml\" />\n        <StyleInclude Source=\"avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml\" />\n        <StyleInclude Source=\"avares://AsyncImageLoader.Avalonia/AdvancedImage.axaml\" />\n        <StyleInclude Source=\"avares://Avalonia.Xaml.Interactions.Draggable/Styles.axaml\" />\n        <StyleInclude Source=\"avares://FluentAvalonia.BreadcrumbBar/Styling/Styles.axaml\" />\n        <StyleInclude Source=\"Styles/ProgressRing.axaml\" />\n        <StyleInclude Source=\"Styles/ButtonStyles.axaml\" />\n        <StyleInclude Source=\"Styles/SplitButtonStyles.axaml\" />\n        <StyleInclude Source=\"Styles/ToggleButtonStyles.axaml\" />\n        <StyleInclude Source=\"Styles/DockStyles.axaml\" />\n        <StyleInclude Source=\"Styles/BorderStyles.axaml\" />\n        <StyleInclude Source=\"Styles/TextBoxStyles.axaml\" />\n        <StyleInclude Source=\"Styles/CommandBarButtonStyles.axaml\" />\n        <StyleInclude Source=\"Controls/AdvancedImageBox.axaml\" />\n        <StyleInclude Source=\"Controls/FrameCarousel.axaml\" />\n        <StyleInclude Source=\"Controls/CodeCompletion/CompletionWindow.axaml\" />\n        <StyleInclude Source=\"Controls/Paginator.axaml\" />\n        <StyleInclude Source=\"Controls/SelectableImageCard/SelectableImageButton.axaml\" />\n        <StyleInclude Source=\"Controls/SettingsAccountLinkExpander.axaml\" />\n        <StyleInclude Source=\"Controls/StarsRating.axaml\" />\n        <StyleInclude Source=\"Controls/VideoGenerationSettingsCard.axaml\" />\n        <StyleInclude Source=\"Controls/VideoOutputSettingsCard.axaml\" />\n        <StyleInclude Source=\"Controls/TreeFileExplorer.axaml\" />\n        <StyleInclude Source=\"Controls/GitVersionSelector.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/StackCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/StackEditableCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/StackExpander.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/SeedCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/SamplerCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/ImageGalleryCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/PromptCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/UpscalerCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/ModelCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/FaceDetailerCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/BatchSizeCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/SelectImageCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/SharpenCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/FreeUCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/ControlNetCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/PromptExpansionCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/ExtraNetworkCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/LayerDiffuseCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/UnetModelCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/DiscreteModelSamplingCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/RescaleCfgCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/TiledVAECard.axaml\" />\n        <StyleInclude Source=\"Controls/Painting/PaintCanvas.axaml\" />\n        <StyleInclude Source=\"Controls/MarkdownViewer.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/WanModelCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/PlasmaNoiseCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/NrsCard.axaml\" />\n        <StyleInclude Source=\"Controls/Inference/CfzCudnnToggleCard.axaml\" />\n        <labs:ControlThemes />\n\n        <Style Selector=\"DockControl\">\n            <Setter Property=\"(ControlRecyclingDataTemplate.ControlRecycling)\" Value=\"{StaticResource ControlRecyclingKey}\" />\n        </Style>\n        <Style Selector=\"ScrollViewer\">\n            <Setter Property=\"BringIntoViewOnFocusChange\" Value=\"False\" />\n        </Style>\n\n        <Style Selector=\"DataValidationErrors\">\n            <Setter Property=\"Template\">\n                <ControlTemplate>\n                    <DockPanel LastChildFill=\"True\">\n                        <ContentControl\n                            Content=\"{Binding (DataValidationErrors.Errors)}\"\n                            ContentTemplate=\"{TemplateBinding ErrorTemplate}\"\n                            DataContext=\"{TemplateBinding Owner}\"\n                            DockPanel.Dock=\"Bottom\"\n                            IsVisible=\"{Binding (DataValidationErrors.HasErrors)}\" />\n                        <ContentPresenter\n                            Name=\"PART_ContentPresenter\"\n                            Padding=\"{TemplateBinding Padding}\"\n                            Background=\"{TemplateBinding Background}\"\n                            BorderBrush=\"{TemplateBinding BorderBrush}\"\n                            BorderThickness=\"{TemplateBinding BorderThickness}\"\n                            Content=\"{TemplateBinding Content}\"\n                            ContentTemplate=\"{TemplateBinding ContentTemplate}\"\n                            CornerRadius=\"{TemplateBinding CornerRadius}\" />\n                    </DockPanel>\n                </ControlTemplate>\n            </Setter>\n            <Setter Property=\"ErrorTemplate\">\n                <DataTemplate x:DataType=\"{x:Type x:Object}\">\n                    <StackPanel\n                        Margin=\"4,8,1,0\"\n                        Background=\"Transparent\"\n                        Orientation=\"Horizontal\">\n                        <Path\n                            VerticalAlignment=\"Center\"\n                            Data=\"M14,7 A7,7 0 0,0 0,7 M0,7 A7,7 0 1,0 14,7 M7,3l0,5 M7,9l0,2\"\n                            Stroke=\"OrangeRed\"\n                            StrokeThickness=\"2\" />\n                        <ItemsControl\n                            Margin=\"4,0,0,0\"\n                            VerticalAlignment=\"Center\"\n                            Foreground=\"OrangeRed\"\n                            ItemsSource=\"{Binding}\" />\n                    </StackPanel>\n                </DataTemplate>\n            </Setter>\n        </Style>\n    </Application.Styles>\n</Application>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/App.axaml.cs",
    "content": "using System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Net;\nusing System.Net.Http.Headers;\nusing System.Reflection;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing Apizr;\nusing Apizr.Logging;\nusing AsyncAwaitBestPractices;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.ApplicationLifetimes;\nusing Avalonia.Data.Core.Plugins;\nusing Avalonia.Input.Platform;\nusing Avalonia.Markup.Xaml;\nusing Avalonia.Media;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Platform;\nusing Avalonia.Platform.Storage;\nusing Avalonia.Styling;\nusing Avalonia.Threading;\nusing FluentAvalonia.Interop;\nusing FluentAvalonia.UI.Controls;\nusing MessagePipe;\nusing MessagePipe.Interprocess.Workers;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Options;\nusing NLog;\nusing NLog.Config;\nusing NLog.Extensions.Logging;\nusing NLog.Targets;\nusing Octokit;\nusing OpenIddict.Abstractions;\nusing OpenIddict.Client;\nusing Polly;\nusing Polly.Contrib.WaitAndRetry;\nusing Polly.Extensions.Http;\nusing Polly.Timeout;\nusing Refit;\nusing StabilityMatrix.Avalonia.Behaviors;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Logging;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Progress;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Api.LykosAuthApi;\nusing StabilityMatrix.Core.Api.PromptGenApi;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Converters.Json;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Analytics;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Configs;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Core.Updater;\nusing ApiOptions = StabilityMatrix.Core.Models.Configs.ApiOptions;\nusing Application = Avalonia.Application;\nusing Debug = System.Diagnostics.Debug;\nusing Logger = NLog.Logger;\nusing LogLevel = Microsoft.Extensions.Logging.LogLevel;\nusing ProductHeaderValue = Octokit.ProductHeaderValue;\n#if SM_LOG_WINDOW\nusing StabilityMatrix.Avalonia.Diagnostics.LogViewer;\nusing StabilityMatrix.Avalonia.Diagnostics.LogViewer.Extensions;\n#endif\n\nnamespace StabilityMatrix.Avalonia;\n\npublic sealed class App : Application\n{\n    private static readonly Lazy<Logger> LoggerLazy = new(LogManager.GetCurrentClassLogger);\n    private static Logger Logger => LoggerLazy.Value;\n\n    private readonly SemaphoreSlim onExitSemaphore = new(1, 1);\n\n    /// <summary>\n    /// True if <see cref=\"OnShutdownRequested\"/> has started async dispose of services.\n    /// </summary>\n    private bool isAsyncDisposeStarted;\n\n    /// <summary>\n    /// True if <see cref=\"OnShutdownRequested\"/> has completed async dispose of services.\n    /// </summary>\n    private bool isAsyncDisposeComplete;\n\n    private bool isOnExitComplete;\n\n    private ServiceProvider? serviceProvider;\n\n    [NotNull]\n    public static Visual? VisualRoot { get; internal set; }\n\n    public static TopLevel TopLevel => TopLevel.GetTopLevel(VisualRoot).Unwrap();\n\n    public static IStorageProvider StorageProvider => TopLevel.StorageProvider;\n\n    public static IClipboard? Clipboard => TopLevel.Clipboard;\n\n    // ReSharper disable once MemberCanBePrivate.Global\n    [NotNull]\n    public static IConfiguration? Config { get; private set; }\n\n    // ReSharper disable once MemberCanBePrivate.Global\n    public IClassicDesktopStyleApplicationLifetime? DesktopLifetime =>\n        ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;\n\n    public static new App? Current => (App?)Application.Current;\n\n    [NotNull]\n    public static IServiceProvider? Services =>\n        Design.IsDesignMode ? DesignData.DesignData.Services : Current?.serviceProvider;\n\n    internal static bool IsHeadlessMode =>\n        TopLevel.TryGetPlatformHandle()?.HandleDescriptor is null or \"STUB\";\n\n    /// <summary>\n    /// Called before <see cref=\"Services\"/> is built.\n    /// Can be used by UI tests to override services.\n    /// </summary>\n    internal static event EventHandler<IServiceCollection>? BeforeBuildServiceProvider;\n\n    public override void Initialize()\n    {\n        AvaloniaXamlLoader.Load(this);\n\n        SetFontFamily(GetPlatformDefaultFontFamily());\n\n        // Set design theme\n        if (Design.IsDesignMode)\n        {\n            RequestedThemeVariant = ThemeVariant.Dark;\n        }\n    }\n\n    public override void OnFrameworkInitializationCompleted()\n    {\n        base.OnFrameworkInitializationCompleted();\n\n        if (Design.IsDesignMode)\n        {\n            DesignData.DesignData.Initialize();\n            // serviceProvider = (ServiceProvider?) DesignData.DesignData.Services;\n        }\n        else\n        {\n            ConfigureServiceProvider();\n        }\n\n        // Remove DataAnnotations validation plugin since we're using INotifyDataErrorInfo from MvvmToolkit\n        var dataValidationPluginsToRemove = BindingPlugins\n            .DataValidators.OfType<DataAnnotationsValidationPlugin>()\n            .ToArray();\n\n        foreach (var plugin in dataValidationPluginsToRemove)\n        {\n            BindingPlugins.DataValidators.Remove(plugin);\n        }\n\n        if (DesktopLifetime is not null)\n        {\n            DesktopLifetime.ShutdownMode = ShutdownMode.OnExplicitShutdown;\n\n            Setup();\n\n            // First time setup if needed\n            var settingsManager = Services.GetRequiredService<ISettingsManager>();\n            if (!settingsManager.IsEulaAccepted())\n            {\n                var setupWindow = Services.GetRequiredService<FirstLaunchSetupWindow>();\n                var setupViewModel = Services.GetRequiredService<FirstLaunchSetupViewModel>();\n                setupWindow.DataContext = setupViewModel;\n                setupWindow.ShowAsDialog = true;\n                setupWindow.ShowActivated = true;\n                setupWindow.ShowAsyncCts = new CancellationTokenSource();\n\n                setupWindow.ExtendClientAreaChromeHints = Program.Args.NoWindowChromeEffects\n                    ? ExtendClientAreaChromeHints.NoChrome\n                    : ExtendClientAreaChromeHints.PreferSystemChrome;\n\n                DesktopLifetime.MainWindow = setupWindow;\n\n                setupWindow.ShowAsyncCts.Token.Register(() =>\n                {\n                    if (setupWindow.Result == ContentDialogResult.Primary)\n                    {\n                        settingsManager.SetEulaAccepted();\n                        ShowMainWindow();\n                        DesktopLifetime.MainWindow.Show();\n                    }\n                    else\n                    {\n                        Shutdown();\n                    }\n                });\n            }\n            else\n            {\n                ShowMainWindow();\n            }\n        }\n    }\n\n    /// <summary>\n    /// Set the default font family for the application.\n    /// </summary>\n    private void SetFontFamily(FontFamily fontFamily)\n    {\n        Resources[\"ContentControlThemeFontFamily\"] = fontFamily;\n    }\n\n    /// <summary>\n    /// Get the default font family for the current platform and language.\n    /// </summary>\n    public FontFamily GetPlatformDefaultFontFamily()\n    {\n        try\n        {\n            var fonts = new List<string>();\n\n            if (Cultures.Current?.Name == \"ja-JP\")\n            {\n                return Resources[\"NotoSansJP\"] as FontFamily\n                    ?? throw new ApplicationException(\"Font NotoSansJP not found\");\n            }\n\n            if (Compat.IsWindows)\n            {\n                fonts.Add(OSVersionHelper.IsWindows11() ? \"Segoe UI Variable Text\" : \"Segoe UI\");\n            }\n            else if (Compat.IsMacOS)\n            {\n                // Use Segoe fonts if installed, but we can't distribute them\n                fonts.Add(\"Segoe UI Variable\");\n                fonts.Add(\"Segoe UI\");\n\n                fonts.Add(\"San Francisco\");\n                fonts.Add(\"Helvetica Neue\");\n                fonts.Add(\"Helvetica\");\n            }\n            else if (Compat.IsLinux)\n            {\n                // For Chinese locales, prioritize CJK-capable fonts first\n                if (Cultures.Current?.Name is \"zh-Hans\" or \"zh-Hant\")\n                {\n                    // Common Chinese fonts on Linux systems\n                    fonts.Add(\"Noto Sans CJK SC\");\n                    fonts.Add(\"Noto Sans CJK TC\");\n                    fonts.Add(\"Source Han Sans\");\n                    fonts.Add(\"WenQuanYi Micro Hei\");\n                    fonts.Add(\"WenQuanYi Zen Hei\");\n                }\n\n                // Add common Linux fonts\n                fonts.Add(\"Ubuntu\");\n                fonts.Add(\"DejaVu Sans\");\n\n                // Fallback to system default\n                fonts.Add(FontFamily.Default.Name);\n\n                return new FontFamily(string.Join(\",\", fonts));\n            }\n            else\n            {\n                return FontFamily.Default;\n            }\n\n            return new FontFamily(string.Join(\",\", fonts));\n        }\n        catch (Exception e)\n        {\n            Logger.Error(e);\n\n            return FontFamily.Default;\n        }\n    }\n\n    /// <summary>\n    /// Setup tasks to be run shortly before any window is shown\n    /// </summary>\n    private void Setup()\n    {\n        using var _ = CodeTimer.StartNew();\n\n        // Setup uri handler for `stabilitymatrix://` protocol\n        Program.UriHandler.RegisterUriScheme();\n\n        // Setup activation protocol handlers (uri handler on macOS)\n        if (Compat.IsMacOS && this.TryGetFeature<IActivatableLifetime>() is { } activatableLifetime)\n        {\n            Logger.Debug(\"ActivatableLifetime available, setting up activation protocol handlers\");\n            activatableLifetime.Activated += OnActivated;\n        }\n\n        // Update font when culture/language changes\n        EventManager.Instance.CultureChanged += (_, _) => SetFontFamily(GetPlatformDefaultFontFamily());\n    }\n\n    private void ShowMainWindow()\n    {\n        if (DesktopLifetime is null)\n            return;\n\n        var mainWindow = Services.GetRequiredService<MainWindow>();\n        VisualRoot = mainWindow;\n\n        DesktopLifetime.MainWindow = mainWindow;\n        DesktopLifetime.Exit += OnApplicationLifetimeExit;\n        DesktopLifetime.ShutdownRequested += OnShutdownRequested;\n\n        AppDomain.CurrentDomain.ProcessExit += OnExit;\n        TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;\n\n        // Since we're manually shutting down NLog in OnExit\n        LogManager.AutoShutdown = false;\n    }\n\n    [MemberNotNull(nameof(serviceProvider))]\n    private void ConfigureServiceProvider()\n    {\n        var services = ConfigureServices();\n\n        BeforeBuildServiceProvider?.Invoke(null, services);\n\n        serviceProvider = services.BuildServiceProvider();\n\n        var settingsManager = Services.GetRequiredService<ISettingsManager>();\n\n        if (Program.Args.DataDirectoryOverride is not null)\n        {\n            var normalizedDataDirPath = Path.GetFullPath(Program.Args.DataDirectoryOverride);\n\n            if (Compat.IsWindows)\n            {\n                // ReSharper disable twice LocalizableElement\n                normalizedDataDirPath = normalizedDataDirPath.Replace(\"\\\\\\\\\", \"\\\\\");\n            }\n\n            settingsManager.SetLibraryDirOverride(normalizedDataDirPath);\n        }\n\n        if (settingsManager.TryFindLibrary())\n        {\n            Cultures.SetSupportedCultureOrDefault(\n                settingsManager.Settings.Language,\n                settingsManager.Settings.NumberFormatMode\n            );\n        }\n        else\n        {\n            Cultures.TrySetSupportedCulture(Settings.GetDefaultCulture());\n        }\n\n        Services.GetRequiredService<ProgressManagerViewModel>().StartEventListener();\n    }\n\n    internal static void ConfigurePageViewModels(IServiceCollection services)\n    {\n        services.AddSingleton<MainWindowViewModel>(provider => new MainWindowViewModel(\n            provider.GetRequiredService<ISettingsManager>(),\n            provider.GetRequiredService<IDiscordRichPresenceService>(),\n            provider.GetRequiredService<IServiceManager<ViewModelBase>>(),\n            provider.GetRequiredService<ITrackedDownloadService>(),\n            provider.GetRequiredService<IModelIndexService>(),\n            provider.GetRequiredService<Lazy<IModelDownloadLinkHandler>>(),\n            provider.GetRequiredService<INotificationService>(),\n            provider.GetRequiredService<IAnalyticsHelper>(),\n            provider.GetRequiredService<IUpdateHelper>(),\n            provider.GetRequiredService<ISecretsManager>(),\n            provider.GetRequiredService<INavigationService<MainWindowViewModel>>(),\n            provider.GetRequiredService<INavigationService<SettingsViewModel>>()\n        )\n        {\n            Pages =\n            {\n                provider.GetRequiredService<PackageManagerViewModel>(),\n                provider.GetRequiredService<InferenceViewModel>(),\n                provider.GetRequiredService<CheckpointsPageViewModel>(),\n                provider.GetRequiredService<CheckpointBrowserViewModel>(),\n                provider.GetRequiredService<OutputsPageViewModel>(),\n                provider.GetRequiredService<WorkflowsPageViewModel>(),\n            },\n            FooterPages = { provider.GetRequiredService<SettingsViewModel>() },\n        });\n    }\n\n    [MemberNotNull(nameof(Config))]\n    internal static IServiceCollection ConfigureServices(bool disableMessagePipeInterprocess = false)\n    {\n        var services = new ServiceCollection();\n\n        // --- Configuration ---\n        Config = new ConfigurationBuilder()\n            .AddJsonFile(\"appsettings.json\", optional: true, reloadOnChange: false)\n            .AddEnvironmentVariables()\n            .Build();\n\n        services.AddOptions();\n        services.AddSingleton(Config);\n\n        services.Configure<DebugOptions>(Config.GetSection(\"Debug\"));\n        services.Configure<ApiOptions>(Config.GetSection(\"Api\"));\n\n        var apiOptions = Config.GetSection(\"Api\").Get<ApiOptions>() ?? new ApiOptions();\n\n        // --- Logging ---\n        ConditionalAddLogViewer(services);\n        var logConfig = ConfigureLogging();\n\n        services.AddLogging(builder =>\n        {\n            // builder.ClearProviders();\n            builder\n                .AddFilter(\"Microsoft.Extensions.Http\", LogLevel.Warning)\n                .AddFilter(\"Microsoft.Extensions.Http.DefaultHttpClientFactory\", LogLevel.Warning)\n                .AddFilter(\"Microsoft\", LogLevel.Warning)\n                .AddFilter(\"System\", LogLevel.Warning);\n\n            builder.SetMinimumLevel(LogLevel.Trace);\n\n#if SM_LOG_WINDOW\n            builder.AddNLog(\n                logConfig,\n                new NLogProviderOptions\n                {\n                    IgnoreEmptyEventId = false,\n                    CaptureEventId = EventIdCaptureType.Legacy,\n                }\n            );\n#else\n            builder.AddNLog(logConfig);\n#endif\n        });\n\n        // --- Services ---\n        services.AddMemoryCache();\n        services.AddLazyInstance();\n\n        // Named pipe interprocess communication on Windows and Linux for uri handling\n        if (!disableMessagePipeInterprocess && (Compat.IsWindows || Compat.IsLinux))\n        {\n            services.AddMessagePipe().AddNamedPipeInterprocess(\"StabilityMatrix\");\n        }\n        else\n        {\n            // Use activation events on macOS, so just in-memory message pipe\n            services.AddMessagePipe().AddInMemoryDistributedMessageBroker();\n        }\n\n        // Register services by attributes\n        services.AddServicesByAttributes();\n\n        ConfigurePageViewModels(services);\n\n        services.AddServiceManagerWithCurrentCollectionServices<ViewModelBase>(s =>\n            s.ServiceType.GetCustomAttributes<ManagedServiceAttribute>().Any()\n        );\n\n        // Other services\n        services.AddSingleton<ITrackedDownloadService, TrackedDownloadService>();\n        services.AddSingleton<IDisposable>(provider =>\n            (IDisposable)provider.GetRequiredService<ITrackedDownloadService>()\n        );\n\n        // Rich presence\n        services.AddSingleton<IDiscordRichPresenceService, DiscordRichPresenceService>();\n        services.AddSingleton<IDisposable>(provider =>\n            provider.GetRequiredService<IDiscordRichPresenceService>()\n        );\n\n        if (Compat.IsWindows)\n        {\n            services.AddSingleton<IPrerequisiteHelper, WindowsPrerequisiteHelper>();\n        }\n        else if (Compat.IsLinux || Compat.IsMacOS)\n        {\n            services.AddSingleton<IPrerequisiteHelper, UnixPrerequisiteHelper>();\n        }\n\n        if (!Design.IsDesignMode)\n        {\n            services.AddSingleton<ILiteDbContext, LiteDbContext>();\n            services.AddSingleton<IDisposable>(p => p.GetRequiredService<ILiteDbContext>());\n        }\n\n        services.AddTransient<IGitHubClient, GitHubClient>(_ =>\n        {\n            var client = new GitHubClient(new ProductHeaderValue(\"StabilityMatrix\"));\n            // var githubApiKey = Config[\"GithubApiKey\"];\n            // if (string.IsNullOrWhiteSpace(githubApiKey))\n            //     return client;\n            //\n            // client.Credentials = new Credentials(\n            //     \"\"\n            // );\n            return client;\n        });\n\n        // Configure Refit and Polly\n        var jsonSerializerOptions = new JsonSerializerOptions\n        {\n            PropertyNameCaseInsensitive = true,\n            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,\n        };\n        jsonSerializerOptions.Converters.Add(new ObjectToInferredTypesConverter());\n        jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter<CivitFileType>());\n        jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter<CivitModelType>());\n        jsonSerializerOptions.Converters.Add(new DefaultUnknownEnumConverter<CivitModelFormat>());\n        jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));\n        jsonSerializerOptions.Converters.Add(new AnalyticsRequestConverter());\n        jsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;\n\n        var defaultRefitSettings = new RefitSettings\n        {\n            ContentSerializer = new SystemTextJsonContentSerializer(jsonSerializerOptions),\n        };\n\n        // Refit settings for IApiFactory\n        var defaultSystemTextJsonSettings = SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions();\n        defaultSystemTextJsonSettings.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;\n        var apiFactoryRefitSettings = new RefitSettings\n        {\n            ContentSerializer = new SystemTextJsonContentSerializer(defaultSystemTextJsonSettings),\n        };\n\n        // HTTP Policies\n        var retryStatusCodes = new[]\n        {\n            HttpStatusCode.RequestTimeout, // 408\n            HttpStatusCode.InternalServerError, // 500\n            HttpStatusCode.BadGateway, // 502\n            HttpStatusCode.ServiceUnavailable, // 503\n            HttpStatusCode.GatewayTimeout, // 504\n        };\n\n        // Default retry policy: ~30s max\n        var retryPolicy = HttpPolicyExtensions\n            .HandleTransientHttpError()\n            .Or<TimeoutRejectedException>()\n            .OrResult(r => retryStatusCodes.Contains(r.StatusCode))\n            .WaitAndRetryAsync(\n                Backoff.DecorrelatedJitterBackoffV2(\n                    medianFirstRetryDelay: TimeSpan.FromMilliseconds(750),\n                    retryCount: 6\n                ),\n                onRetry: (result, timeSpan, retryCount, _) =>\n                {\n                    if (retryCount > 3)\n                    {\n                        Logger.Info(\n                            \"Retry attempt {Count}/{Max} after {Seconds:N2}s due to ({Status}) {Msg}\",\n                            retryCount,\n                            6,\n                            timeSpan.TotalSeconds,\n                            result?.Result?.StatusCode,\n                            result?.Result?.ToString()\n                        );\n                    }\n                }\n            )\n            // 10s timeout for each attempt\n            .WrapAsync(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(60)));\n\n        // Longer retry policy: ~60s max\n        var retryPolicyLonger = HttpPolicyExtensions\n            .HandleTransientHttpError()\n            .Or<TimeoutRejectedException>()\n            .OrResult(r => retryStatusCodes.Contains(r.StatusCode))\n            .WaitAndRetryAsync(\n                Backoff.DecorrelatedJitterBackoffV2(\n                    medianFirstRetryDelay: TimeSpan.FromMilliseconds(1000),\n                    retryCount: 7\n                ),\n                onRetry: (result, timeSpan, retryCount, _) =>\n                {\n                    if (retryCount > 4)\n                    {\n                        Logger.Info(\n                            \"Retry attempt {Count}/{Max} after {Seconds:N2}s due to ({Status}) {Msg}\",\n                            retryCount,\n                            7,\n                            timeSpan.TotalSeconds,\n                            result?.Result?.StatusCode,\n                            result?.Result?.ToString()\n                        );\n                    }\n                }\n            )\n            // 30s timeout for each attempt\n            .WrapAsync(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(120)));\n\n        // Shorter local retry policy: ~5s total\n        var localRetryPolicy = HttpPolicyExtensions\n            .HandleTransientHttpError()\n            .Or<TimeoutRejectedException>()\n            .OrResult(r => retryStatusCodes.Contains(r.StatusCode))\n            .WaitAndRetryAsync(\n                Backoff.DecorrelatedJitterBackoffV2(\n                    medianFirstRetryDelay: TimeSpan.FromMilliseconds(320),\n                    retryCount: 5\n                )\n            )\n            // 3s timeout for each attempt\n            .WrapAsync(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(3)));\n\n        // named client for update\n        services.AddHttpClient(\"UpdateClient\").AddPolicyHandler(retryPolicy);\n\n        // Add Refit clients\n        // Note: HttpClient.Timeout should be high to allow Polly to handle timeouts instead\n        services\n            .AddRefitClient<ICivitApi>(defaultRefitSettings)\n            .ConfigureHttpClient(c =>\n            {\n                c.BaseAddress = new Uri(\"https://civitai.com\");\n                c.Timeout = TimeSpan.FromHours(1);\n            })\n            .AddPolicyHandler(retryPolicyLonger);\n\n        services\n            .AddRefitClient<ICivitTRPCApi>(defaultRefitSettings)\n            .ConfigureHttpClient(c =>\n            {\n                c.BaseAddress = new Uri(\"https://civitai.com\");\n                c.Timeout = TimeSpan.FromHours(1);\n            })\n            .AddPolicyHandler(retryPolicyLonger);\n\n        services\n            .AddRefitClient<ILykosModelDiscoveryApi>(defaultRefitSettings)\n            .ConfigureHttpClient(c =>\n            {\n                c.BaseAddress = new Uri(\"https://discovery.lykos.ai/api/v1\");\n                c.Timeout = TimeSpan.FromHours(1);\n                c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", \"\");\n            })\n            .AddPolicyHandler(retryPolicy)\n            .AddHttpMessageHandler(serviceProvider => new TokenAuthHeaderHandler(\n                serviceProvider.GetRequiredService<LykosAuthTokenProvider>()\n            ));\n\n        services\n            .AddRefitClient<IPyPiApi>(defaultRefitSettings)\n            .ConfigureHttpClient(c =>\n            {\n                c.BaseAddress = new Uri(\"https://pypi.org\");\n                c.Timeout = TimeSpan.FromHours(1);\n            })\n            .AddPolicyHandler(retryPolicyLonger);\n\n        services\n            .AddRefitClient<ILykosAuthApiV1>(defaultRefitSettings)\n            .ConfigureHttpClient(\n                (sp, c) =>\n                {\n                    var options = sp.GetRequiredService<IOptions<ApiOptions>>().Value;\n                    c.BaseAddress = options.LykosAuthApiBaseUrl;\n                    c.Timeout = TimeSpan.FromHours(1);\n                }\n            )\n            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false })\n            .AddPolicyHandler(retryPolicy)\n            .AddHttpMessageHandler(serviceProvider => new TokenAuthHeaderHandler(\n                serviceProvider.GetRequiredService<LykosAuthTokenProvider>()\n            ));\n\n        services\n            .AddRefitClient<ILykosAuthApiV2>(defaultRefitSettings)\n            .ConfigureHttpClient(\n                (sp, c) =>\n                {\n                    var options = sp.GetRequiredService<IOptions<ApiOptions>>().Value;\n                    c.BaseAddress = options.LykosAuthApiBaseUrl;\n                    c.Timeout = TimeSpan.FromHours(1);\n                    c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", \"\");\n                }\n            )\n            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false })\n            .AddPolicyHandler(retryPolicy)\n            .AddHttpMessageHandler(serviceProvider => new TokenAuthHeaderHandler(\n                serviceProvider.GetRequiredService<LykosAuthTokenProvider>()\n            ));\n\n        services\n            .AddRefitClient<IRecommendedModelsApi>(defaultRefitSettings)\n            .ConfigureHttpClient(\n                (sp, c) =>\n                {\n                    var options = sp.GetRequiredService<IOptions<ApiOptions>>().Value;\n                    c.BaseAddress = options.LykosAuthApiBaseUrl;\n                    c.Timeout = TimeSpan.FromHours(1);\n                }\n            )\n            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false })\n            .AddPolicyHandler(retryPolicy);\n\n        services\n            .AddRefitClient<IPromptGenApi>(defaultRefitSettings)\n            .ConfigureHttpClient(\n                (sp, c) =>\n                {\n                    var options = sp.GetRequiredService<IOptions<ApiOptions>>().Value;\n                    c.BaseAddress = options.LykosPromptGenApiBaseUrl;\n                    c.Timeout = TimeSpan.FromHours(1);\n                    c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", \"\");\n                }\n            )\n            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false })\n            .AddPolicyHandler(retryPolicy)\n            .AddHttpMessageHandler(serviceProvider => new TokenAuthHeaderHandler(\n                serviceProvider.GetRequiredService<LykosAuthTokenProvider>()\n            ));\n\n        services\n            .AddRefitClient<ILykosAnalyticsApi>(defaultRefitSettings)\n            .ConfigureHttpClient(\n                (sp, c) =>\n                {\n                    var options = sp.GetRequiredService<IOptions<ApiOptions>>().Value;\n                    c.BaseAddress = options.LykosAnalyticsApiBaseUrl;\n                    c.Timeout = TimeSpan.FromMinutes(5);\n                }\n            )\n            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false })\n            .AddPolicyHandler(retryPolicy);\n\n        services\n            .AddRefitClient<IOpenArtApi>(defaultRefitSettings)\n            .ConfigureHttpClient(c =>\n            {\n                c.BaseAddress = new Uri(\"https://openart.ai/api/public/workflows\");\n                c.Timeout = TimeSpan.FromHours(1);\n            })\n            .AddPolicyHandler(retryPolicy);\n\n        services\n            .AddRefitClient<IHuggingFaceApi>(defaultRefitSettings) // Assuming defaultRefitSettings is suitable\n            .ConfigureHttpClient(c =>\n            {\n                c.BaseAddress = new Uri(\"https://huggingface.co\");\n                c.Timeout = TimeSpan.FromHours(1); // Or a more appropriate timeout like 60 seconds, consistent with retry policy\n            })\n            .AddPolicyHandler(retryPolicy); // Assuming retryPolicy is suitable\n\n        // Apizr clients\n        services.AddApizrManagerFor<IOpenModelDbApi, OpenModelDbManager>(options =>\n        {\n            options\n                .WithRefitSettings(\n                    new RefitSettings(\n                        new SystemTextJsonContentSerializer(OpenModelDbApiJsonContext.Default.Options)\n                    )\n                )\n                .ConfigureHttpClientBuilder(c => c.AddPolicyHandler(retryPolicy))\n                .WithInMemoryCacheHandler()\n                .WithLogging(HttpTracerMode.ExceptionsOnly, HttpMessageParts.AllButResponseBody);\n        });\n        services.AddSingleton<OpenModelDbManager>(sp =>\n            (OpenModelDbManager)sp.GetRequiredService<IApizrManager<IOpenModelDbApi>>()\n        );\n\n        // Add Refit client managers\n        services.AddHttpClient(\"A3Client\").AddPolicyHandler(localRetryPolicy);\n\n        services\n            .AddHttpClient(\"DontFollowRedirects\")\n            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false })\n            .AddPolicyHandler(retryPolicy);\n\n        // Add Refit client factory\n        services.AddSingleton<IApiFactory, ApiFactory>(provider => new ApiFactory(\n            provider.GetRequiredService<IHttpClientFactory>()\n        )\n        {\n            RefitSettings = apiFactoryRefitSettings,\n        });\n\n        // Add OpenId\n        services\n            .AddOpenIddict()\n            .AddClient(options =>\n            {\n                options.AllowDeviceCodeFlow().AllowRefreshTokenFlow();\n\n                options.DisableTokenStorage();\n                options.AddEphemeralEncryptionKey().AddEphemeralSigningKey();\n\n                options.UseSystemNetHttp().SetProductInformation(\"StabilityMatrix\", \"2.0\");\n\n                options.AddRegistration(\n                    new OpenIddictClientRegistration\n                    {\n                        ProviderName = OpenIdClientConstants.LykosAccount.ProviderName,\n                        Issuer = apiOptions.LykosAccountApiBaseUrl,\n                        ClientId = \"ai.lykos.stabilitymatrix\",\n                        Scopes =\n                        {\n                            OpenIddictConstants.Scopes.Profile,\n                            OpenIddictConstants.Scopes.Email,\n                            OpenIddictConstants.Scopes.OpenId,\n                            \"api\",\n                            OpenIddictConstants.Scopes.OfflineAccess,\n                        },\n                        RedirectUri = Program.MessagePipeUri.Append(\"/callback/login/lykos\"),\n                    }\n                );\n            });\n\n        return services;\n    }\n\n    /// <summary>\n    /// Requests shutdown of the Current Application.\n    /// </summary>\n    /// <remarks>This returns asynchronously *without waiting* for Shutdown</remarks>\n    /// <param name=\"exitCode\">Exit code for the application.</param>\n    /// <exception cref=\"NullReferenceException\">If Application.Current is null</exception>\n    public static void Shutdown(int exitCode = 0)\n    {\n        if (Current is null)\n            throw new NullReferenceException(\"Current Application was null when Shutdown called\");\n\n        if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)\n        {\n            try\n            {\n                var result = lifetime.TryShutdown(exitCode);\n                Debug.WriteLine($\"Shutdown: {result}\");\n\n                if (result)\n                {\n                    Environment.Exit(exitCode);\n                }\n            }\n            catch (InvalidOperationException)\n            {\n                // Ignore in case already shutting down\n            }\n        }\n        else\n        {\n            Environment.Exit(exitCode);\n        }\n    }\n\n    private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e)\n    {\n        Logger.Trace(\"Start OnShutdownRequested\");\n\n        if (e.Cancel)\n            return;\n\n        // Skip if Async Dispose already started, shutdown will be handled by it\n        if (isAsyncDisposeStarted)\n            return;\n\n        // Cancel shutdown for now to dispose\n        e.Cancel = true;\n        isAsyncDisposeStarted = true;\n\n        Logger.Trace(\"OnShutdownRequested Canceled: Disposing IAsyncDisposables\");\n\n        Dispatcher\n            .UIThread.InvokeAsync(async () =>\n            {\n                if (serviceProvider is null)\n                {\n                    Logger.Warn(\"Service Provider is null, skipping Async Dispose\");\n                    return;\n                }\n\n                var settingsManager = Services.GetRequiredService<ISettingsManager>();\n\n                Logger.Debug(\"Disposing App Services\");\n                try\n                {\n                    OnServiceProviderDisposing(serviceProvider);\n                    await serviceProvider.DisposeAsync();\n                    isAsyncDisposeComplete = true;\n                }\n                catch (Exception disposeEx)\n                {\n                    Logger.Error(disposeEx, \"Failed to dispose ServerProvider\");\n                }\n\n                Logger.Debug(\"Flushing SettingsManager\");\n                try\n                {\n                    var cts = new CancellationTokenSource(5000);\n                    await settingsManager.FlushAsync(cts.Token);\n                }\n                catch (OperationCanceledException)\n                {\n                    Logger.Error(\"Timeout Flushing SettingsManager\");\n                }\n            })\n            .ContinueWith(_ =>\n            {\n                // Shutdown again\n                Logger.Debug(\"Finished async shutdown tasks, shutting down\");\n\n                if (Dispatcher.UIThread.SupportsRunLoops)\n                {\n                    Dispatcher.UIThread.Invoke(() => Shutdown());\n                }\n\n                Environment.Exit(0);\n            })\n            .SafeFireAndForget();\n    }\n\n    private void OnApplicationLifetimeExit(object? sender, ControlledApplicationLifetimeExitEventArgs args)\n    {\n        Logger.Debug(\"OnApplicationLifetimeExit: {@Args}\", args);\n\n        OnExit(sender, args);\n    }\n\n    private void OnExit(object? sender, EventArgs _)\n    {\n        // Skip if already run\n        if (isOnExitComplete)\n        {\n            return;\n        }\n\n        // Skip if another OnExit is running\n        if (!onExitSemaphore.Wait(0))\n        {\n            // Block until the other OnExit is done to delay shutdown\n            onExitSemaphore.Wait();\n            onExitSemaphore.Release();\n            return;\n        }\n\n        try\n        {\n            if (serviceProvider is null)\n            {\n                Logger.Warn(\"Service Provider is null, skipping OnExit\");\n                return;\n            }\n\n            // Dispose services only if async dispose has not completed\n            if (!isAsyncDisposeComplete)\n            {\n                Logger.Debug(\"OnExit: Disposing App Services\");\n\n                OnServiceProviderDisposing(serviceProvider);\n                serviceProvider.Dispose();\n            }\n\n            Logger.Debug(\"OnExit: Finished\");\n        }\n        finally\n        {\n            isOnExitComplete = true;\n            onExitSemaphore.Release();\n\n            LogManager.Shutdown();\n        }\n    }\n\n    private static void OnServiceProviderDisposing(ServiceProvider serviceProvider)\n    {\n        // Force materialize SharedFolders so its DisposeAsync is called\n        // since it's not used by anything at the moment\n        _ = serviceProvider.GetService<ISharedFolders>();\n\n        // Remove the NamedPipeWorker disposable if present\n        // causes crash on avalonia dispatcher thread for some reason\n        // https://github.com/dotnet/runtime/issues/39902\n        var disposables = serviceProvider.GetDisposables();\n        disposables.RemoveAll(d => d is NamedPipeWorker);\n\n        Logger.Trace(\"Disposing {Count} Disposables\", disposables.Count);\n    }\n\n    private static void TaskScheduler_UnobservedTaskException(\n        object? sender,\n        UnobservedTaskExceptionEventArgs e\n    )\n    {\n        if (e.Observed || e.Exception is not Exception unobservedEx)\n            return;\n\n        try\n        {\n            var notificationService = Services.GetRequiredService<INotificationService>();\n\n            Dispatcher.UIThread.Invoke(() =>\n            {\n                var originException = unobservedEx.InnerException ?? unobservedEx;\n                notificationService.ShowPersistent(\n                    $\"Unobserved Task Exception - {originException.GetType().Name}\",\n                    originException.Message\n                );\n            });\n\n            // Consider the exception observed if we were able to show a notification\n            e.SetObserved();\n        }\n        catch (Exception ex)\n        {\n            Logger.Error(ex, \"Failed to show Unobserved Task Exception notification\");\n        }\n    }\n\n    private static async void OnActivated(object? sender, ActivatedEventArgs args)\n    {\n        if (args is not ProtocolActivatedEventArgs protocolArgs)\n        {\n            Logger.Warn(\"Activated with unknown args: {Args}\", args);\n            return;\n        }\n\n        if (protocolArgs.Kind is ActivationKind.OpenUri)\n        {\n            Logger.Info(\"Activated with Protocol OpenUri: {Uri}\", protocolArgs.Uri);\n\n            // Ensure the uri scheme is our custom scheme\n            if (\n                !protocolArgs.Uri.Scheme.Equals(Program.UriHandler.Scheme, StringComparison.OrdinalIgnoreCase)\n            )\n            {\n                Logger.Warn(\"Unknown scheme for OpenUri: {Uri}\", protocolArgs.Uri);\n                return;\n            }\n\n            var publisher = Services.GetRequiredService<IDistributedPublisher<string, Uri>>();\n\n            await publisher.PublishAsync(UriHandler.IpcKeySend, protocolArgs.Uri);\n        }\n    }\n\n    private static LoggingConfiguration ConfigureLogging()\n    {\n        var setupBuilder = LogManager.Setup();\n\n        ConditionalAddLogViewerNLog(setupBuilder);\n\n        LogManager\n            .Setup()\n            .SetupExtensions(builder => builder.RegisterTarget<RichConsoleTarget>(\"RichConsole\"));\n\n        setupBuilder.LoadConfigurationFromSection(Config);\n        setupBuilder.LoadConfiguration(builder =>\n        {\n            // Filter some sources to be 'Warn' level or above only\n            builder.ForLogger(\"System.*\").WriteToNil(NLog.LogLevel.Warn);\n            builder.ForLogger(\"Microsoft.*\").WriteToNil(NLog.LogLevel.Warn);\n            builder.ForLogger(\"Microsoft.Extensions.Http.*\").WriteToNil(NLog.LogLevel.Warn);\n            // Info or above\n            builder.ForLogger(\"OpenIddict.*\").WriteToNil(NLog.LogLevel.Warn);\n\n            // Disable some trace logging by default, unless overriden by app settings\n            var typesToDisableTrace = new[]\n            {\n                typeof(ConsoleViewModel),\n                typeof(LoadableViewModelBase),\n                typeof(TextEditorCompletionBehavior),\n                typeof(CompletionProvider),\n            };\n\n            foreach (var type in typesToDisableTrace)\n            {\n                // Skip if app settings already set a level for this type\n                if (\n                    Config[$\"Logging:LogLevel:{type.FullName}\"] is { } levelStr\n                    && Enum.TryParse<LogLevel>(levelStr, true, out _)\n                )\n                {\n                    continue;\n                }\n\n                // Set minimum level to Debug for these types\n                builder.ForLogger(type.FullName).WriteToNil(NLog.LogLevel.Debug);\n            }\n\n            // Debug console logging\n            /*if (Debugger.IsAttached)\n            {\n                builder\n                    .ForLogger()\n                    .FilterMinLevel(NLog.LogLevel.Trace)\n                    .WriteTo(\n                        new DebuggerTarget(\"debugger\")\n                        {\n                            Layout = \"[${level:uppercase=true}]\\t${logger:shortName=true}\\t${message}\"\n                        }\n                    )\n                    .WithAsync();\n            }*/\n\n            // Console logging\n            var consoleTarget = new RichConsoleTarget(\"console\") { Theme = RichNLogTheme.Code2 };\n            builder.ForLogger().FilterMinLevel(NLog.LogLevel.Trace).WriteTo(consoleTarget).WithAsync();\n\n            // File logging\n            var fileTarget = new FileTarget(\"logfile\")\n            {\n                Layout = \"${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}\",\n                FileName = \"${specialfolder:folder=ApplicationData}/StabilityMatrix/Logs/app.log\",\n                ArchiveOldFileOnStartup = true,\n                ArchiveFileName = \"${specialfolder:folder=ApplicationData}/StabilityMatrix/Logs/app.{#}.log\",\n                ArchiveDateFormat = \"yyyy-MM-dd HH_mm_ss\",\n                ArchiveNumbering = ArchiveNumberingMode.Date,\n                MaxArchiveFiles = 9,\n            };\n            builder.ForLogger().FilterMinLevel(NLog.LogLevel.Debug).WriteTo(fileTarget).WithAsync();\n\n#if SM_LOG_WINDOW\n            // LogViewer target when debug mode\n            builder\n                .ForLogger()\n                .FilterMinLevel(NLog.LogLevel.Trace)\n                .WriteTo(new DataStoreLoggerTarget { Layout = \"${message}\" });\n#endif\n        });\n\n        // Sentry\n        if (SentrySdk.IsEnabled)\n        {\n            LogManager.Configuration.AddSentry(o =>\n            {\n                o.InitializeSdk = false;\n                o.Layout = \"${message}\";\n                o.ShutdownTimeoutSeconds = 5;\n                o.IncludeEventDataOnBreadcrumbs = true;\n                o.BreadcrumbLayout = \"${logger}: ${message}\";\n                // Debug and higher are stored as breadcrumbs (default is Info)\n                o.MinimumBreadcrumbLevel = NLog.LogLevel.Debug;\n                // Error and higher is sent as event (default is Error)\n                o.MinimumEventLevel = NLog.LogLevel.Error;\n            });\n        }\n\n        LogManager.ReconfigExistingLoggers();\n\n        return LogManager.Configuration;\n    }\n\n    /// <summary>\n    /// Opens a dialog to save the current view as a screenshot.\n    /// </summary>\n    /// <remarks>Only available in debug builds.</remarks>\n    [Conditional(\"DEBUG\")]\n    internal static void DebugSaveScreenshot(int dpi = 96)\n    {\n        const int scale = 2;\n        dpi *= scale;\n\n        var results = new List<MemoryStream>();\n        var targets = new List<Visual?> { VisualRoot };\n\n        foreach (var visual in targets.Where(x => x != null))\n        {\n            var rect = new Rect(visual!.Bounds.Size);\n\n            var pixelSize = new PixelSize((int)rect.Width * scale, (int)rect.Height * scale);\n            var dpiVector = new Vector(dpi, dpi);\n\n            var ms = new MemoryStream();\n\n            using (var bitmap = new RenderTargetBitmap(pixelSize, dpiVector))\n            {\n                bitmap.Render(visual);\n                bitmap.Save(ms);\n            }\n\n            results.Add(ms);\n        }\n\n        Dispatcher.UIThread.InvokeAsync(async () =>\n        {\n            var dest = await StorageProvider.SaveFilePickerAsync(\n                new FilePickerSaveOptions()\n                {\n                    SuggestedFileName = \"screenshot.png\",\n                    ShowOverwritePrompt = true,\n                }\n            );\n\n            if (dest?.TryGetLocalPath() is { } localPath)\n            {\n                var localFile = new FilePath(localPath);\n                foreach (var (i, stream) in results.Enumerate())\n                {\n                    var name = localFile.NameWithoutExtension;\n                    if (results.Count > 1)\n                    {\n                        name += $\"_{i + 1}\";\n                    }\n\n                    localFile = localFile.Directory!.JoinFile(name + \".png\");\n                    localFile.Create();\n\n                    await using var fileStream = localFile.Info.OpenWrite();\n                    stream.Seek(0, SeekOrigin.Begin);\n                    await stream.CopyToAsync(fileStream);\n                }\n            }\n        });\n    }\n\n    [Conditional(\"SM_LOG_WINDOW\")]\n    private static void ConditionalAddLogViewer(IServiceCollection services)\n    {\n#if SM_LOG_WINDOW\n        services.AddLogViewer();\n#endif\n    }\n\n    [Conditional(\"SM_LOG_WINDOW\")]\n    private static void ConditionalAddLogViewerNLog(ISetupBuilder setupBuilder)\n    {\n#if SM_LOG_WINDOW\n        setupBuilder.SetupExtensions(extensionBuilder =>\n            extensionBuilder.RegisterTarget<DataStoreLoggerTarget>(\"DataStoreLogger\")\n        );\n#endif\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/Fonts/NotoSansJP/OFL.txt",
    "content": "Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at:\nhttp://scripts.sil.org/OFL\n\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe goals of the Open Font License (OFL) are to stimulate worldwide\ndevelopment of collaborative font projects, to support the font creation\nefforts of academic and linguistic communities, and to provide a free and\nopen framework in which fonts may be shared and improved in partnership\nwith others.\n\nThe OFL allows the licensed fonts to be used, studied, modified and\nredistributed freely as long as they are not sold by themselves. The\nfonts, including any derivative works, can be bundled, embedded, \nredistributed and/or sold with any software provided that any reserved\nnames are not used by derivative works. The fonts and derivatives,\nhowever, cannot be released under any other type of license. The\nrequirement for fonts to remain under this license does not apply\nto any document created using the fonts or their derivatives.\n\nDEFINITIONS\n\"Font Software\" refers to the set of files released by the Copyright\nHolder(s) under this license and clearly marked as such. This may\ninclude source files, build scripts and documentation.\n\n\"Reserved Font Name\" refers to any names specified as such after the\ncopyright statement(s).\n\n\"Original Version\" refers to the collection of Font Software components as\ndistributed by the Copyright Holder(s).\n\n\"Modified Version\" refers to any derivative made by adding to, deleting,\nor substituting -- in part or in whole -- any of the components of the\nOriginal Version, by changing formats or by porting the Font Software to a\nnew environment.\n\n\"Author\" refers to any designer, engineer, programmer, technical\nwriter or other person who contributed to the Font Software.\n\nPERMISSION & CONDITIONS\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the Font Software, to use, study, copy, merge, embed, modify,\nredistribute, and sell modified and unmodified copies of the Font\nSoftware, subject to the following conditions:\n\n1) Neither the Font Software nor any of its individual components,\nin Original or Modified Versions, may be sold by itself.\n\n2) Original or Modified Versions of the Font Software may be bundled,\nredistributed and/or sold with any software, provided that each copy\ncontains the above copyright notice and this license. These can be\nincluded either as stand-alone text files, human-readable headers or\nin the appropriate machine-readable metadata fields within text or\nbinary files as long as those fields can be easily viewed by the user.\n\n3) No Modified Version of the Font Software may use the Reserved Font\nName(s) unless explicit written permission is granted by the corresponding\nCopyright Holder. This restriction only applies to the primary font name as\npresented to the users.\n\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\nSoftware shall not be used to promote, endorse or advertise any\nModified Version, except to acknowledge the contribution(s) of the\nCopyright Holder(s) and the Author(s) or with their explicit written\npermission.\n\n5) The Font Software, modified or unmodified, in part or in whole,\nmust be distributed entirely under this license, and must not be\ndistributed under any other license. The requirement for fonts to\nremain under this license does not apply to any document created\nusing the Font Software.\n\nTERMINATION\nThis license becomes null and void if any of the above conditions are\nnot met.\n\nDISCLAIMER\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\nOTHER DEALINGS IN THE FONT SOFTWARE.\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/ImagePrompt.tmLanguage.json",
    "content": "{\n  \"name\": \"Image Prompt\",\n  \"scopeName\": \"source.prompt\",\n  \"uuid\": \"A5283894-BA62-4BFE-BB29-7892AE7ACCDC\",\n  \"foldingStartMarker\": \"^.*\\b(\\\\#)\\b.*$\",\n  \"foldingStopMarker\": \"(\\r?\\n){2}\",\n  \"patterns\": [\n    {\n      \"include\": \"#value\"\n    }\n  ],\n  \"repository\": {\n    \"comment\": {\n      \"captures\": {\n        \"1\": {\n          \"name\": \"punctuation.definition.comment.prompt\"\n        }\n      },\n      \"match\": \"(#).*$\\\\n?\",\n      \"name\": \"comment.line.number-sign.prompt\"\n    },\n    \"escape\": {\n      \"begin\": \"\\\\\\\\\",\n      \"beginCaptures\": {\n        \"0\": {\n          \"name\": \"constant.character.escape.prompt\"\n        }\n      },\n      \"end\": \"[-+.!(){}\\\\[\\\\]<\\\\>:|\\\\\\\\]\",\n      \"endCaptures\": {\n        \"0\": {\n          \"name\": \"constant.character.escape.target.prompt\"\n        }\n      },\n      \"name\": \"meta.structure.escape.prompt\",\n      \"patterns\": [\n        {\n          \"match\": \"[^-+.!(){}\\\\[\\\\]<\\\\>:|\\\\\\\\]\",\n          \"name\": \"invalid.illegal.escape.prompt\"\n        }\n      ]\n    },\n    \"weight\": {\n      \"begin\": \":\",\n      \"beginCaptures\": {\n        \"0\": {\n          \"name\": \"punctuation.separator.weight.prompt\"\n        }\n      },\n      \"end\": \"[\\\\)\\\\b]\",\n      \"name\": \"meta.structure.weight.prompt\",\n      \"patterns\": [\n        {\n          \"include\": \"#number\",\n          \"name\": \"constant.numeric.weight.prompt\"\n        },\n        {\n          \"match\": \"[^\\\\s\\\\)\\\\b]\",\n          \"name\": \"invalid.illegal.expected-weight-separator.prompt\"\n        }\n      ]\n    },\n    \"parenthesized\": {\n      \"begin\": \"\\\\(\",\n      \"beginCaptures\": {\n        \"0\": {\n          \"name\": \"punctuation.definition.array.begin.prompt\"\n        }\n      },\n      \"end\": \"\\\\)\",\n      \"endCaptures\": {\n        \"0\": {\n          \"name\": \"punctuation.definition.array.end.prompt\"\n        }\n      },\n      \"name\": \"meta.structure.array.prompt\",\n      \"patterns\": [\n        {\n          \"include\": \"#comment\"\n        },\n        {\n          \"include\": \"#escape\"\n        },\n        {\n          \"include\": \"#weight\"\n        },\n        {\n          \"include\": \"#value\"\n        },\n        {\n          \"match\": \"[^\\\\s\\\\)]\",\n          \"name\": \"invalid.illegal.expected-array-separator.prompt\"\n        }\n      ]\n    },\n    \"array\": {\n      \"begin\": \"\\\\[\",\n      \"beginCaptures\": {\n        \"0\": {\n          \"name\": \"punctuation.definition.array.begin.prompt\"\n        }\n      },\n      \"end\": \"\\\\]\",\n      \"endCaptures\": {\n        \"0\": {\n          \"name\": \"punctuation.definition.array.end.prompt\"\n        }\n      },\n      \"name\": \"meta.structure.array.prompt\",\n      \"patterns\": [\n        {\n          \"include\": \"#value\"\n        },\n        {\n          \"match\": \"[^\\\\s\\\\]]\",\n          \"name\": \"invalid.illegal.expected-array-separator.prompt\"\n        }\n      ]\n    },\n    \"network\": {\n      \"begin\": \"<\",\n      \"beginCaptures\": {\n        \"0\": {\n          \"name\": \"punctuation.definition.network.begin.prompt\"\n        }\n      },\n      \"end\": \">\",\n      \"endCaptures\": {\n        \"0\": {\n          \"name\": \"punctuation.definition.network.end.prompt\"\n        }\n      },\n      \"name\": \"meta.structure.network.prompt\",\n      \"patterns\": [\n        {\n          \"match\": \"(?<=\\\\<)([^,:\\\\<\\\\>]+)(:)([^,:\\\\<\\\\>]+)(:)([-+]?\\\\d+(?:\\\\.\\\\d+)?)\",\n          \"captures\": {\n            \"1\": {\n              \"name\": \"meta.embedded.network.type.prompt\"\n            },\n            \"2\": {\n              \"name\": \"punctuation.separator.variable.prompt\"\n            },\n            \"3\": {\n              \"name\": \"meta.embedded.network.model.prompt\"\n            },\n            \"4\": {\n              \"name\": \"punctuation.separator.variable.prompt\"\n            },\n            \"5\": {\n              \"name\": \"constant.numeric\"\n            }\n          }\n        },\n        {\n          \"match\": \"(?<=\\\\<)([^,:\\\\<\\\\>]+)(:)([^,:\\\\<\\\\>]+)?\",\n          \"captures\": {\n            \"1\": {\n              \"name\": \"meta.embedded.network.type.prompt\"\n            },\n            \"2\": {\n              \"name\": \"punctuation.separator.variable.prompt\"\n            },\n            \"3\": {\n              \"name\": \"meta.embedded.network.model.prompt\"\n            }\n          }\n        },\n        {\n          \"match\": \"(?<=\\\\<)([^,:\\\\<\\\\>]+)\",\n          \"captures\": {\n            \"1\": {\n              \"name\": \"meta.embedded.network.type.prompt\"\n            }\n          }\n        },\n        {\n          \"include\": \"#comment\"\n        },\n        {\n          \"include\": \"#escape\"\n        },\n        {\n          \"match\": \"[^\\\\s\\\\>]+\",\n          \"name\": \"invalid.illegal.expected-array-separator.prompt\"\n        }\n      ]\n    },\n    \"separator\": {\n      \"match\": \",\\\\s*\",\n      \"name\": \"punctuation.separator.variable.prompt\"\n    },\n    \"colon\": {\n      \"match\": \":\",\n      \"name\": \"punctuation.separator.variable.prompt\"\n    },\n    \"number\": {\n      \"match\": \"(?x)        # turn on extended mode\\n  -?        # an optional minus\\n  (?:\\n    0       # a zero\\n    |       # ...or...\\n    [1-9]   # a 1-9 character\\n    \\\\d*     # followed by zero or more digits\\n  )\\n  (?:\\n    (?:\\n      \\\\.    # a period\\n      \\\\d+   # followed by one or more digits\\n    )?\\n    (?:\\n      [eE]  # an e character\\n      [+-]? # followed by an option +/-\\n      \\\\d+   # followed by one or more digits\\n    )?      # make exponent optional\\n  )?        # make decimal portion optional\",\n      \"name\": \"constant.numeric\"\n    },\n    \"keyword\": {\n      \"match\": \"\\\\b(?:BREAK|AND)\\\\b\",\n      \"name\": \"keyword.control\"\n    },\n    \"whitespace\": {\n      \"match\": \"\\\\s+\",\n      \"name\": \"meta.embedded.whitespace\"\n    },\n    \"model\": {\n      \"match\": \"\\\\b(?<type>[\\\\w\\\\d_]+):(?<model>\\\\w+)(?::(?<weight>\\\\d+(\\\\.\\\\d+)?))?\\\\b\",\n      \"name\": \"meta.embedded.model\"\n    },\n    \"text\": {\n      \"match\": \"[^#,:\\\\[\\\\]\\\\(\\\\)\\\\<\\\\> \\\\\\\\]+\",\n      \"name\": \"meta.embedded\"\n    },\n    \"invalid_reserved\": {\n      \"name\": \"invalid.illegal.reserved.prompt\",\n      \"patterns\": [\n        {\n          \"match\": \":\",\n          \"name\": \"invalid.illegal.reserved.prompt\"\n        },\n        {\n          \"match\": \"\\\\)\",\n          \"name\": \"invalid.illegal.mismatched.parenthesis.closing.prompt\"\n        },\n        {\n          \"match\": \"\\\\(\",\n          \"name\": \"invalid.illegal.mismatched.parenthesis.opening.prompt\"\n        }\n      ]\n    },\n    \"wildcard\": {\n      \"begin\": \"(?<!\\\\\\\\)\\\\{\",\n      \"beginCaptures\": {\n        \"0\": {\n          \"name\": \"punctuation.definition.wildcard.begin.prompt\"\n        }\n      },\n      \"end\": \"\\\\}|$\",\n      \"endCaptures\": {\n        \"0\": {\n          \"name\": \"punctuation.definition.wildcard.end.prompt\"\n        }\n      },\n      \"name\": \"meta.structure.wildcard.prompt\",\n      \"patterns\": [\n        {\n          \"include\": \"#network\"\n        },\n        {\n          \"match\": \"\\\\|\\\\|+|\\\\|(?=\\\\})|(?<=\\\\{)\\\\||\\\\|\\\\s*\\\\|\",\n          \"name\": \"invalid.illegal.empty-option.prompt\"\n        },\n        {\n          \"match\": \"\\\\|\",\n          \"name\": \"keyword.operator.choice.prompt\"\n        },\n        {\n          \"match\": \"\\\\s+\",\n          \"name\": \"meta.embedded.whitespace\"\n        },\n        {\n          \"include\": \"#escape\"\n        },\n        {\n          \"include\": \"#parenthesized\"\n        },\n        {\n          \"include\": \"#array\"\n        },\n        {\n          \"include\": \"#separator\"\n        },\n        {\n          \"include\": \"#keyword\"\n        },\n        {\n          \"match\": \"[^\\\\{\\\\}\\\\|\\\\s,]+\",\n          \"name\": \"string.unquoted.wildcard-option.prompt\"\n        }\n      ]\n    },\n    \"value\": {\n      \"patterns\": [\n        {\n          \"include\": \"#comment\"\n        },\n        {\n          \"include\": \"#escape\"\n        },\n        {\n          \"include\": \"#wildcard\"\n        },\n        {\n          \"include\": \"#parenthesized\"\n        },\n        {\n          \"include\": \"#array\"\n        },\n        {\n          \"include\": \"#network\"\n        },\n        {\n          \"include\": \"#separator\"\n        },\n        {\n          \"include\": \"#keyword\"\n        },\n        {\n          \"include\": \"#whitespace\"\n        },\n        {\n          \"include\": \"#text\"\n        },\n        {\n          \"include\": \"#invalid_reserved\"\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/ThemeMatrixDark.json",
    "content": "{\n  \"type\": \"dark\",\n  \"colors\": {\n    \"dropdown.background\": \"#525252\",\n    \"list.activeSelectionBackground\": \"#707070\",\n    \"quickInputList.focusBackground\": \"#707070\",\n    \"list.inactiveSelectionBackground\": \"#4e4e4e\",\n    \"list.hoverBackground\": \"#444444\",\n    \"list.highlightForeground\": \"#e58520\",\n    \"button.background\": \"#565656\",\n    \"editor.background\": \"#1e1e1e\",\n    \"editor.foreground\": \"#c5c8c6\",\n    \"editor.selectionBackground\": \"#676b7180\",\n    \"minimap.selectionHighlight\": \"#676b7180\",\n    \"editor.selectionHighlightBackground\": \"#575b6180\",\n    \"editor.lineHighlightBackground\": \"#303030\",\n    \"editorLineNumber.activeForeground\": \"#949494\",\n    \"editor.wordHighlightBackground\": \"#4747a180\",\n    \"editor.wordHighlightStrongBackground\": \"#6767ce80\",\n    \"editorCursor.foreground\": \"#c07020\",\n    \"editorWhitespace.foreground\": \"#505037\",\n    \"editorIndentGuide.background\": \"#505037\",\n    \"editorIndentGuide.activeBackground\": \"#707057\",\n    \"editorGroupHeader.tabsBackground\": \"#282828\",\n    \"tab.inactiveBackground\": \"#404040\",\n    \"tab.border\": \"#303030\",\n    \"tab.inactiveForeground\": \"#d8d8d8\",\n    \"tab.lastPinnedBorder\": \"#505050\",\n    \"peekView.border\": \"#3655b5\",\n    \"panelTitle.activeForeground\": \"#ffffff\",\n    \"statusBar.background\": \"#505050\",\n    \"statusBar.debuggingBackground\": \"#505050\",\n    \"statusBar.noFolderBackground\": \"#505050\",\n    \"titleBar.activeBackground\": \"#505050\",\n    \"statusBarItem.remoteBackground\": \"#3655b5\",\n    \"ports.iconRunningProcessForeground\": \"#CCCCCC\",\n    \"activityBar.background\": \"#353535\",\n    \"activityBar.foreground\": \"#ffffff\",\n    \"activityBarBadge.background\": \"#3655b5\",\n    \"sideBar.background\": \"#272727\",\n    \"sideBarSectionHeader.background\": \"#505050\",\n    \"menu.background\": \"#272727\",\n    \"menu.foreground\": \"#CCCCCC\",\n    \"pickerGroup.foreground\": \"#b0b0b0\",\n    \"inputOption.activeBorder\": \"#3655b5\",\n    \"focusBorder\": \"#3655b5\",\n    \"terminal.ansiBlack\": \"#1e1e1e\",\n    \"terminal.ansiRed\": \"#C4265E\",\n    \"terminal.ansiGreen\": \"#86B42B\",\n    \"terminal.ansiYellow\": \"#B3B42B\",\n    \"terminal.ansiBlue\": \"#6A7EC8\",\n    \"terminal.ansiMagenta\": \"#8C6BC8\",\n    \"terminal.ansiCyan\": \"#56ADBC\",\n    \"terminal.ansiWhite\": \"#e3e3dd\",\n    \"terminal.ansiBrightBlack\": \"#666666\",\n    \"terminal.ansiBrightRed\": \"#f92672\",\n    \"terminal.ansiBrightGreen\": \"#A6E22E\",\n    \"terminal.ansiBrightYellow\": \"#e2e22e\",\n    \"terminal.ansiBrightBlue\": \"#819aff\",\n    \"terminal.ansiBrightMagenta\": \"#AE81FF\",\n    \"terminal.ansiBrightCyan\": \"#66D9EF\",\n    \"terminal.ansiBrightWhite\": \"#f8f8f2\"\n  },\n  \"tokenColors\": [\n    {\n      \"settings\": {\n        \"foreground\": \"#C5C8C6\"\n      }\n    },\n    {\n      \"scope\": [\n        \"meta.embedded\",\n        \"source.groovy.embedded\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#C5C8C6\"\n      }\n    },\n    {\n      \"name\": \"Network Type\",\n      \"scope\": [\n        \"meta.embedded.network.type\"\n      ],\n      \"settings\": {\n        \"fontStyle\": \"italic\",\n        \"foreground\": \"#3990F6\"\n      }\n    },\n    {\n      \"name\": \"Network Model\",\n      \"scope\": [\n        \"meta.embedded.network.model\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#D0B344\"\n      }\n    },\n    {\n      \"name\": \"Comment\",\n      \"scope\": \"comment\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9A9B99\"\n      }\n    },\n    {\n      \"name\": \"String\",\n      \"scope\": \"string\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9AA83A\"\n      }\n    },\n    {\n      \"name\": \"String Embedded Source\",\n      \"scope\": \"string source\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#D08442\"\n      }\n    },\n    {\n      \"name\": \"Number\",\n      \"scope\": \"constant.numeric\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#6089B4\"\n      }\n    },\n    {\n      \"name\": \"Built-in constant\",\n      \"scope\": \"constant.language\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#408080\"\n      }\n    },\n    {\n      \"name\": \"Escape character\",\n      \"scope\": \"constant.character.escape\",\n      \"settings\": {\n          \"fontStyle\": \"\",\n          \"foreground\": \"#408080\"\n      }\n    },\n    {\n      \"name\": \"Escape sequence target\",\n      \"scope\": \"constant.character.escape.target\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#C5C8C6\"\n      }\n    },\n    {\n      \"name\": \"User-defined constant\",\n      \"scope\": \"constant.character, constant.other\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#8080FF\"\n      }\n    },\n    {\n      \"name\": \"Keyword\",\n      \"scope\": \"keyword\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#6089B4\"\n      }\n    },\n    {\n      \"name\": \"Support\",\n      \"scope\": \"support\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#C7444A\"\n      }\n    },\n    {\n      \"name\": \"Storage\",\n      \"scope\": \"storage\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9872A2\"\n      }\n    },\n    {\n      \"name\": \"Class name\",\n      \"scope\": \"entity.name.class, entity.name.type, entity.name.namespace, entity.name.scope-resolution\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9B0000\"\n      }\n    },\n    {\n      \"name\": \"Inherited class\",\n      \"scope\": \"entity.other.inherited-class\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#C7444A\"\n      }\n    },\n    {\n      \"name\": \"Function name\",\n      \"scope\": \"entity.name.function\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#CE6700\"\n      }\n    },\n    {\n      \"name\": \"Function argument\",\n      \"scope\": \"variable.parameter\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#6089B4\"\n      }\n    },\n    {\n      \"name\": \"Tag name\",\n      \"scope\": \"entity.name.tag\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9872A2\"\n      }\n    },\n    {\n      \"name\": \"Tag attribute\",\n      \"scope\": \"entity.other.attribute-name\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9872A2\"\n      }\n    },\n    {\n      \"name\": \"Library function\",\n      \"scope\": \"support.function\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9872A2\"\n      }\n    },\n    {\n      \"name\": \"Keyword\",\n      \"scope\": \"keyword\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#676867\"\n      }\n    },\n    {\n      \"name\": \"Class Variable\",\n      \"scope\": \"variable.other, variable.js, punctuation.separator.variable\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#6089B4\"\n      }\n    },\n    {\n      \"name\": \"Meta Brace\",\n      \"scope\": \"punctuation.section.embedded -(source string source punctuation.section.embedded), meta.brace.erb.html\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#008200\"\n      }\n    },\n    {\n      \"name\": \"Invalid\",\n      \"scope\": \"invalid\",\n      \"settings\": {\n        \"fontStyle\": \"underline\",\n        \"foreground\": \"#FF0B00\"\n      }\n    },\n    {\n      \"name\": \"Normal Variable\",\n      \"scope\": \"variable.other.php, variable.other.normal\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#6089B4\"\n      }\n    },\n    {\n      \"name\": \"Function Object\",\n      \"scope\": \"meta.function-call.object\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9872A2\"\n      }\n    },\n    {\n      \"name\": \"Function Call Variable\",\n      \"scope\": \"variable.other.property\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9872A2\"\n      }\n    },\n    {\n      \"name\": \"Keyword Control / Special\",\n      \"scope\": [\n        \"keyword.control\",\n        \"keyword.operator.new.cpp\",\n        \"keyword.operator.delete.cpp\",\n        \"keyword.other.using\",\n        \"keyword.other.operator\"\n      ],\n      \"settings\": {\n        \"fontStyle\": \"italic\",\n        \"foreground\": \"#9872A2\"\n      }\n    },\n    {\n      \"name\": \"Tag\",\n      \"scope\": \"meta.tag\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#D0B344\"\n      }\n    },\n    {\n      \"name\": \"Tag Name\",\n      \"scope\": \"entity.name.tag\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#6089B4\"\n      }\n    },\n    {\n      \"name\": \"Doctype\",\n      \"scope\": \"meta.doctype, meta.tag.sgml-declaration.doctype, meta.tag.sgml.doctype\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9AA83A\"\n      }\n    },\n    {\n      \"name\": \"Tag Inline Source\",\n      \"scope\": \"meta.tag.inline source, text.html.php.source\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9AA83A\"\n      }\n    },\n    {\n      \"name\": \"Tag Other\",\n      \"scope\": \"meta.tag.other, entity.name.tag.style, entity.name.tag.script, meta.tag.block.script, source.js.embedded punctuation.definition.tag.html, source.css.embedded punctuation.definition.tag.html\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9872A2\"\n      }\n    },\n    {\n      \"name\": \"Tag Attribute\",\n      \"scope\": \"entity.other.attribute-name, meta.tag punctuation.definition.string\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#D0B344\"\n      }\n    },\n    {\n      \"name\": \"Tag Value\",\n      \"scope\": \"meta.tag string -source -punctuation, text source text meta.tag string -punctuation\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#6089B4\"\n      }\n    },\n    {\n      \"name\": \"Meta Brace\",\n      \"scope\": \"punctuation.section.embedded -(source string source punctuation.section.embedded), meta.brace.erb.html\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#D0B344\"\n      }\n    },\n    {\n      \"name\": \"HTML ID\",\n      \"scope\": \"meta.toc-list.id\",\n      \"settings\": {\n        \"foreground\": \"#9AA83A\"\n      }\n    },\n    {\n      \"name\": \"HTML String\",\n      \"scope\": \"string.quoted.double.html, punctuation.definition.string.begin.html, punctuation.definition.string.end.html, punctuation.definition.string.end.html source, string.quoted.double.html source\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9AA83A\"\n      }\n    },\n    {\n      \"name\": \"HTML Tags\",\n      \"scope\": \"punctuation.definition.tag.html, punctuation.definition.tag.begin, punctuation.definition.tag.end\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#6089B4\"\n      }\n    },\n    {\n      \"name\": \"CSS ID\",\n      \"scope\": \"meta.selector.css entity.other.attribute-name.id\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9872A2\"\n      }\n    },\n    {\n      \"name\": \"CSS Property Name\",\n      \"scope\": \"support.type.property-name.css\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#676867\"\n      }\n    },\n    {\n      \"name\": \"CSS Property Value\",\n      \"scope\": \"meta.property-group support.constant.property-value.css, meta.property-value support.constant.property-value.css\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#C7444A\"\n      }\n    },\n    {\n      \"name\": \"JavaScript Variable\",\n      \"scope\": \"variable.language.js\",\n      \"settings\": {\n        \"foreground\": \"#CC555A\"\n      }\n    },\n    {\n      \"name\": \"Template Definition\",\n      \"scope\": [\n        \"punctuation.definition.template-expression\",\n        \"punctuation.section.embedded.coffee\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#D08442\"\n      }\n    },\n    {\n      \"name\": \"Reset JavaScript string interpolation expression\",\n      \"scope\": [\n        \"meta.template.expression\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#C5C8C6\"\n      }\n    },\n    {\n      \"name\": \"PHP Function Call\",\n      \"scope\": \"meta.function-call.object.php\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#D0B344\"\n      }\n    },\n    {\n      \"name\": \"PHP Single Quote HMTL Fix\",\n      \"scope\": \"punctuation.definition.string.end.php, punctuation.definition.string.begin.php\",\n      \"settings\": {\n        \"foreground\": \"#9AA83A\"\n      }\n    },\n    {\n      \"name\": \"PHP Parenthesis HMTL Fix\",\n      \"scope\": \"source.php.embedded.line.html\",\n      \"settings\": {\n        \"foreground\": \"#676867\"\n      }\n    },\n    {\n      \"name\": \"PHP Punctuation Embedded\",\n      \"scope\": \"punctuation.section.embedded.begin.php, punctuation.section.embedded.end.php\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#D08442\"\n      }\n    },\n    {\n      \"name\": \"Ruby Symbol\",\n      \"scope\": \"constant.other.symbol.ruby\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#9AA83A\"\n      }\n    },\n    {\n      \"name\": \"Ruby Variable\",\n      \"scope\": \"variable.language.ruby\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#D0B344\"\n      }\n    },\n    {\n      \"name\": \"Ruby Special Method\",\n      \"scope\": \"keyword.other.special-method.ruby\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#D9B700\"\n      }\n    },\n    {\n      \"name\": \"Ruby Embedded Source\",\n      \"scope\": [\n        \"punctuation.section.embedded.begin.ruby\",\n        \"punctuation.section.embedded.end.ruby\"\n      ],\n      \"settings\": {\n        \"foreground\": \"#D08442\"\n      }\n    },\n    {\n      \"name\": \"SQL\",\n      \"scope\": \"keyword.other.DML.sql\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#D0B344\"\n      }\n    },\n    {\n      \"name\": \"diff: header\",\n      \"scope\": \"meta.diff, meta.diff.header\",\n      \"settings\": {\n        \"fontStyle\": \"italic\",\n        \"foreground\": \"#E0EDDD\"\n      }\n    },\n    {\n      \"name\": \"diff: deleted\",\n      \"scope\": \"markup.deleted\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#dc322f\"\n      }\n    },\n    {\n      \"name\": \"diff: changed\",\n      \"scope\": \"markup.changed\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#cb4b16\"\n      }\n    },\n    {\n      \"name\": \"diff: inserted\",\n      \"scope\": \"markup.inserted\",\n      \"settings\": {\n        \"foreground\": \"#219186\"\n      }\n    },\n    {\n      \"name\": \"Markup Quote\",\n      \"scope\": \"markup.quote\",\n      \"settings\": {\n        \"foreground\": \"#9872A2\"\n      }\n    },\n    {\n      \"name\": \"Markup Lists\",\n      \"scope\": \"markup.list\",\n      \"settings\": {\n        \"foreground\": \"#9AA83A\"\n      }\n    },\n    {\n      \"name\": \"Markup Styling\",\n      \"scope\": \"markup.bold, markup.italic\",\n      \"settings\": {\n        \"foreground\": \"#6089B4\"\n      }\n    },\n    {\n      \"name\": \"Markup Inline\",\n      \"scope\": \"markup.inline.raw\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#FF0080\"\n      }\n    },\n    {\n      \"name\": \"Markup Headings\",\n      \"scope\": \"markup.heading\",\n      \"settings\": {\n        \"foreground\": \"#D0B344\"\n      }\n    },\n    {\n      \"name\": \"Markup Setext Header\",\n      \"scope\": \"markup.heading.setext\",\n      \"settings\": {\n        \"fontStyle\": \"\",\n        \"foreground\": \"#D0B344\"\n      }\n    },\n    {\n      \"name\": \"Markdown Headings\",\n      \"scope\": \"markup.heading.markdown\",\n      \"settings\": {\n        \"fontStyle\": \"bold\"\n      }\n    },\n    {\n      \"name\": \"Markdown Quote\",\n      \"scope\": \"markup.quote.markdown\",\n      \"settings\": {\n        \"fontStyle\": \"italic\",\n        \"foreground\": \"\"\n      }\n    },\n    {\n      \"name\": \"Markdown Bold\",\n      \"scope\": \"markup.bold.markdown\",\n      \"settings\": {\n        \"fontStyle\": \"bold\"\n      }\n    },\n    {\n      \"name\": \"Markdown Link Title/Description\",\n      \"scope\": \"string.other.link.title.markdown,string.other.link.description.markdown\",\n      \"settings\": {\n        \"foreground\": \"#AE81FF\"\n      }\n    },\n    {\n      \"name\": \"Markdown Underline Link/Image\",\n      \"scope\": \"markup.underline.link.markdown,markup.underline.link.image.markdown\",\n      \"settings\": {\n        \"foreground\": \"\"\n      }\n    },\n    {\n      \"name\": \"Markdown Emphasis\",\n      \"scope\": \"markup.italic.markdown\",\n      \"settings\": {\n        \"fontStyle\": \"italic\"\n      }\n    },\n    {\n      \"scope\": \"markup.strikethrough\",\n      \"settings\": {\n        \"fontStyle\": \"strikethrough\"\n      }\n    },\n    {\n      \"name\": \"Markdown Punctuation Definition Link\",\n      \"scope\": \"markup.list.unnumbered.markdown, markup.list.numbered.markdown\",\n      \"settings\": {\n        \"foreground\": \"\"\n      }\n    },\n    {\n      \"name\": \"Markdown List Punctuation\",\n      \"scope\": [\n        \"punctuation.definition.list.begin.markdown\"\n      ],\n      \"settings\": {\n        \"foreground\": \"\"\n      }\n    },\n    {\n      \"scope\": \"token.info-token\",\n      \"settings\": {\n        \"foreground\": \"#6796e6\"\n      }\n    },\n    {\n      \"scope\": \"token.warn-token\",\n      \"settings\": {\n        \"foreground\": \"#cd9731\"\n      }\n    },\n    {\n      \"scope\": \"token.error-token\",\n      \"settings\": {\n        \"foreground\": \"#f44747\"\n      }\n    },\n    {\n      \"scope\": \"token.debug-token\",\n      \"settings\": {\n        \"foreground\": \"#b267e6\"\n      }\n    },\n    {\n      \"name\": \"this.self\",\n      \"scope\": \"variable.language\",\n      \"settings\": {\n        \"foreground\": \"#c7444a\"\n      }\n    }\n  ],\n  \"semanticHighlighting\": true\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/hf-packages.json",
    "content": "[\n  {\n    \"ModelCategory\": \"BaseModel\",\n    \"ModelName\": \"Stable Diffusion 1.5\",\n    \"RepositoryPath\": \"runwayml/stable-diffusion-v1-5\",\n    \"Files\": [\n      \"v1-5-pruned-emaonly.safetensors\"\n    ],\n    \"LicenseType\": \"CreativeML Open RAIL-M\"\n  },\n  {\n    \"ModelCategory\": \"BaseModel\",\n    \"ModelName\": \"Stable Diffusion 2.1\",\n    \"RepositoryPath\": \"stabilityai/stable-diffusion-2-1\",\n    \"Files\": [\n      \"v2-1_768-ema-pruned.safetensors\"\n    ],\n    \"LicenseType\": \"Open RAIL++\"\n  },\n  {\n    \"ModelCategory\": \"BaseModel\",\n    \"ModelName\": \"Stable Diffusion XL (Base)\",\n    \"RepositoryPath\": \"stabilityai/stable-diffusion-xl-base-1.0\",\n    \"Files\": [\n      \"sd_xl_base_1.0_0.9vae.safetensors\",\n      \"sd_xl_offset_example-lora_1.0.safetensors\"\n    ],\n    \"LicenseType\": \"Open RAIL++\",\n    \"LicensePath\": \"LICENSE.md\"\n  },\n  {\n    \"ModelCategory\": \"BaseModel\",\n    \"ModelName\": \"Stable Diffusion XL (Refiner)\",\n    \"RepositoryPath\": \"stabilityai/stable-diffusion-xl-refiner-1.0\",\n    \"Files\": [\n      \"sd_xl_refiner_1.0_0.9vae.safetensors\"\n    ],\n    \"LicenseType\": \"Open RAIL++\",\n    \"LicensePath\": \"LICENSE.md\"\n  },\n  {\n    \"ModelCategory\": \"BaseModel\",\n    \"ModelName\": \"SDXL Turbo\",\n    \"RepositoryPath\": \"stabilityai/sdxl-turbo\",\n    \"Files\": [\n      \"sd_xl_turbo_1.0_fp16.safetensors\"\n    ],\n    \"LicenseType\": \"SAI-NC-Community\",\n    \"LicensePath\": \"LICENSE.TXT\"\n  },\n  {\n    \"ModelCategory\": \"BaseModel\",\n    \"ModelName\": \"Stable Video Diffusion\",\n    \"RepositoryPath\": \"stabilityai/stable-video-diffusion-img2vid\",\n    \"Files\": [\n      \"svd.safetensors\"\n    ],\n    \"LicenseType\": \"Stable-Video-Diffusion-NC-Community\",\n    \"LicensePath\": \"LICENSE\"\n  },\n  {\n    \"ModelCategory\": \"BaseModel\",\n    \"ModelName\": \"Stable Cascade\",\n    \"RepositoryPath\": \"stabilityai/stable-cascade\",\n    \"Files\": [\n      \"comfyui_checkpoints/stable_cascade_stage_b.safetensors\",\n      \"comfyui_checkpoints/stable_cascade_stage_c.safetensors\"\n    ],\n    \"LicenseType\": \"stable-cascade-nc-community\",\n    \"LicensePath\": \"LICENSE\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"Canny\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11p_sd15_canny.pth\",\n      \"control_v11p_sd15_canny.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"Depth\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11f1p_sd15_depth.pth\",\n      \"control_v11f1p_sd15_depth.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"MLSD\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11p_sd15_mlsd.pth\",\n      \"control_v11p_sd15_mlsd.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"Inpaint\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11p_sd15_inpaint.pth\",\n      \"control_v11p_sd15_inpaint.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"IP2P\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11e_sd15_ip2p.pth\",\n      \"control_v11e_sd15_ip2p.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"Tile\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11f1e_sd15_tile.pth\",\n      \"control_v11f1e_sd15_tile.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"OpenPose\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11p_sd15_openpose.pth\",\n      \"control_v11p_sd15_openpose.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"LineArt\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11p_sd15_lineart.pth\",\n      \"control_v11p_sd15_lineart.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"LineArt Anime\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11p_sd15s2_lineart_anime.pth\",\n      \"control_v11p_sd15s2_lineart_anime.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"NormalBae\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11p_sd15_normalbae.pth\",\n      \"control_v11p_sd15_normalbae.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"Seg\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11p_sd15_seg.pth\",\n      \"control_v11p_sd15_seg.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"Soft Edge\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11p_sd15_softedge.pth\",\n      \"control_v11p_sd15_softedge.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"Scribble\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11p_sd15_scribble.pth\",\n      \"control_v11p_sd15_scribble.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"ControlNet\",\n    \"ModelName\": \"Shuffle\",\n    \"RepositoryPath\": \"lllyasviel/ControlNet-v1-1\",\n    \"Files\": [\n      \"control_v11e_sd15_shuffle.pth\",\n      \"control_v11e_sd15_shuffle.yaml\"\n    ],\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"Canny\",\n    \"RepositoryPath\": \"lllyasviel/control_v11p_sd15_canny\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"canny\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"Depth\",\n    \"RepositoryPath\": \"lllyasviel/control_v11f1p_sd15_depth\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"depth\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"MLSD\",\n    \"RepositoryPath\": \"lllyasviel/control_v11p_sd15_mlsd\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"MLSD\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"Inpaint\",\n    \"RepositoryPath\": \"lllyasviel/control_v11p_sd15_inpaint\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"inpaint\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"IP2P\",\n    \"RepositoryPath\": \"lllyasviel/control_v11e_sd15_ip2p\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"ip2p\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"Tile\",\n    \"RepositoryPath\": \"lllyasviel/control_v11f1e_sd15_tile\",\n    \"Files\": [\n      \"diffusion_pytorch_model.bin\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"tile\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"OpenPose\",\n    \"RepositoryPath\": \"lllyasviel/control_v11p_sd15_openpose\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"openpose\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"LineArt\",\n    \"RepositoryPath\": \"lllyasviel/control_v11p_sd15_lineart\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"lineart\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"LineArt Anime\",\n    \"RepositoryPath\": \"lllyasviel/control_v11p_sd15s2_lineart_anime\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"lineart_anime\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"NormalBae\",\n    \"RepositoryPath\": \"lllyasviel/control_v11p_sd15_normalbae\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"normalbae\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"Seg\",\n    \"RepositoryPath\": \"lllyasviel/control_v11p_sd15_seg\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"seg\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"Soft Edge\",\n    \"RepositoryPath\": \"lllyasviel/control_v11p_sd15_softedge\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"softedge\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"Scribble\",\n    \"RepositoryPath\": \"lllyasviel/control_v11p_sd15_scribble\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"scribble\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersControlNet\",\n    \"ModelName\": \"Shuffle\",\n    \"RepositoryPath\": \"lllyasviel/control_v11e_sd15_shuffle\",\n    \"Files\": [\n      \"diffusion_pytorch_model.safetensors\",\n      \"config.json\"\n    ],\n    \"Subfolder\": \"shuffle\",\n    \"LicenseType\": \"Open RAIL\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersClipVision\",\n    \"ModelName\": \"CLIP Vision H\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.1_ComfyUI_repackaged\",\n    \"Files\": [\n      \"split_files/clip_vision/clip_vision_h.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersClipVision\",\n    \"ModelName\": \"IP Adapter Encoder\",\n    \"RepositoryPath\": \"InvokeAI/ip_adapter_sd_image_encoder\",\n    \"Files\": [\n      \"config.json\",\n      \"model.safetensors\"\n    ],\n    \"Subfolder\": \"ip_adapter_sd_image_encoder\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersClipVision\",\n    \"ModelName\": \"IP Adapter Encoder (SDXL)\",\n    \"RepositoryPath\": \"InvokeAI/ip_adapter_sdxl_image_encoder\",\n    \"Files\": [\n      \"config.json\",\n      \"model.safetensors\"\n    ],\n    \"Subfolder\": \"ip_adapter_sdxl_image_encoder\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersIpAdapter\",\n    \"ModelName\": \"SD 1.5 Adapter\",\n    \"RepositoryPath\": \"InvokeAI/ip_adapter_sd15\",\n    \"Files\": [\n      \"ip-adapter_sd15.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersIpAdapter\",\n    \"ModelName\": \"SD 1.5 Light Adapter\",\n    \"RepositoryPath\": \"InvokeAI/ip_adapter_sd15_light\",\n    \"Files\": [\n      \"ip-adapter_sd15_light.safetensors\"\n    ],\n    \"Subfolder\": \"ip_adapter_sd15_light\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersIpAdapter\",\n    \"ModelName\": \"SD 1.5 Plus Adapter\",\n    \"RepositoryPath\": \"InvokeAI/ip_adapter_plus_sd15\",\n    \"Files\": [\n      \"ip-adapter-plus_sd15.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersIpAdapter\",\n    \"ModelName\": \"SD 1.5 Face Plus Adapter\",\n    \"RepositoryPath\": \"InvokeAI/ip_adapter_plus_face_sd15\",\n    \"Files\": [\n      \"ip-adapter-plus-face_sd15.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersIpAdapterXl\",\n    \"ModelName\": \"SDXL Adapter\",\n    \"RepositoryPath\": \"InvokeAI/ip_adapter_sdxl\",\n    \"Files\": [\n      \"ip-adapter_sdxl.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersIpAdapterXl\",\n    \"ModelName\": \"SDXL Plus Adapter\",\n    \"RepositoryPath\": \"InvokeAI/ip-adapter-plus_sdxl_vit-h\",\n    \"Files\": [\n      \"ip-adapter-plus_sdxl_vit-h.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersIpAdapterXl\",\n    \"ModelName\": \"SDXL Face Plus Adapter\",\n    \"RepositoryPath\": \"InvokeAI/ip-adapter-plus-face_sdxl_vit-h\",\n    \"Files\": [\n      \"ip-adapter-plus-face_sdxl_vit-h.safetensors\"\n    ],\n    \"LicenseType\": \"CreativeML Open RAIL-M\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersT2IAdapter\",\n    \"ModelName\": \"Sketch\",\n    \"RepositoryPath\": \"TencentARC/t2iadapter_sketch_sd15v2\",\n    \"Files\": [\n      \"config.json\",\n      \"diffusion_pytorch_model.bin\"\n    ],\n    \"Subfolder\": \"t2iadapter_sketch_sd15v2\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersT2IAdapter\",\n    \"ModelName\": \"Depth\",\n    \"RepositoryPath\": \"TencentARC/t2iadapter_depth_sd15v2\",\n    \"Files\": [\n      \"config.json\",\n      \"diffusion_pytorch_model.bin\"\n    ],\n    \"Subfolder\": \"t2iadapter_depth_sd15v2\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersT2IAdapter\",\n    \"ModelName\": \"Canny\",\n    \"RepositoryPath\": \"TencentARC/t2iadapter_canny_sd15v2\",\n    \"Files\": [\n      \"config.json\",\n      \"diffusion_pytorch_model.bin\"\n    ],\n    \"Subfolder\": \"t2iadapter_canny_sd15v2\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersT2IAdapter\",\n    \"ModelName\": \"Depth-Zoe\",\n    \"RepositoryPath\": \"TencentARC/t2iadapter_zoedepth_sd15v1\",\n    \"Files\": [\n      \"config.json\",\n      \"diffusion_pytorch_model.bin\"\n    ],\n    \"Subfolder\": \"t2iadapter_zoedepth_sd15v1\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersT2IAdapter\",\n    \"ModelName\": \"Sketch (SDXL)\",\n    \"RepositoryPath\": \"TencentARC/t2i-adapter-sketch-sdxl-1.0\",\n    \"Files\": [\n      \"config.json\",\n      \"diffusion_pytorch_model.safetensors\"\n    ],\n    \"Subfolder\": \"t2i-adapter-sketch-sdxl-1.0\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersT2IAdapter\",\n    \"ModelName\": \"Depth-Zoe (SDXL)\",\n    \"RepositoryPath\": \"TencentARC/t2i-adapter-depth-zoe-sdxl-1.0\",\n    \"Files\": [\n      \"config.json\",\n      \"diffusion_pytorch_model.safetensors\"\n    ],\n    \"Subfolder\": \"t2i-adapter-depth-zoe-sdxl-1.0\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersT2IAdapter\",\n    \"ModelName\": \"OpenPose (SDXL)\",\n    \"RepositoryPath\": \"TencentARC/t2i-adapter-openpose-sdxl-1.0\",\n    \"Files\": [\n      \"config.json\",\n      \"diffusion_pytorch_model.safetensors\"\n    ],\n    \"Subfolder\": \"t2i-adapter-openpose-sdxl-1.0\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersT2IAdapter\",\n    \"ModelName\": \"Depth-MiDaS (SDXL)\",\n    \"RepositoryPath\": \"TencentARC/t2i-adapter-depth-midas-sdxl-1.0\",\n    \"Files\": [\n      \"config.json\",\n      \"diffusion_pytorch_model.safetensors\"\n    ],\n    \"Subfolder\": \"t2i-adapter-depth-midas-sdxl-1.0\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersT2IAdapter\",\n    \"ModelName\": \"LineArt (SDXL)\",\n    \"RepositoryPath\": \"TencentARC/t2i-adapter-lineart-sdxl-1.0\",\n    \"Files\": [\n      \"config.json\",\n      \"diffusion_pytorch_model.safetensors\"\n    ],\n    \"Subfolder\": \"t2i-adapter-lineart-sdxl-1.0\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"DiffusersT2IAdapter\",\n    \"ModelName\": \"Canny (SDXL)\",\n    \"RepositoryPath\": \"TencentARC/t2i-adapter-canny-sdxl-1.0\",\n    \"Files\": [\n      \"config.json\",\n      \"diffusion_pytorch_model.safetensors\"\n    ],\n    \"Subfolder\": \"t2i-adapter-canny-sdxl-1.0\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Canny (Small, Diffusers)\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"diffusers_xl_canny_small.safetensors\"\n    ],\n    \"LicenseType\": \"Open RAIL++\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Canny (Mid, Diffusers)\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"diffusers_xl_canny_mid.safetensors\"\n    ],\n    \"LicenseType\": \"Open RAIL++\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Canny (Full, Diffusers)\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"diffusers_xl_canny_full.safetensors\"\n    ],\n    \"LicenseType\": \"Open RAIL++\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Depth (Small, Diffusers)\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"diffusers_xl_depth_small.safetensors\"\n    ],\n    \"LicenseType\": \"Open RAIL++\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Depth (Mid, Diffusers)\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"diffusers_xl_depth_mid.safetensors\"\n    ],\n    \"LicenseType\": \"Open RAIL++\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Depth (Full, Diffusers)\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"diffusers_xl_depth_full.safetensors\"\n    ],\n    \"LicenseType\": \"Open RAIL++\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Ioclab Recolor\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"ioclab_sd15_recolor.safetensors\"\n    ],\n    \"LicenseType\": \"Open RAIL++\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Kohya Control-lllite Blur\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"kohya_controllllite_xl_blur.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Kohya Control-lllite Blur (Anime)\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"kohya_controllllite_xl_blur_anime.safetensors\",\n      \"kohya_controllllite_xl_blur_anime_beta.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Kohya Control-lllite Canny\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"kohya_controllllite_xl_canny.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Kohya Control-lllite Canny (Anime)\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"kohya_controllllite_xl_canny_anime.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Kohya Control-lllite Depth\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"kohya_controllllite_xl_depth.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Kohya Control-lllite Depth (Anime)\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"kohya_controllllite_xl_depth_anime.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Kohya Control-lllite Openpose (Anime)\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"kohya_controllllite_xl_openpose_anime.safetensors\",\n      \"kohya_controllllite_xl_openpose_anime_v2.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Kohya Controllllite Scribble Anime\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"kohya_controllllite_xl_scribble_anime.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"SAI XL Depth 256 Lora\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"sai_xl_depth_256lora.safetensors\"\n    ],\n    \"LicenseType\": \"Stability AI Control-Lora CLA\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"SAI XL Depth 128 Lora\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"sai_xl_depth_128lora.safetensors\"\n    ],\n    \"LicenseType\": \"Stability AI Control-Lora CLA\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"SAI XL Canny 256 Lora\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"sai_xl_canny_256lora.safetensors\"\n    ],\n    \"LicenseType\": \"Stability AI Control-Lora CLA\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"SAI XL Canny 128 Lora\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"sai_xl_canny_128lora.safetensors\"\n    ],\n    \"LicenseType\": \"Stability AI Control-Lora CLA\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"SAI XL Recolor 256 Lora\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"sai_xl_recolor_256lora.safetensors\"\n    ],\n    \"LicenseType\": \"Stability AI Control-Lora CLA\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"SAI XL Recolor 128 Lora\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"sai_xl_recolor_128lora.safetensors\"\n    ],\n    \"LicenseType\": \"Stability AI Control-Lora CLA\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"SAI XL Sketch 256 Lora\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"sai_xl_sketch_256lora.safetensors\"\n    ],\n    \"LicenseType\": \"Stability AI Control-Lora CLA\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"SAI XL Sketch 128 Lora\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"sai_xl_sketch_128lora.safetensors\"\n    ],\n    \"LicenseType\": \"Stability AI Control-Lora CLA\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"SargeZT XL Depth\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"sargezt_xl_depth.safetensors\"\n    ],\n    \"LicenseType\": \"CreativeML Open RAIL-M\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"SargeZT XL Depth Faid Vidit\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"sargezt_xl_depth_faid_vidit.safetensors\"\n    ],\n    \"LicenseType\": \"CreativeML Open RAIL-M\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"SargeZT XL Depth Zeed\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"sargezt_xl_depth_zeed.safetensors\"\n    ],\n    \"LicenseType\": \"CreativeML Open RAIL-M\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"SargeZT XL Softedge\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"sargezt_xl_softedge.safetensors\"\n    ],\n    \"LicenseType\": \"CreativeML Open RAIL-M\"\n  },\n  {\n    \"ModelCategory\": \"ControlNetXl\",\n    \"ModelName\": \"Thibaud XL Openpose\",\n    \"RepositoryPath\": \"lllyasviel/sd_control_collection\",\n    \"Files\": [\n      \"thibaud_xl_openpose.safetensors\"\n    ],\n    \"LicenseType\": \"Unknown\"\n  },\n  {\n    \"ModelCategory\": \"Ultralytics\",\n    \"ModelName\": \"YOLOv8m (Face)\",\n    \"RepositoryPath\": \"Bingsu/adetailer\",\n    \"Files\": [\n      \"face_yolov8m.pt\"\n    ],\n    \"Subfolder\": \"bbox\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Ultralytics\",\n    \"ModelName\": \"YOLOv8n (Face)\",\n    \"RepositoryPath\": \"Bingsu/adetailer\",\n    \"Files\": [\n      \"face_yolov8n.pt\"\n    ],\n    \"Subfolder\": \"bbox\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Ultralytics\",\n    \"ModelName\": \"YOLOv8n v2 (Face)\",\n    \"RepositoryPath\": \"Bingsu/adetailer\",\n    \"Files\": [\n      \"face_yolov8n_v2.pt\"\n    ],\n    \"Subfolder\": \"bbox\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Ultralytics\",\n    \"ModelName\": \"YOLOv8s (Face)\",\n    \"RepositoryPath\": \"Bingsu/adetailer\",\n    \"Files\": [\n      \"face_yolov8s.pt\"\n    ],\n    \"Subfolder\": \"bbox\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Ultralytics\",\n    \"ModelName\": \"YOLOv9c (Face)\",\n    \"RepositoryPath\": \"Bingsu/adetailer\",\n    \"Files\": [\n      \"face_yolov9c.pt\"\n    ],\n    \"Subfolder\": \"bbox\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Ultralytics\",\n    \"ModelName\": \"YOLOv8n (Hand)\",\n    \"RepositoryPath\": \"Bingsu/adetailer\",\n    \"Files\": [\n      \"hand_yolov8n.pt\"\n    ],\n    \"Subfolder\": \"bbox\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Ultralytics\",\n    \"ModelName\": \"YOLOv8s (Hand)\",\n    \"RepositoryPath\": \"Bingsu/adetailer\",\n    \"Files\": [\n      \"hand_yolov8s.pt\"\n    ],\n    \"Subfolder\": \"bbox\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Ultralytics\",\n    \"ModelName\": \"YOLOv9c (Hand)\",\n    \"RepositoryPath\": \"Bingsu/adetailer\",\n    \"Files\": [\n      \"hand_yolov9c.pt\"\n    ],\n    \"Subfolder\": \"bbox\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Ultralytics\",\n    \"ModelName\": \"YOLOv8m-seg (Person)\",\n    \"RepositoryPath\": \"Bingsu/adetailer\",\n    \"Files\": [\n      \"person_yolov8m-seg.pt\"\n    ],\n    \"Subfolder\": \"segm\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Ultralytics\",\n    \"ModelName\": \"YOLOv8n-seg (Person)\",\n    \"RepositoryPath\": \"Bingsu/adetailer\",\n    \"Files\": [\n      \"person_yolov8n-seg.pt\"\n    ],\n    \"Subfolder\": \"segm\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Ultralytics\",\n    \"ModelName\": \"YOLOv8s-seg (Person)\",\n    \"RepositoryPath\": \"Bingsu/adetailer\",\n    \"Files\": [\n      \"person_yolov8s-seg.pt\"\n    ],\n    \"Subfolder\": \"segm\",\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Clip\",\n    \"ModelName\": \"CLIP L\",\n    \"RepositoryPath\": \"comfyanonymous/flux_text_encoders\",\n    \"Files\": [\n      \"clip_l.safetensors\"\n    ],\n    \"LicenseType\": \"MIT\"\n  },\n  {\n    \"ModelCategory\": \"Clip\",\n    \"ModelName\": \"CLIP G\",\n    \"RepositoryPath\": \"Comfy-Org/stable-diffusion-3.5-fp8\",\n    \"Files\": [\n      \"text_encoders/clip_g.safetensors\"\n    ],\n    \"LicenseType\": \"MIT\"\n  },\n  {\n    \"ModelCategory\": \"Clip\",\n    \"ModelName\": \"T5XXL fp16\",\n    \"RepositoryPath\": \"comfyanonymous/flux_text_encoders\",\n    \"Files\": [\n      \"t5xxl_fp16.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Clip\",\n    \"ModelName\": \"T5XXL fp8 e4m3fn\",\n    \"RepositoryPath\": \"comfyanonymous/flux_text_encoders\",\n    \"Files\": [\n      \"t5xxl_fp8_e4m3fn.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Clip\",\n    \"ModelName\": \"UMT5XXL fp16\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.1_ComfyUI_repackaged\",\n    \"Files\": [\n      \"split_files/text_encoders/umt5_xxl_fp16.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Clip\",\n    \"ModelName\": \"UMT5XXL fp8 e4m3fn\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.1_ComfyUI_repackaged\",\n    \"Files\": [\n      \"split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Clip\",\n    \"ModelName\": \"CLIP G (HiDream)\",\n    \"RepositoryPath\": \"Comfy-Org/HiDream-I1_ComfyUI\",\n    \"Files\": [\n      \"split_files/text_encoders/clip_g_hidream.safetensors\"\n    ],\n    \"LicenseType\": \"MIT\"\n  },\n  {\n    \"ModelCategory\": \"Clip\",\n    \"ModelName\": \"CLIP L (HiDream)\",\n    \"RepositoryPath\": \"Comfy-Org/HiDream-I1_ComfyUI\",\n    \"Files\": [\n      \"split_files/text_encoders/clip_l_hidream.safetensors\"\n    ],\n    \"LicenseType\": \"MIT\"\n  },\n  {\n    \"ModelCategory\": \"Clip\",\n    \"ModelName\": \"Llama 3.1 8b Instruct fp8\",\n    \"RepositoryPath\": \"Comfy-Org/HiDream-I1_ComfyUI\",\n    \"Files\": [\n      \"split_files/text_encoders/split_files/text_encoders/llama_3.1_8b_instruct_fp8_scaled.safetensors\"\n    ],\n    \"LicenseType\": \"Llama3.1\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.1 I2V 480p bf16\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.1_ComfyUI_repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.1_i2v_480p_14B_bf16.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.1 I2V 480p fp8 e4m3fn\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.1_ComfyUI_repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.1 I2V 720p bf16\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.1_ComfyUI_repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.1_i2v_720p_14B_bf16.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.1 I2V 720p fp8 e4m3fn\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.1_ComfyUI_repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.1 T2V 1.3B bf16\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.1_ComfyUI_repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.1_t2v_1.3B_bf16.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.1 T2V 1.3B fp16\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.1_ComfyUI_repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.1_t2v_1.3B_fp16.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.1 T2V 14B bf16\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.1_ComfyUI_repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.1_t2v_14B_bf16.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.1 T2V 14B fp8 e4m3fn\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.1_ComfyUI_repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.1_t2v_14B_fp8_e4m3fn.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.2 TI2V 5B fp16\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.2_ComfyUI_Repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.2_ti2v_5B_fp16.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.2 I2V High Noise 14B fp16\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.2_ComfyUI_Repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.2_i2v_high_noise_14B_fp16.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.2 I2V High Noise 14B fp8\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.2_ComfyUI_Repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.2 I2V Low Noise 14B fp16\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.2_ComfyUI_Repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.2_i2v_low_noise_14B_fp16.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.2 I2V Low Noise 14B fp8\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.2_ComfyUI_Repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.2 T2V High Noise 14B fp16\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.2_ComfyUI_Repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp16.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.2 T2V High Noise 14B fp8\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.2_ComfyUI_Repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.2 T2V Low Noise 14B fp16\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.2_ComfyUI_Repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp16.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"Wan 2.2 T2V Low Noise 14B fp8\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.2_ComfyUI_Repackaged\",\n    \"Files\": [\n      \"split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"HiDream I1 Dev bf16\",\n    \"RepositoryPath\": \"Comfy-Org/HiDream-I1_ComfyUI\",\n    \"Files\": [\n      \"split_files/diffusion_models/hidream_i1_dev_bf16.safetensors\"\n    ],\n    \"LicenseType\": \"MIT\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"HiDream I1 Dev fp8\",\n    \"RepositoryPath\": \"Comfy-Org/HiDream-I1_ComfyUI\",\n    \"Files\": [\n      \"split_files/diffusion_models/hidream_i1_dev_fp8.safetensors\"\n    ],\n    \"LicenseType\": \"MIT\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"HiDream I1 Fast bf16\",\n    \"RepositoryPath\": \"Comfy-Org/HiDream-I1_ComfyUI\",\n    \"Files\": [\n      \"split_files/diffusion_models/hidream_i1_fast_bf16.safetensors\"\n    ],\n    \"LicenseType\": \"MIT\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"HiDream I1 Fast fp8\",\n    \"RepositoryPath\": \"Comfy-Org/HiDream-I1_ComfyUI\",\n    \"Files\": [\n      \"split_files/diffusion_models/hidream_i1_fast_fp8.safetensors\"\n    ],\n    \"LicenseType\": \"MIT\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"HiDream I1 Full fp16\",\n    \"RepositoryPath\": \"Comfy-Org/HiDream-I1_ComfyUI\",\n    \"Files\": [\n      \"split_files/diffusion_models/hidream_i1_full_fp16.safetensors\"\n    ],\n    \"LicenseType\": \"MIT\"\n  },\n  {\n    \"ModelCategory\": \"Unet\",\n    \"ModelName\": \"HiDream I1 Full fp8\",\n    \"RepositoryPath\": \"Comfy-Org/HiDream-I1_ComfyUI\",\n    \"Files\": [\n      \"split_files/diffusion_models/hidream_i1_full_fp8.safetensors\"\n    ],\n    \"LicenseType\": \"MIT\"\n  },\n  {\n    \"ModelCategory\": \"Vae\",\n    \"ModelName\": \"Flux.1 VAE\",\n    \"RepositoryPath\": \"black-forest-labs/FLUX.1-schnell\",\n    \"Files\": [\n      \"ae.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\",\n    \"LoginRequired\": true\n  },\n  {\n    \"ModelCategory\": \"Vae\",\n    \"ModelName\": \"Wan 2.1 VAE\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.1_ComfyUI_repackaged\",\n    \"Files\": [\n      \"split_files/vae/wan_2.1_vae.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Vae\",\n    \"ModelName\": \"Wan 2.2 VAE\",\n    \"RepositoryPath\": \"Comfy-Org/Wan_2.2_ComfyUI_Repackaged\",\n    \"Files\": [\n      \"split_files/vae/wan2.2_vae.safetensors\"\n    ],\n    \"LicenseType\": \"Apache 2.0\"\n  },\n  {\n    \"ModelCategory\": \"Vae\",\n    \"ModelName\": \"HiDream I1 VAE\",\n    \"RepositoryPath\": \"Comfy-Org/HiDream-I1_ComfyUI\",\n    \"Files\": [\n      \"split_files/vae/ae.safetensors\"\n    ],\n    \"LicenseType\": \"MIT\"\n  }\n]\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/licenses.json",
    "content": "[{\"PackageName\":\"AsyncImageLoader.Avalonia\",\"PackageVersion\":\"3.0.0-avalonia11-preview6\",\"PackageUrl\":\"https://github.com/AvaloniaUtils/AsyncImageLoader.Avalonia\",\"Copyright\":\"\",\"Authors\":[\"SKProCH\"],\"Description\":\"Provides way to asynchronous bitmap loading from web for Avalonia Image control and more\",\"LicenseUrl\":\"https://github.com/AvaloniaUtils/AsyncImageLoader.Avalonia/blob/master/LICENSE\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUtils/AsyncImageLoader.Avalonia.git\",\"Commit\":\"\"}},{\"PackageName\":\"Avalonia\",\"PackageVersion\":\"11.0.0-rc1.1\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"8acc41c94f664ba67069beab173a1c0feb562dce\"}},{\"PackageName\":\"Avalonia\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.Angle.Windows.Natives\",\"PackageVersion\":\"2.1.0.2023020321\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://www.nuget.org/packages/Avalonia.Angle.Windows.Natives/2.1.0.2023020321/License\",\"LicenseType\":\"LICENSE.txt\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/angle/\",\"Commit\":\"1b9ed3b11888067a93a5ea552d705ddaab21adb1\"}},{\"PackageName\":\"Avalonia.AvaloniaEdit\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"\",\"Copyright\":\"\",\"Authors\":[\"AvaloniaEdit\"],\"Description\":\"This project is a port of AvalonEdit, a WPF-based text editor for Avalonia.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"Avalonia.BuildServices\",\"PackageVersion\":\"0.0.19\",\"PackageUrl\":\"\",\"Copyright\":\"\",\"Authors\":[\"Avalonia.BuildServices\"],\"Description\":\"Package Description\",\"LicenseUrl\":\"\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"Avalonia.BuildServices\",\"PackageVersion\":\"0.0.28\",\"PackageUrl\":\"\",\"Copyright\":\"\",\"Authors\":[\"Avalonia.BuildServices\"],\"Description\":\"Package Description\",\"LicenseUrl\":\"\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"Avalonia.Controls.ColorPicker\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.Controls.DataGrid\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.Controls.ItemsRepeater\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.Desktop\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.Diagnostics\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.Fonts.Inter\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.FreeDesktop\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.Native\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.Remote.Protocol\",\"PackageVersion\":\"11.0.0-rc1.1\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"8acc41c94f664ba67069beab173a1c0feb562dce\"}},{\"PackageName\":\"Avalonia.Remote.Protocol\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.Skia\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.Svg\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://github.com/wieslawsoltes/Svg.Skia\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"An SVG image control for Avalonia.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/wieslawsoltes/Svg.Skia\",\"Commit\":\"2d9ef9767e31fc5b87655cad1022c2632b3fd33a\"}},{\"PackageName\":\"Avalonia.Themes.Simple\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.Win32\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.X11\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://avaloniaui.net/\",\"Copyright\":\"Copyright 2013-2023 © The AvaloniaUI Project\",\"Authors\":[\"Avalonia Team\"],\"Description\":\"Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia/\",\"Commit\":\"194044692eb3967b8c6bd0ed140a954f53b48e0e\"}},{\"PackageName\":\"Avalonia.Xaml.Behaviors\",\"PackageVersion\":\"11.0.0.1\",\"PackageUrl\":\"https://github.com/wieslawsoltes/AvaloniaBehaviors\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"Easily add interactivity to your Avalonia apps using XAML Behaviors. Behaviors encapsulate reusable functionalities for elements that can be easily added to your XAML without the need for more imperative code.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia.Xaml.Behaviors\",\"Commit\":\"2fd5e3c6f58db51afd1f04e6c5704b08318c1599\"}},{\"PackageName\":\"Avalonia.Xaml.Interactions\",\"PackageVersion\":\"11.0.0.1\",\"PackageUrl\":\"https://github.com/wieslawsoltes/AvaloniaBehaviors\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"Easily add interactivity to your Avalonia apps using XAML Behaviors. Behaviors encapsulate reusable functionalities for elements that can be easily added to your XAML without the need for more imperative code.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia.Xaml.Behaviors\",\"Commit\":\"2fd5e3c6f58db51afd1f04e6c5704b08318c1599\"}},{\"PackageName\":\"Avalonia.Xaml.Interactions.Custom\",\"PackageVersion\":\"11.0.0.1\",\"PackageUrl\":\"https://github.com/wieslawsoltes/AvaloniaBehaviors\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"Easily add interactivity to your Avalonia apps using XAML Behaviors. Behaviors encapsulate reusable functionalities for elements that can be easily added to your XAML without the need for more imperative code.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia.Xaml.Behaviors\",\"Commit\":\"2fd5e3c6f58db51afd1f04e6c5704b08318c1599\"}},{\"PackageName\":\"Avalonia.Xaml.Interactions.DragAndDrop\",\"PackageVersion\":\"11.0.0.1\",\"PackageUrl\":\"https://github.com/wieslawsoltes/AvaloniaBehaviors\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"Easily add interactivity to your Avalonia apps using XAML Behaviors. Behaviors encapsulate reusable functionalities for elements that can be easily added to your XAML without the need for more imperative code.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia.Xaml.Behaviors\",\"Commit\":\"2fd5e3c6f58db51afd1f04e6c5704b08318c1599\"}},{\"PackageName\":\"Avalonia.Xaml.Interactions.Draggable\",\"PackageVersion\":\"11.0.0.1\",\"PackageUrl\":\"https://github.com/wieslawsoltes/AvaloniaBehaviors\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"Easily add interactivity to your Avalonia apps using XAML Behaviors. Behaviors encapsulate reusable functionalities for elements that can be easily added to your XAML without the need for more imperative code.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia.Xaml.Behaviors\",\"Commit\":\"2fd5e3c6f58db51afd1f04e6c5704b08318c1599\"}},{\"PackageName\":\"Avalonia.Xaml.Interactions.Events\",\"PackageVersion\":\"11.0.0.1\",\"PackageUrl\":\"https://github.com/wieslawsoltes/AvaloniaBehaviors\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"Easily add interactivity to your Avalonia apps using XAML Behaviors. Behaviors encapsulate reusable functionalities for elements that can be easily added to your XAML without the need for more imperative code.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia.Xaml.Behaviors\",\"Commit\":\"2fd5e3c6f58db51afd1f04e6c5704b08318c1599\"}},{\"PackageName\":\"Avalonia.Xaml.Interactions.Reactive\",\"PackageVersion\":\"11.0.0.1\",\"PackageUrl\":\"https://github.com/wieslawsoltes/AvaloniaBehaviors\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"Easily add interactivity to your Avalonia apps using XAML Behaviors. Behaviors encapsulate reusable functionalities for elements that can be easily added to your XAML without the need for more imperative code.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia.Xaml.Behaviors\",\"Commit\":\"2fd5e3c6f58db51afd1f04e6c5704b08318c1599\"}},{\"PackageName\":\"Avalonia.Xaml.Interactions.Responsive\",\"PackageVersion\":\"11.0.0.1\",\"PackageUrl\":\"https://github.com/wieslawsoltes/AvaloniaBehaviors\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"Easily add interactivity to your Avalonia apps using XAML Behaviors. Behaviors encapsulate reusable functionalities for elements that can be easily added to your XAML without the need for more imperative code.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia.Xaml.Behaviors\",\"Commit\":\"2fd5e3c6f58db51afd1f04e6c5704b08318c1599\"}},{\"PackageName\":\"Avalonia.Xaml.Interactivity\",\"PackageVersion\":\"11.0.0.1\",\"PackageUrl\":\"https://github.com/wieslawsoltes/AvaloniaBehaviors\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"Easily add interactivity to your Avalonia apps using XAML Behaviors. Behaviors encapsulate reusable functionalities for elements that can be easily added to your XAML without the need for more imperative code.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/AvaloniaUI/Avalonia.Xaml.Behaviors\",\"Commit\":\"2fd5e3c6f58db51afd1f04e6c5704b08318c1599\"}},{\"PackageName\":\"AvaloniaEdit.TextMate\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"\",\"Copyright\":\"\",\"Authors\":[\"AvaloniaEdit.TextMate\"],\"Description\":\"TextMate integration for AvaloniaEdit.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"ColorTextBlock.Avalonia\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://github.com/whistyun/Markdown.Avalonia/tree/master/ColorTextBlock.Avalonia/\",\"Copyright\":\"Copyright (c) 2020 whistyun\",\"Authors\":[\"whistyun\"],\"Description\":\"Package Description\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"CommunityToolkit.Mvvm\",\"PackageVersion\":\"8.2.1\",\"PackageUrl\":\"https://github.com/CommunityToolkit/dotnet\",\"Copyright\":\"(c) .NET Foundation and Contributors. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"This package includes a .NET MVVM library with helpers such as:\\r\\n      - ObservableObject: a base class for objects implementing the INotifyPropertyChanged interface.\\r\\n      - ObservableRecipient: a base class for observable objects with support for the IMessenger service.\\r\\n      - ObservableValidator: a base class for objects implementing the INotifyDataErrorInfo interface.\\r\\n      - RelayCommand: a simple delegate command implementing the ICommand interface.\\r\\n      - AsyncRelayCommand: a delegate command supporting asynchronous operations and cancellation.\\r\\n      - WeakReferenceMessenger: a messaging system to exchange messages through different loosely-coupled objects.\\r\\n      - StrongReferenceMessenger: a high-performance messaging system that trades weak references for speed.\\r\\n      - Ioc: a helper class to configure dependency injection service containers.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/CommunityToolkit/dotnet\",\"Commit\":\"2258fd310359fb7434d2040b34f04366efbacbf8\"}},{\"PackageName\":\"ExCSS\",\"PackageVersion\":\"4.1.4\",\"PackageUrl\":\"\",\"Copyright\":\"\",\"Authors\":[\"Tyler Brinks\"],\"Description\":\"ExCSS is a CSS 2.1 and CSS 3 parser for .NET. ExCSS makes it easy to read and parse stylesheets into a friendly object model with full LINQ support.\",\"LicenseUrl\":\"\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"\",\"Url\":\"https://github.com/TylerBrinks/ExCSS\",\"Commit\":\"\"}},{\"PackageName\":\"Fizzler\",\"PackageVersion\":\"1.2.1\",\"PackageUrl\":\"https://github.com/atifaziz/Fizzler\",\"Copyright\":\"Copyright © 2009 Atif Aziz, Colin Ramsay. All rights reserved. Portions Copyright © 2008 Novell, Inc.\",\"Authors\":[\"Atif Aziz\",\"Colin Ramsay\"],\"Description\":\"Fizzler is a W3C Selectors parser and generic selector framework for document hierarchies.\",\"LicenseUrl\":\"https://www.nuget.org/packages/Fizzler/1.2.1/License\",\"LicenseType\":\"COPYING.txt\",\"Repository\":{\"Type\":\"Git\",\"Url\":\"https://github.com/atifaziz/Fizzler\",\"Commit\":\"8b3773e58a471266e4024f980b40099adccbab41\"}},{\"PackageName\":\"FluentAvaloniaUI\",\"PackageVersion\":\"2.0.0\",\"PackageUrl\":\"\",\"Copyright\":\"\",\"Authors\":[\"FluentAvalonia\"],\"Description\":\"Control library focused on fluent design and bringing more WinUI controls into Avalonia\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/amwx/FluentAvalonia\",\"Commit\":\"84a2a5cfd5e4a5d4a29856ff43253db57a908b33\"}},{\"PackageName\":\"FluentIcons.Avalonia\",\"PackageVersion\":\"1.1.207\",\"PackageUrl\":\"\",\"Copyright\":\"\",\"Authors\":[\"davidxuang\"],\"Description\":\"FluentUI System Icons wrapper for Avalonia.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"https://github.com/davidxuang/FluentIcons\",\"Commit\":\"\"}},{\"PackageName\":\"FluentIcons.Common\",\"PackageVersion\":\"1.1.207\",\"PackageUrl\":\"\",\"Copyright\":\"\",\"Authors\":[\"davidxuang\"],\"Description\":\"A shared library for FluentUI System Icons wrapper.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"https://github.com/davidxuang/FluentIcons\",\"Commit\":\"\"}},{\"PackageName\":\"FluentIcons.FluentAvalonia\",\"PackageVersion\":\"1.1.207\",\"PackageUrl\":\"\",\"Copyright\":\"\",\"Authors\":[\"davidxuang\"],\"Description\":\"FluentUI System Icons wrapper for FluentAvalonia.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"https://github.com/davidxuang/FluentIcons\",\"Commit\":\"\"}},{\"PackageName\":\"FuzzySharp\",\"PackageVersion\":\"2.0.2\",\"PackageUrl\":\"https://github.com/JakeBayer/FuzzySharp\",\"Copyright\":\"\",\"Authors\":[\"Jacob Bayer\"],\"Description\":\"Fuzzy string matcher based on FuzzyWuzzy algorithm from SeatGeek\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/JakeBayer/FuzzySharp\",\"Commit\":\"53b71acd66e53a4ff9f4229348de48295f99c0a5\"}},{\"PackageName\":\"HarfBuzzSharp\",\"PackageVersion\":\"2.8.2.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"HarfBuzzSharp is a cross-platform OpenType text shaping engine for .NET platforms.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"HarfBuzzSharp.NativeAssets.Android\",\"PackageVersion\":\"2.8.2.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"HarfBuzzSharp is a cross-platform OpenType text shaping engine for .NET platforms.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"HarfBuzzSharp.NativeAssets.iOS\",\"PackageVersion\":\"2.8.2.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"HarfBuzzSharp is a cross-platform OpenType text shaping engine for .NET platforms.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"HarfBuzzSharp.NativeAssets.Linux\",\"PackageVersion\":\"2.8.2.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"HarfBuzzSharp is a cross-platform OpenType text shaping engine for .NET platforms.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"HarfBuzzSharp.NativeAssets.MacCatalyst\",\"PackageVersion\":\"2.8.2.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"HarfBuzzSharp is a cross-platform OpenType text shaping engine for .NET platforms.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"HarfBuzzSharp.NativeAssets.macOS\",\"PackageVersion\":\"2.8.2.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"HarfBuzzSharp is a cross-platform OpenType text shaping engine for .NET platforms.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"HarfBuzzSharp.NativeAssets.Tizen\",\"PackageVersion\":\"2.8.2.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"HarfBuzzSharp is a cross-platform OpenType text shaping engine for .NET platforms.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"HarfBuzzSharp.NativeAssets.tvOS\",\"PackageVersion\":\"2.8.2.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"HarfBuzzSharp is a cross-platform OpenType text shaping engine for .NET platforms.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"HarfBuzzSharp.NativeAssets.UWP\",\"PackageVersion\":\"2.8.2.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"HarfBuzzSharp is a cross-platform OpenType text shaping engine for .NET platforms.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"HarfBuzzSharp.NativeAssets.watchOS\",\"PackageVersion\":\"2.8.2.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"HarfBuzzSharp is a cross-platform OpenType text shaping engine for .NET platforms.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"HarfBuzzSharp.NativeAssets.WebAssembly\",\"PackageVersion\":\"2.8.2.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"HarfBuzzSharp is a cross-platform OpenType text shaping engine for .NET platforms.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"HarfBuzzSharp.NativeAssets.Win32\",\"PackageVersion\":\"2.8.2.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"HarfBuzzSharp is a cross-platform OpenType text shaping engine for .NET platforms.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"HtmlAgilityPack\",\"PackageVersion\":\"1.11.42\",\"PackageUrl\":\"http://html-agility-pack.net/\",\"Copyright\":\"Copyright © ZZZ Projects Inc.\",\"Authors\":[\"ZZZ Projects\",\"Simon Mourrier\",\"Jeff Klawiter\",\"Stephan Grell\"],\"Description\":\"This is an agile HTML parser that builds a read/write DOM and supports plain XPATH or XSLT (you actually don't HAVE to understand XPATH nor XSLT to use it, don't worry...). It is a .NET code library that allows you to parse \\\"out of the web\\\" HTML files. The parser is very tolerant with \\\"real world\\\" malformed HTML. The object model is very similar to what proposes System.Xml, but for HTML documents (or streams).\",\"LicenseUrl\":\"https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/zzzprojects/html-agility-pack/\",\"Commit\":\"\"}},{\"PackageName\":\"K4os.Compression.LZ4\",\"PackageVersion\":\"1.3.5\",\"PackageUrl\":\"https://github.com/MiloszKrajewski/K4os.Compression.LZ4\",\"Copyright\":\"\",\"Authors\":[\"Milosz Krajewski\"],\"Description\":\"Port of LZ4 compression algorithm for .NET\",\"LicenseUrl\":\"https://raw.githubusercontent.com/MiloszKrajewski/K4os.Compression.LZ4/master/LICENSE\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"\",\"Url\":\"https://github.com/MiloszKrajewski/K4os.Compression.LZ4\",\"Commit\":\"\"}},{\"PackageName\":\"Markdown.Avalonia\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://github.com/whistyun/Markdown.Avalonia\",\"Copyright\":\"Copyright (c) 2010 Bevan Arps, 2020 whistyun\",\"Authors\":[\"Bevan Arps(original)\",\"whistyun\"],\"Description\":\"Markdown Controls for Avalonia\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"Markdown.Avalonia.Html\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://github.com/whistyun/Markdown.Avalonia\",\"Copyright\":\"© Simon Baynes 2013; whistyun 2023\",\"Authors\":[\"whistyun\"],\"Description\":\"html tag processor for Markdown.Avalonia\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"Markdown.Avalonia.Svg\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://github.com/whistyun/Markdown.Avalonia\",\"Copyright\":\"Copyright (c) 2023 grifsun, whistyun\",\"Authors\":[\"grifsun\",\"whistyun\"],\"Description\":\"Package Description\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"Markdown.Avalonia.SyntaxHigh\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://github.com/whistyun/Markdown.Avalonia\",\"Copyright\":\"Copyright (c) 2021 whistyun\",\"Authors\":[\"whistyun\"],\"Description\":\"Package Description\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"Markdown.Avalonia.Tight\",\"PackageVersion\":\"11.0.0\",\"PackageUrl\":\"https://github.com/whistyun/Markdown.Avalonia\",\"Copyright\":\"Copyright (c) 2010 Bevan Arps, 2020 whistyun\",\"Authors\":[\"Bevan Arps(original)\",\"whistyun\"],\"Description\":\"Markdown Controls for Avalonia\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"MicroCom.CodeGenerator.MSBuild\",\"PackageVersion\":\"0.11.0\",\"PackageUrl\":\"\",\"Copyright\":\"Copyright 2021 © Nikita Tsukanov\",\"Authors\":[\"MicroCom.CodeGenerator.MSBuild\"],\"Description\":\"IDL-based COM interop codegen. Consumes MIDL-like IDL file, generates efficient cross-platform C# interop and C++ header files.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/kekekeks/MicroCom\",\"Commit\":\"\"}},{\"PackageName\":\"MicroCom.Runtime\",\"PackageVersion\":\"0.11.0\",\"PackageUrl\":\"\",\"Copyright\":\"Copyright 2021 © Nikita Tsukanov\",\"Authors\":[\"MicroCom.Runtime\"],\"Description\":\"IDL-based COM interop codegen. Consumes MIDL-like IDL file, generates efficient cross-platform C# interop and C++ header files.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/kekekeks/MicroCom\",\"Commit\":\"\"}},{\"PackageName\":\"NETStandard.Library\",\"PackageVersion\":\"1.6.1\",\"PackageUrl\":\"https://dot.net/\",\"Copyright\":\"© Microsoft Corporation.  All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"A set of standard .NET APIs that are prescribed to be used and supported together. This includes all of the APIs in the NETStandard.Platform package plus additional libraries that are core to .NET but built on top of NETStandard.Platform. \\r\\nWhen using NuGet 3.x this package requires at least version 3.4.\",\"LicenseUrl\":\"http://go.microsoft.com/fwlink/?LinkId=329770\",\"LicenseType\":\"MS-EULA\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"NETStandard.Library\",\"PackageVersion\":\"1.6.0\",\"PackageUrl\":\"https://dot.net/\",\"Copyright\":\"© Microsoft Corporation.  All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"A set of standard .NET APIs that are prescribed to be used and supported together. This includes all of the APIs in the NETStandard.Platform package plus additional libraries that are core to .NET but built on top of NETStandard.Platform. \\r\\nWhen using NuGet 3.x this package requires at least version 3.4.\",\"LicenseUrl\":\"http://go.microsoft.com/fwlink/?LinkId=329770\",\"LicenseType\":\"MS-EULA\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"Nito.AsyncEx\",\"PackageVersion\":\"5.1.2\",\"PackageUrl\":\"https://github.com/StephenCleary/AsyncEx\",\"Copyright\":\"\",\"Authors\":[\"Stephen Cleary\"],\"Description\":\"A helper library for the Task-Based Asynchronous Pattern (TAP).\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/StephenCleary/AsyncEx\",\"Commit\":\"0361015459938f2eb8f3c1ad1021d19ee01c93a4\"}},{\"PackageName\":\"Nito.AsyncEx.Context\",\"PackageVersion\":\"5.1.2\",\"PackageUrl\":\"https://github.com/StephenCleary/AsyncEx\",\"Copyright\":\"\",\"Authors\":[\"Stephen Cleary\"],\"Description\":\"A single-threaded async-compatible context.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/StephenCleary/AsyncEx\",\"Commit\":\"0361015459938f2eb8f3c1ad1021d19ee01c93a4\"}},{\"PackageName\":\"Nito.AsyncEx.Coordination\",\"PackageVersion\":\"5.1.2\",\"PackageUrl\":\"https://github.com/StephenCleary/AsyncEx\",\"Copyright\":\"\",\"Authors\":[\"Stephen Cleary\"],\"Description\":\"Asynchronous coordination primitives.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/StephenCleary/AsyncEx\",\"Commit\":\"0361015459938f2eb8f3c1ad1021d19ee01c93a4\"}},{\"PackageName\":\"Nito.AsyncEx.Interop.WaitHandles\",\"PackageVersion\":\"5.1.2\",\"PackageUrl\":\"https://github.com/StephenCleary/AsyncEx\",\"Copyright\":\"\",\"Authors\":[\"Stephen Cleary\"],\"Description\":\"Task wrappers for WaitHandles.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/StephenCleary/AsyncEx\",\"Commit\":\"0361015459938f2eb8f3c1ad1021d19ee01c93a4\"}},{\"PackageName\":\"Nito.AsyncEx.Oop\",\"PackageVersion\":\"5.1.2\",\"PackageUrl\":\"https://github.com/StephenCleary/AsyncEx\",\"Copyright\":\"\",\"Authors\":[\"Stephen Cleary\"],\"Description\":\"Interfaces and utility methods for combining async with OOP.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/StephenCleary/AsyncEx\",\"Commit\":\"0361015459938f2eb8f3c1ad1021d19ee01c93a4\"}},{\"PackageName\":\"Nito.AsyncEx.Tasks\",\"PackageVersion\":\"5.1.2\",\"PackageUrl\":\"https://github.com/StephenCleary/AsyncEx\",\"Copyright\":\"\",\"Authors\":[\"Stephen Cleary\"],\"Description\":\"Common helper methods for tasks as used in asynchronous programming.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/StephenCleary/AsyncEx\",\"Commit\":\"0361015459938f2eb8f3c1ad1021d19ee01c93a4\"}},{\"PackageName\":\"Nito.Cancellation\",\"PackageVersion\":\"1.1.2\",\"PackageUrl\":\"https://github.com/StephenCleary/Cancellation\",\"Copyright\":\"\",\"Authors\":[\"Stephen Cleary\"],\"Description\":\"Helper types for working with cancellation tokens and sources.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/StephenCleary/Cancellation\",\"Commit\":\"3f41c18da9f9bd71a17de6329aaea2bf7388d0d7\"}},{\"PackageName\":\"Nito.Collections.Deque\",\"PackageVersion\":\"1.1.1\",\"PackageUrl\":\"https://github.com/StephenCleary/Deque\",\"Copyright\":\"\",\"Authors\":[\"Stephen Cleary\"],\"Description\":\"A double-ended queue.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/StephenCleary/Deque\",\"Commit\":\"fb12d9918339a9affd11f6d5f82a29bb7e26c4a0\"}},{\"PackageName\":\"Nito.Disposables\",\"PackageVersion\":\"2.2.1\",\"PackageUrl\":\"https://github.com/StephenCleary/Disposables\",\"Copyright\":\"\",\"Authors\":[\"Stephen Cleary\"],\"Description\":\"IDisposable and IAsyncDisposable helper types.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/StephenCleary/Disposables\",\"Commit\":\"421e8c3af9e9baf1c885671887671971aa2d7a29\"}},{\"PackageName\":\"NLog\",\"PackageVersion\":\"5.2.2\",\"PackageUrl\":\"https://nlog-project.org/\",\"Copyright\":\"Copyright (c) 2004-2023 NLog Project - https://nlog-project.org/\",\"Authors\":[\"Jarek Kowalski\",\"Kim Christensen\",\"Julian Verdurmen\"],\"Description\":\"NLog is a logging platform for .NET with rich log routing and management capabilities.\\r\\nNLog supports traditional logging, structured logging and the combination of both.\\r\\n\\r\\nSupported platforms:\\r\\n\\r\\n- .NET 5, 6 and 7\\r\\n- .NET Core 1, 2 and 3\\r\\n- .NET Standard 1.3+ and 2.0+\\r\\n- .NET Framework 3.5 - 4.8\\r\\n- Xamarin Android + iOS (.NET Standard)\\r\\n- Mono 4\\r\\n\\r\\nFor ASP.NET Core, check: https://www.nuget.org/packages/NLog.Web.AspNetCore\",\"LicenseUrl\":\"https://licenses.nuget.org/BSD-3-Clause\",\"LicenseType\":\"BSD-3-Clause\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/NLog/NLog.git\",\"Commit\":\"\"}},{\"PackageName\":\"NLog\",\"PackageVersion\":\"4.5.11\",\"PackageUrl\":\"http://nlog-project.org/\",\"Copyright\":\"Copyright (c) 2004-2018 NLog Project - http://nlog-project.org/\",\"Authors\":[\"Jarek Kowalski\",\"Kim Christensen\",\"Julian Verdurmen\"],\"Description\":\"NLog is a logging platform for .NET with rich log routing and management capabilities.\\r\\nNLog supports traditional logging, structured logging and the combination of both.\\r\\n\\r\\nSupported platforms:\\r\\n\\r\\n- .NET Framework 3.5, 4, 4.5, 4.6 & 4.7\\r\\n- .NET Standard 1.3+ and 2.0+;\\r\\n- .NET Framework 4 client profile\\r\\n- Xamarin Android, Xamarin iOs\\r\\n- UWP\\r\\n- Windows Phone 8\\r\\n- Silverlight 4 and 5\\r\\n- Mono 4\\r\\n\\r\\nFor ASP.NET Core, check: https://www.nuget.org/packages/NLog.Web.AspNetCore\",\"LicenseUrl\":\"https://github.com/NLog/NLog/blob/master/LICENSE.txt\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"git://github.com/NLog/NLog\",\"Commit\":\"\"}},{\"PackageName\":\"NLog.Extensions.Logging\",\"PackageVersion\":\"5.3.2\",\"PackageUrl\":\"https://github.com/NLog/NLog.Extensions.Logging\",\"Copyright\":\"\",\"Authors\":[\"Microsoft\",\"Julian Verdurmen\"],\"Description\":\"NLog LoggerProvider for Microsoft.Extensions.Logging for logging in .NET Standard libraries and .NET Core applications.\\r\\n\\r\\nFor ASP.NET Core, check: https://www.nuget.org/packages/NLog.Web.AspNetCore\",\"LicenseUrl\":\"https://licenses.nuget.org/BSD-2-Clause\",\"LicenseType\":\"BSD-2-Clause\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/NLog/NLog.Extensions.Logging.git\",\"Commit\":\"d47e232ba867eedbff8f7972047a17715b8c1c2f\"}},{\"PackageName\":\"Polly\",\"PackageVersion\":\"7.2.4\",\"PackageUrl\":\"https://github.com/App-vNext/Polly\",\"Copyright\":\"Copyright (c) 2023, App vNext\",\"Authors\":[\"Michael Wolfenden\",\" App vNext\"],\"Description\":\"Polly is a library that allows developers to express resilience and transient fault handling policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.\",\"LicenseUrl\":\"https://licenses.nuget.org/BSD-3-Clause\",\"LicenseType\":\"BSD-3-Clause\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/App-vNext/Polly\",\"Commit\":\"b1a5a17de8ef5d0bc86e5a4502ad30891e674379\"}},{\"PackageName\":\"Polly\",\"PackageVersion\":\"7.2.3\",\"PackageUrl\":\"https://github.com/App-vNext/Polly\",\"Copyright\":\"Copyright (c) 2022, App vNext\",\"Authors\":[\"Michael Wolfenden\",\" App vNext\"],\"Description\":\"Polly is a library that allows developers to express resilience and transient fault handling policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.\",\"LicenseUrl\":\"https://licenses.nuget.org/BSD-3-Clause\",\"LicenseType\":\"BSD-3-Clause\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/App-vNext/Polly.git\",\"Commit\":\"c27faf89ef459fd9a8e0131ae1d4cd835ca4f31d\"}},{\"PackageName\":\"Polly.Contrib.WaitAndRetry\",\"PackageVersion\":\"1.1.1\",\"PackageUrl\":\"https://github.com/Polly-Contrib/Polly.Contrib.WaitAndRetry\",\"Copyright\":\"Copyright (c) 2020, App vNext and contributors\",\"Authors\":[\"Grant Dickinson\",\" App vNext\"],\"Description\":\"Polly.Contrib.WaitAndRetry is an extension library for Polly containing helper methods for a variety of wait-and-retry strategies.\",\"LicenseUrl\":\"https://licenses.nuget.org/BSD-3-Clause\",\"LicenseType\":\"BSD-3-Clause\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/Polly-Contrib/Polly.Contrib.WaitAndRetry.git\",\"Commit\":\"7596d2dacf22d88bbd814bc49c28424fb6e921e9\"}},{\"PackageName\":\"Polly.Extensions.Http\",\"PackageVersion\":\"3.0.0\",\"PackageUrl\":\"https://github.com/App-vNext/Polly.Extensions.Http\",\"Copyright\":\"Copyright (c) 2019, App vNext\",\"Authors\":[\"App vNext\"],\"Description\":\"Polly.Extensions.Http is an extensions package containing opinionated convenience methods for configuring Polly policies to handle transient faults typical of calls through HttpClient.\",\"LicenseUrl\":\"https://licenses.nuget.org/BSD-3-Clause\",\"LicenseType\":\"BSD-3-Clause\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/App-vNext/Polly.Extensions.Http.git\",\"Commit\":\"69fd292bc603cb3032e57b028522737255f03a49\"}},{\"PackageName\":\"Projektanker.Icons.Avalonia\",\"PackageVersion\":\"6.6.0\",\"PackageUrl\":\"\",\"Copyright\":\"\",\"Authors\":[\"Sebastian Rumohr\"],\"Description\":\"A library to easily display icons in an Avalonia App.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"https://github.com/Projektanker/Icons.Avalonia\",\"Commit\":\"\"}},{\"PackageName\":\"Projektanker.Icons.Avalonia.FontAwesome\",\"PackageVersion\":\"6.6.0\",\"PackageUrl\":\"\",\"Copyright\":\"\",\"Authors\":[\"Sebastian Rumohr\"],\"Description\":\"A library to easily display FontAwesome icons in an Avalonia App.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"https://github.com/Projektanker/Icons.Avalonia\",\"Commit\":\"\"}},{\"PackageName\":\"Sentry\",\"PackageVersion\":\"3.33.1\",\"PackageUrl\":\"https://sentry.io/\",\"Copyright\":\"\",\"Authors\":[\"Sentry Team and Contributors\"],\"Description\":\"Official SDK for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/getsentry/sentry-dotnet\",\"Commit\":\"2d6b2eb429da501e3da5cc7eb362345532058f09\"}},{\"PackageName\":\"Sentry.Android.AssemblyReader\",\"PackageVersion\":\"3.33.1\",\"PackageUrl\":\"https://sentry.io/\",\"Copyright\":\"\",\"Authors\":[\"Sentry Team and Contributors\"],\"Description\":\".NET assembly reader for Android\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/getsentry/sentry-dotnet\",\"Commit\":\"2d6b2eb429da501e3da5cc7eb362345532058f09\"}},{\"PackageName\":\"Sentry.Bindings.Android\",\"PackageVersion\":\"3.33.1\",\"PackageUrl\":\"https://sentry.io/\",\"Copyright\":\"\",\"Authors\":[\"Sentry Team and Contributors\"],\"Description\":\".NET Bindings for the Sentry Android SDK\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/getsentry/sentry-dotnet\",\"Commit\":\"2d6b2eb429da501e3da5cc7eb362345532058f09\"}},{\"PackageName\":\"Sentry.Bindings.Cocoa\",\"PackageVersion\":\"3.33.1\",\"PackageUrl\":\"https://sentry.io/\",\"Copyright\":\"\",\"Authors\":[\"Sentry Team and Contributors\"],\"Description\":\".NET Bindings for the Sentry Cocoa SDK\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/getsentry/sentry-dotnet\",\"Commit\":\"2d6b2eb429da501e3da5cc7eb362345532058f09\"}},{\"PackageName\":\"Sentry.NLog\",\"PackageVersion\":\"3.33.1\",\"PackageUrl\":\"https://sentry.io/\",\"Copyright\":\"\",\"Authors\":[\"Sentry Team and Contributors\"],\"Description\":\"Official NLog integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/getsentry/sentry-dotnet\",\"Commit\":\"2d6b2eb429da501e3da5cc7eb362345532058f09\"}},{\"PackageName\":\"ShimSkiaSharp\",\"PackageVersion\":\"1.0.0\",\"PackageUrl\":\"https://github.com/wieslawsoltes/Svg.Skia\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"SkiaSharp picture recorder object model shim.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/wieslawsoltes/Svg.Skia\",\"Commit\":\"2d9ef9767e31fc5b87655cad1022c2632b3fd33a\"}},{\"PackageName\":\"SkiaSharp\",\"PackageVersion\":\"2.88.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\\r\\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"SkiaSharp.NativeAssets.Android\",\"PackageVersion\":\"2.88.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\\r\\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"SkiaSharp.NativeAssets.iOS\",\"PackageVersion\":\"2.88.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\\r\\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"SkiaSharp.NativeAssets.Linux\",\"PackageVersion\":\"2.88.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\\r\\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"SkiaSharp.NativeAssets.MacCatalyst\",\"PackageVersion\":\"2.88.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\\r\\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"SkiaSharp.NativeAssets.macOS\",\"PackageVersion\":\"2.88.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\\r\\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"SkiaSharp.NativeAssets.Tizen\",\"PackageVersion\":\"2.88.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\\r\\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"SkiaSharp.NativeAssets.tvOS\",\"PackageVersion\":\"2.88.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\\r\\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"SkiaSharp.NativeAssets.UWP\",\"PackageVersion\":\"2.88.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\\r\\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"SkiaSharp.NativeAssets.watchOS\",\"PackageVersion\":\"2.88.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\\r\\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"SkiaSharp.NativeAssets.WebAssembly\",\"PackageVersion\":\"2.88.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\\r\\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"SkiaSharp.NativeAssets.Win32\",\"PackageVersion\":\"2.88.3\",\"PackageUrl\":\"https://go.microsoft.com/fwlink/?linkid=868515\",\"Copyright\":\"© Microsoft Corporation. All rights reserved.\",\"Authors\":[\"Microsoft\"],\"Description\":\"SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library.\\r\\nIt provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.\",\"LicenseUrl\":\"https://go.microsoft.com/fwlink/?linkid=868514\",\"LicenseType\":\"\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/mono/SkiaSharp\",\"Commit\":\"655cff084fa9365efde6e989004eb818294c9b0f\"}},{\"PackageName\":\"Svg.Custom\",\"PackageVersion\":\"1.0.0\",\"PackageUrl\":\"https://github.com/wieslawsoltes/Svg.Skia\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"Custom build of the SVG rendering library.\",\"LicenseUrl\":\"https://licenses.nuget.org/MS-PL\",\"LicenseType\":\"MS-PL\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/wieslawsoltes/Svg.Skia\",\"Commit\":\"2d9ef9767e31fc5b87655cad1022c2632b3fd33a\"}},{\"PackageName\":\"Svg.Model\",\"PackageVersion\":\"1.0.0\",\"PackageUrl\":\"https://github.com/wieslawsoltes/Svg.Skia\",\"Copyright\":\"Copyright © Wiesław Šoltés 2023\",\"Authors\":[\"Wiesław Šoltés\"],\"Description\":\"An SVG rendering object model library.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/wieslawsoltes/Svg.Skia\",\"Commit\":\"2d9ef9767e31fc5b87655cad1022c2632b3fd33a\"}},{\"PackageName\":\"TextMateSharp\",\"PackageVersion\":\"1.0.55\",\"PackageUrl\":\"https://github.com/danipen/TextMateSharp\",\"Copyright\":\"\",\"Authors\":[\"Daniel Peñalba\"],\"Description\":\"An interpreter for grammar files as defined by TextMate. TextMate grammars use the oniguruma dialect (https://github.com/kkos/oniguruma). Supports loading grammar files only from JSON format. Cross - grammar injections are currently not supported.\\n\\nTextMateSharp is a port of microsoft/vscode-textmate that brings TextMate grammars to dotnet ecosystem. The implementation is based the Java port eclipse/tm4e.\\n\\nTextMateSharp uses a wrapper around Oniguruma regex engine. Read below to learn how to build Oniguruma bindings.\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/danipen/TextMateSharp\",\"Commit\":\"c2d1d7d228a59b30d429b3f288092a3e326fa4c5\"}},{\"PackageName\":\"TextMateSharp.Grammars\",\"PackageVersion\":\"1.0.55\",\"PackageUrl\":\"https://github.com/danipen/TextMateSharp\",\"Copyright\":\"\",\"Authors\":[\"Daniel Peñalba\"],\"Description\":\"A set of grammars and Themes that can be used by TextMateSharp\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"\",\"Url\":\"\",\"Commit\":\"\"}},{\"PackageName\":\"Tmds.DBus.Protocol\",\"PackageVersion\":\"0.15.0\",\"PackageUrl\":\"\",\"Copyright\":\"Tom Deseyn\",\"Authors\":[\"Tom Deseyn\"],\"Description\":\"Tmds.DBus.Protocol Library\",\"LicenseUrl\":\"https://licenses.nuget.org/MIT\",\"LicenseType\":\"MIT\",\"Repository\":{\"Type\":\"git\",\"Url\":\"https://github.com/tmds/Tmds.DBus.git\",\"Commit\":\"b2834c5e1b2800a2eb92d8c5932fd77639441e9a\"}}]"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/linux-x64/7zzs - LICENSE.txt",
    "content": "  7-Zip\n  ~~~~~\n  License for use and distribution\n  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n  7-Zip Copyright (C) 1999-2023 Igor Pavlov.\n\n  The licenses for 7zz and 7zzs files are:\n\n         - The \"GNU LGPL\" as main license for most of the code\n         - The \"GNU LGPL\" with \"unRAR license restriction\" for some code\n         - The \"BSD 3-clause License\" for some code\n\n  Redistributions in binary form must reproduce related license information from this file.\n\n  Note:\n    You can use 7-Zip on any computer, including a computer in a commercial\n    organization. You don't need to register or pay for 7-Zip.\n\n\n  GNU LGPL information\n  --------------------\n\n    This library is free software; you can redistribute it and/or\n    modify it under the terms of the GNU Lesser General Public\n    License as published by the Free Software Foundation; either\n    version 2.1 of the License, or (at your option) any later version.\n\n    This library is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n    Lesser General Public License for more details.\n\n    You can receive a copy of the GNU Lesser General Public License from\n    http://www.gnu.org/\n\n\n\n\n  BSD 3-clause License\n  --------------------\n\n    The \"BSD 3-clause License\" is used for the code in 7z.dll that implements LZFSE data decompression.\n    That code was derived from the code in the \"LZFSE compression library\" developed by Apple Inc,\n    that also uses the \"BSD 3-clause License\":\n\n    ----\n    Copyright (c) 2015-2016, Apple Inc. All rights reserved.\n\n    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n    1.  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n    2.  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer\n        in the documentation and/or other materials provided with the distribution.\n\n    3.  Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived\n        from this software without specific prior written permission.\n\n    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n    ----\n\n\n\n\n  unRAR license restriction\n  -------------------------\n\n    The decompression engine for RAR archives was developed using source\n    code of unRAR program.\n    All copyrights to original unRAR code are owned by Alexander Roshal.\n\n    The license for original unRAR code has the following restriction:\n\n      The unRAR sources cannot be used to re-create the RAR compression algorithm,\n      which is proprietary. Distribution of modified unRAR sources in separate form\n      or as a part of other software is permitted, provided that it is clearly\n      stated in the documentation and source comments that the code may\n      not be used to develop a RAR (WinRAR) compatible archiver.\n\n\n  --\n  Igor Pavlov\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/macos-arm64/7zz - LICENSE.txt",
    "content": "  7-Zip\n  ~~~~~\n  License for use and distribution\n  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n  7-Zip Copyright (C) 1999-2023 Igor Pavlov.\n\n  The licenses for 7zz and 7zzs files are:\n\n         - The \"GNU LGPL\" as main license for most of the code\n         - The \"GNU LGPL\" with \"unRAR license restriction\" for some code\n         - The \"BSD 3-clause License\" for some code\n\n  Redistributions in binary form must reproduce related license information from this file.\n\n  Note:\n    You can use 7-Zip on any computer, including a computer in a commercial\n    organization. You don't need to register or pay for 7-Zip.\n\n\n  GNU LGPL information\n  --------------------\n\n    This library is free software; you can redistribute it and/or\n    modify it under the terms of the GNU Lesser General Public\n    License as published by the Free Software Foundation; either\n    version 2.1 of the License, or (at your option) any later version.\n\n    This library is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n    Lesser General Public License for more details.\n\n    You can receive a copy of the GNU Lesser General Public License from\n    http://www.gnu.org/\n\n\n\n\n  BSD 3-clause License\n  --------------------\n\n    The \"BSD 3-clause License\" is used for the code in 7z.dll that implements LZFSE data decompression.\n    That code was derived from the code in the \"LZFSE compression library\" developed by Apple Inc,\n    that also uses the \"BSD 3-clause License\":\n\n    ----\n    Copyright (c) 2015-2016, Apple Inc. All rights reserved.\n\n    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n    1.  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n    2.  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer\n        in the documentation and/or other materials provided with the distribution.\n\n    3.  Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived\n        from this software without specific prior written permission.\n\n    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n    ----\n\n\n\n\n  unRAR license restriction\n  -------------------------\n\n    The decompression engine for RAR archives was developed using source\n    code of unRAR program.\n    All copyrights to original unRAR code are owned by Alexander Roshal.\n\n    The license for original unRAR code has the following restriction:\n\n      The unRAR sources cannot be used to re-create the RAR compression algorithm,\n      which is proprietary. Distribution of modified unRAR sources in separate form\n      or as a part of other software is permitted, provided that it is clearly\n      stated in the documentation and source comments that the code may\n      not be used to develop a RAR (WinRAR) compatible archiver.\n\n\n  --\n  Igor Pavlov\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/markdown.css",
    "content": "﻿/* dark */\n.markdown-body {\n    color-scheme: dark;\n    -ms-text-size-adjust: 100%;\n    -webkit-text-size-adjust: 100%;\n    margin: 0;\n    color: #f0f6fc;\n    font-family: -apple-system,BlinkMacSystemFont,\"Segoe UI\",\"Noto Sans\",Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\";\n    font-size: 14px;\n    line-height: 1.5;\n    word-wrap: break-word;\n    scroll-behavior: auto !important;\n    padding: 8px;\n}\n\n.markdown-body .octicon {\n    display: inline-block;\n    fill: currentColor;\n    vertical-align: text-bottom;\n}\n\n.markdown-body h1:hover .anchor .octicon-link:before,\n.markdown-body h2:hover .anchor .octicon-link:before,\n.markdown-body h3:hover .anchor .octicon-link:before,\n.markdown-body h4:hover .anchor .octicon-link:before,\n.markdown-body h5:hover .anchor .octicon-link:before,\n.markdown-body h6:hover .anchor .octicon-link:before {\n    width: 16px;\n    height: 16px;\n    content: ' ';\n    display: inline-block;\n    background-color: currentColor;\n    -webkit-mask-image: url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>\");\n    mask-image: url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>\");\n}\n\n.markdown-body details,\n.markdown-body figcaption,\n.markdown-body figure {\n    display: block;\n}\n\n.markdown-body summary {\n    display: list-item;\n}\n\n.markdown-body [hidden] {\n    display: none !important;\n}\n\n.markdown-body a {\n    background-color: transparent;\n    color: #4493f8;\n    text-decoration: none;\n}\n\n.markdown-body abbr[title] {\n    border-bottom: none;\n    -webkit-text-decoration: underline dotted;\n    text-decoration: underline dotted;\n}\n\n.markdown-body b,\n.markdown-body strong {\n    font-weight: 600;\n}\n\n.markdown-body dfn {\n    font-style: italic;\n}\n\n.markdown-body h1 {\n    margin: .67em 0;\n    font-weight: 600;\n    padding-bottom: .3em;\n    font-size: 2em;\n    /*border-bottom: 1px solid #3d444db3;*/\n}\n\n.markdown-body mark {\n    background-color: #bb800926;\n    color: #f0f6fc;\n}\n\n.markdown-body small {\n    font-size: 90%;\n}\n\n.markdown-body sub,\n.markdown-body sup {\n    font-size: 75%;\n    line-height: 0;\n    position: relative;\n    vertical-align: baseline;\n}\n\n.markdown-body sub {\n    bottom: -0.25em;\n}\n\n.markdown-body sup {\n    top: -0.5em;\n}\n\n.markdown-body img {\n    border-style: none;\n    max-width: 100%;\n    box-sizing: content-box;\n}\n\n.markdown-body code,\n.markdown-body kbd,\n.markdown-body pre,\n.markdown-body samp {\n    font-family: monospace;\n    font-size: 1em;\n}\n\n.markdown-body figure {\n    margin: 1em 2.5rem;\n}\n\n.markdown-body hr {\n    box-sizing: content-box;\n    overflow: hidden;\n    background: transparent;\n    /*border-bottom: 1px solid #3d444db3;*/\n    height: .25em;\n    padding: 0;\n    margin: 1.5rem 0;\n    background-color: #3d444d;\n    border: 0;\n}\n\n.markdown-body input {\n    font: inherit;\n    margin: 0;\n    overflow: visible;\n    font-family: inherit;\n    font-size: inherit;\n    line-height: inherit;\n}\n\n.markdown-body [type=button],\n.markdown-body [type=reset],\n.markdown-body [type=submit] {\n    -webkit-appearance: button;\n    appearance: button;\n}\n\n.markdown-body [type=checkbox],\n.markdown-body [type=radio] {\n    box-sizing: border-box;\n    padding: 0;\n}\n\n.markdown-body [type=number]::-webkit-inner-spin-button,\n.markdown-body [type=number]::-webkit-outer-spin-button {\n    height: auto;\n}\n\n.markdown-body [type=search]::-webkit-search-cancel-button,\n.markdown-body [type=search]::-webkit-search-decoration {\n    -webkit-appearance: none;\n    appearance: none;\n}\n\n.markdown-body ::-webkit-input-placeholder {\n    color: inherit;\n    opacity: .54;\n}\n\n.markdown-body ::-webkit-file-upload-button {\n    -webkit-appearance: button;\n    appearance: button;\n    font: inherit;\n}\n\n.markdown-body a:hover {\n    text-decoration: underline;\n}\n\n.markdown-body ::placeholder {\n    color: #9198a1;\n    opacity: 1;\n}\n\n.markdown-body hr::before {\n    display: table;\n    content: \"\";\n}\n\n.markdown-body hr::after {\n    display: table;\n    clear: both;\n    content: \"\";\n}\n\n.markdown-body table {\n    border-spacing: 0;\n    border-collapse: collapse;\n    display: block;\n    width: max-content;\n    max-width: 100%;\n    overflow: auto;\n}\n\n.markdown-body td,\n.markdown-body th {\n    padding: 0;\n}\n\n.markdown-body details summary {\n    cursor: pointer;\n}\n\n.markdown-body a:focus,\n.markdown-body [role=button]:focus,\n.markdown-body input[type=radio]:focus,\n.markdown-body input[type=checkbox]:focus {\n    outline: 2px solid #1f6feb;\n    outline-offset: -2px;\n    box-shadow: none;\n}\n\n.markdown-body a:focus:not(:focus-visible),\n.markdown-body [role=button]:focus:not(:focus-visible),\n.markdown-body input[type=radio]:focus:not(:focus-visible),\n.markdown-body input[type=checkbox]:focus:not(:focus-visible) {\n    outline: solid 1px transparent;\n}\n\n.markdown-body a:focus-visible,\n.markdown-body [role=button]:focus-visible,\n.markdown-body input[type=radio]:focus-visible,\n.markdown-body input[type=checkbox]:focus-visible {\n    outline: 2px solid #1f6feb;\n    outline-offset: -2px;\n    box-shadow: none;\n}\n\n.markdown-body a:not([class]):focus,\n.markdown-body a:not([class]):focus-visible,\n.markdown-body input[type=radio]:focus,\n.markdown-body input[type=radio]:focus-visible,\n.markdown-body input[type=checkbox]:focus,\n.markdown-body input[type=checkbox]:focus-visible {\n    outline-offset: 0;\n}\n\n.markdown-body kbd {\n    display: inline-block;\n    padding: 0.25rem;\n    font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;\n    line-height: 10px;\n    color: #f0f6fc;\n    vertical-align: middle;\n    background-color: #151b23;\n    border: solid 1px #3d444db3;\n    border-bottom-color: #3d444db3;\n    border-radius: 6px;\n    box-shadow: inset 0 -1px 0 #3d444db3;\n}\n\n.markdown-body h1,\n.markdown-body h2,\n.markdown-body h3,\n.markdown-body h4,\n.markdown-body h5,\n.markdown-body h6 {\n    margin-top: 1.5rem;\n    margin-bottom: 1rem;\n    font-weight: 600;\n    line-height: 1.25;\n}\n\n.markdown-body h2 {\n    font-weight: 600;\n    padding-bottom: .3em;\n    font-size: 1.5em;\n}\n\n.markdown-body h3 {\n    font-weight: 600;\n    font-size: 1.25em;\n    padding-bottom: .3em;\n}\n\n.markdown-body h4 {\n    font-weight: 600;\n    font-size: 1em;\n    padding-bottom: .3em;\n}\n\n.markdown-body h5 {\n    font-weight: 600;\n    font-size: .875em;\n    padding-bottom: .3em;\n}\n\n.markdown-body h6 {\n    font-weight: 600;\n    font-size: .85em;\n    color: #9198a1;\n    padding-bottom: .3em;\n}\n\n.markdown-body p {\n    margin-top: 0;\n    margin-bottom: 10px;\n}\n\n.markdown-body blockquote {\n    margin: 0;\n    padding: 0 1em;\n    color: #9198a1;\n    border-left: .25em solid #3d444d;\n}\n\n.markdown-body ul,\n.markdown-body ol {\n    margin-top: 0;\n    margin-bottom: 0;\n    padding-left: 0.5em;\n}\n\n.markdown-body ol ol,\n.markdown-body ul ol {\n    list-style-type: lower-roman;\n}\n\n.markdown-body ul ul ol,\n.markdown-body ul ol ol,\n.markdown-body ol ul ol,\n.markdown-body ol ol ol {\n    list-style-type: lower-alpha;\n}\n\n.markdown-body dd {\n    margin-left: 0;\n}\n\n.markdown-body tt,\n.markdown-body code,\n.markdown-body samp {\n    font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;\n    font-size: 12px;\n}\n\n.markdown-body pre {\n    margin-top: 0;\n    margin-bottom: 0;\n    font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;\n    font-size: 12px;\n    word-wrap: normal;\n}\n\n.markdown-body .octicon {\n    display: inline-block;\n    overflow: visible !important;\n    vertical-align: text-bottom;\n    fill: currentColor;\n}\n\n.markdown-body input::-webkit-outer-spin-button,\n.markdown-body input::-webkit-inner-spin-button {\n    margin: 0;\n    -webkit-appearance: none;\n    appearance: none;\n}\n\n.markdown-body .mr-2 {\n    margin-right: 0.5rem !important;\n}\n\n.markdown-body::before {\n    display: table;\n    content: \"\";\n}\n\n.markdown-body::after {\n    display: table;\n    clear: both;\n    content: \"\";\n}\n\n.markdown-body>*:first-child {\n    margin-top: 0 !important;\n}\n\n.markdown-body>*:last-child {\n    margin-bottom: 0 !important;\n}\n\n.markdown-body a:not([href]) {\n    color: inherit;\n    text-decoration: none;\n}\n\n.markdown-body .absent {\n    color: #f85149;\n}\n\n.markdown-body .anchor {\n    float: left;\n    padding-right: 0.25rem;\n    margin-left: -20px;\n    line-height: 1;\n}\n\n.markdown-body .anchor:focus {\n    outline: none;\n}\n\n.markdown-body p,\n.markdown-body blockquote,\n.markdown-body ul,\n.markdown-body ol,\n.markdown-body dl,\n.markdown-body table,\n.markdown-body pre,\n.markdown-body details {\n    margin-top: 0;\n    margin-bottom: 1rem;\n}\n\n.markdown-body blockquote>:first-child {\n    margin-top: 0;\n}\n\n.markdown-body blockquote>:last-child {\n    margin-bottom: 0;\n}\n\n.markdown-body h1 .octicon-link,\n.markdown-body h2 .octicon-link,\n.markdown-body h3 .octicon-link,\n.markdown-body h4 .octicon-link,\n.markdown-body h5 .octicon-link,\n.markdown-body h6 .octicon-link {\n    color: #f0f6fc;\n    vertical-align: middle;\n    visibility: hidden;\n}\n\n.markdown-body h1:hover .anchor,\n.markdown-body h2:hover .anchor,\n.markdown-body h3:hover .anchor,\n.markdown-body h4:hover .anchor,\n.markdown-body h5:hover .anchor,\n.markdown-body h6:hover .anchor {\n    text-decoration: none;\n}\n\n.markdown-body h1:hover .anchor .octicon-link,\n.markdown-body h2:hover .anchor .octicon-link,\n.markdown-body h3:hover .anchor .octicon-link,\n.markdown-body h4:hover .anchor .octicon-link,\n.markdown-body h5:hover .anchor .octicon-link,\n.markdown-body h6:hover .anchor .octicon-link {\n    visibility: visible;\n}\n\n.markdown-body h1 tt,\n.markdown-body h1 code,\n.markdown-body h2 tt,\n.markdown-body h2 code,\n.markdown-body h3 tt,\n.markdown-body h3 code,\n.markdown-body h4 tt,\n.markdown-body h4 code,\n.markdown-body h5 tt,\n.markdown-body h5 code,\n.markdown-body h6 tt,\n.markdown-body h6 code {\n    padding: 0 .2em;\n    font-size: inherit;\n}\n\n.markdown-body summary h1,\n.markdown-body summary h2,\n.markdown-body summary h3,\n.markdown-body summary h4,\n.markdown-body summary h5,\n.markdown-body summary h6 {\n    display: inline-block;\n}\n\n.markdown-body summary h1 .anchor,\n.markdown-body summary h2 .anchor,\n.markdown-body summary h3 .anchor,\n.markdown-body summary h4 .anchor,\n.markdown-body summary h5 .anchor,\n.markdown-body summary h6 .anchor {\n    margin-left: -40px;\n}\n\n.markdown-body summary h1,\n.markdown-body summary h2 {\n    padding-bottom: 0;\n    border-bottom: 0;\n}\n\n.markdown-body ul.no-list,\n.markdown-body ol.no-list {\n    padding: 0;\n    list-style-type: none;\n}\n\n.markdown-body ol[type=\"a s\"] {\n    list-style-type: lower-alpha;\n}\n\n.markdown-body ol[type=\"A s\"] {\n    list-style-type: upper-alpha;\n}\n\n.markdown-body ol[type=\"i s\"] {\n    list-style-type: lower-roman;\n}\n\n.markdown-body ol[type=\"I s\"] {\n    list-style-type: upper-roman;\n}\n\n.markdown-body ol[type=\"1\"] {\n    list-style-type: decimal;\n}\n\n.markdown-body div>ol:not([type]) {\n    list-style-type: decimal;\n}\n\n.markdown-body ul ul,\n.markdown-body ul ol,\n.markdown-body ol ol,\n.markdown-body ol ul {\n    margin-top: 0;\n    margin-bottom: 0;\n}\n\n.markdown-body li>p {\n    margin-top: 1rem;\n}\n\n.markdown-body li+li {\n    margin-top: .25em;\n}\n\n.markdown-body dl {\n    padding: 0;\n}\n\n.markdown-body dl dt {\n    padding: 0;\n    margin-top: 1rem;\n    font-size: 1em;\n    font-style: italic;\n    font-weight: 600;\n}\n\n.markdown-body dl dd {\n    padding: 0 1rem;\n    margin-bottom: 1rem;\n}\n\n.markdown-body table th {\n    font-weight: 600;\n}\n\n.markdown-body table th,\n.markdown-body table td {\n    padding: 6px 13px;\n    border: 1px solid #3d444d;\n}\n\n.markdown-body table td>:last-child {\n    margin-bottom: 0;\n}\n\n.markdown-body table tr {\n    background-color: #0d1117;\n    border-top: 1px solid #3d444db3;\n}\n\n.markdown-body table tr:nth-child(2n) {\n    background-color: #151b23;\n}\n\n.markdown-body table img {\n    background-color: transparent;\n}\n\n.markdown-body img[align=right] {\n    padding-left: 20px;\n}\n\n.markdown-body img[align=left] {\n    padding-right: 20px;\n}\n\n.markdown-body .emoji {\n    max-width: none;\n    vertical-align: text-top;\n    background-color: transparent;\n}\n\n.markdown-body span.frame {\n    display: block;\n    overflow: hidden;\n}\n\n.markdown-body span.frame>span {\n    display: block;\n    float: left;\n    width: auto;\n    padding: 7px;\n    margin: 13px 0 0;\n    overflow: hidden;\n    border: 1px solid #3d444d;\n}\n\n.markdown-body span.frame span img {\n    display: block;\n    float: left;\n}\n\n.markdown-body span.frame span span {\n    display: block;\n    padding: 5px 0 0;\n    clear: both;\n    color: #f0f6fc;\n}\n\n.markdown-body span.align-center {\n    display: block;\n    overflow: hidden;\n    clear: both;\n}\n\n.markdown-body span.align-center>span {\n    display: block;\n    margin: 13px auto 0;\n    overflow: hidden;\n    text-align: center;\n}\n\n.markdown-body span.align-center span img {\n    margin: 0 auto;\n    text-align: center;\n}\n\n.markdown-body span.align-right {\n    display: block;\n    overflow: hidden;\n    clear: both;\n}\n\n.markdown-body span.align-right>span {\n    display: block;\n    margin: 13px 0 0;\n    overflow: hidden;\n    text-align: right;\n}\n\n.markdown-body span.align-right span img {\n    margin: 0;\n    text-align: right;\n}\n\n.markdown-body span.float-left {\n    display: block;\n    float: left;\n    margin-right: 13px;\n    overflow: hidden;\n}\n\n.markdown-body span.float-left span {\n    margin: 13px 0 0;\n}\n\n.markdown-body span.float-right {\n    display: block;\n    float: right;\n    margin-left: 13px;\n    overflow: hidden;\n}\n\n.markdown-body span.float-right>span {\n    display: block;\n    margin: 13px auto 0;\n    overflow: hidden;\n    text-align: right;\n}\n\n.markdown-body code,\n.markdown-body tt {\n    padding: .2em .4em;\n    margin: 0;\n    font-size: 85%;\n    white-space: break-spaces;\n    background-color: #606c7633;\n    border-radius: 6px;\n}\n\n.markdown-body code br,\n.markdown-body tt br {\n    display: none;\n}\n\n.markdown-body del code {\n    text-decoration: inherit;\n}\n\n.markdown-body samp {\n    font-size: 85%;\n}\n\n.markdown-body pre code {\n    font-size: 100%;\n}\n\n.markdown-body pre>code {\n    padding: 0;\n    margin: 0;\n    word-break: normal;\n    white-space: pre;\n    background: transparent;\n    border: 0;\n}\n\n.markdown-body .highlight {\n    margin-bottom: 1rem;\n}\n\n.markdown-body .highlight pre {\n    margin-bottom: 0;\n    word-break: normal;\n}\n\n.markdown-body .highlight pre,\n.markdown-body pre {\n    padding: 1rem;\n    overflow: auto;\n    font-size: 85%;\n    line-height: 1.45;\n    color: #f0f6fc;\n    background-color: #151b23;\n    border-radius: 6px;\n}\n\n.markdown-body pre code,\n.markdown-body pre tt {\n    display: inline;\n    max-width: auto;\n    padding: 0;\n    margin: 0;\n    overflow: visible;\n    line-height: inherit;\n    word-wrap: normal;\n    background-color: transparent;\n    border: 0;\n}\n\n.markdown-body .csv-data td,\n.markdown-body .csv-data th {\n    padding: 5px;\n    overflow: hidden;\n    font-size: 12px;\n    line-height: 1;\n    text-align: left;\n    white-space: nowrap;\n}\n\n.markdown-body .csv-data .blob-num {\n    padding: 10px 0.5rem 9px;\n    text-align: right;\n    background: #0d1117;\n    border: 0;\n}\n\n.markdown-body .csv-data tr {\n    border-top: 0;\n}\n\n.markdown-body .csv-data th {\n    font-weight: 600;\n    background: #151b23;\n    border-top: 0;\n}\n\n.markdown-body [data-footnote-ref]::before {\n    content: \"[\";\n}\n\n.markdown-body [data-footnote-ref]::after {\n    content: \"]\";\n}\n\n.markdown-body .footnotes {\n    font-size: 12px;\n    color: #9198a1;\n    border-top: 1px solid #3d444d;\n}\n\n.markdown-body .footnotes ol {\n    padding-left: 1rem;\n}\n\n.markdown-body .footnotes ol ul {\n    display: inline-block;\n    padding-left: 1rem;\n    margin-top: 1rem;\n}\n\n.markdown-body .footnotes li {\n    position: relative;\n}\n\n.markdown-body .footnotes li:target::before {\n    position: absolute;\n    top: calc(0.5rem*-1);\n    right: calc(0.5rem*-1);\n    bottom: calc(0.5rem*-1);\n    left: calc(1.5rem*-1);\n    pointer-events: none;\n    content: \"\";\n    border: 2px solid #1f6feb;\n    border-radius: 6px;\n}\n\n.markdown-body .footnotes li:target {\n    color: #f0f6fc;\n}\n\n.markdown-body .footnotes .data-footnote-backref g-emoji {\n    font-family: monospace;\n}\n\n.markdown-body .pl-c {\n    color: #9198a1;\n}\n\n.markdown-body .pl-c1,\n.markdown-body .pl-s .pl-v {\n    color: #79c0ff;\n}\n\n.markdown-body .pl-e,\n.markdown-body .pl-en {\n    color: #d2a8ff;\n}\n\n.markdown-body .pl-smi,\n.markdown-body .pl-s .pl-s1 {\n    color: #f0f6fc;\n}\n\n.markdown-body .pl-ent {\n    color: #7ee787;\n}\n\n.markdown-body .pl-k {\n    color: #ff7b72;\n}\n\n.markdown-body .pl-s,\n.markdown-body .pl-pds,\n.markdown-body .pl-s .pl-pse .pl-s1,\n.markdown-body .pl-sr,\n.markdown-body .pl-sr .pl-cce,\n.markdown-body .pl-sr .pl-sre,\n.markdown-body .pl-sr .pl-sra {\n    color: #a5d6ff;\n}\n\n.markdown-body .pl-v,\n.markdown-body .pl-smw {\n    color: #ffa657;\n}\n\n.markdown-body .pl-bu {\n    color: #f85149;\n}\n\n.markdown-body .pl-ii {\n    color: #f0f6fc;\n    background-color: #8e1519;\n}\n\n.markdown-body .pl-c2 {\n    color: #f0f6fc;\n    background-color: #b62324;\n}\n\n.markdown-body .pl-sr .pl-cce {\n    font-weight: bold;\n    color: #7ee787;\n}\n\n.markdown-body .pl-ml {\n    color: #f2cc60;\n}\n\n.markdown-body .pl-mh,\n.markdown-body .pl-mh .pl-en,\n.markdown-body .pl-ms {\n    font-weight: bold;\n    color: #1f6feb;\n}\n\n.markdown-body .pl-mi {\n    font-style: italic;\n    color: #f0f6fc;\n}\n\n.markdown-body .pl-mb {\n    font-weight: bold;\n    color: #f0f6fc;\n}\n\n.markdown-body .pl-md {\n    color: #ffdcd7;\n    background-color: #67060c;\n}\n\n.markdown-body .pl-mi1 {\n    color: #aff5b4;\n    background-color: #033a16;\n}\n\n.markdown-body .pl-mc {\n    color: #ffdfb6;\n    background-color: #5a1e02;\n}\n\n.markdown-body .pl-mi2 {\n    color: #f0f6fc;\n    background-color: #1158c7;\n}\n\n.markdown-body .pl-mdr {\n    font-weight: bold;\n    color: #d2a8ff;\n}\n\n.markdown-body .pl-ba {\n    color: #9198a1;\n}\n\n.markdown-body .pl-sg {\n    color: #3d444d;\n}\n\n.markdown-body .pl-corl {\n    text-decoration: underline;\n    color: #a5d6ff;\n}\n\n.markdown-body [role=button]:focus:not(:focus-visible),\n.markdown-body [role=tabpanel][tabindex=\"0\"]:focus:not(:focus-visible),\n.markdown-body button:focus:not(:focus-visible),\n.markdown-body summary:focus:not(:focus-visible),\n.markdown-body a:focus:not(:focus-visible) {\n    outline: none;\n    box-shadow: none;\n}\n\n.markdown-body [tabindex=\"0\"]:focus:not(:focus-visible),\n.markdown-body details-dialog:focus:not(:focus-visible) {\n    outline: none;\n}\n\n.markdown-body g-emoji {\n    display: inline-block;\n    min-width: 1ch;\n    font-family: \"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\";\n    font-size: 1em;\n    font-style: normal !important;\n    font-weight: 400;\n    line-height: 1;\n    vertical-align: -0.075em;\n}\n\n.markdown-body g-emoji img {\n    width: 1em;\n    height: 1em;\n}\n\n.markdown-body .task-list-item {\n    list-style-type: none;\n}\n\n.markdown-body .task-list-item label {\n    font-weight: 400;\n}\n\n.markdown-body .task-list-item.enabled label {\n    cursor: pointer;\n}\n\n.markdown-body .task-list-item+.task-list-item {\n    margin-top: 0.25rem;\n}\n\n.markdown-body .task-list-item .handle {\n    display: none;\n}\n\n.markdown-body .task-list-item-checkbox {\n    margin: 0 .2em .25em -1.4em;\n    vertical-align: middle;\n}\n\n.markdown-body ul:dir(rtl) .task-list-item-checkbox {\n    margin: 0 -1.6em .25em .2em;\n}\n\n.markdown-body ol:dir(rtl) .task-list-item-checkbox {\n    margin: 0 -1.6em .25em .2em;\n}\n\n.markdown-body .contains-task-list:hover .task-list-item-convert-container,\n.markdown-body .contains-task-list:focus-within .task-list-item-convert-container {\n    display: block;\n    width: auto;\n    height: 24px;\n    overflow: visible;\n    clip: auto;\n}\n\n.markdown-body ::-webkit-calendar-picker-indicator {\n    filter: invert(50%);\n}\n\n.markdown-body .markdown-alert {\n    padding: 0.5rem 1rem;\n    margin-bottom: 1rem;\n    color: inherit;\n    border-left: .25em solid #3d444d;\n}\n\n.markdown-body .markdown-alert>:first-child {\n    margin-top: 0;\n}\n\n.markdown-body .markdown-alert>:last-child {\n    margin-bottom: 0;\n}\n\n.markdown-body .markdown-alert .markdown-alert-title {\n    display: flex;\n    font-weight: 500;\n    align-items: center;\n    line-height: 1;\n}\n\n.markdown-body .markdown-alert.markdown-alert-note {\n    border-left-color: #1f6feb;\n}\n\n.markdown-body .markdown-alert.markdown-alert-note .markdown-alert-title {\n    color: #4493f8;\n}\n\n.markdown-body .markdown-alert.markdown-alert-important {\n    border-left-color: #8957e5;\n}\n\n.markdown-body .markdown-alert.markdown-alert-important .markdown-alert-title {\n    color: #ab7df8;\n}\n\n.markdown-body .markdown-alert.markdown-alert-warning {\n    border-left-color: #9e6a03;\n}\n\n.markdown-body .markdown-alert.markdown-alert-warning .markdown-alert-title {\n    color: #d29922;\n}\n\n.markdown-body .markdown-alert.markdown-alert-tip {\n    border-left-color: #238636;\n}\n\n.markdown-body .markdown-alert.markdown-alert-tip .markdown-alert-title {\n    color: #3fb950;\n}\n\n.markdown-body .markdown-alert.markdown-alert-caution {\n    border-left-color: #da3633;\n}\n\n.markdown-body .markdown-alert.markdown-alert-caution .markdown-alert-title {\n    color: #f85149;\n}\n\n.markdown-body>*:first-child>.heading-element:first-child {\n    margin-top: 0 !important;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/sdprompt.xshd",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<SyntaxDefinition name=\"ImagePrompt\" extensions=\".prompt\" xmlns=\"http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008\">\n    <!-- Colors -->\n    <Color name=\"punctuation\" foreground=\"DarkGreen\"/>\n    <Color name=\"comment\" foreground=\"#008000\"/>\n    <Color name=\"keyword\" foreground=\"#0000FF\" fontWeight=\"bold\"/>\n    <Color name=\"string\" foreground=\"#A31515\"/>\n    <Color name=\"charescape\" foreground=\"#6A8759\"/>\n    <Color name=\"invalid\" foreground=\"#FF0000\" background=\"#FFD0D0\"/>\n    <Color name=\"number\" foreground=\"#FF0000\"/>\n    <Color name=\"operator\" foreground=\"#808080\"/>\n    <Color name=\"extranetwork\" foreground=\"#2B91AF\"/>\n    <Color name=\"promptstep\" foreground=\"#2B91AF\"/>\n    <Color name=\"promptweight\" foreground=\"#2B91AF\"/>\n    <Color name=\"promptvariable\" foreground=\"#2B91AF\"/>\n    <Color name=\"dynamicselection\" foreground=\"#2B91AF\"/>\n    <Color name=\"wildcard\" foreground=\"#2B91AF\"/>\n    <Color name=\"whitespace\" foreground=\"#808080\"/>\n    <Color name=\"qualitytag\" foreground=\"#2B91AF\"/>\n    <Color name=\"split\" foreground=\"#808080\"/>\n    <Color name=\"csscolor\" foreground=\"#FF00FF\"/>\n    <Color name=\"promptkeyword\" foreground=\"#2B91AF\"/>\n    <Color name=\"supercmd\" foreground=\"#2B91AF\"/>\n\n    <!-- Rules -->\n    <RuleSet ignoreCase=\"false\">\n        <Rule color=\"punctuation\">\n            [?,.()\\[\\]{}+\\-/%*&lt;&gt;^!|]+\n        </Rule>\n        <Span color=\"comment\" begin=\"#\"/>\n<!--        <Span color=\"string\" multiline=\"true\" begin=\"&quot;\" end=\"&quot;\"/>-->\n        <Span color=\"charescape\" begin=\"\\\\u\" end=\".*\"/>\n<!--        <Span color=\"invalid\" begin=\">\"/>\n        <Span color=\"invalid\" begin=\"\\\\)\"/>\n        <Span color=\"invalid\" begin=\"\\\\]\"/>\n        <Span color=\"invalid\" begin=\"\\\\}\"/>-->\n        \n<!--        <MarkFollowing color=\"operator\" bold=\"true\" begin=\":\"/>\n        <MarkPrevious color=\"operator\" bold=\"true\" begin=\",\"/>-->\n        \n<!--        <Span color=\"extranetwork\" begin=\"&lt;\" end=\"&gt;\"/>\n        <Span color=\"promptstep\">\n            <Begin>\\[</Begin>\n            <End>\\]</End>\n        </Span>\n        <Span color=\"promptweight\">\n            <Begin>\\(</Begin>\n            <End>\\)</End>\n        </Span>-->\n        \n<!--    <Span color=\"promptvariable\" begin=\"\\\\$\\\\{\" end=\"\\\\}\"/>\n        <Span color=\"dynamicselection\" begin=\"\\\\{\" end=\"\\\\}\"/>-->\n<!--        <Span color=\"wildcard\" begin=\"__\" end=\"__\"/>\n        <Span color=\"whitespace\" begin=\"\\\\s+\"/>-->\n<!--        <Span color=\"qualitytag\" begin=\"\\\\{\\\\{\" end=\"\\\\}\\\\}\"/>-->\n<!--        <Span color=\"split\" begin=\"|\"/>-->\n\n<!--        <MarkPrevious color=\"csscolor\" begin=\"#\"/>-->\n\n        <Keywords color=\"keyword\" ignoreCase=\"false\">\n            <Word>AND</Word>\n            <Word>BREAK</Word>\n        </Keywords>\n\n        <Keywords color=\"promptkeyword\">\n            <Word>PROMPT</Word>\n            <Word>WEIGHT</Word>\n        </Keywords>\n\n        <Keywords color=\"supercmd\">\n            <Word>SELECT</Word>\n            <Word>DYNAMIC</Word>\n        </Keywords>\n\n        <Rule color=\"number\">\\b0[xX][0-9a-fA-F]+|(\\b\\d+(\\.[0-9]+)?|\\.[0-9]+)([eE][+-]?[0-9]+)?</Rule>\n\n<!--        <Rule color=\"invalid\">\\b\\d+[a-zA-Z_]+\\b</Rule>\n\n        <Rule color=\"invalid\">[a-zA-Z_]+\\d+\\b</Rule>-->\n    </RuleSet>\n</SyntaxDefinition>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/sitecustomize.py",
    "content": "﻿\"\"\"\nStartup site customization for Stability Matrix.\n\nCurrently this installs an audit hook to notify the parent process when input() is called,\nso we can prompt the user to enter something.\n\"\"\"\n\nimport sys\nimport json\n\n# Application Program Command escape sequence\n# This wraps messages sent to the parent process.\nesc_apc = \"\\x9F\"\nesc_prefix = \"[SM;\"\nesc_st = \"\\x9C\"\n\n\ndef send_apc(msg: str):\n    \"\"\"Send an Application Program Command to the parent process.\"\"\"\n    sys.stdout.flush()\n    sys.stdout.write(esc_apc + esc_prefix + msg + esc_st)\n    sys.stdout.flush()\n\ndef send_apc_json(type: str, data: str):\n    \"\"\"Send an APC Json message.\"\"\"\n    send_apc(json.dumps({\"type\": type, \"data\": data}))\n\ndef send_apc_input(prompt: str):\n    \"\"\"Apc message for input() prompt.\"\"\"\n    send_apc_json(\"input\", prompt)\n\ndef audit(event: str, *args):\n    \"\"\"Main audit hook function.\"\"\"\n    # https://docs.python.org/3/library/functions.html#input\n    # input() raises audit event `builtins.input` with args (prompt: str) *before* reading from stdin.\n    # `builtins.input/result` raised after reading from stdin.\n\n    if event == \"builtins.input\":\n        try:\n            prompts = args[0] if args else ()\n            prompt = \"\".join(prompts)\n            send_apc_input(prompt)\n        except Exception:\n            pass\n\n\n# Reconfigure stdout to UTF-8\n# noinspection PyUnresolvedReferences\nsys.stdin.reconfigure(encoding=\"utf-8\")\nsys.stdout.reconfigure(encoding=\"utf-8\")\nsys.stderr.reconfigure(encoding=\"utf-8\")\n\n# Install the audit hook\nsys.addaudithook(audit)\n\n# Patch Rich terminal detection\ndef _patch_rich_console():\n    try:\n        from rich import console\n        \n        class _Console(console.Console):\n            @property\n            def is_terminal(self) -> bool:\n                return True\n        \n        console.Console = _Console\n    except ImportError:\n        pass\n    except Exception as e:\n        print(\"[sitecustomize error]:\", e)\n        \n    try:\n        from pip._vendor.rich import console\n\n        class _Console(console.Console):\n            @property\n            def is_terminal(self) -> bool:\n                return True\n\n        console.Console = _Console\n    except ImportError:\n        pass\n    except Exception as e:\n        print(\"[sitecustomize error]:\", e)    \n\n_patch_rich_console()\n\n# Patch tqdm to use stdout instead of stderr\ndef _patch_tqdm():\n    try:\n        import sys\n        from tqdm import std\n        \n        sys.stderr = sys.stdout\n    except ImportError:\n        pass\n    except Exception as e:\n        print(\"[sitecustomize error]:\", e)\n\n_patch_tqdm()\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/win-x64/7za - LICENSE.txt",
    "content": "7-Zip Extra 18.01\n-----------------\n\n7-Zip Extra is package of extra modules of 7-Zip. \n\n7-Zip Copyright (C) 1999-2018 Igor Pavlov.\n\n7-Zip is free software. Read License.txt for more information about license.\n\nSource code of binaries can be found at:\n  http://www.7-zip.org/\n\n\n7-Zip Extra\n~~~~~~~~~~~\nLicense for use and distribution\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nCopyright (C) 1999-2018 Igor Pavlov.\n\n7-Zip Extra files are under the GNU LGPL license.\n\n\nNotes: \n  You can use 7-Zip Extra on any computer, including a computer in a commercial \n  organization. You don't need to register or pay for 7-Zip.\n\n\nGNU LGPL information\n--------------------\n\n  This library is free software; you can redistribute it and/or\n  modify it under the terms of the GNU Lesser General Public\n  License as published by the Free Software Foundation; either\n  version 2.1 of the License, or (at your option) any later version.\n\n  This library is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n  Lesser General Public License for more details.\n\n  You can receive a copy of the GNU Lesser General Public License from \n  http://www.gnu.org/\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/win-x64/venv/__init__.py",
    "content": "\"\"\"\nVirtual environment (venv) package for Python. Based on PEP 405.\n\nCopyright (C) 2011-2014 Vinay Sajip.\nLicensed to the PSF under a contributor agreement.\n\"\"\"\nimport logging\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport sysconfig\nimport types\n\n\nCORE_VENV_DEPS = ('pip', 'setuptools')\nlogger = logging.getLogger(__name__)\n\n\nclass EnvBuilder:\n    \"\"\"\n    This class exists to allow virtual environment creation to be\n    customized. The constructor parameters determine the builder's\n    behaviour when called upon to create a virtual environment.\n\n    By default, the builder makes the system (global) site-packages dir\n    *un*available to the created environment.\n\n    If invoked using the Python -m option, the default is to use copying\n    on Windows platforms but symlinks elsewhere. If instantiated some\n    other way, the default is to *not* use symlinks.\n\n    :param system_site_packages: If True, the system (global) site-packages\n                                 dir is available to created environments.\n    :param clear: If True, delete the contents of the environment directory if\n                  it already exists, before environment creation.\n    :param symlinks: If True, attempt to symlink rather than copy files into\n                     virtual environment.\n    :param upgrade: If True, upgrade an existing virtual environment.\n    :param with_pip: If True, ensure pip is installed in the virtual\n                     environment\n    :param prompt: Alternative terminal prefix for the environment.\n    :param upgrade_deps: Update the base venv modules to the latest on PyPI\n    \"\"\"\n\n    def __init__(self, system_site_packages=False, clear=False,\n                 symlinks=False, upgrade=False, with_pip=False, prompt=None,\n                 upgrade_deps=False):\n        self.system_site_packages = system_site_packages\n        self.clear = clear\n        self.symlinks = symlinks\n        self.upgrade = upgrade\n        self.with_pip = with_pip\n        if prompt == '.':  # see bpo-38901\n            prompt = os.path.basename(os.getcwd())\n        self.prompt = prompt\n        self.upgrade_deps = upgrade_deps\n\n    def create(self, env_dir):\n        \"\"\"\n        Create a virtual environment in a directory.\n\n        :param env_dir: The target directory to create an environment in.\n\n        \"\"\"\n        env_dir = os.path.abspath(env_dir)\n        context = self.ensure_directories(env_dir)\n        # See issue 24875. We need system_site_packages to be False\n        # until after pip is installed.\n        true_system_site_packages = self.system_site_packages\n        self.system_site_packages = False\n        self.create_configuration(context)\n        self.setup_python(context)\n        if self.with_pip:\n            self._setup_pip(context)\n        if not self.upgrade:\n            self.setup_scripts(context)\n            self.post_setup(context)\n        if true_system_site_packages:\n            # We had set it to False before, now\n            # restore it and rewrite the configuration\n            self.system_site_packages = True\n            self.create_configuration(context)\n        if self.upgrade_deps:\n            self.upgrade_dependencies(context)\n\n    def clear_directory(self, path):\n        for fn in os.listdir(path):\n            fn = os.path.join(path, fn)\n            if os.path.islink(fn) or os.path.isfile(fn):\n                os.remove(fn)\n            elif os.path.isdir(fn):\n                shutil.rmtree(fn)\n\n    def ensure_directories(self, env_dir):\n        \"\"\"\n        Create the directories for the environment.\n\n        Returns a context object which holds paths in the environment,\n        for use by subsequent logic.\n        \"\"\"\n\n        def create_if_needed(d):\n            if not os.path.exists(d):\n                os.makedirs(d)\n            elif os.path.islink(d) or os.path.isfile(d):\n                raise ValueError('Unable to create directory %r' % d)\n\n        if os.path.exists(env_dir) and self.clear:\n            self.clear_directory(env_dir)\n        context = types.SimpleNamespace()\n        context.env_dir = env_dir\n        context.env_name = os.path.split(env_dir)[1]\n        prompt = self.prompt if self.prompt is not None else context.env_name\n        context.prompt = '(%s) ' % prompt\n        create_if_needed(env_dir)\n        executable = sys._base_executable\n        if not executable:  # see gh-96861\n            raise ValueError('Unable to determine path to the running '\n                             'Python interpreter. Provide an explicit path or '\n                             'check that your PATH environment variable is '\n                             'correctly set.')\n        dirname, exename = os.path.split(os.path.abspath(executable))\n        context.executable = executable\n        context.python_dir = dirname\n        context.python_exe = exename\n        if sys.platform == 'win32':\n            binname = 'Scripts'\n            incpath = 'Include'\n            libpath = os.path.join(env_dir, 'Lib', 'site-packages')\n        else:\n            binname = 'bin'\n            incpath = 'include'\n            libpath = os.path.join(env_dir, 'lib',\n                                   'python%d.%d' % sys.version_info[:2],\n                                   'site-packages')\n        context.inc_path = path = os.path.join(env_dir, incpath)\n        create_if_needed(path)\n        create_if_needed(libpath)\n        # Issue 21197: create lib64 as a symlink to lib on 64-bit non-OS X POSIX\n        if ((sys.maxsize > 2**32) and (os.name == 'posix') and\n            (sys.platform != 'darwin')):\n            link_path = os.path.join(env_dir, 'lib64')\n            if not os.path.exists(link_path):   # Issue #21643\n                os.symlink('lib', link_path)\n        context.bin_path = binpath = os.path.join(env_dir, binname)\n        context.bin_name = binname\n        context.env_exe = os.path.join(binpath, exename)\n        create_if_needed(binpath)\n        # Assign and update the command to use when launching the newly created\n        # environment, in case it isn't simply the executable script (e.g. bpo-45337)\n        context.env_exec_cmd = context.env_exe\n        if sys.platform == 'win32':\n            # bpo-45337: Fix up env_exec_cmd to account for file system redirections.\n            # Some redirects only apply to CreateFile and not CreateProcess\n            real_env_exe = os.path.realpath(context.env_exe)\n            if os.path.normcase(real_env_exe) != os.path.normcase(context.env_exe):\n                logger.warning('Actual environment location may have moved due to '\n                               'redirects, links or junctions.\\n'\n                               '  Requested location: \"%s\"\\n'\n                               '  Actual location:    \"%s\"',\n                               context.env_exe, real_env_exe)\n                context.env_exec_cmd = real_env_exe\n        return context\n\n    def create_configuration(self, context):\n        \"\"\"\n        Create a configuration file indicating where the environment's Python\n        was copied from, and whether the system site-packages should be made\n        available in the environment.\n\n        :param context: The information for the environment creation request\n                        being processed.\n        \"\"\"\n        context.cfg_path = path = os.path.join(context.env_dir, 'pyvenv.cfg')\n        with open(path, 'w', encoding='utf-8') as f:\n            f.write('home = %s\\n' % context.python_dir)\n            if self.system_site_packages:\n                incl = 'true'\n            else:\n                incl = 'false'\n            f.write('include-system-site-packages = %s\\n' % incl)\n            f.write('version = %d.%d.%d\\n' % sys.version_info[:3])\n            if self.prompt is not None:\n                f.write(f'prompt = {self.prompt!r}\\n')\n\n    if os.name != 'nt':\n        def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):\n            \"\"\"\n            Try symlinking a file, and if that fails, fall back to copying.\n            \"\"\"\n            force_copy = not self.symlinks\n            if not force_copy:\n                try:\n                    if not os.path.islink(dst): # can't link to itself!\n                        if relative_symlinks_ok:\n                            assert os.path.dirname(src) == os.path.dirname(dst)\n                            os.symlink(os.path.basename(src), dst)\n                        else:\n                            os.symlink(src, dst)\n                except Exception:   # may need to use a more specific exception\n                    logger.warning('Unable to symlink %r to %r', src, dst)\n                    force_copy = True\n            if force_copy:\n                shutil.copyfile(src, dst)\n    else:\n        def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):\n            \"\"\"\n            Try symlinking a file, and if that fails, fall back to copying.\n            \"\"\"\n            bad_src = os.path.lexists(src) and not os.path.exists(src)\n            if self.symlinks and not bad_src and not os.path.islink(dst):\n                try:\n                    if relative_symlinks_ok:\n                        assert os.path.dirname(src) == os.path.dirname(dst)\n                        os.symlink(os.path.basename(src), dst)\n                    else:\n                        os.symlink(src, dst)\n                    return\n                except Exception:   # may need to use a more specific exception\n                    logger.warning('Unable to symlink %r to %r', src, dst)\n\n            # On Windows, we rewrite symlinks to our base python.exe into\n            # copies of venvlauncher.exe\n            basename, ext = os.path.splitext(os.path.basename(src))\n            srcfn = os.path.join(os.path.dirname(__file__),\n                                 \"scripts\",\n                                 \"nt\",\n                                 basename + ext)\n            # Builds or venv's from builds need to remap source file\n            # locations, as we do not put them into Lib/venv/scripts\n            if sysconfig.is_python_build(True) or not os.path.isfile(srcfn):\n                if basename.endswith('_d'):\n                    ext = '_d' + ext\n                    basename = basename[:-2]\n                if basename == 'python':\n                    basename = 'venvlauncher'\n                elif basename == 'pythonw':\n                    basename = 'venvwlauncher'\n                src = os.path.join(os.path.dirname(src), basename + ext)\n            else:\n                src = srcfn\n            if not os.path.exists(src):\n                if not bad_src:\n                    logger.warning('Unable to copy %r', src)\n                return\n\n            shutil.copyfile(src, dst)\n\n    def setup_python(self, context):\n        \"\"\"\n        Set up a Python executable in the environment.\n\n        :param context: The information for the environment creation request\n                        being processed.\n        \"\"\"\n        binpath = context.bin_path\n        path = context.env_exe\n        copier = self.symlink_or_copy\n        dirname = context.python_dir\n        if os.name != 'nt':\n            copier(context.executable, path)\n            if not os.path.islink(path):\n                os.chmod(path, 0o755)\n            for suffix in ('python', 'python3', f'python3.{sys.version_info[1]}'):\n                path = os.path.join(binpath, suffix)\n                if not os.path.exists(path):\n                    # Issue 18807: make copies if\n                    # symlinks are not wanted\n                    copier(context.env_exe, path, relative_symlinks_ok=True)\n                    if not os.path.islink(path):\n                        os.chmod(path, 0o755)\n        else:\n            if self.symlinks:\n                # For symlinking, we need a complete copy of the root directory\n                # If symlinks fail, you'll get unnecessary copies of files, but\n                # we assume that if you've opted into symlinks on Windows then\n                # you know what you're doing.\n                suffixes = [\n                    f for f in os.listdir(dirname) if\n                    os.path.normcase(os.path.splitext(f)[1]) in ('.exe', '.dll')\n                ]\n                if sysconfig.is_python_build(True):\n                    suffixes = [\n                        f for f in suffixes if\n                        os.path.normcase(f).startswith(('python', 'vcruntime'))\n                    ]\n            else:\n                suffixes = {'python.exe', 'python_d.exe', 'pythonw.exe', 'pythonw_d.exe'}\n                base_exe = os.path.basename(context.env_exe)\n                suffixes.add(base_exe)\n\n            for suffix in suffixes:\n                src = os.path.join(dirname, suffix)\n                if os.path.lexists(src):\n                    copier(src, os.path.join(binpath, suffix))\n\n            if sysconfig.is_python_build(True):\n                # copy init.tcl\n                for root, dirs, files in os.walk(context.python_dir):\n                    if 'init.tcl' in files:\n                        tcldir = os.path.basename(root)\n                        tcldir = os.path.join(context.env_dir, 'Lib', tcldir)\n                        if not os.path.exists(tcldir):\n                            os.makedirs(tcldir)\n                        src = os.path.join(root, 'init.tcl')\n                        dst = os.path.join(tcldir, 'init.tcl')\n                        shutil.copyfile(src, dst)\n                        break\n\n    def _call_new_python(self, context, *py_args, **kwargs):\n        \"\"\"Executes the newly created Python using safe-ish options\"\"\"\n        # gh-98251: We do not want to just use '-I' because that masks\n        # legitimate user preferences (such as not writing bytecode). All we\n        # really need is to ensure that the path variables do not overrule\n        # normal venv handling.\n        args = [context.env_exec_cmd, *py_args]\n        kwargs['env'] = env = os.environ.copy()\n        env['VIRTUAL_ENV'] = context.env_dir\n        env.pop('PYTHONHOME', None)\n        env.pop('PYTHONPATH', None)\n        kwargs['cwd'] = context.env_dir\n        kwargs['executable'] = context.env_exec_cmd\n        subprocess.check_output(args, **kwargs)\n\n    def _setup_pip(self, context):\n        \"\"\"Installs or upgrades pip in a virtual environment\"\"\"\n        self._call_new_python(context, '-m', 'ensurepip', '--upgrade',\n                              '--default-pip', stderr=subprocess.STDOUT)\n\n    def setup_scripts(self, context):\n        \"\"\"\n        Set up scripts into the created environment from a directory.\n\n        This method installs the default scripts into the environment\n        being created. You can prevent the default installation by overriding\n        this method if you really need to, or if you need to specify\n        a different location for the scripts to install. By default, the\n        'scripts' directory in the venv package is used as the source of\n        scripts to install.\n        \"\"\"\n        path = os.path.abspath(os.path.dirname(__file__))\n        path = os.path.join(path, 'scripts')\n        self.install_scripts(context, path)\n\n    def post_setup(self, context):\n        \"\"\"\n        Hook for post-setup modification of the venv. Subclasses may install\n        additional packages or scripts here, add activation shell scripts, etc.\n\n        :param context: The information for the environment creation request\n                        being processed.\n        \"\"\"\n        pass\n\n    def replace_variables(self, text, context):\n        \"\"\"\n        Replace variable placeholders in script text with context-specific\n        variables.\n\n        Return the text passed in , but with variables replaced.\n\n        :param text: The text in which to replace placeholder variables.\n        :param context: The information for the environment creation request\n                        being processed.\n        \"\"\"\n        text = text.replace('__VENV_DIR__', context.env_dir)\n        text = text.replace('__VENV_NAME__', context.env_name)\n        text = text.replace('__VENV_PROMPT__', context.prompt)\n        text = text.replace('__VENV_BIN_NAME__', context.bin_name)\n        text = text.replace('__VENV_PYTHON__', context.env_exe)\n        return text\n\n    def install_scripts(self, context, path):\n        \"\"\"\n        Install scripts into the created environment from a directory.\n\n        :param context: The information for the environment creation request\n                        being processed.\n        :param path:    Absolute pathname of a directory containing script.\n                        Scripts in the 'common' subdirectory of this directory,\n                        and those in the directory named for the platform\n                        being run on, are installed in the created environment.\n                        Placeholder variables are replaced with environment-\n                        specific values.\n        \"\"\"\n        binpath = context.bin_path\n        plen = len(path)\n        for root, dirs, files in os.walk(path):\n            if root == path: # at top-level, remove irrelevant dirs\n                for d in dirs[:]:\n                    if d not in ('common', os.name):\n                        dirs.remove(d)\n                continue # ignore files in top level\n            for f in files:\n                if (os.name == 'nt' and f.startswith('python')\n                        and f.endswith(('.exe', '.pdb'))):\n                    continue\n                srcfile = os.path.join(root, f)\n                suffix = root[plen:].split(os.sep)[2:]\n                if not suffix:\n                    dstdir = binpath\n                else:\n                    dstdir = os.path.join(binpath, *suffix)\n                if not os.path.exists(dstdir):\n                    os.makedirs(dstdir)\n                dstfile = os.path.join(dstdir, f)\n                with open(srcfile, 'rb') as f:\n                    data = f.read()\n                if not srcfile.endswith(('.exe', '.pdb')):\n                    try:\n                        data = data.decode('utf-8')\n                        data = self.replace_variables(data, context)\n                        data = data.encode('utf-8')\n                    except UnicodeError as e:\n                        data = None\n                        logger.warning('unable to copy script %r, '\n                                       'may be binary: %s', srcfile, e)\n                if data is not None:\n                    with open(dstfile, 'wb') as f:\n                        f.write(data)\n                    shutil.copymode(srcfile, dstfile)\n\n    def upgrade_dependencies(self, context):\n        logger.debug(\n            f'Upgrading {CORE_VENV_DEPS} packages in {context.bin_path}'\n        )\n        self._call_new_python(context, '-m', 'pip', 'install', '--upgrade',\n                              *CORE_VENV_DEPS)\n\n\ndef create(env_dir, system_site_packages=False, clear=False,\n           symlinks=False, with_pip=False, prompt=None, upgrade_deps=False):\n    \"\"\"Create a virtual environment in a directory.\"\"\"\n    builder = EnvBuilder(system_site_packages=system_site_packages,\n                         clear=clear, symlinks=symlinks, with_pip=with_pip,\n                         prompt=prompt, upgrade_deps=upgrade_deps)\n    builder.create(env_dir)\n\ndef main(args=None):\n    compatible = True\n    if sys.version_info < (3, 3):\n        compatible = False\n    elif not hasattr(sys, 'base_prefix'):\n        compatible = False\n    if not compatible:\n        raise ValueError('This script is only for use with Python >= 3.3')\n    else:\n        import argparse\n\n        parser = argparse.ArgumentParser(prog=__name__,\n                                         description='Creates virtual Python '\n                                                     'environments in one or '\n                                                     'more target '\n                                                     'directories.',\n                                         epilog='Once an environment has been '\n                                                'created, you may wish to '\n                                                'activate it, e.g. by '\n                                                'sourcing an activate script '\n                                                'in its bin directory.')\n        parser.add_argument('dirs', metavar='ENV_DIR', nargs='+',\n                            help='A directory to create the environment in.')\n        parser.add_argument('--system-site-packages', default=False,\n                            action='store_true', dest='system_site',\n                            help='Give the virtual environment access to the '\n                                 'system site-packages dir.')\n        if os.name == 'nt':\n            use_symlinks = False\n        else:\n            use_symlinks = True\n        group = parser.add_mutually_exclusive_group()\n        group.add_argument('--symlinks', default=use_symlinks,\n                           action='store_true', dest='symlinks',\n                           help='Try to use symlinks rather than copies, '\n                                'when symlinks are not the default for '\n                                'the platform.')\n        group.add_argument('--copies', default=not use_symlinks,\n                           action='store_false', dest='symlinks',\n                           help='Try to use copies rather than symlinks, '\n                                'even when symlinks are the default for '\n                                'the platform.')\n        parser.add_argument('--clear', default=False, action='store_true',\n                            dest='clear', help='Delete the contents of the '\n                                               'environment directory if it '\n                                               'already exists, before '\n                                               'environment creation.')\n        parser.add_argument('--upgrade', default=False, action='store_true',\n                            dest='upgrade', help='Upgrade the environment '\n                                               'directory to use this version '\n                                               'of Python, assuming Python '\n                                               'has been upgraded in-place.')\n        parser.add_argument('--without-pip', dest='with_pip',\n                            default=True, action='store_false',\n                            help='Skips installing or upgrading pip in the '\n                                 'virtual environment (pip is bootstrapped '\n                                 'by default)')\n        parser.add_argument('--prompt',\n                            help='Provides an alternative prompt prefix for '\n                                 'this environment.')\n        parser.add_argument('--upgrade-deps', default=False, action='store_true',\n                            dest='upgrade_deps',\n                            help='Upgrade core dependencies: {} to the latest '\n                                 'version in PyPI'.format(\n                                 ' '.join(CORE_VENV_DEPS)))\n        options = parser.parse_args(args)\n        if options.upgrade and options.clear:\n            raise ValueError('you cannot supply --upgrade and --clear together.')\n        builder = EnvBuilder(system_site_packages=options.system_site,\n                             clear=options.clear,\n                             symlinks=options.symlinks,\n                             upgrade=options.upgrade,\n                             with_pip=options.with_pip,\n                             prompt=options.prompt,\n                             upgrade_deps=options.upgrade_deps)\n        for d in options.dirs:\n            builder.create(d)\n\nif __name__ == '__main__':\n    rc = 1\n    try:\n        main()\n        rc = 0\n    except Exception as e:\n        print('Error: %s' % e, file=sys.stderr)\n    sys.exit(rc)\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/win-x64/venv/__main__.py",
    "content": "import sys\nfrom . import main\n\nrc = 1\ntry:\n    main()\n    rc = 0\nexcept Exception as e:\n    print('Error: %s' % e, file=sys.stderr)\nsys.exit(rc)\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/win-x64/venv/scripts/common/Activate.ps1",
    "content": "<#\n.Synopsis\nActivate a Python virtual environment for the current PowerShell session.\n\n.Description\nPushes the python executable for a virtual environment to the front of the\n$Env:PATH environment variable and sets the prompt to signify that you are\nin a Python virtual environment. Makes use of the command line switches as\nwell as the `pyvenv.cfg` file values present in the virtual environment.\n\n.Parameter VenvDir\nPath to the directory that contains the virtual environment to activate. The\ndefault value for this is the parent of the directory that the Activate.ps1\nscript is located within.\n\n.Parameter Prompt\nThe prompt prefix to display when this virtual environment is activated. By\ndefault, this prompt is the name of the virtual environment folder (VenvDir)\nsurrounded by parentheses and followed by a single space (ie. '(.venv) ').\n\n.Example\nActivate.ps1\nActivates the Python virtual environment that contains the Activate.ps1 script.\n\n.Example\nActivate.ps1 -Verbose\nActivates the Python virtual environment that contains the Activate.ps1 script,\nand shows extra information about the activation as it executes.\n\n.Example\nActivate.ps1 -VenvDir C:\\Users\\MyUser\\Common\\.venv\nActivates the Python virtual environment located in the specified location.\n\n.Example\nActivate.ps1 -Prompt \"MyPython\"\nActivates the Python virtual environment that contains the Activate.ps1 script,\nand prefixes the current prompt with the specified string (surrounded in\nparentheses) while the virtual environment is active.\n\n.Notes\nOn Windows, it may be required to enable this Activate.ps1 script by setting the\nexecution policy for the user. You can do this by issuing the following PowerShell\ncommand:\n\nPS C:\\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser\n\nFor more information on Execution Policies: \nhttps://go.microsoft.com/fwlink/?LinkID=135170\n\n#>\nParam(\n    [Parameter(Mandatory = $false)]\n    [String]\n    $VenvDir,\n    [Parameter(Mandatory = $false)]\n    [String]\n    $Prompt\n)\n\n<# Function declarations --------------------------------------------------- #>\n\n<#\n.Synopsis\nRemove all shell session elements added by the Activate script, including the\naddition of the virtual environment's Python executable from the beginning of\nthe PATH variable.\n\n.Parameter NonDestructive\nIf present, do not remove this function from the global namespace for the\nsession.\n\n#>\nfunction global:deactivate ([switch]$NonDestructive) {\n    # Revert to original values\n\n    # The prior prompt:\n    if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {\n        Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt\n        Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT\n    }\n\n    # The prior PYTHONHOME:\n    if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {\n        Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME\n        Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME\n    }\n\n    # The prior PATH:\n    if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {\n        Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH\n        Remove-Item -Path Env:_OLD_VIRTUAL_PATH\n    }\n\n    # Just remove the VIRTUAL_ENV altogether:\n    if (Test-Path -Path Env:VIRTUAL_ENV) {\n        Remove-Item -Path env:VIRTUAL_ENV\n    }\n\n    # Just remove VIRTUAL_ENV_PROMPT altogether.\n    if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {\n        Remove-Item -Path env:VIRTUAL_ENV_PROMPT\n    }\n\n    # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:\n    if (Get-Variable -Name \"_PYTHON_VENV_PROMPT_PREFIX\" -ErrorAction SilentlyContinue) {\n        Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force\n    }\n\n    # Leave deactivate function in the global namespace if requested:\n    if (-not $NonDestructive) {\n        Remove-Item -Path function:deactivate\n    }\n}\n\n<#\n.Description\nGet-PyVenvConfig parses the values from the pyvenv.cfg file located in the\ngiven folder, and returns them in a map.\n\nFor each line in the pyvenv.cfg file, if that line can be parsed into exactly\ntwo strings separated by `=` (with any amount of whitespace surrounding the =)\nthen it is considered a `key = value` line. The left hand string is the key,\nthe right hand is the value.\n\nIf the value starts with a `'` or a `\"` then the first and last character is\nstripped from the value before being captured.\n\n.Parameter ConfigDir\nPath to the directory that contains the `pyvenv.cfg` file.\n#>\nfunction Get-PyVenvConfig(\n    [String]\n    $ConfigDir\n) {\n    Write-Verbose \"Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg\"\n\n    # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).\n    $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue\n\n    # An empty map will be returned if no config file is found.\n    $pyvenvConfig = @{ }\n\n    if ($pyvenvConfigPath) {\n\n        Write-Verbose \"File exists, parse `key = value` lines\"\n        $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath\n\n        $pyvenvConfigContent | ForEach-Object {\n            $keyval = $PSItem -split \"\\s*=\\s*\", 2\n            if ($keyval[0] -and $keyval[1]) {\n                $val = $keyval[1]\n\n                # Remove extraneous quotations around a string value.\n                if (\"'\"\"\".Contains($val.Substring(0, 1))) {\n                    $val = $val.Substring(1, $val.Length - 2)\n                }\n\n                $pyvenvConfig[$keyval[0]] = $val\n                Write-Verbose \"Adding Key: '$($keyval[0])'='$val'\"\n            }\n        }\n    }\n    return $pyvenvConfig\n}\n\n\n<# Begin Activate script --------------------------------------------------- #>\n\n# Determine the containing directory of this script\n$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition\n$VenvExecDir = Get-Item -Path $VenvExecPath\n\nWrite-Verbose \"Activation script is located in path: '$VenvExecPath'\"\nWrite-Verbose \"VenvExecDir Fullname: '$($VenvExecDir.FullName)\"\nWrite-Verbose \"VenvExecDir Name: '$($VenvExecDir.Name)\"\n\n# Set values required in priority: CmdLine, ConfigFile, Default\n# First, get the location of the virtual environment, it might not be\n# VenvExecDir if specified on the command line.\nif ($VenvDir) {\n    Write-Verbose \"VenvDir given as parameter, using '$VenvDir' to determine values\"\n}\nelse {\n    Write-Verbose \"VenvDir not given as a parameter, using parent directory name as VenvDir.\"\n    $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd(\"\\\\/\")\n    Write-Verbose \"VenvDir=$VenvDir\"\n}\n\n# Next, read the `pyvenv.cfg` file to determine any required value such\n# as `prompt`.\n$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir\n\n# Next, set the prompt from the command line, or the config file, or\n# just use the name of the virtual environment folder.\nif ($Prompt) {\n    Write-Verbose \"Prompt specified as argument, using '$Prompt'\"\n}\nelse {\n    Write-Verbose \"Prompt not specified as argument to script, checking pyvenv.cfg value\"\n    if ($pyvenvCfg -and $pyvenvCfg['prompt']) {\n        Write-Verbose \"  Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'\"\n        $Prompt = $pyvenvCfg['prompt'];\n    }\n    else {\n        Write-Verbose \"  Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)\"\n        Write-Verbose \"  Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'\"\n        $Prompt = Split-Path -Path $venvDir -Leaf\n    }\n}\n\nWrite-Verbose \"Prompt = '$Prompt'\"\nWrite-Verbose \"VenvDir='$VenvDir'\"\n\n# Deactivate any currently active virtual environment, but leave the\n# deactivate function in place.\ndeactivate -nondestructive\n\n# Now set the environment variable VIRTUAL_ENV, used by many tools to determine\n# that there is an activated venv.\n$env:VIRTUAL_ENV = $VenvDir\n\nif (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {\n\n    Write-Verbose \"Setting prompt to '$Prompt'\"\n\n    # Set the prompt to include the env name\n    # Make sure _OLD_VIRTUAL_PROMPT is global\n    function global:_OLD_VIRTUAL_PROMPT { \"\" }\n    Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT\n    New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description \"Python virtual environment prompt prefix\" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt\n\n    function global:prompt {\n        Write-Host -NoNewline -ForegroundColor Green \"($_PYTHON_VENV_PROMPT_PREFIX) \"\n        _OLD_VIRTUAL_PROMPT\n    }\n    $env:VIRTUAL_ENV_PROMPT = $Prompt\n}\n\n# Clear PYTHONHOME\nif (Test-Path -Path Env:PYTHONHOME) {\n    Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME\n    Remove-Item -Path Env:PYTHONHOME\n}\n\n# Add the venv to the PATH\nCopy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH\n$Env:PATH = \"$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH\"\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/win-x64/venv/scripts/common/activate",
    "content": "# This file must be used with \"source bin/activate\" *from bash*\n# you cannot run it directly\n\ndeactivate () {\n    # reset old environment variables\n    if [ -n \"${_OLD_VIRTUAL_PATH:-}\" ] ; then\n        PATH=\"${_OLD_VIRTUAL_PATH:-}\"\n        export PATH\n        unset _OLD_VIRTUAL_PATH\n    fi\n    if [ -n \"${_OLD_VIRTUAL_PYTHONHOME:-}\" ] ; then\n        PYTHONHOME=\"${_OLD_VIRTUAL_PYTHONHOME:-}\"\n        export PYTHONHOME\n        unset _OLD_VIRTUAL_PYTHONHOME\n    fi\n\n    # This should detect bash and zsh, which have a hash command that must\n    # be called to get it to forget past commands.  Without forgetting\n    # past commands the $PATH changes we made may not be respected\n    if [ -n \"${BASH:-}\" -o -n \"${ZSH_VERSION:-}\" ] ; then\n        hash -r 2> /dev/null\n    fi\n\n    if [ -n \"${_OLD_VIRTUAL_PS1:-}\" ] ; then\n        PS1=\"${_OLD_VIRTUAL_PS1:-}\"\n        export PS1\n        unset _OLD_VIRTUAL_PS1\n    fi\n\n    unset VIRTUAL_ENV\n    unset VIRTUAL_ENV_PROMPT\n    if [ ! \"${1:-}\" = \"nondestructive\" ] ; then\n    # Self destruct!\n        unset -f deactivate\n    fi\n}\n\n# unset irrelevant variables\ndeactivate nondestructive\n\nVIRTUAL_ENV=\"__VENV_DIR__\"\nexport VIRTUAL_ENV\n\n_OLD_VIRTUAL_PATH=\"$PATH\"\nPATH=\"$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH\"\nexport PATH\n\n# unset PYTHONHOME if set\n# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)\n# could use `if (set -u; : $PYTHONHOME) ;` in bash\nif [ -n \"${PYTHONHOME:-}\" ] ; then\n    _OLD_VIRTUAL_PYTHONHOME=\"${PYTHONHOME:-}\"\n    unset PYTHONHOME\nfi\n\nif [ -z \"${VIRTUAL_ENV_DISABLE_PROMPT:-}\" ] ; then\n    _OLD_VIRTUAL_PS1=\"${PS1:-}\"\n    PS1=\"__VENV_PROMPT__${PS1:-}\"\n    export PS1\n    VIRTUAL_ENV_PROMPT=\"__VENV_PROMPT__\"\n    export VIRTUAL_ENV_PROMPT\nfi\n\n# This should detect bash and zsh, which have a hash command that must\n# be called to get it to forget past commands.  Without forgetting\n# past commands the $PATH changes we made may not be respected\nif [ -n \"${BASH:-}\" -o -n \"${ZSH_VERSION:-}\" ] ; then\n    hash -r 2> /dev/null\nfi\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/win-x64/venv/scripts/nt/activate.bat",
    "content": "@echo off\n\nrem This file is UTF-8 encoded, so we need to update the current code page while executing it\nfor /f \"tokens=2 delims=:.\" %%a in ('\"%SystemRoot%\\System32\\chcp.com\"') do (\n    set _OLD_CODEPAGE=%%a\n)\nif defined _OLD_CODEPAGE (\n    \"%SystemRoot%\\System32\\chcp.com\" 65001 > nul\n)\n\nset VIRTUAL_ENV=__VENV_DIR__\n\nif not defined PROMPT set PROMPT=$P$G\n\nif defined _OLD_VIRTUAL_PROMPT set PROMPT=%_OLD_VIRTUAL_PROMPT%\nif defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%\n\nset _OLD_VIRTUAL_PROMPT=%PROMPT%\nset PROMPT=__VENV_PROMPT__%PROMPT%\n\nif defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%\nset PYTHONHOME=\n\nif defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH%\nif not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH%\n\nset PATH=%VIRTUAL_ENV%\\__VENV_BIN_NAME__;%PATH%\nset VIRTUAL_ENV_PROMPT=__VENV_PROMPT__\n\n:END\nif defined _OLD_CODEPAGE (\n    \"%SystemRoot%\\System32\\chcp.com\" %_OLD_CODEPAGE% > nul\n    set _OLD_CODEPAGE=\n)\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/win-x64/venv/scripts/nt/deactivate.bat",
    "content": "@echo off\n\nif defined _OLD_VIRTUAL_PROMPT (\n    set \"PROMPT=%_OLD_VIRTUAL_PROMPT%\"\n)\nset _OLD_VIRTUAL_PROMPT=\n\nif defined _OLD_VIRTUAL_PYTHONHOME (\n    set \"PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%\"\n    set _OLD_VIRTUAL_PYTHONHOME=\n)\n\nif defined _OLD_VIRTUAL_PATH (\n    set \"PATH=%_OLD_VIRTUAL_PATH%\"\n)\n\nset _OLD_VIRTUAL_PATH=\n\nset VIRTUAL_ENV=\nset VIRTUAL_ENV_PROMPT=\n\n:END\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/win-x64/venv/scripts/posix/activate.csh",
    "content": "# This file must be used with \"source bin/activate.csh\" *from csh*.\n# You cannot run it directly.\n# Created by Davide Di Blasi <davidedb@gmail.com>.\n# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>\n\nalias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH \"$_OLD_VIRTUAL_PATH\" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt=\"$_OLD_VIRTUAL_PROMPT\" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test \"\\!:*\" != \"nondestructive\" && unalias deactivate'\n\n# Unset irrelevant variables.\ndeactivate nondestructive\n\nsetenv VIRTUAL_ENV \"__VENV_DIR__\"\n\nset _OLD_VIRTUAL_PATH=\"$PATH\"\nsetenv PATH \"$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH\"\n\n\nset _OLD_VIRTUAL_PROMPT=\"$prompt\"\n\nif (! \"$?VIRTUAL_ENV_DISABLE_PROMPT\") then\n    set prompt = \"__VENV_PROMPT__$prompt\"\n    setenv VIRTUAL_ENV_PROMPT \"__VENV_PROMPT__\"\nendif\n\nalias pydoc python -m pydoc\n\nrehash\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets/win-x64/venv/scripts/posix/activate.fish",
    "content": "# This file must be used with \"source <venv>/bin/activate.fish\" *from fish*\n# (https://fishshell.com/); you cannot run it directly.\n\nfunction deactivate  -d \"Exit virtual environment and return to normal shell environment\"\n    # reset old environment variables\n    if test -n \"$_OLD_VIRTUAL_PATH\"\n        set -gx PATH $_OLD_VIRTUAL_PATH\n        set -e _OLD_VIRTUAL_PATH\n    end\n    if test -n \"$_OLD_VIRTUAL_PYTHONHOME\"\n        set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME\n        set -e _OLD_VIRTUAL_PYTHONHOME\n    end\n\n    if test -n \"$_OLD_FISH_PROMPT_OVERRIDE\"\n        set -e _OLD_FISH_PROMPT_OVERRIDE\n        # prevents error when using nested fish instances (Issue #93858)\n        if functions -q _old_fish_prompt\n            functions -e fish_prompt\n            functions -c _old_fish_prompt fish_prompt\n            functions -e _old_fish_prompt\n        end\n    end\n\n    set -e VIRTUAL_ENV\n    set -e VIRTUAL_ENV_PROMPT\n    if test \"$argv[1]\" != \"nondestructive\"\n        # Self-destruct!\n        functions -e deactivate\n    end\nend\n\n# Unset irrelevant variables.\ndeactivate nondestructive\n\nset -gx VIRTUAL_ENV \"__VENV_DIR__\"\n\nset -gx _OLD_VIRTUAL_PATH $PATH\nset -gx PATH \"$VIRTUAL_ENV/__VENV_BIN_NAME__\" $PATH\n\n# Unset PYTHONHOME if set.\nif set -q PYTHONHOME\n    set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME\n    set -e PYTHONHOME\nend\n\nif test -z \"$VIRTUAL_ENV_DISABLE_PROMPT\"\n    # fish uses a function instead of an env var to generate the prompt.\n\n    # Save the current fish_prompt function as the function _old_fish_prompt.\n    functions -c fish_prompt _old_fish_prompt\n\n    # With the original prompt function renamed, we can override with our own.\n    function fish_prompt\n        # Save the return status of the last command.\n        set -l old_status $status\n\n        # Output the venv prompt; color taken from the blue of the Python logo.\n        printf \"%s%s%s\" (set_color 4B8BBE) \"__VENV_PROMPT__\" (set_color normal)\n\n        # Restore the return status of the previous command.\n        echo \"exit $old_status\" | .\n        # Output the original/\"old\" prompt.\n        _old_fish_prompt\n    end\n\n    set -gx _OLD_FISH_PROMPT_OVERRIDE \"$VIRTUAL_ENV\"\n    set -gx VIRTUAL_ENV_PROMPT \"__VENV_PROMPT__\"\nend\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Assets.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Runtime.Versioning;\nusing Avalonia.Platform;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia;\n\ninternal static class Assets\n{\n    public static AvaloniaResource AppIcon { get; } =\n        new(\"avares://StabilityMatrix.Avalonia/Assets/Icon.ico\");\n\n    public static AvaloniaResource AppIconPng { get; } =\n        new(\"avares://StabilityMatrix.Avalonia/Assets/Icon.png\");\n\n    /// <summary>\n    /// Fixed image for models with no images.\n    /// </summary>\n    public static Uri NoImage { get; } = new(\"avares://StabilityMatrix.Avalonia/Assets/noimage.png\");\n\n    public static AvaloniaResource LicensesJson =>\n        new(\"avares://StabilityMatrix.Avalonia/Assets/licenses.json\");\n\n    public static AvaloniaResource ImagePromptLanguageJson =>\n        new(\"avares://StabilityMatrix.Avalonia/Assets/ImagePrompt.tmLanguage.json\");\n\n    public static AvaloniaResource ThemeMatrixDarkJson =>\n        new(\"avares://StabilityMatrix.Avalonia/Assets/ThemeMatrixDark.json\");\n\n    public static AvaloniaResource HfPackagesJson =>\n        new(\"avares://StabilityMatrix.Avalonia/Assets/hf-packages.json\");\n\n    public static AvaloniaResource MarkdownCss =>\n        new(\"avares://StabilityMatrix.Avalonia/Assets/markdown.css\");\n\n    private const UnixFileMode Unix755 =\n        UnixFileMode.UserRead\n        | UnixFileMode.UserWrite\n        | UnixFileMode.UserExecute\n        | UnixFileMode.GroupRead\n        | UnixFileMode.GroupExecute\n        | UnixFileMode.OtherRead\n        | UnixFileMode.OtherExecute;\n\n    [SupportedOSPlatform(\"windows\")]\n    [SupportedOSPlatform(\"linux\")]\n    [SupportedOSPlatform(\"macos\")]\n    public static AvaloniaResource SevenZipExecutable =>\n        Compat.Switch(\n            (\n                PlatformKind.Windows,\n                new AvaloniaResource(\"avares://StabilityMatrix.Avalonia/Assets/win-x64/7za.exe\")\n            ),\n            (\n                PlatformKind.Linux | PlatformKind.X64,\n                new AvaloniaResource(\"avares://StabilityMatrix.Avalonia/Assets/linux-x64/7zzs\", Unix755)\n            ),\n            (\n                PlatformKind.MacOS | PlatformKind.Arm,\n                new AvaloniaResource(\"avares://StabilityMatrix.Avalonia/Assets/macos-arm64/7zz\", Unix755)\n            )\n        );\n\n    [SupportedOSPlatform(\"windows\")]\n    [SupportedOSPlatform(\"linux\")]\n    [SupportedOSPlatform(\"macos\")]\n    public static AvaloniaResource SevenZipLicense =>\n        Compat.Switch(\n            (\n                PlatformKind.Windows,\n                new AvaloniaResource(\"avares://StabilityMatrix.Avalonia/Assets/win-x64/7za - LICENSE.txt\")\n            ),\n            (\n                PlatformKind.Linux | PlatformKind.X64,\n                new AvaloniaResource(\"avares://StabilityMatrix.Avalonia/Assets/linux-x64/7zzs - LICENSE.txt\")\n            ),\n            (\n                PlatformKind.MacOS | PlatformKind.Arm,\n                new AvaloniaResource(\"avares://StabilityMatrix.Avalonia/Assets/macos-arm64/7zz - LICENSE.txt\")\n            )\n        );\n\n    public static AvaloniaResource PyScriptSiteCustomize =>\n        new(\"avares://StabilityMatrix.Avalonia/Assets/sitecustomize.py\");\n\n    [SupportedOSPlatform(\"windows\")]\n    public static AvaloniaResource PyScriptGetPip =>\n        new(\"avares://StabilityMatrix.Avalonia/Assets/win-x64/get-pip.pyc\");\n\n    [SupportedOSPlatform(\"windows\")]\n    public static IEnumerable<(AvaloniaResource resource, string relativePath)> PyModuleVenv =>\n        FindAssets(\"win-x64/venv/\");\n\n    [SupportedOSPlatform(\"windows\")]\n    [SupportedOSPlatform(\"linux\")]\n    [SupportedOSPlatform(\"macos\")]\n    public static RemoteResource PythonDownloadUrl =>\n        Compat.Switch(\n            (\n                PlatformKind.Windows | PlatformKind.X64,\n                new RemoteResource\n                {\n                    Url = new Uri(\"https://www.python.org/ftp/python/3.10.11/python-3.10.11-embed-amd64.zip\"),\n                    HashSha256 = \"608619f8619075629c9c69f361352a0da6ed7e62f83a0e19c63e0ea32eb7629d\"\n                }\n            ),\n            (\n                PlatformKind.Linux | PlatformKind.X64,\n                new RemoteResource\n                {\n                    Url = new Uri(\n                        \"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11+20230507-x86_64-unknown-linux-gnu-install_only.tar.gz\"\n                    ),\n                    HashSha256 = \"c5bcaac91bc80bfc29cf510669ecad12d506035ecb3ad85ef213416d54aecd79\"\n                }\n            ),\n            (\n                PlatformKind.MacOS | PlatformKind.Arm,\n                new RemoteResource\n                {\n                    // Requires our distribution with signed dylib for gatekeeper\n                    Url = new Uri(\"https://cdn.lykos.ai/cpython-3.10.11-macos-arm64.zip\"),\n                    HashSha256 = \"83c00486e0af9c460604a425e519d58e4b9604fbe7a4448efda0f648f86fb6e3\"\n                }\n            )\n        );\n\n    public static IReadOnlyList<RemoteResource> DefaultCompletionTags { get; } =\n        new[]\n        {\n            new RemoteResource\n            {\n                Url = new Uri(\"https://cdn.lykos.ai/tags/danbooru.csv\"),\n                HashSha256 = \"b84a879f1d9c47bf4758d66542598faa565b1571122ae12e7b145da8e7a4c1c6\"\n            },\n            new RemoteResource\n            {\n                Url = new Uri(\"https://cdn.lykos.ai/tags/e621.csv\"),\n                HashSha256 = \"ef7ea148ad865ad936d0c1ee57f0f83de723b43056c70b07fd67dbdbb89cae35\"\n            },\n            new RemoteResource\n            {\n                Url = new Uri(\"https://cdn.lykos.ai/tags/danbooru_e621_merged.csv\"),\n                HashSha256 = \"ac405ebce8b0caae363a7ef91f89beb4b8f60a7e218deb5078833686da6d497d\"\n            }\n        };\n\n    public static Uri DiscordServerUrl { get; } = new(\"https://discord.com/invite/TUrgfECxHz\");\n\n    public static Uri LykosUrl { get; } = new(\"https://lykos.ai\");\n\n    public static Uri PatreonUrl { get; } = new(\"https://patreon.com/StabilityMatrix\");\n\n    public static Uri CivitAIUrl { get; } = new(\"https://civitai.com\");\n\n    public static Uri LykosForgotPasswordUrl { get; } = new(\"https://lykos.ai/forgot-password\");\n\n    /// <summary>\n    /// Yield AvaloniaResources given a relative directory path within the 'Assets' folder.\n    /// </summary>\n    public static IEnumerable<(AvaloniaResource resource, string relativePath)> FindAssets(\n        string relativeAssetPath\n    )\n    {\n        var baseUri = new Uri(\"avares://StabilityMatrix.Avalonia/Assets/\");\n        var targetUri = new Uri(baseUri, relativeAssetPath);\n        var files = AssetLoader.GetAssets(targetUri, null);\n        foreach (var file in files)\n        {\n            yield return (new AvaloniaResource(file), targetUri.MakeRelativeUri(file).ToString());\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Behaviors/ConditionalToolTipBehavior.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Xaml.Interactivity;\n\nnamespace StabilityMatrix.Avalonia.Behaviors;\n\n/// <summary>\n/// Behavior that sets tooltip to null if the DisableOn condition is true.\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class ConditionalToolTipBehavior : Behavior<Control>\n{\n    public static readonly StyledProperty<bool> DisableOnProperty = AvaloniaProperty.Register<\n        ConditionalToolTipBehavior,\n        bool\n    >(\"DisableOn\");\n\n    public bool DisableOn\n    {\n        get => GetValue(DisableOnProperty);\n        set => SetValue(DisableOnProperty, value);\n    }\n\n    protected override void OnAttached()\n    {\n        base.OnAttached();\n\n        if (DisableOn)\n        {\n            ToolTip.SetTip(AssociatedObject!, null);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Behaviors/ResizeBehavior.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Data;\nusing Avalonia.Input;\nusing Avalonia.Media;\nusing Avalonia.Xaml.Interactivity;\nusing FluentAvalonia.UI.Controls;\n\nnamespace StabilityMatrix.Avalonia.Behaviors;\n\npublic class ResizeBehavior : Behavior<Control>\n{\n    public static readonly StyledProperty<Control?> PointerWheelTargetObjectProperty =\n        AvaloniaProperty.Register<ResizeBehavior, Control?>(nameof(PointerWheelTargetObject));\n\n    public Control? PointerWheelTargetObject\n    {\n        get => GetValue(PointerWheelTargetObjectProperty);\n        set => SetValue(PointerWheelTargetObjectProperty, value);\n    }\n\n    public static readonly StyledProperty<double> MinResizeFactorProperty = AvaloniaProperty.Register<\n        ResizeBehavior,\n        double\n    >(nameof(MinResizeFactor), 0.5);\n\n    public double MinResizeFactor\n    {\n        get => GetValue(MinResizeFactorProperty);\n        set => SetValue(MinResizeFactorProperty, value);\n    }\n\n    public static readonly StyledProperty<double> MaxResizeFactorProperty = AvaloniaProperty.Register<\n        ResizeBehavior,\n        double\n    >(nameof(MaxResizeFactor), 1.5);\n\n    public double MaxResizeFactor\n    {\n        get => GetValue(MaxResizeFactorProperty);\n        set => SetValue(MaxResizeFactorProperty, value);\n    }\n\n    public static readonly StyledProperty<double> ResizeFactorProperty = AvaloniaProperty.Register<\n        ResizeBehavior,\n        double\n    >(nameof(ResizeFactor), 1, defaultBindingMode: BindingMode.TwoWay, coerce: CoerceResizeFactor);\n\n    public double ResizeFactor\n    {\n        get => GetValue(ResizeFactorProperty);\n        set => SetValue(ResizeFactorProperty, value);\n    }\n\n    private static double CoerceResizeFactor(AvaloniaObject sender, double value)\n    {\n        return ValidateDouble(value)\n            ? Math.Clamp(\n                value,\n                sender.GetValue(MinResizeFactorProperty),\n                sender.GetValue(MaxResizeFactorProperty)\n            )\n            : sender.GetValue(ResizeFactorProperty);\n    }\n\n    public static readonly StyledProperty<double> SmallChangeProperty = AvaloniaProperty.Register<\n        ResizeBehavior,\n        double\n    >(nameof(SmallChange), 0.05);\n\n    public double SmallChange\n    {\n        get => GetValue(SmallChangeProperty);\n        set => SetValue(SmallChangeProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> UseMouseWheelResizeProperty = AvaloniaProperty.Register<\n        ResizeBehavior,\n        bool\n    >(nameof(UseMouseWheelResize), true);\n\n    public bool UseMouseWheelResize\n    {\n        get => GetValue(UseMouseWheelResizeProperty);\n        set => SetValue(UseMouseWheelResizeProperty, value);\n    }\n\n    protected override void OnAttached()\n    {\n        base.OnAttached();\n\n        if (PointerWheelTargetObject is null)\n            return;\n\n        PointerWheelTargetObject.PointerWheelChanged += OnPointerWheelChanged;\n    }\n\n    protected override void OnDetaching()\n    {\n        base.OnDetaching();\n\n        if (PointerWheelTargetObject is null)\n            return;\n\n        PointerWheelTargetObject.PointerWheelChanged -= OnPointerWheelChanged;\n    }\n\n    private void OnPointerWheelChanged(object? sender, PointerWheelEventArgs e)\n    {\n        if (e.KeyModifiers != KeyModifiers.Control)\n            return;\n\n        if (!UseMouseWheelResize)\n            return;\n\n        if (e.Delta.Y > 0 && ResizeFactor < MaxResizeFactor)\n        {\n            ResizeFactor += SmallChange;\n        }\n        else if (e.Delta.Y < 0 && ResizeFactor > MinResizeFactor)\n        {\n            ResizeFactor -= SmallChange;\n        }\n\n        e.Handled = true;\n\n        AssociatedObject?.InvalidateMeasure();\n        AssociatedObject?.InvalidateArrange();\n    }\n\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        if (change.Property == ResizeFactorProperty)\n        {\n            CoerceValue(ResizeFactorProperty);\n            AssociatedObject?.InvalidateMeasure();\n            AssociatedObject?.InvalidateArrange();\n        }\n    }\n\n    private static bool ValidateDouble(double value)\n    {\n        return !double.IsInfinity(value) && !double.IsNaN(value);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Behaviors/TextEditorCompletionBehavior.cs",
    "content": "using System;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Linq;\nusing System.Runtime.CompilerServices;\nusing Avalonia;\nusing Avalonia.Input;\nusing Avalonia.Threading;\nusing Avalonia.Xaml.Interactivity;\nusing AvaloniaEdit;\nusing AvaloniaEdit.Document;\nusing AvaloniaEdit.Editing;\nusing NLog;\nusing StabilityMatrix.Avalonia.Controls.CodeCompletion;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Tokens;\nusing TextMateSharp.Grammars;\n\nnamespace StabilityMatrix.Avalonia.Behaviors;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class TextEditorCompletionBehavior : Behavior<TextEditor>\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private TextEditor textEditor = null!;\n\n    /// <summary>\n    /// The current completion window, if open.\n    /// Is set to null when the window is closed.\n    /// </summary>\n    private CompletionWindow? completionWindow;\n\n    public static readonly StyledProperty<ICompletionProvider?> CompletionProviderProperty =\n        AvaloniaProperty.Register<TextEditorCompletionBehavior, ICompletionProvider?>(\n            nameof(CompletionProvider)\n        );\n\n    public ICompletionProvider? CompletionProvider\n    {\n        get => GetValue(CompletionProviderProperty);\n        set => SetValue(CompletionProviderProperty, value);\n    }\n\n    public static readonly StyledProperty<ITokenizerProvider?> TokenizerProviderProperty =\n        AvaloniaProperty.Register<TextEditorCompletionBehavior, ITokenizerProvider?>(\"TokenizerProvider\");\n\n    public ITokenizerProvider? TokenizerProvider\n    {\n        get => GetValue(TokenizerProviderProperty);\n        set => SetValue(TokenizerProviderProperty, value);\n    }\n\n    protected override void OnAttached()\n    {\n        base.OnAttached();\n\n        if (AssociatedObject is not { } editor)\n        {\n            throw new NullReferenceException(\"AssociatedObject is null\");\n        }\n\n        textEditor = editor;\n        textEditor.TextArea.TextEntered += TextArea_TextEntered;\n        textEditor.TextArea.KeyDown += TextArea_KeyDown;\n    }\n\n    protected override void OnDetaching()\n    {\n        base.OnDetaching();\n\n        textEditor.TextArea.TextEntered -= TextArea_TextEntered;\n        textEditor.TextArea.KeyDown -= TextArea_KeyDown;\n    }\n\n    private CompletionWindow CreateCompletionWindow(TextArea textArea)\n    {\n        var window = new CompletionWindow(textArea, CompletionProvider!, TokenizerProvider!)\n        {\n            WindowManagerAddShadowHint = false,\n            CloseWhenCaretAtBeginning = true,\n            CloseAutomatically = true,\n            IsLightDismissEnabled = true,\n            CompletionList = { IsFiltering = true }\n        };\n        return window;\n    }\n\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public void InvokeManualCompletion()\n    {\n        if (CompletionProvider is null)\n        {\n            throw new NullReferenceException(\"CompletionProvider is null\");\n        }\n\n        // If window already open, skip since handled by completion window\n        // Unless this is an end char, where we'll open a new window\n        if (completionWindow is { ToolTipIsOpen: true })\n        {\n            Logger.ConditionalTrace(\"Skipping, completion window already open\");\n            return;\n        }\n        completionWindow?.Hide();\n        completionWindow = null;\n\n        // Get the segment of the token the caret is currently in\n        if (GetCaretCompletionToken() is not { } completionRequest)\n        {\n            Logger.ConditionalTrace(\"Token segment not found\");\n            return;\n        }\n\n        // If type is not available, skip\n        if (!CompletionProvider.AvailableCompletionTypes.HasFlag(completionRequest.Type))\n        {\n            Logger.ConditionalTrace(\n                \"Skipping, completion type {CompletionType} not available in {AvailableTypes}\",\n                completionRequest.Type,\n                CompletionProvider.AvailableCompletionTypes\n            );\n            return;\n        }\n\n        var tokenSegment = completionRequest.Segment;\n\n        var token = textEditor.Document.GetText(tokenSegment);\n        Logger.ConditionalTrace(\"Using token {Token} ({@Segment})\", token, tokenSegment);\n\n        var newWindow = CreateCompletionWindow(textEditor.TextArea);\n        newWindow.StartOffset = tokenSegment.Offset;\n        newWindow.EndOffset = tokenSegment.EndOffset;\n\n        newWindow.UpdateQuery(completionRequest);\n\n        newWindow.Closed += CompletionWindow_OnClosed;\n\n        completionWindow = newWindow;\n\n        newWindow.Show();\n    }\n\n    private void CompletionWindow_OnClosed(object? sender, EventArgs e)\n    {\n        if (ReferenceEquals(sender, completionWindow))\n        {\n            completionWindow = null;\n        }\n\n        Logger.ConditionalTrace(\"Completion window closed\");\n\n        if (sender is CompletionWindow window)\n        {\n            window.Closed -= CompletionWindow_OnClosed;\n        }\n    }\n\n    private void TextArea_TextEntered(object? sender, TextInputEventArgs e)\n    {\n        Logger.ConditionalTrace(\"Text entered: {Text}\", e.Text);\n\n        if (!IsEnabled || CompletionProvider is null)\n        {\n            Logger.ConditionalTrace(\"Skipping, not enabled\");\n            return;\n        }\n\n        if (e.Text is not { } triggerText)\n        {\n            Logger.ConditionalTrace(\"Skipping, null trigger text\");\n            return;\n        }\n\n        if (!triggerText.All(IsCompletionChar))\n        {\n            Logger.ConditionalTrace($\"Skipping, invalid trigger text: {triggerText.ToRepr()}\");\n            return;\n        }\n\n        Dispatcher.UIThread.Post(InvokeManualCompletion, DispatcherPriority.Input);\n    }\n\n    private void TextArea_KeyDown(object? sender, KeyEventArgs e)\n    {\n        if (e is { Key: Key.Space, KeyModifiers: KeyModifiers.Control })\n        {\n            InvokeManualCompletion();\n            e.Handled = true;\n        }\n    }\n\n    /// <summary>\n    /// Highlights the text segment in the text editor\n    /// </summary>\n    private void HighlightTextSegment(ISegment segment)\n    {\n        textEditor.TextArea.Selection = Selection.Create(textEditor.TextArea, segment);\n    }\n\n    private static bool IsCompletionChar(char c)\n    {\n        const string extraAllowedChars = \"._-:<\";\n        return char.IsLetterOrDigit(c) || extraAllowedChars.Contains(c);\n    }\n\n    private static bool IsCompletionEndChar(char c)\n    {\n        const string endChars = \":\";\n        return endChars.Contains(c);\n    }\n\n    /// <summary>\n    /// Gets a segment of the token the caret is currently in for completions.\n    /// Returns null if caret is not on a valid completion token (i.e. comments)\n    /// </summary>\n    private EditorCompletionRequest? GetCaretCompletionToken()\n    {\n        var caret = textEditor.CaretOffset;\n\n        // Get the line the caret is on\n        var line = textEditor.Document.GetLineByOffset(caret);\n        var lineText = textEditor.Document.GetText(line.Offset, line.Length);\n\n        var caretAbsoluteOffset = caret - line.Offset;\n\n        // Tokenize\n        var result = TokenizerProvider!.TokenizeLine(lineText);\n\n        var currentTokenIndex = -1;\n        IToken? currentToken = null;\n        // Get the token the caret is after\n        foreach (var (i, token) in result.Tokens.Enumerate())\n        {\n            // If we see a line comment token anywhere, return null\n            var isComment = token.Scopes.Any(s => s.Contains(\"comment.line\"));\n            if (isComment)\n            {\n                Logger.Trace(\"Caret is in a comment\");\n                return null;\n            }\n\n            // Find match\n            if (caretAbsoluteOffset >= token.StartIndex && caretAbsoluteOffset <= token.EndIndex)\n            {\n                currentTokenIndex = i;\n                currentToken = token;\n                break;\n            }\n        }\n\n        // Still not found\n        if (currentToken is null || currentTokenIndex == -1)\n        {\n            Logger.Info($\"Could not find token at caret offset {caret} for line {lineText.ToRepr()}\");\n            return null;\n        }\n\n        var startOffset = currentToken.StartIndex + line.Offset;\n        var endOffset = currentToken.EndIndex + line.Offset;\n\n        // Cap the offsets by the line offsets\n        var segment = new TextSegment\n        {\n            StartOffset = Math.Max(startOffset, line.Offset),\n            EndOffset = Math.Min(endOffset, line.EndOffset)\n        };\n\n        // Check if this is an extra network request\n        if (currentToken.Scopes.Contains(\"meta.structure.network.prompt\"))\n        {\n            // (case for initial '<')\n            // - Current token is \"punctuation.definition.network.begin.prompt\"\n            if (currentToken.Scopes.Contains(\"punctuation.definition.network.begin.prompt\"))\n            {\n                // Offset the segment\n                var offsetSegment = new TextSegment\n                {\n                    StartOffset = segment.StartOffset + 1,\n                    EndOffset = segment.EndOffset\n                };\n                return new EditorCompletionRequest\n                {\n                    Text = \"\",\n                    Segment = offsetSegment,\n                    Type = CompletionType.ExtraNetworkType\n                };\n            }\n\n            // Next steps require a previous token\n            if (result.Tokens.ElementAtOrDefault(currentTokenIndex - 1) is not { } prevToken)\n            {\n                return null;\n            }\n\n            // (case for initial '<type')\n            // - Current token has \"meta.embedded.network.type.prompt\"\n            if (currentToken.Scopes.Contains(\"meta.embedded.network.type.prompt\"))\n            {\n                return new EditorCompletionRequest\n                {\n                    Text = textEditor.Document.GetText(segment),\n                    Segment = segment,\n                    Type = CompletionType.ExtraNetworkType\n                };\n            }\n\n            // (case for initial '<type:')\n            // - Current token has \"meta.structure.network\" and \"punctuation.separator.variable\"\n            // - Previous token has \"meta.structure.network\" and \"meta.embedded.network.type\"\n            if (\n                currentToken.Scopes.Contains(\"punctuation.separator.variable.prompt\")\n                && prevToken.Scopes.Contains(\"meta.structure.network.prompt\")\n                && prevToken.Scopes.Contains(\"meta.embedded.network.type.prompt\")\n            )\n            {\n                var networkToken = textEditor.Document.GetText(\n                    prevToken.StartIndex + line.Offset,\n                    prevToken.Length\n                );\n\n                PromptExtraNetworkType? networkTypeResult = networkToken.ToLowerInvariant() switch\n                {\n                    \"lora\" => PromptExtraNetworkType.Lora,\n                    \"lyco\" => PromptExtraNetworkType.LyCORIS,\n                    \"embedding\" => PromptExtraNetworkType.Embedding,\n                    _ => null\n                };\n\n                if (networkTypeResult is not { } networkType)\n                {\n                    return null;\n                }\n\n                // Use offset segment to not replace the ':'\n                var offsetSegment = new TextSegment\n                {\n                    StartOffset = segment.StartOffset + 1,\n                    EndOffset = segment.EndOffset\n                };\n\n                return new EditorCompletionRequest\n                {\n                    Text = \"\",\n                    Segment = offsetSegment,\n                    Type = CompletionType.ExtraNetwork,\n                    ExtraNetworkTypes = networkType,\n                };\n            }\n\n            // (case for already in model token '<type:network')\n            // - Current token has \"meta.embedded.network.model\"\n            if (currentToken.Scopes.Contains(\"meta.embedded.network.model.prompt\"))\n            {\n                var secondPrevToken = result.Tokens.ElementAtOrDefault(currentTokenIndex - 2);\n                if (secondPrevToken is null)\n                {\n                    return null;\n                }\n\n                var networkToken = textEditor.Document.GetText(\n                    secondPrevToken.StartIndex + line.Offset,\n                    secondPrevToken.Length\n                );\n\n                PromptExtraNetworkType? networkTypeResult = networkToken.ToLowerInvariant() switch\n                {\n                    \"lora\" => PromptExtraNetworkType.Lora,\n                    \"lyco\" => PromptExtraNetworkType.LyCORIS,\n                    \"embedding\" => PromptExtraNetworkType.Embedding,\n                    _ => null\n                };\n\n                if (networkTypeResult is not { } networkType)\n                {\n                    return null;\n                }\n\n                return new EditorCompletionRequest\n                {\n                    Text = textEditor.Document.GetText(segment),\n                    Segment = segment,\n                    Type = CompletionType.ExtraNetwork,\n                    ExtraNetworkTypes = networkType,\n                };\n            }\n        }\n\n        // Otherwise treat as tag\n        return new EditorCompletionRequest\n        {\n            Text = textEditor.Document.GetText(segment),\n            Segment = segment,\n            Type = CompletionType.Tag\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Behaviors/TextEditorToolTipBehavior.cs",
    "content": "﻿using System;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Input;\nusing Avalonia.Media;\nusing Avalonia.Xaml.Interactivity;\nusing AvaloniaEdit;\nusing AvaloniaEdit.Document;\nusing NLog;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Core.Extensions;\nusing TextMateSharp.Grammars;\n\nnamespace StabilityMatrix.Avalonia.Behaviors;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class TextEditorToolTipBehavior : Behavior<TextEditor>\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private TextEditor textEditor = null!;\n\n    /// <summary>\n    /// The current ToolTip, if open.\n    /// Is set to null when the Tooltip is closed.\n    /// </summary>\n    private ToolTip? toolTip;\n\n    public static readonly StyledProperty<ITokenizerProvider?> TokenizerProviderProperty =\n        AvaloniaProperty.Register<TextEditorCompletionBehavior, ITokenizerProvider?>(\"TokenizerProvider\");\n\n    public ITokenizerProvider? TokenizerProvider\n    {\n        get => GetValue(TokenizerProviderProperty);\n        set => SetValue(TokenizerProviderProperty, value);\n    }\n\n    protected override void OnAttached()\n    {\n        base.OnAttached();\n\n        if (AssociatedObject is not { } editor)\n        {\n            throw new NullReferenceException(\"AssociatedObject is null\");\n        }\n\n        textEditor = editor;\n        textEditor.PointerHover += TextEditor_OnPointerHover;\n        textEditor.PointerHoverStopped += TextEditor_OnPointerHoverStopped;\n    }\n\n    protected override void OnDetaching()\n    {\n        base.OnDetaching();\n\n        textEditor.PointerHover -= TextEditor_OnPointerHover;\n        textEditor.PointerHoverStopped -= TextEditor_OnPointerHoverStopped;\n    }\n\n    /*private void OnVisualLinesChanged(object? sender, EventArgs e)\n    {\n        _toolTip?.Close(this);\n    }*/\n\n    private void TextEditor_OnPointerHoverStopped(object? sender, PointerEventArgs e)\n    {\n        if (!IsEnabled)\n            return;\n\n        if (sender is TextEditor editor)\n        {\n            ToolTip.SetIsOpen(editor, false);\n            e.Handled = true;\n        }\n    }\n\n    private void TextEditor_OnPointerHover(object? sender, PointerEventArgs e)\n    {\n        if (!IsEnabled)\n            return;\n\n        TextViewPosition? position;\n\n        var textArea = textEditor.TextArea;\n\n        try\n        {\n            position = textArea.TextView.GetPositionFloor(\n                e.GetPosition(textArea.TextView) + textArea.TextView.ScrollOffset\n            );\n        }\n        catch (ArgumentOutOfRangeException)\n        {\n            // TODO: check why this happens\n            e.Handled = true;\n            return;\n        }\n\n        if (!position.HasValue || position.Value.Location.IsEmpty || position.Value.IsAtEndOfLine)\n        {\n            return;\n        }\n\n        /*var args = new ToolTipRequestEventArgs { InDocument = position.HasValue };\n\n        args.LogicalPosition = position.Value.Location;\n        args.Position = textEditor.Document.GetOffset(position.Value.Line, position.Value.Column);*/\n\n        // Get the ToolTip data\n        if (GetCaretToolTipData(position.Value) is not { } data)\n        {\n            return;\n        }\n\n        if (toolTip == null)\n        {\n            toolTip = new ToolTip { MaxWidth = 400 };\n\n            ToolTip.SetShowDelay(textEditor, 0);\n            ToolTip.SetPlacement(textEditor, PlacementMode.Pointer);\n            ToolTip.SetTip(textEditor, toolTip);\n\n            toolTip\n                .GetPropertyChangedObservable(ToolTip.IsOpenProperty)\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe(c =>\n                {\n                    if (c.NewValue as bool? != true)\n                    {\n                        toolTip = null;\n                    }\n                });\n        }\n\n        toolTip.Content = new TextBlock { Text = data.Message, TextWrapping = TextWrapping.Wrap };\n\n        e.Handled = true;\n        ToolTip.SetIsOpen(textEditor, true);\n        toolTip.InvalidateVisual();\n    }\n\n    /// <summary>\n    /// Get ToolTip data to show at the caret position, can be null if no ToolTip should be shown.\n    /// </summary>\n    private ToolTipData? GetCaretToolTipData(TextViewPosition position)\n    {\n        var logicalPosition = position.Location;\n        var pointerOffset = textEditor.Document.GetOffset(logicalPosition.Line, logicalPosition.Column);\n\n        var line = textEditor.Document.GetLineByOffset(pointerOffset);\n        var lineText = textEditor.Document.GetText(line.Offset, line.Length);\n\n        var lineOffset = pointerOffset - line.Offset;\n        /*var caret = textEditor.CaretOffset;\n        \n        // Get the line the caret is on\n        var line = textEditor.Document.GetLineByOffset(caret);\n        var lineText = textEditor.Document.GetText(line.Offset, line.Length);\n        \n        var caretAbsoluteOffset = caret - line.Offset;*/\n\n        // Tokenize\n        var result = TokenizerProvider!.TokenizeLine(lineText);\n\n        var currentTokenIndex = -1;\n        IToken? currentToken = null;\n        // Get the token the caret is after\n        foreach (var (i, token) in result.Tokens.Enumerate())\n        {\n            // If we see a line comment token anywhere, return null\n            var isComment = token.Scopes.Any(s => s.Contains(\"comment.line\"));\n            if (isComment)\n            {\n                Logger.Trace(\"Caret is in a comment\");\n                return null;\n            }\n\n            // Find match\n            if (lineOffset >= token.StartIndex && lineOffset <= token.EndIndex)\n            {\n                currentTokenIndex = i;\n                currentToken = token;\n                break;\n            }\n        }\n\n        // Still not found\n        if (currentToken is null || currentTokenIndex == -1)\n        {\n            Logger.Info(\n                $\"Could not find token at pointer offset {pointerOffset} for line {lineText.ToRepr()}\"\n            );\n            return null;\n        }\n\n        var startOffset = currentToken.StartIndex + line.Offset;\n        var endOffset = currentToken.EndIndex + line.Offset;\n\n        // Cap the offsets by the line offsets\n        var segment = new TextSegment\n        {\n            StartOffset = Math.Max(startOffset, line.Offset),\n            EndOffset = Math.Min(endOffset, line.EndOffset)\n        };\n\n        // Only return for supported scopes\n        // Attempt with first current, then next and previous\n        foreach (var tokenOffset in new[] { 0, 1, -1 })\n        {\n            if (result.Tokens.ElementAtOrDefault(currentTokenIndex + tokenOffset) is { } token)\n            {\n                // Check supported scopes\n                if (token.Scopes.Where(s => s.Contains(\"invalid\")).ToArray() is { Length: > 0 } results)\n                {\n                    // Special cases\n                    if (results.Contains(\"invalid.illegal.mismatched.parenthesis.closing.prompt\"))\n                    {\n                        return new ToolTipData(segment, \"Mismatched closing parenthesis ')'\");\n                    }\n                    if (results.Contains(\"invalid.illegal.mismatched.parenthesis.opening.prompt\"))\n                    {\n                        return new ToolTipData(segment, \"Mismatched opening parenthesis '('\");\n                    }\n                    if (results.Contains(\"invalid.illegal.expected-weight-separator.prompt\"))\n                    {\n                        return new ToolTipData(segment, \"Expected numeric weight\");\n                    }\n\n                    return new ToolTipData(segment, \"Syntax error: \" + string.Join(\", \", results));\n                }\n            }\n        }\n\n        return null;\n    }\n\n    internal record ToolTipData(ISegment Segment, string Message);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Behaviors/TextEditorWeightAdjustmentBehavior.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Globalization;\nusing Avalonia;\nusing Avalonia.Input;\nusing Avalonia.Xaml.Interactivity;\nusing AvaloniaEdit;\nusing NLog;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.PromptSyntax;\nusing TextMateSharp.Grammars;\n\nnamespace StabilityMatrix.Avalonia.Behaviors;\n\n[Localizable(false)]\npublic class TextEditorWeightAdjustmentBehavior : Behavior<TextEditor>\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private TextEditor? textEditor;\n\n    public static readonly StyledProperty<ITokenizerProvider?> TokenizerProviderProperty =\n        AvaloniaProperty.Register<TextEditorWeightAdjustmentBehavior, ITokenizerProvider?>(\n            nameof(TokenizerProvider)\n        );\n\n    public ITokenizerProvider? TokenizerProvider\n    {\n        get => GetValue(TokenizerProviderProperty);\n        set => SetValue(TokenizerProviderProperty, value);\n    }\n\n    public static readonly StyledProperty<double> WeightIncrementProperty = AvaloniaProperty.Register<\n        TextEditorWeightAdjustmentBehavior,\n        double\n    >(nameof(WeightIncrement), 0.1);\n\n    public double WeightIncrement\n    {\n        get => GetValue(WeightIncrementProperty);\n        set => SetValue(WeightIncrementProperty, value);\n    }\n\n    public static readonly StyledProperty<double> MinWeightProperty = AvaloniaProperty.Register<\n        TextEditorWeightAdjustmentBehavior,\n        double\n    >(nameof(MinWeight), -10.0);\n\n    public double MinWeight\n    {\n        get => GetValue(MinWeightProperty);\n        set => SetValue(MinWeightProperty, value);\n    }\n\n    public static readonly StyledProperty<double> MaxWeightProperty = AvaloniaProperty.Register<\n        TextEditorWeightAdjustmentBehavior,\n        double\n    >(nameof(MaxWeight), 10.0);\n\n    public double MaxWeight\n    {\n        get => GetValue(MaxWeightProperty);\n        set => SetValue(MaxWeightProperty, value);\n    }\n\n    protected override void OnAttached()\n    {\n        base.OnAttached();\n\n        if (AssociatedObject is { } editor)\n        {\n            textEditor = editor;\n            textEditor.KeyDown += TextEditor_KeyDown;\n        }\n    }\n\n    protected override void OnDetaching()\n    {\n        base.OnDetaching();\n\n        if (textEditor != null)\n        {\n            textEditor.KeyDown -= TextEditor_KeyDown;\n        }\n    }\n\n    private void TextEditor_KeyDown(object? sender, KeyEventArgs e)\n    {\n        // Control for Win / Cmd for macOS\n        var modifier = Compat.IsMacOS ? KeyModifiers.Meta : KeyModifiers.Control;\n\n        if (!e.KeyModifiers.HasFlag(modifier) || e.Key is not (Key.Up or Key.Down))\n            return;\n\n        e.Handled = true;\n\n        // Calculate the weight delta\n        var delta = e.Key == Key.Up ? WeightIncrement : -WeightIncrement;\n\n        try\n        {\n            HandleWeightAdjustment(delta);\n        }\n        catch (Exception exception)\n        {\n            Logger.Warn(exception, \"Failed to handle weight adjustment: {Msg}\", exception.Message);\n        }\n    }\n\n    [Localizable(false)]\n    private void HandleWeightAdjustment(double delta)\n    {\n        if (TokenizerProvider is null || textEditor is null)\n            return;\n\n        // Backup the current caret position\n        var caretOffset = textEditor.CaretOffset;\n\n        // 1. Determine the range to operate on (selection or caret token).\n        var editorSelectionSegment = textEditor.GetSelectionSegment();\n\n        Logger.Info(\n            \"Adjusting weight ({Delta}) (Caret: {Caret}, Selection: {Selection})\",\n            delta,\n            caretOffset,\n            editorSelectionSegment\n        );\n\n        try\n        {\n            // 1. Tokenize the entire text\n            var text = textEditor.Document.Text;\n            var tokenizeResult = TokenizerProvider.TokenizeLine(text);\n\n            // 2. Get the token segment\n            if (\n                (\n                    editorSelectionSegment != null\n                        ? GetSelectedTokenSpan(tokenizeResult)\n                        : GetCaretTokenSpan(tokenizeResult)\n                )\n                is not { } tokenSegment\n            )\n            {\n                Logger.Warn(\"No token segment found\");\n                return;\n            }\n\n            Logger.Debug(\"Token segment: {Segment}\", tokenSegment);\n\n            // 3. Build the AST\n            var astBuilder = new PromptSyntaxBuilder(tokenizeResult, text);\n            var ast = astBuilder.BuildAST();\n\n            // Find *all* nodes that contains the selection/caret range\n            var selectedNodes = ast.RootNode.Content.Where(node => node.Span.Contains(tokenSegment)).ToList();\n\n            // If empty use intersection instead\n            if (selectedNodes.Count == 0)\n            {\n                selectedNodes = ast\n                    .RootNode.Content.Where(node => node.Span.IntersectsWith(tokenSegment))\n                    .ToList();\n            }\n\n            Logger.Trace(\"Selected nodes: {Nodes}\", selectedNodes);\n\n            // Trim any leading or trailing separator nodes\n            selectedNodes = selectedNodes\n                .SkipWhile(node => node is SeperatorNode)\n                .Reverse()\n                .SkipWhile(node => node is SeperatorNode)\n                .Reverse()\n                .ToList();\n\n            // Find smallest containing nodes\n            var smallestNodes = selectedNodes\n                .Select(node =>\n                {\n                    // For nodes that *fully contain* the selection, get smallest containing descendant instead\n                    if (!node.Span.Contains(tokenSegment))\n                        return node;\n                    return node.FindSmallestContainingDescendant(tokenSegment);\n                })\n                .ToList();\n\n            Logger.Trace(\"Smallest nodes: {Nodes}\", smallestNodes);\n\n            // Go up and find the first parenthesized node, if any\n            // (Considering only the first and last of the smallest nodes)\n            var parenthesisTargets = smallestNodes.Take(1).ToList();\n            if (smallestNodes.Count > 1)\n            {\n                parenthesisTargets.Add(smallestNodes.Last());\n            }\n\n            Logger.Trace(\"Parenthesis targets: {Nodes}\", parenthesisTargets);\n\n            var parenthesizedNode = parenthesisTargets\n                .SelectMany(x => x.AncestorsAndSelf())\n                .OfType<ParenthesizedNode>()\n                .FirstOrDefault();\n\n            // Logger.Trace(\"Parenthesized node: {Node} of {Nodes}\", parenthesizedNode, parenthesisTargets);\n\n            var currentWeight = 1.0;\n            int replacementOffset; // Offset to start replacing text\n            int replacementLength; // Length of text to replace (0 if inserting)\n            string newText;\n\n            if (parenthesizedNode != null)\n            {\n                // We're inside parentheses.  Get the existing weight, if any.\n                currentWeight = (double?)parenthesizedNode.Weight?.Value ?? 1.0;\n\n                // Calculate new weight\n                var newWeight = Math.Clamp(currentWeight + delta, MinWeight, MaxWeight);\n\n                if (parenthesizedNode.Weight is not null) // if the weight exists\n                {\n                    // Replace existing weight\n                    replacementOffset = parenthesizedNode.Weight.StartIndex;\n                    replacementLength = parenthesizedNode.Weight.Length;\n                    newText = FormatWeight(newWeight);\n                }\n                else\n                {\n                    // Insert the weight before the closing parenthesis.\n                    replacementOffset = parenthesizedNode.EndIndex - 1; // EndIndex is exclusive, so -1\n                    replacementLength = 0; // insert\n                    newText = $\":{FormatWeight(newWeight)}\";\n                }\n            }\n            else\n            {\n                // Not inside parentheses. Wrap the selected tokens and add the weight.\n                var selectedText = ast.GetSourceText(tokenSegment);\n\n                var newWeight = Math.Clamp(currentWeight + delta, MinWeight, MaxWeight);\n\n                replacementOffset = tokenSegment.Start;\n                replacementLength = tokenSegment.Length;\n                newText = $\"({selectedText}:{FormatWeight(newWeight)})\";\n            }\n\n            // 8. Replace the text.\n            Logger.Debug(\n                \"Replacing source text {SrcText} at {SrcRange}, with new text {NewText}\",\n                text[replacementOffset..(replacementOffset + replacementLength)],\n                new TextSpan(replacementOffset, replacementLength),\n                newText\n            );\n            textEditor.Document.Replace(replacementOffset, replacementLength, newText);\n\n            // Plus 1 to caret if we added parenthesis\n            if (parenthesizedNode == null)\n            {\n                caretOffset += 1;\n            }\n\n            // 9. Update caret/selection.\n            if (editorSelectionSegment is not null)\n            {\n                // Restore the caret position\n                textEditor.CaretOffset = caretOffset;\n\n                // textEditor.SelectionStart = tokenSegment.Offset;\n                // TODO: textEditor.SelectionEnd = tokenSegment.Offset + newText.Length;\n            }\n            else\n            {\n                // Restore the caret position\n                textEditor.CaretOffset = caretOffset;\n\n                // Put it inside the parenthesis\n                // textEditor.CaretOffset = replacementOffset + newText.Length;\n            }\n        }\n        catch (Exception exception)\n        {\n            Logger.Warn(exception, \"Failed to handle weight adjustment: {Msg}\", exception.Message);\n        }\n    }\n\n    private TextSpan? GetSelectedTokenSpan(ITokenizeLineResult result)\n    {\n        if (textEditor is null)\n            return null;\n\n        if (textEditor.SelectionLength == 0)\n            return null;\n\n        var selectionStart = textEditor.SelectionStart;\n        var selectionEnd = selectionStart + textEditor.SelectionLength;\n\n        IToken? startToken = null;\n        IToken? endToken = null;\n\n        // Find the tokens that intersect the selection.\n        foreach (var token in result.Tokens)\n        {\n            var tokenStart = token.StartIndex;\n            var tokenEnd = token.EndIndex;\n\n            if (tokenEnd > selectionStart && startToken is null)\n            {\n                startToken = token;\n            }\n            if (tokenStart <= selectionEnd)\n            {\n                endToken = token;\n            }\n\n            if (tokenStart > selectionEnd || tokenEnd >= selectionEnd)\n            {\n                break; // Optimization: We've passed the selection, so we can stop.\n            }\n        }\n\n        if (startToken is null || endToken is null)\n            return null;\n\n        // Ensure end index is within length of text\n        var endIndex = Math.Min(endToken.EndIndex, textEditor.Document.TextLength);\n\n        return TextSpan.FromBounds(startToken.StartIndex, endIndex);\n    }\n\n    private TextSpan? GetCaretTokenSpan(ITokenizeLineResult result)\n    {\n        var caretAbsoluteOffset = textEditor!.CaretOffset;\n        var textEndOffset = textEditor.Document.TextLength;\n\n        IToken? currentToken = null;\n        var currentTokenIndex = -1;\n        // Get the token the caret is after\n        for (var i = 0; i < result.Tokens.Length; i++)\n        {\n            var token = result.Tokens[i];\n            // If we see a line comment token anywhere, return null\n            /*var isComment = token.Scopes.Any(s => s.Contains(\"comment.line\"));\n            if (isComment)\n            {\n                return null;\n            }*/\n\n            // Find match\n            if (caretAbsoluteOffset >= token.StartIndex && caretAbsoluteOffset < token.EndIndex)\n            {\n                currentToken = token;\n                currentTokenIndex = i;\n                break;\n            }\n\n            // If last token, also allow just start match\n            if (i == result.Tokens.Length - 1 && caretAbsoluteOffset >= token.StartIndex)\n            {\n                currentToken = token;\n                currentTokenIndex = i;\n                break;\n            }\n        }\n\n        // Check if the token is a separator, if so check the previous or next token instead\n        if (currentToken?.Scopes is { } scopes && scopes.Contains(\"punctuation.separator.variable.prompt\"))\n        {\n            // Check if we have a prev token\n            if (\n                result.Tokens.ElementAtOrDefault(currentTokenIndex - 1) is { } prevToken\n                && !prevToken.Scopes.Contains(\"punctuation.separator.variable.prompt\")\n            )\n            {\n                Logger.Trace(\n                    \"Matched on seperator, using previous token: {Current} -> {Prev}\",\n                    currentToken,\n                    prevToken\n                );\n                currentToken = prevToken;\n            }\n            // Check if we have a next token\n            else if (\n                result.Tokens.ElementAtOrDefault(currentTokenIndex + 1) is { } nextToken\n                && !nextToken.Scopes.Contains(\"punctuation.separator.variable.prompt\")\n            )\n            {\n                Logger.Trace(\n                    \"Matched on seperator, using next token: {Current} -> {Next}\",\n                    currentToken,\n                    nextToken\n                );\n                currentToken = nextToken;\n            }\n        }\n\n        // Still not found or not \"text\" token (meta.embedded).\n        if (\n            currentToken?.Scopes is null\n            || !(\n                currentToken.Scopes.Contains(\"meta.embedded\")\n                || currentToken.Scopes.Contains(\"meta.structure.array.prompt\")\n            )\n        )\n        {\n            return null;\n        }\n\n        // Cap the offsets by the line offsets\n        var startOffset = Math.Max(currentToken.StartIndex, 0);\n        var endOffset = Math.Min(currentToken.EndIndex, textEndOffset);\n\n        return TextSpan.FromBounds(startOffset, endOffset);\n    }\n\n    [Localizable(false)]\n    private static string FormatWeight(double weight)\n    {\n        // Format the weight to one decimal place\n        var formattedWeight = weight.ToString(\"F1\", CultureInfo.InvariantCulture);\n\n        // Strip trailing 0\n        if (formattedWeight.EndsWith(\".0\", StringComparison.InvariantCulture))\n        {\n            formattedWeight = formattedWeight[..^2];\n        }\n\n        return formattedWeight;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Collections/SearchCollection.cs",
    "content": "using System;\nusing System.Linq;\nusing System.Reactive;\nusing System.Reactive.Disposables;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing DynamicData;\nusing DynamicData.Binding;\nusing JetBrains.Annotations;\n\nnamespace StabilityMatrix.Avalonia.Collections;\n\n[PublicAPI]\npublic class SearchCollection<TObject, TKey, TQuery> : AbstractNotifyPropertyChanged, IDisposable\n    where TObject : notnull\n    where TKey : notnull\n{\n    private readonly IDisposable cleanUp;\n\n    private Func<TQuery?, Func<TObject, bool>>? PredicateSelector { get; }\n    private Func<TQuery?, Func<TObject, (bool, int)>>? ScorerSelector { get; }\n    private Func<TObject, (bool, int)>? Scorer { get; set; }\n\n    private TQuery? _query;\n    public TQuery? Query\n    {\n        get => _query;\n        set => SetAndRaise(ref _query, value);\n    }\n\n    private SortExpressionComparer<TObject> _sortComparer = [];\n    public SortExpressionComparer<TObject> SortComparer\n    {\n        get => _sortComparer;\n        set => SetAndRaise(ref _sortComparer, value);\n    }\n\n    /// <summary>\n    /// Converts <see cref=\"SortComparer\"/> to <see cref=\"SortExpressionComparer{SearchItem}\"/>.\n    /// </summary>\n    private SortExpressionComparer<SearchItem> SearchItemSortComparer =>\n        [\n            ..SortComparer\n        .Select(sortExpression => new SortExpression<SearchItem>(\n            item => sortExpression.Expression(item.Item),\n            sortExpression.Direction\n        )).Prepend(new SortExpression<SearchItem>(item => item.Score, SortDirection.Descending))\n        ];\n\n    public IObservableCollection<TObject> Items { get; } = new ObservableCollectionExtended<TObject>();\n\n    public IObservableCollection<TObject> FilteredItems { get; } =\n        new ObservableCollectionExtended<TObject>();\n\n    public SearchCollection(\n        IObservable<IChangeSet<TObject, TKey>> source,\n        Func<TQuery?, Func<TObject, bool>> predicateSelector,\n        SortExpressionComparer<TObject>? sortComparer = null\n    )\n    {\n        PredicateSelector = predicateSelector;\n\n        if (sortComparer is not null)\n        {\n            SortComparer = sortComparer;\n        }\n\n        // Observable which creates a new predicate whenever Query property changes\n        var dynamicPredicate = this.WhenValueChanged(@this => @this.Query).Select(predicateSelector);\n\n        cleanUp = source\n            .Bind(Items)\n            .Filter(dynamicPredicate)\n            .Sort(SortComparer)\n            .Bind(FilteredItems)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n    }\n\n    public SearchCollection(\n        IObservable<IChangeSet<TObject, TKey>> source,\n        Func<TQuery?, Func<TObject, (bool, int)>> scorerSelector,\n        SortExpressionComparer<TObject>? sortComparer = null\n    )\n    {\n        ScorerSelector = scorerSelector;\n\n        if (sortComparer is not null)\n        {\n            SortComparer = sortComparer;\n        }\n\n        // Monitor Query property for changes\n        var queryChanged = this.WhenValueChanged(@this => @this.Query).Select(_ => Unit.Default);\n\n        cleanUp = new CompositeDisposable(\n            // Update Scorer property whenever Query property changes\n            queryChanged.Subscribe(_ => Scorer = scorerSelector(Query)),\n            // Transform source items into SearchItems\n            source\n                .Transform(\n                    obj =>\n                    {\n                        var (isMatch, score) = Scorer?.Invoke(obj) ?? (true, 0);\n\n                        return new SearchItem\n                        {\n                            Item = obj,\n                            IsMatch = isMatch,\n                            Score = score\n                        };\n                    },\n                    forceTransform: queryChanged\n                )\n                .Filter(item => item.IsMatch)\n                .Sort(SearchItemSortComparer, SortOptimisations.ComparesImmutableValuesOnly)\n                .Transform(searchItem => searchItem.Item)\n                .Bind(FilteredItems)\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe()\n        );\n    }\n\n    /// <summary>\n    /// Clears <see cref=\"Query\"/> property by setting it to default value.\n    /// </summary>\n    public void ClearQuery()\n    {\n        Query = default;\n    }\n\n    public void Dispose()\n    {\n        cleanUp.Dispose();\n        GC.SuppressFinalize(this);\n    }\n\n    private readonly record struct SearchItem\n    {\n        public TObject Item { get; init; }\n        public int Score { get; init; }\n        public bool IsMatch { get; init; }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/AdvancedImageBox.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:mocks=\"using:StabilityMatrix.Avalonia.DesignData\"\n    x:DataType=\"controls:AdvancedImageBox\">\n\n    <Design.PreviewWith>\n        <Panel Width=\"600\" Height=\"800\">\n            <controls:AdvancedImageBox Source=\"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/fb99d197-2c3d-49d2-ba21-d7aa7dbc46d1/width=2000\" />\n        </Panel>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|AdvancedImageBox\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Focusable\" Value=\"True\" />\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"VerticalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Grid HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\" VerticalAlignment=\"{TemplateBinding VerticalAlignment}\">\n                    <ContentPresenter\n                        Name=\"PART_ViewPort\"\n                        Grid.Row=\"0\"\n                        Grid.Column=\"0\"\n                        Background=\"Transparent\" />\n\n                    <ScrollBar\n                        Name=\"PART_VerticalScrollBar\"\n                        Grid.Row=\"0\"\n                        Grid.Column=\"0\"\n                        HorizontalAlignment=\"Right\"\n                        Background=\"Transparent\"\n                        Maximum=\"0\"\n                        Minimum=\"0\"\n                        Orientation=\"Vertical\"\n                        ViewportSize=\"{Binding ElementName=PART_ViewPort, Path=Bounds.Height}\"\n                        Visibility=\"Auto\" />\n\n                    <ScrollBar\n                        Name=\"PART_HorizontalScrollBar\"\n                        Grid.Row=\"0\"\n                        Grid.Column=\"0\"\n                        VerticalAlignment=\"Bottom\"\n                        Background=\"Transparent\"\n                        Maximum=\"0\"\n                        Minimum=\"0\"\n                        Orientation=\"Horizontal\"\n                        ViewportSize=\"{Binding ElementName=PART_ViewPort, Path=Bounds.Width}\"\n                        Visibility=\"Auto\" />\n\n                    <!--<Border\n                        Grid.Row=\"1\"\n                        Grid.Column=\"1\"\n                        Background=\"{DynamicResource ThemeBackgroundColor}\" />-->\n\n                </Grid>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/AdvancedImageBox.axaml.cs",
    "content": "﻿/*\n *                               The MIT License (MIT)\n * Permission is hereby granted, free of charge, to any person obtaining a copy of\n * this software and associated documentation files (the \"Software\"), to deal in\n * the Software without restriction, including without limitation the rights to\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n * the Software, and to permit persons to whom the Software is furnished to do so.\n */\n// Port from: https://github.com/cyotek/Cyotek.Windows.Forms.ImageBox to AvaloniaUI\n// Modified from: https://github.com/sn4k3/UVtools/blob/master/UVtools.AvaloniaControls/AdvancedImageBox.axaml.cs\n\nusing System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Drawing;\nusing System.IO;\nusing System.Runtime.CompilerServices;\nusing AsyncAwaitBestPractices;\nusing AsyncImageLoader;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Presenters;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Input;\nusing Avalonia.Interactivity;\nusing Avalonia.Media;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Platform;\nusing Avalonia.Threading;\nusing Bitmap = Avalonia.Media.Imaging.Bitmap;\nusing Brushes = Avalonia.Media.Brushes;\nusing Color = Avalonia.Media.Color;\nusing Pen = Avalonia.Media.Pen;\nusing Point = Avalonia.Point;\nusing Size = Avalonia.Size;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class AdvancedImageBox : TemplatedControlBase\n{\n    #region Bindable Base\n    /// <summary>\n    /// Multicast event for property change notifications.\n    /// </summary>\n    private PropertyChangedEventHandler? _propertyChanged;\n\n    public new event PropertyChangedEventHandler PropertyChanged\n    {\n        add => _propertyChanged += value;\n        remove => _propertyChanged -= value;\n    }\n\n    protected bool RaiseAndSetIfChanged<T>(\n        ref T field,\n        T value,\n        [CallerMemberName] string? propertyName = null\n    )\n    {\n        if (EqualityComparer<T>.Default.Equals(field, value))\n            return false;\n        field = value;\n        RaisePropertyChanged(propertyName);\n        return true;\n    }\n\n    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { }\n\n    /// <summary>\n    ///     Notifies listeners that a property value has changed.\n    /// </summary>\n    /// <param name=\"propertyName\">\n    ///     Name of the property used to notify listeners.  This\n    ///     value is optional and can be provided automatically when invoked from compilers\n    ///     that support <see cref=\"CallerMemberNameAttribute\" />.\n    /// </param>\n    protected void RaisePropertyChanged([CallerMemberName] string? propertyName = null)\n    {\n        var e = new PropertyChangedEventArgs(propertyName);\n        OnPropertyChanged(e);\n        _propertyChanged?.Invoke(this, e);\n    }\n    #endregion\n\n    #region Sub Classes\n\n    /// <summary>\n    /// Represents available levels of zoom in an <see cref=\"AdvancedImageBox\"/> control\n    /// </summary>\n    public class ZoomLevelCollection : IList<int>\n    {\n        #region Public Constructors\n\n        /// <summary>\n        /// Initializes a new instance of the <see cref=\"ZoomLevelCollection\"/> class.\n        /// </summary>\n        public ZoomLevelCollection()\n        {\n            List = new SortedList<int, int>();\n        }\n\n        /// <summary>\n        /// Initializes a new instance of the <see cref=\"ZoomLevelCollection\"/> class.\n        /// </summary>\n        /// <param name=\"collection\">The default values to populate the collection with.</param>\n        /// <exception cref=\"System.ArgumentNullException\">Thrown if the <c>collection</c> parameter is null</exception>\n        public ZoomLevelCollection(IEnumerable<int> collection)\n            : this()\n        {\n            if (collection == null)\n            {\n                throw new ArgumentNullException(nameof(collection));\n            }\n\n            AddRange(collection);\n        }\n\n        #endregion\n\n        #region Public Class Properties\n\n        /// <summary>\n        /// Returns the default zoom levels\n        /// </summary>\n        public static ZoomLevelCollection Default =>\n            new(\n                new[]\n                {\n                    7,\n                    10,\n                    13,\n                    16,\n                    20,\n                    24,\n                    // 10 increments\n                    30,\n                    40,\n                    50,\n                    60,\n                    70,\n                    // 100 increments\n                    100,\n                    200,\n                    300,\n                    400,\n                    500,\n                    600,\n                    700,\n                    800,\n                    // 400 increments\n                    1200,\n                    1600,\n                    2000,\n                    2400,\n                    // 800 increments\n                    3200,\n                    4000,\n                    4800,\n                    // 1000 increments\n                    5800,\n                    6800,\n                    7800,\n                    8800,\n                }\n            );\n\n        #endregion\n\n        #region Public Properties\n\n        /// <summary>\n        /// Gets the number of elements contained in the <see cref=\"ZoomLevelCollection\" />.\n        /// </summary>\n        /// <returns>\n        /// The number of elements contained in the <see cref=\"ZoomLevelCollection\" />.\n        /// </returns>\n        public int Count => List.Count;\n\n        /// <summary>\n        /// Gets a value indicating whether the <see cref=\"T:System.Collections.Generic.ICollection`1\" /> is read-only.\n        /// </summary>\n        /// <value><c>true</c> if this instance is read only; otherwise, <c>false</c>.</value>\n        /// <returns>true if the <see cref=\"T:System.Collections.Generic.ICollection`1\" /> is read-only; otherwise, false.\n        /// </returns>\n        public bool IsReadOnly => false;\n\n        /// <summary>\n        /// Gets or sets the zoom level at the specified index.\n        /// </summary>\n        /// <param name=\"index\">The index.</param>\n        public int this[int index]\n        {\n            get => List.Values[index];\n            set\n            {\n                List.RemoveAt(index);\n                Add(value);\n            }\n        }\n\n        #endregion\n\n        #region Protected Properties\n\n        /// <summary>\n        /// Gets or sets the backing list.\n        /// </summary>\n        protected SortedList<int, int> List { get; set; }\n\n        #endregion\n\n        #region Public Members\n\n        /// <summary>\n        /// Adds an item to the <see cref=\"T:System.Collections.Generic.ICollection`1\" />.\n        /// </summary>\n        /// <param name=\"item\">The object to add to the <see cref=\"T:System.Collections.Generic.ICollection`1\" />.</param>\n        public void Add(int item)\n        {\n            List.Add(item, item);\n        }\n\n        /// <summary>\n        /// Adds a range of items to the <see cref=\"ZoomLevelCollection\"/>.\n        /// </summary>\n        /// <param name=\"collection\">The items to add to the collection.</param>\n        /// <exception cref=\"System.ArgumentNullException\">Thrown if the <c>collection</c> parameter is null.</exception>\n        public void AddRange(IEnumerable<int> collection)\n        {\n            if (collection == null)\n            {\n                throw new ArgumentNullException(nameof(collection));\n            }\n\n            foreach (var value in collection)\n            {\n                Add(value);\n            }\n        }\n\n        /// <summary>\n        /// Removes all items from the <see cref=\"T:System.Collections.Generic.ICollection`1\" />.\n        /// </summary>\n        public void Clear()\n        {\n            List.Clear();\n        }\n\n        /// <summary>\n        /// Determines whether the <see cref=\"T:System.Collections.Generic.ICollection`1\" /> contains a specific value.\n        /// </summary>\n        /// <param name=\"item\">The object to locate in the <see cref=\"T:System.Collections.Generic.ICollection`1\" />.</param>\n        /// <returns>true if <paramref name=\"item\" /> is found in the <see cref=\"T:System.Collections.Generic.ICollection`1\" />; otherwise, false.</returns>\n        public bool Contains(int item)\n        {\n            return List.ContainsKey(item);\n        }\n\n        /// <summary>\n        /// Copies a range of elements this collection into a destination <see cref=\"Array\"/>.\n        /// </summary>\n        /// <param name=\"array\">The <see cref=\"Array\"/> that receives the data.</param>\n        /// <param name=\"arrayIndex\">A 64-bit integer that represents the index in the <see cref=\"Array\"/> at which storing begins.</param>\n        public void CopyTo(int[] array, int arrayIndex)\n        {\n            for (var i = 0; i < Count; i++)\n            {\n                array[arrayIndex + i] = List.Values[i];\n            }\n        }\n\n        /// <summary>\n        /// Finds the index of a zoom level matching or nearest to the specified value.\n        /// </summary>\n        /// <param name=\"zoomLevel\">The zoom level.</param>\n        public int FindNearest(int zoomLevel)\n        {\n            var nearestValue = List.Values[0];\n            var nearestDifference = Math.Abs(nearestValue - zoomLevel);\n            for (var i = 1; i < Count; i++)\n            {\n                var value = List.Values[i];\n                var difference = Math.Abs(value - zoomLevel);\n                if (difference < nearestDifference)\n                {\n                    nearestValue = value;\n                    nearestDifference = difference;\n                }\n            }\n            return nearestValue;\n        }\n\n        /// <summary>\n        /// Returns an enumerator that iterates through the collection.\n        /// </summary>\n        /// <returns>A <see cref=\"T:System.Collections.Generic.IEnumerator`1\" /> that can be used to iterate through the collection.</returns>\n        public IEnumerator<int> GetEnumerator()\n        {\n            return List.Values.GetEnumerator();\n        }\n\n        /// <summary>\n        /// Determines the index of a specific item in the <see cref=\"T:System.Collections.Generic.IList`1\" />.\n        /// </summary>\n        /// <param name=\"item\">The object to locate in the <see cref=\"T:System.Collections.Generic.IList`1\" />.</param>\n        /// <returns>The index of <paramref name=\"item\" /> if found in the list; otherwise, -1.</returns>\n        public int IndexOf(int item)\n        {\n            return List.IndexOfKey(item);\n        }\n\n        /// <summary>\n        /// Not implemented.\n        /// </summary>\n        /// <param name=\"index\">The index.</param>\n        /// <param name=\"item\">The item.</param>\n        /// <exception cref=\"System.NotImplementedException\">Not implemented</exception>\n        public void Insert(int index, int item)\n        {\n            throw new NotImplementedException();\n        }\n\n        /// <summary>\n        /// Returns the next increased zoom level for the given current zoom.\n        /// </summary>\n        /// <param name=\"zoomLevel\">The current zoom level.</param>\n        /// <param name=\"constrainZoomLevel\">When positive, constrain maximum zoom to this value</param>\n        /// <returns>The next matching increased zoom level for the given current zoom if applicable, otherwise the nearest zoom.</returns>\n        public int NextZoom(int zoomLevel, int constrainZoomLevel = 0)\n        {\n            var index = IndexOf(FindNearest(zoomLevel));\n            if (index < Count - 1)\n                index++;\n\n            return constrainZoomLevel > 0 && this[index] >= constrainZoomLevel\n                ? constrainZoomLevel\n                : this[index];\n        }\n\n        /// <summary>\n        /// Returns the next decreased zoom level for the given current zoom.\n        /// </summary>\n        /// <param name=\"zoomLevel\">The current zoom level.</param>\n        /// <param name=\"constrainZoomLevel\">When positive, constrain minimum zoom to this value</param>\n        /// <returns>The next matching decreased zoom level for the given current zoom if applicable, otherwise the nearest zoom.</returns>\n        public int PreviousZoom(int zoomLevel, int constrainZoomLevel = 0)\n        {\n            var index = IndexOf(FindNearest(zoomLevel));\n            if (index > 0)\n                index--;\n\n            return constrainZoomLevel > 0 && this[index] <= constrainZoomLevel\n                ? constrainZoomLevel\n                : this[index];\n        }\n\n        /// <summary>\n        /// Removes the first occurrence of a specific object from the <see cref=\"T:System.Collections.Generic.ICollection`1\" />.\n        /// </summary>\n        /// <param name=\"item\">The object to remove from the <see cref=\"T:System.Collections.Generic.ICollection`1\" />.</param>\n        /// <returns>true if <paramref name=\"item\" /> was successfully removed from the <see cref=\"T:System.Collections.Generic.ICollection`1\" />; otherwise, false. This method also returns false if <paramref name=\"item\" /> is not found in the original <see cref=\"T:System.Collections.Generic.ICollection`1\" />.</returns>\n        public bool Remove(int item)\n        {\n            return List.Remove(item);\n        }\n\n        /// <summary>\n        /// Removes the element at the specified index of the <see cref=\"ZoomLevelCollection\"/>.\n        /// </summary>\n        /// <param name=\"index\">The zero-based index of the element to remove.</param>\n        public void RemoveAt(int index)\n        {\n            List.RemoveAt(index);\n        }\n\n        /// <summary>\n        /// Copies the elements of the <see cref=\"ZoomLevelCollection\"/> to a new array.\n        /// </summary>\n        /// <returns>An array containing copies of the elements of the <see cref=\"ZoomLevelCollection\"/>.</returns>\n        public int[] ToArray()\n        {\n            var results = new int[Count];\n            CopyTo(results, 0);\n\n            return results;\n        }\n\n        #endregion\n\n        #region IList<int> Members\n\n        /// <summary>\n        /// Returns an enumerator that iterates through a collection.\n        /// </summary>\n        /// <returns>An <see cref=\"ZoomLevelCollection\" /> object that can be used to iterate through the collection.</returns>\n        IEnumerator IEnumerable.GetEnumerator()\n        {\n            return GetEnumerator();\n        }\n\n        #endregion\n    }\n\n    #endregion\n\n    #region Enums\n\n    /// <summary>\n    /// Determines the sizing mode of an image hosted in an <see cref=\"AdvancedImageBox\" /> control.\n    /// </summary>\n    public enum SizeModes : byte\n    {\n        /// <summary>\n        /// The image is displayed according to current zoom and scroll properties.\n        /// </summary>\n        Normal,\n\n        /// <summary>\n        /// The image is stretched to fill the client area of the control.\n        /// </summary>\n        Stretch,\n\n        /// <summary>\n        /// The image is stretched to fill as much of the client area of the control as possible, whilst retaining the same aspect ratio for the width and height.\n        /// </summary>\n        Fit,\n    }\n\n    [Flags]\n    public enum MouseButtons : byte\n    {\n        None = 0,\n        LeftButton = 1,\n        MiddleButton = 2,\n        RightButton = 4,\n    }\n\n    /// <summary>\n    /// Describes the zoom action occurring\n    /// </summary>\n    [Flags]\n    public enum ZoomActions : byte\n    {\n        /// <summary>\n        /// No action.\n        /// </summary>\n        None = 0,\n\n        /// <summary>\n        /// The control is increasing the zoom.\n        /// </summary>\n        ZoomIn = 1,\n\n        /// <summary>\n        /// The control is decreasing the zoom.\n        /// </summary>\n        ZoomOut = 2,\n\n        /// <summary>\n        /// The control zoom was reset.\n        /// </summary>\n        ActualSize = 4,\n    }\n\n    public enum SelectionModes\n    {\n        /// <summary>\n        ///   No selection.\n        /// </summary>\n        None,\n\n        /// <summary>\n        ///   Rectangle selection.\n        /// </summary>\n        Rectangle,\n\n        /// <summary>\n        ///   Zoom selection.\n        /// </summary>\n        Zoom,\n    }\n\n    #endregion\n\n    #region UI Controls\n\n    [NotNull]\n    public ScrollBar? HorizontalScrollBar { get; private set; }\n\n    [NotNull]\n    public ScrollBar? VerticalScrollBar { get; private set; }\n\n    [NotNull]\n    public ContentPresenter? ViewPort { get; private set; }\n\n    // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract\n    public bool IsViewPortLoaded => ViewPort is not null;\n\n    public Vector Offset\n    {\n        get => new(HorizontalScrollBar.Value, VerticalScrollBar.Value);\n        set\n        {\n            HorizontalScrollBar.Value = value.X;\n            VerticalScrollBar.Value = value.Y;\n            RaisePropertyChanged();\n            TriggerRender();\n        }\n    }\n\n    public Size ViewPortSize => ViewPort?.Bounds.Size ?? new Size(50, 50);\n    #endregion\n\n    #region Private Members\n    private Point _startMousePosition;\n    private Vector _startScrollPosition;\n    private bool _isPanning;\n    private bool _isSelecting;\n    private Bitmap? _trackerImage;\n    private bool _canRender = true;\n    private Point _pointerPosition;\n    #endregion\n\n    #region Properties\n    public static readonly DirectProperty<AdvancedImageBox, bool> CanRenderProperty =\n        AvaloniaProperty.RegisterDirect<AdvancedImageBox, bool>(nameof(CanRender), o => o.CanRender);\n\n    /// <summary>\n    /// Gets or sets if control can render the image\n    /// </summary>\n    public bool CanRender\n    {\n        get => _canRender;\n        set\n        {\n            if (!SetAndRaise(CanRenderProperty, ref _canRender, value))\n                return;\n            if (_canRender)\n                TriggerRender();\n        }\n    }\n\n    public static readonly StyledProperty<byte> GridCellSizeProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        byte\n    >(nameof(GridCellSize), 15);\n\n    /// <summary>\n    /// Gets or sets the grid cell size\n    /// </summary>\n    public byte GridCellSize\n    {\n        get => GetValue(GridCellSizeProperty);\n        set => SetValue(GridCellSizeProperty, value);\n    }\n\n    public static readonly StyledProperty<ISolidColorBrush> GridColorProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        ISolidColorBrush\n    >(nameof(GridColor), SolidColorBrush.Parse(\"#181818\"));\n\n    /// <summary>\n    /// Gets or sets the color used to create the checkerboard style background\n    /// </summary>\n    public ISolidColorBrush GridColor\n    {\n        get => GetValue(GridColorProperty);\n        set => SetValue(GridColorProperty, value);\n    }\n\n    public static readonly StyledProperty<ISolidColorBrush> GridColorAlternateProperty =\n        AvaloniaProperty.Register<AdvancedImageBox, ISolidColorBrush>(\n            nameof(GridColorAlternate),\n            SolidColorBrush.Parse(\"#252525\")\n        );\n\n    /// <summary>\n    /// Gets or sets the color used to create the checkerboard style background\n    /// </summary>\n    public ISolidColorBrush GridColorAlternate\n    {\n        get => GetValue(GridColorAlternateProperty);\n        set => SetValue(GridColorAlternateProperty, value);\n    }\n\n    public static readonly StyledProperty<string?> SourceProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        string?\n    >(\"Source\");\n\n    public string? Source\n    {\n        get => GetValue(SourceProperty);\n        set\n        {\n            SetValue(SourceProperty, value);\n            // Also set the image\n            if (value is not null)\n            {\n                var loader = ImageLoader.AsyncImageLoader;\n\n                Dispatcher\n                    .UIThread.InvokeAsync(async () =>\n                    {\n                        Image = await loader.ProvideImageAsync(value);\n                    })\n                    .SafeFireAndForget();\n            }\n        }\n    }\n\n    public static readonly StyledProperty<Bitmap?> ImageProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        Bitmap?\n    >(nameof(Image));\n\n    /// <summary>\n    /// Gets or sets the image to be displayed\n    /// </summary>\n    public Bitmap? Image\n    {\n        get => GetValue(ImageProperty);\n        set => SetValue(ImageProperty, value);\n    }\n\n    /// <inheritdoc />\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        if (change.Property == ImageProperty)\n        {\n            var (oldImage, newImage) = change.GetOldAndNewValue<Bitmap?>();\n\n            Vector? offsetBackup = null;\n\n            if (newImage is null)\n            {\n                SelectNone();\n            }\n            else if (IsViewPortLoaded)\n            {\n                if (oldImage is null)\n                {\n                    ZoomToFit();\n                    RestoreSizeMode();\n                }\n                else if (newImage.Size != oldImage.Size)\n                {\n                    offsetBackup = Offset;\n\n                    var zoomFactorScale = (float)GetZoomLevelToFit(newImage) / GetZoomLevelToFit(oldImage);\n                    var imageScale = newImage.Size / oldImage.Size;\n\n                    Debug.WriteLine($\"Image scale: {imageScale}\");\n\n                    /*var oldScaledSize = oldImage.Size * ZoomFactor;\n                    var newScaledSize = newImage.Size * ZoomFactor;\n                    \n                    Debug.WriteLine($\"Old scaled {oldScaledSize} -> new scaled {newScaledSize}\");*/\n\n                    var currentZoom = Zoom;\n                    var currentFactor = ZoomFactor;\n                    // var currentOffset = Offset;\n\n                    // Scale zoom and offset to new size\n                    Zoom = (int)Math.Floor(Zoom * zoomFactorScale);\n                    /*Offset = new Vector(\n                        Offset.X * imageScale.X,\n                        Offset.Y * imageScale.Y\n                    );*/\n\n                    Debug.WriteLine($\"Zoom changed from {currentZoom} to {Zoom}\");\n                    Debug.WriteLine($\"Zoom factor changed from {currentFactor} to {ZoomFactor}\");\n                }\n\n                if (offsetBackup is not null)\n                {\n                    Offset = offsetBackup.Value;\n                }\n\n                UpdateViewPort();\n                TriggerRender();\n            }\n\n            RaisePropertyChanged(nameof(IsImageLoaded));\n        }\n    }\n\n    /*public WriteableBitmap? ImageAsWriteableBitmap\n    {\n        get\n        {\n            if (Image is null)\n                return null;\n            return (WriteableBitmap)Image;\n        }\n    }*/\n\n    public bool IsImageLoaded => Image is not null;\n\n    public static readonly DirectProperty<AdvancedImageBox, Bitmap?> TrackerImageProperty =\n        AvaloniaProperty.RegisterDirect<AdvancedImageBox, Bitmap?>(\n            nameof(TrackerImage),\n            o => o.TrackerImage,\n            (o, v) => o.TrackerImage = v\n        );\n\n    /// <summary>\n    /// Gets or sets an image to follow the mouse pointer\n    /// </summary>\n    public Bitmap? TrackerImage\n    {\n        get => _trackerImage;\n        set\n        {\n            if (!SetAndRaise(TrackerImageProperty, ref _trackerImage, value))\n                return;\n            TriggerRender();\n            RaisePropertyChanged(nameof(HaveTrackerImage));\n        }\n    }\n\n    public bool HaveTrackerImage => _trackerImage is not null;\n\n    public static readonly StyledProperty<bool> TrackerImageAutoZoomProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        bool\n    >(nameof(TrackerImageAutoZoom), true);\n\n    /// <summary>\n    /// Gets or sets if the tracker image will be scaled to the current zoom\n    /// </summary>\n    public bool TrackerImageAutoZoom\n    {\n        get => GetValue(TrackerImageAutoZoomProperty);\n        set => SetValue(TrackerImageAutoZoomProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> IsTrackerImageEnabledProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        bool\n    >(\"IsTrackerImageEnabled\");\n\n    public bool IsTrackerImageEnabled\n    {\n        get => GetValue(IsTrackerImageEnabledProperty);\n        set => SetValue(IsTrackerImageEnabledProperty, value);\n    }\n\n    public bool IsHorizontalBarVisible\n    {\n        get\n        {\n            if (Image is null)\n                return false;\n            if (SizeMode != SizeModes.Normal)\n                return false;\n            return ScaledImageWidth > ViewPortSize.Width;\n        }\n    }\n\n    public bool IsVerticalBarVisible\n    {\n        get\n        {\n            if (Image is null)\n                return false;\n            if (SizeMode != SizeModes.Normal)\n                return false;\n            return ScaledImageHeight > ViewPortSize.Height;\n        }\n    }\n\n    public static readonly StyledProperty<bool> ShowGridProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        bool\n    >(nameof(ShowGrid), true);\n\n    /// <summary>\n    /// Gets or sets the grid visibility when reach high zoom levels\n    /// </summary>\n    public bool ShowGrid\n    {\n        get => GetValue(ShowGridProperty);\n        set => SetValue(ShowGridProperty, value);\n    }\n\n    public static readonly DirectProperty<AdvancedImageBox, Point> PointerPositionProperty =\n        AvaloniaProperty.RegisterDirect<AdvancedImageBox, Point>(\n            nameof(PointerPosition),\n            o => o.PointerPosition\n        );\n\n    /// <summary>\n    /// Gets the current pointer position\n    /// </summary>\n    public Point PointerPosition\n    {\n        get => _pointerPosition;\n        private set => SetAndRaise(PointerPositionProperty, ref _pointerPosition, value);\n    }\n\n    public static readonly DirectProperty<AdvancedImageBox, bool> IsPanningProperty =\n        AvaloniaProperty.RegisterDirect<AdvancedImageBox, bool>(nameof(IsPanning), o => o.IsPanning);\n\n    /// <summary>\n    /// Gets if control is currently panning\n    /// </summary>\n    public bool IsPanning\n    {\n        get => _isPanning;\n        protected set\n        {\n            if (!SetAndRaise(IsPanningProperty, ref _isPanning, value))\n                return;\n            _startScrollPosition = Offset;\n\n            if (value)\n            {\n                Cursor = new Cursor(StandardCursorType.SizeAll);\n                //this.OnPanStart(EventArgs.Empty);\n            }\n            else\n            {\n                Cursor = Cursor.Default;\n                //this.OnPanEnd(EventArgs.Empty);\n            }\n        }\n    }\n\n    public static readonly DirectProperty<AdvancedImageBox, bool> IsSelectingProperty =\n        AvaloniaProperty.RegisterDirect<AdvancedImageBox, bool>(nameof(IsSelecting), o => o.IsSelecting);\n\n    /// <summary>\n    /// Gets if control is currently selecting a ROI\n    /// </summary>\n    public bool IsSelecting\n    {\n        get => _isSelecting;\n        protected set => SetAndRaise(IsSelectingProperty, ref _isSelecting, value);\n    }\n\n    /// <summary>\n    /// Gets the center point of the viewport\n    /// </summary>\n    public Point CenterPoint\n    {\n        get\n        {\n            var viewport = GetImageViewPort();\n            return new(viewport.Width / 2, viewport.Height / 2);\n        }\n    }\n\n    public static readonly StyledProperty<bool> AutoPanProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        bool\n    >(nameof(AutoPan), true);\n\n    /// <summary>\n    /// Gets or sets if the control can pan with the mouse\n    /// </summary>\n    public bool AutoPan\n    {\n        get => GetValue(AutoPanProperty);\n        set => SetValue(AutoPanProperty, value);\n    }\n\n    public static readonly StyledProperty<MouseButtons> PanWithMouseButtonsProperty =\n        AvaloniaProperty.Register<AdvancedImageBox, MouseButtons>(\n            nameof(PanWithMouseButtons),\n            MouseButtons.LeftButton | MouseButtons.MiddleButton\n        );\n\n    /// <summary>\n    /// Gets or sets the mouse buttons to pan the image\n    /// </summary>\n    public MouseButtons PanWithMouseButtons\n    {\n        get => GetValue(PanWithMouseButtonsProperty);\n        set => SetValue(PanWithMouseButtonsProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> PanWithArrowsProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        bool\n    >(nameof(PanWithArrows), true);\n\n    /// <summary>\n    /// Gets or sets if the control can pan with the keyboard arrows\n    /// </summary>\n    public bool PanWithArrows\n    {\n        get => GetValue(PanWithArrowsProperty);\n        set => SetValue(PanWithArrowsProperty, value);\n    }\n\n    public static readonly StyledProperty<MouseButtons> SelectWithMouseButtonsProperty =\n        AvaloniaProperty.Register<AdvancedImageBox, MouseButtons>(\n            nameof(SelectWithMouseButtons),\n            MouseButtons.LeftButton\n        );\n\n    /// <summary>\n    /// Gets or sets the mouse buttons to select a region on image\n    /// </summary>\n    public MouseButtons SelectWithMouseButtons\n    {\n        get => GetValue(SelectWithMouseButtonsProperty);\n        set => SetValue(SelectWithMouseButtonsProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> InvertMousePanProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        bool\n    >(nameof(InvertMousePan));\n\n    /// <summary>\n    /// Gets or sets if mouse pan is inverted\n    /// </summary>\n    public bool InvertMousePan\n    {\n        get => GetValue(InvertMousePanProperty);\n        set => SetValue(InvertMousePanProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> AutoCenterProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        bool\n    >(nameof(AutoCenter), true);\n\n    /// <summary>\n    /// Gets or sets if image is auto centered\n    /// </summary>\n    public bool AutoCenter\n    {\n        get => GetValue(AutoCenterProperty);\n        set => SetValue(AutoCenterProperty, value);\n    }\n\n    public static readonly StyledProperty<SizeModes> SizeModeProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        SizeModes\n    >(nameof(SizeMode));\n\n    /// <summary>\n    /// Gets or sets the image size mode\n    /// </summary>\n    public SizeModes SizeMode\n    {\n        get => GetValue(SizeModeProperty);\n        set\n        {\n            SetValue(SizeModeProperty, value);\n\n            // Run changed if loaded\n            if (IsViewPortLoaded)\n            {\n                SizeModeChanged();\n            }\n\n            RaisePropertyChanged(nameof(IsHorizontalBarVisible));\n            RaisePropertyChanged(nameof(IsVerticalBarVisible));\n        }\n    }\n\n    private void SizeModeChanged()\n    {\n        switch (SizeMode)\n        {\n            case SizeModes.Normal:\n                HorizontalScrollBar.Visibility = ScrollBarVisibility.Auto;\n                VerticalScrollBar.Visibility = ScrollBarVisibility.Auto;\n                break;\n            case SizeModes.Stretch:\n            case SizeModes.Fit:\n                HorizontalScrollBar.Visibility = ScrollBarVisibility.Hidden;\n                VerticalScrollBar.Visibility = ScrollBarVisibility.Hidden;\n                break;\n            default:\n                throw new ArgumentOutOfRangeException(nameof(SizeMode), SizeMode, null);\n        }\n    }\n\n    public static readonly StyledProperty<bool> AllowZoomProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        bool\n    >(nameof(AllowZoom), true);\n\n    /// <summary>\n    /// Gets or sets if zoom is allowed\n    /// </summary>\n    public bool AllowZoom\n    {\n        get => GetValue(AllowZoomProperty);\n        set => SetValue(AllowZoomProperty, value);\n    }\n\n    public static readonly DirectProperty<AdvancedImageBox, ZoomLevelCollection> ZoomLevelsProperty =\n        AvaloniaProperty.RegisterDirect<AdvancedImageBox, ZoomLevelCollection>(\n            nameof(ZoomLevels),\n            o => o.ZoomLevels,\n            (o, v) => o.ZoomLevels = v\n        );\n\n    ZoomLevelCollection _zoomLevels = ZoomLevelCollection.Default;\n\n    /// <summary>\n    ///   Gets or sets the zoom levels.\n    /// </summary>\n    /// <value>The zoom levels.</value>\n    public ZoomLevelCollection ZoomLevels\n    {\n        get => _zoomLevels;\n        set => SetAndRaise(ZoomLevelsProperty, ref _zoomLevels, value);\n    }\n\n    public static readonly StyledProperty<int> MinZoomProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        int\n    >(nameof(MinZoom), 10);\n\n    /// <summary>\n    /// Gets or sets the minimum possible zoom.\n    /// </summary>\n    /// <value>The zoom.</value>\n    public int MinZoom\n    {\n        get => GetValue(MinZoomProperty);\n        set => SetValue(MinZoomProperty, value);\n    }\n\n    public static readonly StyledProperty<int> MaxZoomProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        int\n    >(nameof(MaxZoom), 6400);\n\n    /// <summary>\n    /// Gets or sets the maximum possible zoom.\n    /// </summary>\n    /// <value>The zoom.</value>\n    public int MaxZoom\n    {\n        get => GetValue(MaxZoomProperty);\n        set => SetValue(MaxZoomProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> ConstrainZoomOutToFitLevelProperty =\n        AvaloniaProperty.Register<AdvancedImageBox, bool>(nameof(ConstrainZoomOutToFitLevel), true);\n\n    /// <summary>\n    /// Gets or sets if the zoom out should constrain to fit image as the lowest zoom level.\n    /// </summary>\n    public bool ConstrainZoomOutToFitLevel\n    {\n        get => GetValue(ConstrainZoomOutToFitLevelProperty);\n        set => SetValue(ConstrainZoomOutToFitLevelProperty, value);\n    }\n\n    public static readonly DirectProperty<AdvancedImageBox, int> OldZoomProperty =\n        AvaloniaProperty.RegisterDirect<AdvancedImageBox, int>(nameof(OldZoom), o => o.OldZoom);\n\n    private int _oldZoom = 100;\n\n    /// <summary>\n    /// Gets the previous zoom value\n    /// </summary>\n    /// <value>The zoom.</value>\n    public int OldZoom\n    {\n        get => _oldZoom;\n        private set => SetAndRaise(OldZoomProperty, ref _oldZoom, value);\n    }\n\n    public static readonly StyledProperty<int> ZoomProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        int\n    >(nameof(Zoom), 100);\n\n    /// <summary>\n    ///  Gets or sets the zoom.\n    /// </summary>\n    /// <value>The zoom.</value>\n    public int Zoom\n    {\n        get => GetValue(ZoomProperty);\n        set\n        {\n            var minZoom = MinZoom;\n            if (ConstrainZoomOutToFitLevel)\n                minZoom = Math.Max(ZoomLevelToFit, minZoom);\n            var newZoom = Math.Clamp(value, minZoom, MaxZoom);\n\n            var previousZoom = Zoom;\n            if (previousZoom == newZoom)\n                return;\n            OldZoom = previousZoom;\n            SetValue(ZoomProperty, newZoom);\n\n            UpdateViewPort();\n            TriggerRender();\n\n            RaisePropertyChanged(nameof(IsHorizontalBarVisible));\n            RaisePropertyChanged(nameof(IsVerticalBarVisible));\n        }\n    }\n\n    /// <summary>\n    /// <para>Gets if the image have zoom.</para>\n    /// <para>True if zoomed in or out</para>\n    /// <para>False if no zoom applied</para>\n    /// </summary>\n    public bool IsActualSize => Zoom == 100;\n\n    /// <summary>\n    /// Gets the zoom factor, the zoom / 100.0\n    /// </summary>\n    public double ZoomFactor => Zoom / 100.0;\n\n    /// <summary>\n    /// Gets the zoom to fit level which shows all the image\n    /// </summary>\n    public int ZoomLevelToFit => Image is null ? 100 : GetZoomLevelToFit(Image);\n\n    private int GetZoomLevelToFit(IImage image)\n    {\n        double zoom;\n        double aspectRatio;\n\n        if (image.Size.Width > image.Size.Height)\n        {\n            aspectRatio = ViewPortSize.Width / image.Size.Width;\n            zoom = aspectRatio * 100.0;\n\n            if (ViewPortSize.Height < image.Size.Height * zoom / 100.0)\n            {\n                aspectRatio = ViewPortSize.Height / image.Size.Height;\n                zoom = aspectRatio * 100.0;\n            }\n        }\n        else\n        {\n            aspectRatio = ViewPortSize.Height / image.Size.Height;\n            zoom = aspectRatio * 100.0;\n\n            if (ViewPortSize.Width < image.Size.Width * zoom / 100.0)\n            {\n                aspectRatio = ViewPortSize.Width / image.Size.Width;\n                zoom = aspectRatio * 100.0;\n            }\n        }\n\n        return (int)zoom;\n    }\n\n    /// <summary>\n    /// Gets the width of the scaled image.\n    /// </summary>\n    /// <value>The width of the scaled image.</value>\n    public double ScaledImageWidth => Image?.Size.Width * ZoomFactor ?? 0;\n\n    /// <summary>\n    /// Gets the height of the scaled image.\n    /// </summary>\n    /// <value>The height of the scaled image.</value>\n    public double ScaledImageHeight => Image?.Size.Height * ZoomFactor ?? 0;\n\n    public static readonly StyledProperty<ISolidColorBrush> PixelGridColorProperty =\n        AvaloniaProperty.Register<AdvancedImageBox, ISolidColorBrush>(\n            nameof(PixelGridColor),\n            Brushes.DimGray\n        );\n\n    /// <summary>\n    /// Gets or sets the color of the pixel grid.\n    /// </summary>\n    /// <value>The color of the pixel grid.</value>\n    public ISolidColorBrush PixelGridColor\n    {\n        get => GetValue(PixelGridColorProperty);\n        set => SetValue(PixelGridColorProperty, value);\n    }\n\n    public static readonly StyledProperty<int> PixelGridZoomThresholdProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        int\n    >(nameof(PixelGridZoomThreshold), 13);\n\n    /// <summary>\n    /// Minimum size of zoomed pixel's before the pixel grid will be drawn\n    /// </summary>\n    public int PixelGridZoomThreshold\n    {\n        get => GetValue(PixelGridZoomThresholdProperty);\n        set => SetValue(PixelGridZoomThresholdProperty, value);\n    }\n\n    public static readonly StyledProperty<SelectionModes> SelectionModeProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        SelectionModes\n    >(nameof(SelectionMode));\n\n    public static readonly StyledProperty<bool> IsPixelGridEnabledProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        bool\n    >(\"IsPixelGridEnabled\", true);\n\n    /// <summary>\n    /// Whether or not to draw the pixel grid at the <see cref=\"PixelGridZoomThreshold\"/>\n    /// </summary>\n    public bool IsPixelGridEnabled\n    {\n        get => GetValue(IsPixelGridEnabledProperty);\n        set => SetValue(IsPixelGridEnabledProperty, value);\n    }\n\n    public SelectionModes SelectionMode\n    {\n        get => GetValue(SelectionModeProperty);\n        set => SetValue(SelectionModeProperty, value);\n    }\n\n    public static readonly StyledProperty<ISolidColorBrush> SelectionColorProperty =\n        AvaloniaProperty.Register<AdvancedImageBox, ISolidColorBrush>(\n            nameof(SelectionColor),\n            new SolidColorBrush(new Color(127, 0, 128, 255))\n        );\n\n    public ISolidColorBrush SelectionColor\n    {\n        get => GetValue(SelectionColorProperty);\n        set => SetValue(SelectionColorProperty, value);\n    }\n\n    public static readonly StyledProperty<Rect> SelectionRegionProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        Rect\n    >(nameof(SelectionRegion), EmptyRect);\n\n    public Rect SelectionRegion\n    {\n        get => GetValue(SelectionRegionProperty);\n        set\n        {\n            SetValue(SelectionRegionProperty, value);\n            //if (!RaiseAndSetIfChanged(ref _selectionRegion, value)) return;\n            TriggerRender();\n            RaisePropertyChanged(nameof(HaveSelection));\n            RaisePropertyChanged(nameof(SelectionRegionNet));\n            RaisePropertyChanged(nameof(SelectionPixelSize));\n        }\n    }\n\n    public Rectangle SelectionRegionNet\n    {\n        get\n        {\n            var rect = SelectionRegion;\n            return new Rectangle(\n                (int)Math.Ceiling(rect.X),\n                (int)Math.Ceiling(rect.Y),\n                (int)rect.Width,\n                (int)rect.Height\n            );\n        }\n    }\n\n    public PixelSize SelectionPixelSize\n    {\n        get\n        {\n            var rect = SelectionRegion;\n            return new PixelSize((int)rect.Width, (int)rect.Height);\n        }\n    }\n\n    public bool HaveSelection => !IsRectEmpty(SelectionRegion);\n\n    private BitmapInterpolationMode? _bitmapInterpolationMode;\n\n    /// <summary>\n    /// Gets or sets the current Bitmap Interpolation Mode\n    /// </summary>\n    public BitmapInterpolationMode BitmapInterpolationMode\n    {\n        get => _bitmapInterpolationMode ??= RenderOptions.GetBitmapInterpolationMode(this);\n        set\n        {\n            if (_bitmapInterpolationMode == value)\n                return;\n            _bitmapInterpolationMode = value;\n            RenderOptions.SetBitmapInterpolationMode(this, value);\n        }\n    }\n\n    #endregion\n\n    #region Constructor\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        // FocusableProperty.OverrideDefaultValue(typeof(AdvancedImageBox), true);\n        AffectsRender<AdvancedImageBox>(ShowGridProperty);\n\n        HorizontalScrollBar =\n            e.NameScope.Find<ScrollBar>(\"PART_HorizontalScrollBar\") ?? throw new NullReferenceException();\n        VerticalScrollBar =\n            e.NameScope.Find<ScrollBar>(\"PART_VerticalScrollBar\") ?? throw new NullReferenceException();\n        ViewPort = e.NameScope.Find<ContentPresenter>(\"PART_ViewPort\") ?? throw new NullReferenceException();\n\n        SizeModeChanged();\n\n        HorizontalScrollBar.Scroll += ScrollBarOnScroll;\n        VerticalScrollBar.Scroll += ScrollBarOnScroll;\n\n        // ViewPort.PointerPressed += ViewPortOnPointerPressed;\n        // ViewPort.PointerExited += ViewPortOnPointerExited;\n        // ViewPort.PointerMoved += ViewPortOnPointerMoved;\n        // ViewPort!.PointerWheelChanged += ViewPort_OnPointerWheelChanged;\n    }\n\n    #endregion\n\n    #region Render methods\n    public void TriggerRender(bool renderOnlyCursorTracker = false)\n    {\n        if (!_canRender)\n            return;\n        if (renderOnlyCursorTracker && _trackerImage is null)\n            return;\n\n        var isHighZoom = ZoomFactor > PixelGridZoomThreshold;\n\n        // If we're in high zoom, switch off bitmap interpolation mode\n        // Otherwise use high quality\n        BitmapInterpolationMode = isHighZoom\n            ? BitmapInterpolationMode.None\n            : BitmapInterpolationMode.HighQuality;\n\n        InvalidateVisual();\n    }\n\n    [MethodImpl(MethodImplOptions.AggressiveInlining)]\n    private void RenderBackgroundGrid(DrawingContext context)\n    {\n        var size = GridCellSize;\n\n        var square1Drawing = new GeometryDrawing\n        {\n            Brush = GridColorAlternate,\n            Geometry = new RectangleGeometry(new Rect(0.0, 0.0, size, size)),\n        };\n\n        var square2Drawing = new GeometryDrawing\n        {\n            Brush = GridColorAlternate,\n            Geometry = new RectangleGeometry(new Rect(size, size, size, size)),\n        };\n\n        var drawingGroup = new DrawingGroup { Children = { square1Drawing, square2Drawing } };\n\n        var tileBrush = new DrawingBrush(drawingGroup)\n        {\n            AlignmentX = AlignmentX.Left,\n            AlignmentY = AlignmentY.Top,\n            DestinationRect = new RelativeRect(new Size(2 * size, 2 * size), RelativeUnit.Absolute),\n            Stretch = Stretch.None,\n            TileMode = TileMode.Tile,\n        };\n\n        context.FillRectangle(GridColor, Bounds);\n        context.FillRectangle(tileBrush, Bounds);\n    }\n\n    public override void Render(DrawingContext context)\n    {\n        var gridCellSize = GridCellSize;\n\n        if (ShowGrid & gridCellSize > 0 && (!IsHorizontalBarVisible || !IsVerticalBarVisible))\n        {\n            RenderBackgroundGrid(context);\n        }\n\n        var zoomFactor = ZoomFactor;\n\n        var shouldDrawPixelGrid =\n            IsPixelGridEnabled && SizeMode == SizeModes.Normal && zoomFactor > PixelGridZoomThreshold;\n\n        // Draw Grid\n        /*var viewPortSize = ViewPortSize;\n        if (ShowGrid & gridCellSize > 0 && (!IsHorizontalBarVisible || !IsVerticalBarVisible))\n        {\n            // draw the background\n            var gridColor = GridColor;\n            var altColor = GridColorAlternate;\n            var currentColor = gridColor;\n            for (var y = 0; y < viewPortSize.Height; y += gridCellSize)\n            {\n                var firstRowColor = currentColor;\n\n                for (var x = 0; x < viewPortSize.Width; x += gridCellSize)\n                {\n                    context.FillRectangle(currentColor, new Rect(x, y, gridCellSize, gridCellSize));\n                    currentColor = ReferenceEquals(currentColor, gridColor) ? altColor : gridColor;\n                }\n\n                if (Equals(firstRowColor, currentColor))\n                    currentColor = ReferenceEquals(currentColor, gridColor) ? altColor : gridColor;\n            }\n        }*/\n        /*else\n        {\n            context.FillRectangle(Background, new Rect(0, 0, Viewport.Width, Viewport.Height));\n        }*/\n\n        var image = Image;\n        if (image is null)\n            return;\n        var imageViewPort = GetImageViewPort();\n\n        // Draw iamge\n        context.DrawImage(image, GetSourceImageRegion(), imageViewPort);\n\n        if (HaveTrackerImage && _pointerPosition is { X: >= 0, Y: >= 0 })\n        {\n            var destSize = TrackerImageAutoZoom\n                ? new Size(_trackerImage!.Size.Width * zoomFactor, _trackerImage.Size.Height * zoomFactor)\n                : image.Size;\n\n            var destPos = new Point(\n                _pointerPosition.X - destSize.Width / 2,\n                _pointerPosition.Y - destSize.Height / 2\n            );\n            context.DrawImage(_trackerImage!, new Rect(destPos, destSize));\n        }\n\n        //SkiaContext.SkCanvas.dr\n        // Draw pixel grid\n        if (shouldDrawPixelGrid)\n        {\n            var offsetX = Offset.X % zoomFactor;\n            var offsetY = Offset.Y % zoomFactor;\n\n            Pen pen = new(PixelGridColor);\n            for (var x = imageViewPort.X + zoomFactor - offsetX; x < imageViewPort.Right; x += zoomFactor)\n            {\n                context.DrawLine(pen, new Point(x, imageViewPort.X), new Point(x, imageViewPort.Bottom));\n            }\n\n            for (var y = imageViewPort.Y + zoomFactor - offsetY; y < imageViewPort.Bottom; y += zoomFactor)\n            {\n                context.DrawLine(pen, new Point(imageViewPort.Y, y), new Point(imageViewPort.Right, y));\n            }\n\n            context.DrawRectangle(pen, imageViewPort);\n        }\n\n        if (!IsRectEmpty(SelectionRegion))\n        {\n            var rect = GetOffsetRectangle(SelectionRegion);\n            var selectionColor = SelectionColor;\n            context.FillRectangle(selectionColor, rect);\n            var color = Color.FromArgb(\n                255,\n                selectionColor.Color.R,\n                selectionColor.Color.G,\n                selectionColor.Color.B\n            );\n            context.DrawRectangle(new Pen(color.ToUInt32()), rect);\n        }\n    }\n\n    private bool UpdateViewPort()\n    {\n        if (Image is null)\n        {\n            HorizontalScrollBar.Maximum = 0;\n            VerticalScrollBar.Maximum = 0;\n            return true;\n        }\n\n        var scaledImageWidth = ScaledImageWidth;\n        var scaledImageHeight = ScaledImageHeight;\n        var width = scaledImageWidth - HorizontalScrollBar.ViewportSize;\n        var height = scaledImageHeight - VerticalScrollBar.ViewportSize;\n        //var width = scaledImageWidth <= Viewport.Width ? Viewport.Width : scaledImageWidth;\n        //var height = scaledImageHeight <= Viewport.Height ? Viewport.Height : scaledImageHeight;\n\n        var changed = false;\n        if (Math.Abs(HorizontalScrollBar.Maximum - width) > 0.01)\n        {\n            HorizontalScrollBar.Maximum = width;\n            changed = true;\n        }\n\n        if (Math.Abs(VerticalScrollBar.Maximum - scaledImageHeight) > 0.01)\n        {\n            VerticalScrollBar.Maximum = height;\n            changed = true;\n        }\n\n        /*if (changed)\n        {\n            var newContainer = new ContentControl\n            {\n                Width = width,\n                Height = height\n            };\n            FillContainer.Content = SizedContainer = newContainer;\n            Debug.WriteLine($\"Updated ViewPort: {DateTime.Now.Ticks}\");\n            //TriggerRender();\n        }*/\n\n        return changed;\n    }\n    #endregion\n\n    #region Events and Overrides\n\n    private void ScrollBarOnScroll(object? sender, ScrollEventArgs e)\n    {\n        TriggerRender();\n    }\n\n    /*protected override void OnScrollChanged(ScrollChangedEventArgs e)\n    {\n        Debug.WriteLine($\"ViewportDelta: {e.ViewportDelta} | OffsetDelta: {e.OffsetDelta} | ExtentDelta: {e.ExtentDelta}\");\n        if (!e.ViewportDelta.IsDefault)\n        {\n            UpdateViewPort();\n        }\n\n        TriggerRender();\n\n        base.OnScrollChanged(e);\n    }*/\n\n    /// <inheritdoc />\n    protected override void OnPointerWheelChanged(PointerWheelEventArgs e)\n    {\n        base.OnPointerWheelChanged(e);\n\n        e.PreventGestureRecognition();\n        e.Handled = true;\n\n        if (Image is null)\n            return;\n\n        if (AllowZoom && SizeMode == SizeModes.Normal)\n        {\n            // The MouseWheel event can contain multiple \"spins\" of the wheel so we need to adjust accordingly\n            //double spins = Math.Abs(e.Delta.Y);\n            //Debug.WriteLine(e.GetPosition(this));\n            // TODO: Really should update the source method to handle multiple increments rather than calling it multiple times\n            /*for (int i = 0; i < spins; i++)\n            {*/\n            ProcessMouseZoom(e.Delta.Y > 0, e.GetPosition(ViewPort));\n            //}\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void OnPointerPressed(PointerPressedEventArgs e)\n    {\n        base.OnPointerPressed(e);\n\n        if (e.Handled || _isPanning || _isSelecting || Image is null)\n            return;\n\n        var pointer = e.GetCurrentPoint(this);\n\n        if (SelectionMode != SelectionModes.None)\n        {\n            if (\n                !(\n                    pointer.Properties.IsLeftButtonPressed\n                        && (SelectWithMouseButtons & MouseButtons.LeftButton) != 0\n                    || pointer.Properties.IsMiddleButtonPressed\n                        && (SelectWithMouseButtons & MouseButtons.MiddleButton) != 0\n                    || pointer.Properties.IsRightButtonPressed\n                        && (SelectWithMouseButtons & MouseButtons.RightButton) != 0\n                )\n            )\n                return;\n            IsSelecting = true;\n        }\n        else\n        {\n            if (\n                !(\n                    pointer.Properties.IsLeftButtonPressed\n                        && (PanWithMouseButtons & MouseButtons.LeftButton) != 0\n                    || pointer.Properties.IsMiddleButtonPressed\n                        && (PanWithMouseButtons & MouseButtons.MiddleButton) != 0\n                    || pointer.Properties.IsRightButtonPressed\n                        && (PanWithMouseButtons & MouseButtons.RightButton) != 0\n                )\n                || !AutoPan\n                || SizeMode != SizeModes.Normal\n            )\n                return;\n\n            IsPanning = true;\n        }\n\n        var location = pointer.Position;\n\n        if (location.X > ViewPortSize.Width)\n            return;\n        if (location.Y > ViewPortSize.Height)\n            return;\n        _startMousePosition = location;\n    }\n\n    protected override void OnPointerReleased(PointerReleasedEventArgs e)\n    {\n        base.OnPointerReleased(e);\n        if (e.Handled)\n            return;\n\n        IsPanning = false;\n        IsSelecting = false;\n    }\n\n    /// <inheritdoc />\n    protected override void OnPointerExited(PointerEventArgs e)\n    {\n        base.OnPointerExited(e);\n\n        PointerPosition = new Point(-1, -1);\n        TriggerRender(true);\n        e.Handled = true;\n    }\n\n    /*private void ViewPortOnPointerExited(object? sender, PointerEventArgs e)\n    {\n        PointerPosition = new Point(-1, -1);\n        TriggerRender(true);\n        e.Handled = true;\n    }*/\n\n    /*protected override void OnPointerLeave(PointerEventArgs e)\n    {\n        base.OnPointerLeave(e);\n        PointerPosition = new Point(-1, -1);\n        TriggerRender(true);\n        e.Handled = true;\n    }*/\n\n    /// <inheritdoc />\n    protected override void OnPointerMoved(PointerEventArgs e)\n    {\n        base.OnPointerMoved(e);\n\n        if (e.Handled)\n            return;\n\n        var pointer = e.GetCurrentPoint(ViewPort);\n        PointerPosition = pointer.Position;\n\n        if (!_isPanning && !_isSelecting)\n        {\n            TriggerRender(true);\n            return;\n        }\n\n        if (_isPanning)\n        {\n            double x;\n            double y;\n\n            if (!InvertMousePan)\n            {\n                x = _startScrollPosition.X + (_startMousePosition.X - _pointerPosition.X);\n                y = _startScrollPosition.Y + (_startMousePosition.Y - _pointerPosition.Y);\n            }\n            else\n            {\n                x = (_startScrollPosition.X - (_startMousePosition.X - _pointerPosition.X));\n                y = (_startScrollPosition.Y - (_startMousePosition.Y - _pointerPosition.Y));\n            }\n\n            Offset = new Vector(x, y);\n        }\n        else if (_isSelecting)\n        {\n            var viewPortPoint = new Point(\n                Math.Min(_pointerPosition.X, ViewPort.Bounds.Right),\n                Math.Min(_pointerPosition.Y, ViewPort.Bounds.Bottom)\n            );\n\n            double x;\n            double y;\n            double w;\n            double h;\n\n            var imageOffset = GetImageViewPort().Position;\n\n            if (viewPortPoint.X < _startMousePosition.X)\n            {\n                x = viewPortPoint.X;\n                w = _startMousePosition.X - viewPortPoint.X;\n            }\n            else\n            {\n                x = _startMousePosition.X;\n                w = viewPortPoint.X - _startMousePosition.X;\n            }\n\n            if (viewPortPoint.Y < _startMousePosition.Y)\n            {\n                y = viewPortPoint.Y;\n                h = _startMousePosition.Y - viewPortPoint.Y;\n            }\n            else\n            {\n                y = _startMousePosition.Y;\n                h = viewPortPoint.Y - _startMousePosition.Y;\n            }\n\n            x -= imageOffset.X - Offset.X;\n            y -= imageOffset.Y - Offset.Y;\n\n            var zoomFactor = ZoomFactor;\n            x /= zoomFactor;\n            y /= zoomFactor;\n            w /= zoomFactor;\n            h /= zoomFactor;\n\n            if (w > 0 && h > 0)\n            {\n                SelectionRegion = FitRectangle(new Rect(x, y, w, h));\n            }\n        }\n\n        e.Handled = true;\n    }\n\n    /*protected override void OnPointerMoved(PointerEventArgs e)\n    {\n        base.OnPointerMoved(e);\n        if (e.Handled || !ViewPort.IsPointerOver) return;\n\n        var pointer = e.GetCurrentPoint(ViewPort);\n        PointerPosition = pointer.Position;\n\n        if (!_isPanning && !_isSelecting)\n        {\n            TriggerRender(true);\n            return;\n        }\n\n        if (_isPanning)\n        {\n            double x;\n            double y;\n\n            if (!InvertMousePan)\n            {\n                x = _startScrollPosition.X + (_startMousePosition.X - _pointerPosition.X);\n                y = _startScrollPosition.Y + (_startMousePosition.Y - _pointerPosition.Y);\n            }\n            else\n            {\n                x = (_startScrollPosition.X - (_startMousePosition.X - _pointerPosition.X));\n                y = (_startScrollPosition.Y - (_startMousePosition.Y - _pointerPosition.Y));\n            }\n\n            Offset = new Vector(x, y);\n        }\n        else if (_isSelecting)\n        {\n            double x;\n            double y;\n            double w;\n            double h;\n\n            var imageOffset = GetImageViewPort().Position;\n\n            if (_pointerPosition.X < _startMousePosition.X)\n            {\n                x = _pointerPosition.X;\n                w = _startMousePosition.X - _pointerPosition.X;\n            }\n            else\n            {\n                x = _startMousePosition.X;\n                w = _pointerPosition.X - _startMousePosition.X;\n            }\n\n            if (_pointerPosition.Y < _startMousePosition.Y)\n            {\n                y = _pointerPosition.Y;\n                h = _startMousePosition.Y - _pointerPosition.Y;\n            }\n            else\n            {\n                y = _startMousePosition.Y;\n                h = _pointerPosition.Y - _startMousePosition.Y;\n            }\n\n            x -= imageOffset.X - Offset.X;\n            y -= imageOffset.Y - Offset.Y;\n\n            var zoomFactor = ZoomFactor;\n            x /= zoomFactor;\n            y /= zoomFactor;\n            w /= zoomFactor;\n            h /= zoomFactor;\n\n            if (w > 0 && h > 0)\n            {\n\n                SelectionRegion = FitRectangle(new Rect(x, y, w, h));\n            }\n        }\n\n        e.Handled = true;\n    }*/\n\n    /// <inheritdoc />\n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        base.OnLoaded(e);\n\n        if (SizeMode == SizeModes.Fit)\n        {\n            try\n            {\n                ZoomToFit();\n            }\n            catch (Exception exception)\n            {\n                Debug.WriteLine(exception);\n            }\n\n            try\n            {\n                RestoreSizeMode();\n            }\n            catch (Exception exception)\n            {\n                Debug.WriteLine(exception);\n            }\n        }\n    }\n\n    #endregion\n\n    #region Zoom and Size modes\n    private void ProcessMouseZoom(bool isZoomIn, Point cursorPosition) =>\n        PerformZoom(isZoomIn ? ZoomActions.ZoomIn : ZoomActions.ZoomOut, true, cursorPosition);\n\n    /// <summary>\n    /// Returns an appropriate zoom level based on the specified action, relative to the current zoom level.\n    /// </summary>\n    /// <param name=\"action\">The action to determine the zoom level.</param>\n    /// <exception cref=\"System.ArgumentOutOfRangeException\">Thrown if an unsupported action is specified.</exception>\n    private int GetZoomLevel(ZoomActions action)\n    {\n        var result = action switch\n        {\n            ZoomActions.None => Zoom,\n            ZoomActions.ZoomIn => _zoomLevels.NextZoom(Zoom),\n            ZoomActions.ZoomOut => _zoomLevels.PreviousZoom(Zoom),\n            ZoomActions.ActualSize => 100,\n            _ => throw new ArgumentOutOfRangeException(nameof(action), action, null),\n        };\n        return result;\n    }\n\n    /// <summary>\n    /// Resets the <see cref=\"SizeModes\"/> property whilsts retaining the original <see cref=\"Zoom\"/>.\n    /// </summary>\n    protected void RestoreSizeMode()\n    {\n        if (SizeMode != SizeModes.Normal)\n        {\n            var previousZoom = Zoom;\n            SizeMode = SizeModes.Normal;\n            Zoom = previousZoom; // Stop the zoom getting reset to 100% before calculating the new zoom\n        }\n    }\n\n    private void PerformZoom(ZoomActions action, bool preservePosition) =>\n        PerformZoom(action, preservePosition, CenterPoint);\n\n    private void PerformZoom(ZoomActions action, bool preservePosition, Point relativePoint)\n    {\n        var currentPixel = PointToImage(relativePoint);\n        var currentZoom = Zoom;\n        var newZoom = GetZoomLevel(action);\n\n        /*if (preservePosition && Zoom != currentZoom)\n            CanRender = false;*/\n\n        RestoreSizeMode();\n        Zoom = newZoom;\n\n        if (preservePosition && Zoom != currentZoom)\n        {\n            ScrollTo(currentPixel, relativePoint);\n        }\n    }\n\n    /// <summary>\n    ///   Zooms into the image\n    /// </summary>\n    public void ZoomIn() => ZoomIn(true);\n\n    /// <summary>\n    ///   Zooms into the image\n    /// </summary>\n    /// <param name=\"preservePosition\"><c>true</c> if the current scrolling position should be preserved relative to the new zoom level, <c>false</c> to reset.</param>\n    public void ZoomIn(bool preservePosition)\n    {\n        PerformZoom(ZoomActions.ZoomIn, preservePosition);\n    }\n\n    /// <summary>\n    ///   Zooms out of the image\n    /// </summary>\n    public void ZoomOut() => ZoomOut(true);\n\n    /// <summary>\n    ///   Zooms out of the image\n    /// </summary>\n    /// <param name=\"preservePosition\"><c>true</c> if the current scrolling position should be preserved relative to the new zoom level, <c>false</c> to reset.</param>\n    public void ZoomOut(bool preservePosition)\n    {\n        PerformZoom(ZoomActions.ZoomOut, preservePosition);\n    }\n\n    /// <summary>\n    /// Zooms to the maximum size for displaying the entire image within the bounds of the control.\n    /// </summary>\n    public void ZoomToFit()\n    {\n        if (!IsImageLoaded)\n            return;\n        Zoom = ZoomLevelToFit;\n    }\n\n    /// <summary>\n    ///   Adjusts the view port to fit the given region\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the selection region.</param>\n    /// <param name=\"y\">The Y co-ordinate of the selection region.</param>\n    /// <param name=\"width\">The width of the selection region.</param>\n    /// <param name=\"height\">The height of the selection region.</param>\n    /// <param name=\"margin\">Give a margin to rectangle by a value to zoom-out that pixel value</param>\n    public void ZoomToRegion(double x, double y, double width, double height, double margin = 0)\n    {\n        ZoomToRegion(new Rect(x, y, width, height), margin);\n    }\n\n    /// <summary>\n    ///   Adjusts the view port to fit the given region\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the selection region.</param>\n    /// <param name=\"y\">The Y co-ordinate of the selection region.</param>\n    /// <param name=\"width\">The width of the selection region.</param>\n    /// <param name=\"height\">The height of the selection region.</param>\n    /// <param name=\"margin\">Give a margin to rectangle by a value to zoom-out that pixel value</param>\n    public void ZoomToRegion(int x, int y, int width, int height, double margin = 0)\n    {\n        ZoomToRegion(new Rect(x, y, width, height), margin);\n    }\n\n    /// <summary>\n    ///   Adjusts the view port to fit the given region\n    /// </summary>\n    /// <param name=\"rectangle\">The rectangle to fit the view port to.</param>\n    /// <param name=\"margin\">Give a margin to rectangle by a value to zoom-out that pixel value</param>\n    public void ZoomToRegion(Rectangle rectangle, double margin = 0) =>\n        ZoomToRegion(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, margin);\n\n    /// <summary>\n    ///   Adjusts the view port to fit the given region\n    /// </summary>\n    /// <param name=\"rectangle\">The rectangle to fit the view port to.</param>\n    /// <param name=\"margin\">Give a margin to rectangle by a value to zoom-out that pixel value</param>\n    public void ZoomToRegion(Rect rectangle, double margin = 0)\n    {\n        if (margin > 0)\n            rectangle = rectangle.Inflate(margin);\n        var ratioX = ViewPortSize.Width / rectangle.Width;\n        var ratioY = ViewPortSize.Height / rectangle.Height;\n        var zoomFactor = Math.Min(ratioX, ratioY);\n        var cx = rectangle.X + rectangle.Width / 2;\n        var cy = rectangle.Y + rectangle.Height / 2;\n\n        CanRender = false;\n        Zoom = (int)(zoomFactor * 100); // This function sets the zoom so viewport will change\n        CenterAt(new Point(cx, cy)); // If i call this here, it will move to the wrong position due wrong viewport\n    }\n\n    /// <summary>\n    /// Zooms to current selection region\n    /// </summary>\n    public void ZoomToSelectionRegion(double margin = 0)\n    {\n        if (!HaveSelection)\n            return;\n        ZoomToRegion(SelectionRegion, margin);\n    }\n\n    /// <summary>\n    /// Resets the zoom to 100%.\n    /// </summary>\n    public void PerformActualSize()\n    {\n        SizeMode = SizeModes.Normal;\n        //SetZoom(100, ImageZoomActions.ActualSize | (Zoom < 100 ? ImageZoomActions.ZoomIn : ImageZoomActions.ZoomOut));\n        Zoom = 100;\n    }\n    #endregion\n\n    #region Utility methods\n    /// <summary>\n    /// Determines whether the specified rectangle is empty\n    /// </summary>\n    private static bool IsRectEmpty(Rect rect)\n    {\n        return rect == EmptyRect;\n    }\n\n    /// <summary>\n    /// Static empty rectangle\n    /// </summary>\n    private static readonly Rect EmptyRect = new();\n\n    /// <summary>\n    ///   Determines whether the specified point is located within the image view port\n    /// </summary>\n    /// <param name=\"point\">The point.</param>\n    /// <returns>\n    ///   <c>true</c> if the specified point is located within the image view port; otherwise, <c>false</c>.\n    /// </returns>\n    public bool IsPointInImage(Point point) => GetImageViewPort().Contains(point);\n\n    /// <summary>\n    ///   Determines whether the specified point is located within the image view port\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the point to check.</param>\n    /// <param name=\"y\">The Y co-ordinate of the point to check.</param>\n    /// <returns>\n    ///   <c>true</c> if the specified point is located within the image view port; otherwise, <c>false</c>.\n    /// </returns>\n    public bool IsPointInImage(int x, int y) => IsPointInImage(new Point(x, y));\n\n    /// <summary>\n    ///   Determines whether the specified point is located within the image view port\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the point to check.</param>\n    /// <param name=\"y\">The Y co-ordinate of the point to check.</param>\n    /// <returns>\n    ///   <c>true</c> if the specified point is located within the image view port; otherwise, <c>false</c>.\n    /// </returns>\n    public bool IsPointInImage(double x, double y) => IsPointInImage(new Point(x, y));\n\n    /// <summary>\n    ///   Converts the given client size point to represent a coordinate on the source image.\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the point to convert.</param>\n    /// <param name=\"y\">The Y co-ordinate of the point to convert.</param>\n    /// <param name=\"fitToBounds\">\n    ///   if set to <c>true</c> and the point is outside the bounds of the source image, it will be mapped to the nearest edge.\n    /// </param>\n    /// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>\n    public Point PointToImage(double x, double y, bool fitToBounds = true) =>\n        PointToImage(new Point(x, y), fitToBounds);\n\n    /// <summary>\n    ///   Converts the given client size point to represent a coordinate on the source image.\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the point to convert.</param>\n    /// <param name=\"y\">The Y co-ordinate of the point to convert.</param>\n    /// <param name=\"fitToBounds\">\n    ///   if set to <c>true</c> and the point is outside the bounds of the source image, it will be mapped to the nearest edge.\n    /// </param>\n    /// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>\n    public Point PointToImage(int x, int y, bool fitToBounds = true)\n    {\n        return PointToImage(new Point(x, y), fitToBounds);\n    }\n\n    /// <summary>\n    ///   Converts the given client size point to represent a coordinate on the source image.\n    /// </summary>\n    /// <param name=\"point\">The source point.</param>\n    /// <param name=\"fitToBounds\">\n    ///   if set to <c>true</c> and the point is outside the bounds of the source image, it will be mapped to the nearest edge.\n    /// </param>\n    /// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>\n    public Point PointToImage(Point point, bool fitToBounds = true)\n    {\n        double x;\n        double y;\n\n        var viewport = GetImageViewPort();\n\n        if (!fitToBounds || viewport.Contains(point))\n        {\n            x = (point.X + Offset.X - viewport.X) / ZoomFactor;\n            y = (point.Y + Offset.Y - viewport.Y) / ZoomFactor;\n\n            var image = Image;\n            if (fitToBounds)\n            {\n                x = Math.Clamp(x, 0, image!.Size.Width - 1);\n                y = Math.Clamp(y, 0, image.Size.Height - 1);\n            }\n        }\n        else\n        {\n            x = 0; // Return Point.Empty if we couldn't match\n            y = 0;\n        }\n\n        return new(x, y);\n    }\n\n    /// <summary>\n    ///   Returns the source <see cref=\"T:System.Drawing.Point\" /> repositioned to include the current image offset and scaled by the current zoom level\n    /// </summary>\n    /// <param name=\"source\">The source <see cref=\"Point\"/> to offset.</param>\n    /// <returns>A <see cref=\"Point\"/> which has been repositioned to match the current zoom level and image offset</returns>\n    public Point GetOffsetPoint(System.Drawing.Point source)\n    {\n        var offset = GetOffsetPoint(new Point(source.X, source.Y));\n\n        return new((int)offset.X, (int)offset.Y);\n    }\n\n    /// <summary>\n    ///   Returns the source co-ordinates repositioned to include the current image offset and scaled by the current zoom level\n    /// </summary>\n    /// <param name=\"x\">The source X co-ordinate.</param>\n    /// <param name=\"y\">The source Y co-ordinate.</param>\n    /// <returns>A <see cref=\"Point\"/> which has been repositioned to match the current zoom level and image offset</returns>\n    public Point GetOffsetPoint(int x, int y)\n    {\n        return GetOffsetPoint(new System.Drawing.Point(x, y));\n    }\n\n    /// <summary>\n    ///   Returns the source co-ordinates repositioned to include the current image offset and scaled by the current zoom level\n    /// </summary>\n    /// <param name=\"x\">The source X co-ordinate.</param>\n    /// <param name=\"y\">The source Y co-ordinate.</param>\n    /// <returns>A <see cref=\"Point\"/> which has been repositioned to match the current zoom level and image offset</returns>\n    public Point GetOffsetPoint(double x, double y)\n    {\n        return GetOffsetPoint(new Point(x, y));\n    }\n\n    /// <summary>\n    ///   Returns the source <see cref=\"T:System.Drawing.PointF\" /> repositioned to include the current image offset and scaled by the current zoom level\n    /// </summary>\n    /// <param name=\"source\">The source <see cref=\"System.Drawing.PointF\"/> to offset.</param>\n    /// <returns>A <see cref=\"System.Drawing.PointF\"/> which has been repositioned to match the current zoom level and image offset</returns>\n    public Point GetOffsetPoint(Point source)\n    {\n        var viewport = GetImageViewPort();\n        var scaled = GetScaledPoint(source);\n        var offsetX = viewport.Left + Offset.X;\n        var offsetY = viewport.Top + Offset.Y;\n\n        return new(scaled.X + offsetX, scaled.Y + offsetY);\n    }\n\n    /// <summary>\n    ///   Returns the source <see cref=\"T:System.Drawing.RectangleF\" /> scaled according to the current zoom level and repositioned to include the current image offset\n    /// </summary>\n    /// <param name=\"source\">The source <see cref=\"System.Drawing.RectangleF\"/> to offset.</param>\n    /// <returns>A <see cref=\"System.Drawing.RectangleF\"/> which has been resized and repositioned to match the current zoom level and image offset</returns>\n    public Rect GetOffsetRectangle(Rect source)\n    {\n        var viewport = GetImageViewPort();\n        var scaled = GetScaledRectangle(source);\n        var offsetX = viewport.Left - Offset.X;\n        var offsetY = viewport.Top - Offset.Y;\n\n        return new(new Point(scaled.Left + offsetX, scaled.Top + offsetY), scaled.Size);\n    }\n\n    /// <summary>\n    ///   Returns the source rectangle scaled according to the current zoom level and repositioned to include the current image offset\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the source rectangle.</param>\n    /// <param name=\"y\">The Y co-ordinate of the source rectangle.</param>\n    /// <param name=\"width\">The width of the rectangle.</param>\n    /// <param name=\"height\">The height of the rectangle.</param>\n    /// <returns>A <see cref=\"Rectangle\"/> which has been resized and repositioned to match the current zoom level and image offset</returns>\n    public Rectangle GetOffsetRectangle(int x, int y, int width, int height)\n    {\n        return GetOffsetRectangle(new Rectangle(x, y, width, height));\n    }\n\n    /// <summary>\n    ///   Returns the source rectangle scaled according to the current zoom level and repositioned to include the current image offset\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the source rectangle.</param>\n    /// <param name=\"y\">The Y co-ordinate of the source rectangle.</param>\n    /// <param name=\"width\">The width of the rectangle.</param>\n    /// <param name=\"height\">The height of the rectangle.</param>\n    /// <returns>A <see cref=\"System.Drawing.RectangleF\"/> which has been resized and repositioned to match the current zoom level and image offset</returns>\n    public Rect GetOffsetRectangle(double x, double y, double width, double height)\n    {\n        return GetOffsetRectangle(new Rect(x, y, width, height));\n    }\n\n    /// <summary>\n    ///   Returns the source <see cref=\"T:System.Drawing.Rectangle\" /> scaled according to the current zoom level and repositioned to include the current image offset\n    /// </summary>\n    /// <param name=\"source\">The source <see cref=\"Rectangle\"/> to offset.</param>\n    /// <returns>A <see cref=\"Rectangle\"/> which has been resized and repositioned to match the current zoom level and image offset</returns>\n    public Rectangle GetOffsetRectangle(Rectangle source)\n    {\n        var viewport = GetImageViewPort();\n        var scaled = GetScaledRectangle(source);\n        var offsetX = viewport.Left + Offset.X;\n        var offsetY = viewport.Top + Offset.Y;\n\n        return new(\n            new System.Drawing.Point((int)(scaled.Left + offsetX), (int)(scaled.Top + offsetY)),\n            new System.Drawing.Size((int)scaled.Size.Width, (int)scaled.Size.Height)\n        );\n    }\n\n    /// <summary>\n    ///   Fits a given <see cref=\"T:System.Drawing.Rectangle\" /> to match image boundaries\n    /// </summary>\n    /// <param name=\"rectangle\">The rectangle.</param>\n    /// <returns>\n    ///   A <see cref=\"T:System.Drawing.Rectangle\" /> structure remapped to fit the image boundaries\n    /// </returns>\n    public Rectangle FitRectangle(Rectangle rectangle)\n    {\n        var image = Image;\n        if (image is null)\n            return Rectangle.Empty;\n        var x = rectangle.X;\n        var y = rectangle.Y;\n        var w = rectangle.Width;\n        var h = rectangle.Height;\n\n        if (x < 0)\n        {\n            x = 0;\n        }\n\n        if (y < 0)\n        {\n            y = 0;\n        }\n\n        if (x + w > image.Size.Width)\n        {\n            w = (int)(image.Size.Width - x);\n        }\n\n        if (y + h > image.Size.Height)\n        {\n            h = (int)(image.Size.Height - y);\n        }\n\n        return new(x, y, w, h);\n    }\n\n    /// <summary>\n    ///   Fits a given <see cref=\"T:System.Drawing.RectangleF\" /> to match image boundaries\n    /// </summary>\n    /// <param name=\"rectangle\">The rectangle.</param>\n    /// <returns>\n    ///   A <see cref=\"T:System.Drawing.RectangleF\" /> structure remapped to fit the image boundaries\n    /// </returns>\n    public Rect FitRectangle(Rect rectangle)\n    {\n        var image = Image;\n        if (image is null)\n            return EmptyRect;\n        var x = rectangle.X;\n        var y = rectangle.Y;\n        var w = rectangle.Width;\n        var h = rectangle.Height;\n\n        if (x < 0)\n        {\n            w -= -x;\n            x = 0;\n        }\n\n        if (y < 0)\n        {\n            h -= -y;\n            y = 0;\n        }\n\n        if (x + w > image.Size.Width)\n        {\n            w = image.Size.Width - x;\n        }\n\n        if (y + h > image.Size.Height)\n        {\n            h = image.Size.Height - y;\n        }\n\n        return new(x, y, w, h);\n    }\n    #endregion\n\n    #region Navigate / Scroll methods\n    /// <summary>\n    ///   Scrolls the control to the given point in the image, offset at the specified display point\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the point to scroll to.</param>\n    /// <param name=\"y\">The Y co-ordinate of the point to scroll to.</param>\n    /// <param name=\"relativeX\">The X co-ordinate relative to the <c>x</c> parameter.</param>\n    /// <param name=\"relativeY\">The Y co-ordinate relative to the <c>y</c> parameter.</param>\n    public void ScrollTo(double x, double y, double relativeX, double relativeY) =>\n        ScrollTo(new Point(x, y), new Point(relativeX, relativeY));\n\n    /// <summary>\n    ///   Scrolls the control to the given point in the image, offset at the specified display point\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the point to scroll to.</param>\n    /// <param name=\"y\">The Y co-ordinate of the point to scroll to.</param>\n    /// <param name=\"relativeX\">The X co-ordinate relative to the <c>x</c> parameter.</param>\n    /// <param name=\"relativeY\">The Y co-ordinate relative to the <c>y</c> parameter.</param>\n    public void ScrollTo(int x, int y, int relativeX, int relativeY) =>\n        ScrollTo(new Point(x, y), new Point(relativeX, relativeY));\n\n    /// <summary>\n    ///   Scrolls the control to the given point in the image, offset at the specified display point\n    /// </summary>\n    /// <param name=\"imageLocation\">The point of the image to attempt to scroll to.</param>\n    /// <param name=\"relativeDisplayPoint\">The relative display point to offset scrolling by.</param>\n    public void ScrollTo(Point imageLocation, Point relativeDisplayPoint)\n    {\n        //CanRender = false;\n        var zoomFactor = ZoomFactor;\n        var x = imageLocation.X * zoomFactor - relativeDisplayPoint.X;\n        var y = imageLocation.Y * zoomFactor - relativeDisplayPoint.Y;\n\n        _canRender = true;\n        Offset = new Vector(x, y);\n\n        /*Debug.WriteLine(\n            $\"X/Y: {x},{y} | \\n\" +\n            $\"Offset: {Offset} | \\n\" +\n            $\"ZoomFactor: {ZoomFactor} | \\n\" +\n            $\"Image Location: {imageLocation}\\n\" +\n            $\"MAX: {HorizontalScrollBar.Maximum},{VerticalScrollBar.Maximum} \\n\" +\n            $\"ViewPort: {Viewport.Width},{Viewport.Height} \\n\" +\n            $\"Container: {HorizontalScrollBar.ViewportSize},{VerticalScrollBar.ViewportSize} \\n\" +\n            $\"Relative: {relativeDisplayPoint}\");*/\n    }\n\n    /// <summary>\n    ///   Centers the given point in the image in the center of the control\n    /// </summary>\n    /// <param name=\"imageLocation\">The point of the image to attempt to center.</param>\n    public void CenterAt(System.Drawing.Point imageLocation) =>\n        ScrollTo(\n            new Point(imageLocation.X, imageLocation.Y),\n            new Point(ViewPortSize.Width / 2, ViewPortSize.Height / 2)\n        );\n\n    /// <summary>\n    ///   Centers the given point in the image in the center of the control\n    /// </summary>\n    /// <param name=\"imageLocation\">The point of the image to attempt to center.</param>\n    public void CenterAt(Point imageLocation) =>\n        ScrollTo(imageLocation, new Point(ViewPortSize.Width / 2, ViewPortSize.Height / 2));\n\n    /// <summary>\n    ///   Centers the given point in the image in the center of the control\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the point to center.</param>\n    /// <param name=\"y\">The Y co-ordinate of the point to center.</param>\n    public void CenterAt(int x, int y) => CenterAt(new Point(x, y));\n\n    /// <summary>\n    ///   Centers the given point in the image in the center of the control\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the point to center.</param>\n    /// <param name=\"y\">The Y co-ordinate of the point to center.</param>\n    public void CenterAt(double x, double y) => CenterAt(new Point(x, y));\n\n    /// <summary>\n    /// Resets the viewport to show the center of the image.\n    /// </summary>\n    public void CenterToImage()\n    {\n        Offset = new Vector(HorizontalScrollBar.Maximum / 2, VerticalScrollBar.Maximum / 2);\n    }\n    #endregion\n\n    #region Selection / ROI methods\n\n    /// <summary>\n    ///   Returns the source <see cref=\"T:System.Drawing.Point\" /> scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the point to scale.</param>\n    /// <param name=\"y\">The Y co-ordinate of the point to scale.</param>\n    /// <returns>A <see cref=\"Point\"/> which has been scaled to match the current zoom level</returns>\n    public Point GetScaledPoint(int x, int y)\n    {\n        return GetScaledPoint(new Point(x, y));\n    }\n\n    /// <summary>\n    ///   Returns the source <see cref=\"T:System.Drawing.Point\" /> scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the point to scale.</param>\n    /// <param name=\"y\">The Y co-ordinate of the point to scale.</param>\n    /// <returns>A <see cref=\"Point\"/> which has been scaled to match the current zoom level</returns>\n    public PointF GetScaledPoint(float x, float y)\n    {\n        return GetScaledPoint(new PointF(x, y));\n    }\n\n    /// <summary>\n    ///   Returns the source <see cref=\"T:System.Drawing.Point\" /> scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"source\">The source <see cref=\"Point\"/> to scale.</param>\n    /// <returns>A <see cref=\"Point\"/> which has been scaled to match the current zoom level</returns>\n    public Point GetScaledPoint(Point source)\n    {\n        return new(source.X * ZoomFactor, source.Y * ZoomFactor);\n    }\n\n    /// <summary>\n    ///   Returns the source <see cref=\"T:System.Drawing.PointF\" /> scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"source\">The source <see cref=\"PointF\"/> to scale.</param>\n    /// <returns>A <see cref=\"PointF\"/> which has been scaled to match the current zoom level</returns>\n    public PointF GetScaledPoint(PointF source)\n    {\n        return new((float)(source.X * ZoomFactor), (float)(source.Y * ZoomFactor));\n    }\n\n    /// <summary>\n    ///   Returns the source rectangle scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the source rectangle.</param>\n    /// <param name=\"y\">The Y co-ordinate of the source rectangle.</param>\n    /// <param name=\"width\">The width of the rectangle.</param>\n    /// <param name=\"height\">The height of the rectangle.</param>\n    /// <returns>A <see cref=\"Rectangle\"/> which has been scaled to match the current zoom level</returns>\n    public Rect GetScaledRectangle(int x, int y, int width, int height)\n    {\n        return GetScaledRectangle(new Rect(x, y, width, height));\n    }\n\n    /// <summary>\n    ///   Returns the source rectangle scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"x\">The X co-ordinate of the source rectangle.</param>\n    /// <param name=\"y\">The Y co-ordinate of the source rectangle.</param>\n    /// <param name=\"width\">The width of the rectangle.</param>\n    /// <param name=\"height\">The height of the rectangle.</param>\n    /// <returns>A <see cref=\"RectangleF\"/> which has been scaled to match the current zoom level</returns>\n    public RectangleF GetScaledRectangle(float x, float y, float width, float height)\n    {\n        return GetScaledRectangle(new RectangleF(x, y, width, height));\n    }\n\n    /// <summary>\n    ///   Returns the source rectangle scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"location\">The location of the source rectangle.</param>\n    /// <param name=\"size\">The size of the source rectangle.</param>\n    /// <returns>A <see cref=\"Rectangle\"/> which has been scaled to match the current zoom level</returns>\n    public Rect GetScaledRectangle(Point location, Size size)\n    {\n        return GetScaledRectangle(new Rect(location, size));\n    }\n\n    /// <summary>\n    ///   Returns the source rectangle scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"location\">The location of the source rectangle.</param>\n    /// <param name=\"size\">The size of the source rectangle.</param>\n    /// <returns>A <see cref=\"Rectangle\"/> which has been scaled to match the current zoom level</returns>\n    public RectangleF GetScaledRectangle(PointF location, SizeF size)\n    {\n        return GetScaledRectangle(new RectangleF(location, size));\n    }\n\n    /// <summary>\n    ///   Returns the source <see cref=\"T:System.Drawing.Rectangle\" /> scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"source\">The source <see cref=\"Rectangle\"/> to scale.</param>\n    /// <returns>A <see cref=\"Rectangle\"/> which has been scaled to match the current zoom level</returns>\n    public Rect GetScaledRectangle(Rect source)\n    {\n        return new(\n            source.Left * ZoomFactor,\n            source.Top * ZoomFactor,\n            source.Width * ZoomFactor,\n            source.Height * ZoomFactor\n        );\n    }\n\n    /// <summary>\n    ///   Returns the source <see cref=\"T:System.Drawing.RectangleF\" /> scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"source\">The source <see cref=\"RectangleF\"/> to scale.</param>\n    /// <returns>A <see cref=\"RectangleF\"/> which has been scaled to match the current zoom level</returns>\n    public RectangleF GetScaledRectangle(RectangleF source)\n    {\n        return new(\n            (float)(source.Left * ZoomFactor),\n            (float)(source.Top * ZoomFactor),\n            (float)(source.Width * ZoomFactor),\n            (float)(source.Height * ZoomFactor)\n        );\n    }\n\n    /// <summary>\n    ///   Returns the source size scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"width\">The width of the size to scale.</param>\n    /// <param name=\"height\">The height of the size to scale.</param>\n    /// <returns>A <see cref=\"SizeF\"/> which has been resized to match the current zoom level</returns>\n    public SizeF GetScaledSize(float width, float height)\n    {\n        return GetScaledSize(new SizeF(width, height));\n    }\n\n    /// <summary>\n    ///   Returns the source size scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"width\">The width of the size to scale.</param>\n    /// <param name=\"height\">The height of the size to scale.</param>\n    /// <returns>A <see cref=\"Size\"/> which has been resized to match the current zoom level</returns>\n    public Size GetScaledSize(int width, int height)\n    {\n        return GetScaledSize(new Size(width, height));\n    }\n\n    /// <summary>\n    ///   Returns the source <see cref=\"T:System.Drawing.SizeF\" /> scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"source\">The source <see cref=\"SizeF\"/> to scale.</param>\n    /// <returns>A <see cref=\"SizeF\"/> which has been resized to match the current zoom level</returns>\n    public SizeF GetScaledSize(SizeF source)\n    {\n        return new((float)(source.Width * ZoomFactor), (float)(source.Height * ZoomFactor));\n    }\n\n    /// <summary>\n    ///   Returns the source <see cref=\"T:System.Drawing.Size\" /> scaled according to the current zoom level\n    /// </summary>\n    /// <param name=\"source\">The source <see cref=\"Size\"/> to scale.</param>\n    /// <returns>A <see cref=\"Size\"/> which has been resized to match the current zoom level</returns>\n    public Size GetScaledSize(Size source)\n    {\n        return new(source.Width * ZoomFactor, source.Height * ZoomFactor);\n    }\n\n    /// <summary>\n    ///   Creates a selection region which encompasses the entire image\n    /// </summary>\n    /// <exception cref=\"System.InvalidOperationException\">Thrown if no image is currently set</exception>\n    public void SelectAll()\n    {\n        var image = Image;\n        if (image is null)\n            return;\n        SelectionRegion = new Rect(0, 0, image.Size.Width, image.Size.Height);\n    }\n\n    /// <summary>\n    /// Clears any existing selection region\n    /// </summary>\n    public void SelectNone()\n    {\n        SelectionRegion = EmptyRect;\n    }\n\n    #endregion\n\n    #region Viewport and image region methods\n    /// <summary>\n    ///   Gets the source image region.\n    /// </summary>\n    /// <returns></returns>\n    public Rect GetSourceImageRegion()\n    {\n        var image = Image;\n        if (image is null)\n            return EmptyRect;\n\n        switch (SizeMode)\n        {\n            case SizeModes.Normal:\n                var offset = Offset;\n                var viewPort = GetImageViewPort();\n                var zoomFactor = ZoomFactor;\n                var sourceLeft = offset.X / zoomFactor;\n                var sourceTop = offset.Y / zoomFactor;\n                var sourceWidth = viewPort.Width / zoomFactor;\n                var sourceHeight = viewPort.Height / zoomFactor;\n\n                return new Rect(sourceLeft, sourceTop, sourceWidth, sourceHeight);\n        }\n\n        return new Rect(0, 0, image.Size.Width, image.Size.Height);\n    }\n\n    /// <summary>\n    /// Gets the image view port.\n    /// </summary>\n    /// <returns></returns>\n    public Rect GetImageViewPort()\n    {\n        var viewPortSize = ViewPortSize;\n        if (!IsImageLoaded || viewPortSize is { Width: 0, Height: 0 })\n            return EmptyRect;\n\n        double xOffset = 0;\n        double yOffset = 0;\n        double width;\n        double height;\n\n        switch (SizeMode)\n        {\n            case SizeModes.Normal:\n                if (AutoCenter)\n                {\n                    xOffset = (!IsHorizontalBarVisible ? (viewPortSize.Width - ScaledImageWidth) / 2 : 0);\n                    yOffset = (!IsVerticalBarVisible ? (viewPortSize.Height - ScaledImageHeight) / 2 : 0);\n                }\n\n                width = Math.Min(ScaledImageWidth - Math.Abs(Offset.X), viewPortSize.Width);\n                height = Math.Min(ScaledImageHeight - Math.Abs(Offset.Y), viewPortSize.Height);\n                break;\n            case SizeModes.Stretch:\n                width = viewPortSize.Width;\n                height = viewPortSize.Height;\n                break;\n            case SizeModes.Fit:\n                var image = Image;\n                var scaleFactor = Math.Min(\n                    viewPortSize.Width / image!.Size.Width,\n                    viewPortSize.Height / image.Size.Height\n                );\n\n                width = Math.Floor(image.Size.Width * scaleFactor);\n                height = Math.Floor(image.Size.Height * scaleFactor);\n\n                if (AutoCenter)\n                {\n                    xOffset = (viewPortSize.Width - width) / 2;\n                    yOffset = (viewPortSize.Height - height) / 2;\n                }\n\n                break;\n            default:\n                throw new ArgumentOutOfRangeException(nameof(SizeMode), SizeMode, null);\n        }\n\n        return new(xOffset, yOffset, width, height);\n    }\n    #endregion\n\n    #region Image methods\n    public void LoadImage(string path)\n    {\n        Image = new Bitmap(path);\n    }\n\n    public Bitmap? GetSelectedBitmap()\n    {\n        if (!HaveSelection || Image is null)\n            return null;\n\n        using var stream = new MemoryStream();\n        Image.Save(stream);\n        var image = WriteableBitmap.Decode(stream);\n        stream.Dispose();\n\n        var selection = SelectionRegionNet;\n        var pixelSize = SelectionPixelSize;\n        using var frameBuffer = image.Lock();\n\n        var newBitmap = new WriteableBitmap(pixelSize, image.Dpi, frameBuffer.Format, AlphaFormat.Unpremul);\n        using var newFrameBuffer = newBitmap.Lock();\n\n        var i = 0;\n\n        unsafe\n        {\n            var inputPixels = (uint*)(void*)frameBuffer.Address;\n            var targetPixels = (uint*)(void*)newFrameBuffer.Address;\n\n            for (var y = selection.Y; y < selection.Bottom; y++)\n            {\n                var thisY = y * frameBuffer.Size.Width;\n                for (var x = selection.X; x < selection.Right; x++)\n                {\n                    targetPixels![i++] = inputPixels![thisY + x];\n                }\n            }\n        }\n\n        return newBitmap;\n    }\n    #endregion\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/AdvancedImageBoxView.axaml",
    "content": "﻿<UserControl\n    x:Class=\"StabilityMatrix.Avalonia.Controls.AdvancedImageBoxView\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:gif=\"clr-namespace:Avalonia.Gif;assembly=Avalonia.Gif\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    d:DataContext=\"{x:Static mocks:DesignData.SampleImageSource}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"models:ImageSource\"\n    mc:Ignorable=\"d\">\n    <Grid>\n        <!-- Tag is not used but sets TemplateKey which is used to select the DataTemplate later -->\n        <ContentPresenter \n            Tag=\"{Binding TemplateKeyAsync^}\"\n            Content=\"{Binding}\">\n            <ContentPresenter.ContentTemplate>\n                <controls:DataTemplateSelector x:TypeArguments=\"models:ImageSourceTemplateType\">\n                    <DataTemplate x:Key=\"{x:Static models:ImageSourceTemplateType.WebpAnimation}\" DataType=\"models:ImageSource\">\n                        <gif:GifImage\n                            Stretch=\"Uniform\"\n                            SourceUri=\"{Binding LocalFile.FullPath}\"/>\n                    </DataTemplate>\n                    \n                    <DataTemplate x:Key=\"{x:Static models:ImageSourceTemplateType.Image}\" DataType=\"models:ImageSource\">\n                        <controls:AdvancedImageBox\n                            CornerRadius=\"4\"\n                            Image=\"{Binding BitmapAsync^}\"\n                            SizeMode=\"Fit\">\n                            <controls:AdvancedImageBox.ContextFlyout>\n                                <ui:FAMenuFlyout>\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{x:Static controls:AdvancedImageBoxView.FlyoutCopyCommand}\"\n                                        CommandParameter=\"{Binding}\"\n                                        HotKey=\"Ctrl+C\"\n                                        IconSource=\"Copy\"\n                                        Text=\"{x:Static lang:Resources.Action_Copy}\" />\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{x:Static controls:AdvancedImageBoxView.FlyoutCopyAsBitmapCommand}\"\n                                        CommandParameter=\"{Binding}\"\n                                        HotKey=\"Shift+Ctrl+C\"\n                                        IsVisible=\"{OnPlatform Windows=True, Default=False}\"\n                                        Text=\"{x:Static lang:Resources.Action_CopyAsBitmap}\" />\n                                </ui:FAMenuFlyout>\n                            </controls:AdvancedImageBox.ContextFlyout>\n                        </controls:AdvancedImageBox>\n                    </DataTemplate>\n                    \n                    <DataTemplate x:Key=\"{x:Static models:ImageSourceTemplateType.Default}\" DataType=\"models:ImageSource\">\n                        <TextBlock \n                            HorizontalAlignment=\"Center\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Unsupported Format\"/>\n                    </DataTemplate>\n                </controls:DataTemplateSelector>\n            </ContentPresenter.ContentTemplate>\n        </ContentPresenter>\n\n        <!-- Label pill card -->\n        <Border\n            IsVisible=\"{Binding Label, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n            Grid.Row=\"0\"\n            Margin=\"4\"\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Bottom\"\n            BoxShadow=\"inset 1.2 0 10 1.8 #66000000\"\n            CornerRadius=\"16\">\n            <Border.Resources>\n                <DropShadowEffect\n                    x:Key=\"TextDropShadowEffect\"\n                    BlurRadius=\"12\"\n                    Opacity=\"0.9\"\n                    Color=\"#FF000000\" />\n                <DropShadowEffect\n                    x:Key=\"ImageDropShadowEffect\"\n                    BlurRadius=\"12\"\n                    Opacity=\"0.2\"\n                    Color=\"#FF000000\" />\n            </Border.Resources>\n            <Button\n                Padding=\"10,4\"\n                Classes=\"transparent\"\n                CornerRadius=\"16\">\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                    <TextBlock\n                        VerticalAlignment=\"Center\"\n                        Effect=\"{StaticResource TextDropShadowEffect}\"\n                        Text=\"{Binding Label}\" />\n                </StackPanel>\n            </Button>\n        </Border>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/AdvancedImageBoxView.axaml.cs",
    "content": "﻿using System.IO;\nusing System.Threading.Tasks;\nusing Avalonia.Controls;\nusing CommunityToolkit.Mvvm.Input;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic partial class AdvancedImageBoxView : UserControl\n{\n    public AdvancedImageBoxView()\n    {\n        InitializeComponent();\n    }\n\n    public static AsyncRelayCommand<ImageSource?> FlyoutCopyCommand { get; } = new(FlyoutCopy);\n\n    public static AsyncRelayCommand<ImageSource?> FlyoutCopyAsBitmapCommand { get; } =\n        new(FlyoutCopyAsBitmap);\n\n    private static async Task FlyoutCopy(ImageSource? imageSource)\n    {\n        if (imageSource is null)\n            return;\n\n        if (imageSource.LocalFile is { } imagePath)\n        {\n            await App.Clipboard.SetFileDataObjectAsync(imagePath);\n        }\n        else if (await imageSource.GetBitmapAsync() is { } bitmap)\n        {\n            // Write to temp file\n            var tempFile = new FilePath(Path.GetTempFileName() + \".png\");\n\n            bitmap.Save(tempFile);\n\n            await App.Clipboard.SetFileDataObjectAsync(tempFile);\n        }\n    }\n\n    private static async Task FlyoutCopyAsBitmap(ImageSource? imageSource)\n    {\n        if (imageSource is null || !Compat.IsWindows)\n            return;\n\n        if (await imageSource.GetBitmapAsync() is { } bitmap)\n        {\n            await WindowsClipboard.SetBitmapAsync(bitmap);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/AppWindowBase.cs",
    "content": "﻿using System;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Interactivity;\nusing Avalonia.Threading;\nusing FluentAvalonia.UI.Windowing;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"VirtualMemberNeverOverridden.Global\")]\npublic class AppWindowBase : AppWindow\n{\n    public CancellationTokenSource? ShowAsyncCts { get; set; }\n    \n    protected AppWindowBase()\n    {\n    }\n    \n    public void ShowWithCts(CancellationTokenSource cts)\n    {\n        ShowAsyncCts?.Cancel();\n        ShowAsyncCts = cts;\n        Show();\n    }\n\n    public Task ShowAsync()\n    {\n        ShowAsyncCts?.Cancel();\n        ShowAsyncCts = new CancellationTokenSource();\n        \n        var tcs = new TaskCompletionSource<bool>();\n        ShowAsyncCts.Token.Register(s =>\n        {\n            ((TaskCompletionSource<bool>) s!).SetResult(true);\n        }, tcs);\n        \n        Show();\n        \n        return tcs.Task;\n    }\n    \n    protected override void OnClosed(EventArgs e)\n    {\n        base.OnClosed(e);\n\n        if (ShowAsyncCts is not null)\n        {\n            ShowAsyncCts.Cancel();\n            ShowAsyncCts = null;\n        }\n    }\n    \n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        base.OnLoaded(e);\n        \n        if (DataContext is ViewModelBase viewModel)\n        {\n            // Run synchronous load then async load\n            viewModel.OnLoaded();\n        \n            // Can't block here so we'll run as async on UI thread\n            Dispatcher.UIThread.InvokeAsync(async () =>\n            {\n                await viewModel.OnLoadedAsync();\n            }).SafeFireAndForget();\n        }\n    }\n\n    protected override void OnUnloaded(RoutedEventArgs e)\n    {\n        base.OnUnloaded(e);\n        \n        if (DataContext is not ViewModelBase viewModel)\n            return;\n        \n        // Run synchronous load then async unload\n        viewModel.OnUnloaded();\n        \n        // Can't block here so we'll run as async on UI thread\n        Dispatcher.UIThread.InvokeAsync(async () =>\n        {\n            await viewModel.OnUnloadedAsync();\n        }).SafeFireAndForget();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/ApplicationSplashScreen.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Avalonia.Media;\nusing FluentAvalonia.UI.Windowing;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\ninternal class ApplicationSplashScreen : IApplicationSplashScreen\n{\n    public string? AppName { get; init; }\n\n    public IImage? AppIcon { get; init; }\n\n    public object? SplashScreenContent { get; init; }\n\n    public int MinimumShowTime { get; init; }\n\n    public Func<CancellationToken, Task>? InitApp { get; init; }\n\n    public Task RunTasks(CancellationToken cancellationToken)\n    {\n        return InitApp?.Invoke(cancellationToken) ?? Task.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/AutoGrid.cs",
    "content": "﻿// Modified from https://github.com/AvaloniaUI/AvaloniaAutoGrid\n/*The MIT License (MIT)\n\nCopyright (c) 2013 Charles Brown (carbonrobot)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/\n\nusing System;\nusing System.ComponentModel;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Linq;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Data;\nusing Avalonia.Layout;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n/// <summary>\n/// Defines a flexible grid area that consists of columns and rows.\n/// Depending on the orientation, either the rows or the columns are auto-generated,\n/// and the children's position is set according to their index.\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class AutoGrid : Grid\n{\n    /// <summary>\n    /// Gets or sets the child horizontal alignment.\n    /// </summary>\n    /// <value>The child horizontal alignment.</value>\n    [Category(\"Layout\"), Description(\"Presets the horizontal alignment of all child controls\")]\n    public HorizontalAlignment? ChildHorizontalAlignment\n    {\n        get => (HorizontalAlignment?)GetValue(ChildHorizontalAlignmentProperty);\n        set => SetValue(ChildHorizontalAlignmentProperty, value);\n    }\n\n    /// <summary>\n    /// Gets or sets the child margin.\n    /// </summary>\n    /// <value>The child margin.</value>\n    [Category(\"Layout\"), Description(\"Presets the margin of all child controls\")]\n    public Thickness? ChildMargin\n    {\n        get => (Thickness?)GetValue(ChildMarginProperty);\n        set => SetValue(ChildMarginProperty, value);\n    }\n\n    /// <summary>\n    /// Gets or sets the child vertical alignment.\n    /// </summary>\n    /// <value>The child vertical alignment.</value>\n    [Category(\"Layout\"), Description(\"Presets the vertical alignment of all child controls\")]\n    public VerticalAlignment? ChildVerticalAlignment\n    {\n        get => (VerticalAlignment?)GetValue(ChildVerticalAlignmentProperty);\n        set => SetValue(ChildVerticalAlignmentProperty, value);\n    }\n\n    /// <summary>\n    /// Gets or sets the column count\n    /// </summary>\n    [Category(\"Layout\"), Description(\"Defines a set number of columns\")]\n    public int ColumnCount\n    {\n        get => (int)GetValue(ColumnCountProperty)!;\n        set => SetValue(ColumnCountProperty, value);\n    }\n\n    /// <summary>\n    /// Gets or sets the fixed column width\n    /// </summary>\n    [Category(\"Layout\"), Description(\"Presets the width of all columns set using the ColumnCount property\")]\n\n    public GridLength ColumnWidth\n    {\n        get => (GridLength)GetValue(ColumnWidthProperty)!;\n        set => SetValue(ColumnWidthProperty, value);\n    }\n\n    /// <summary>\n    /// Gets or sets a value indicating whether the children are automatically indexed.\n    /// <remarks>\n    /// The default is <c>true</c>.\n    /// Note that if children are already indexed, setting this property to <c>false</c> will not remove their indices.\n    /// </remarks>\n    /// </summary>\n    [Category(\"Layout\"), Description(\"Set to false to disable the auto layout functionality\")]\n    public bool IsAutoIndexing\n    {\n        get => (bool)GetValue(IsAutoIndexingProperty)!;\n        set => SetValue(IsAutoIndexingProperty, value);\n    }\n\n    /// <summary>\n    /// Gets or sets the orientation.\n    /// <remarks>The default is Vertical.</remarks>\n    /// </summary>\n    /// <value>The orientation.</value>\n    [Category(\"Layout\"), Description(\"Defines the directionality of the autolayout. Use vertical for a column first layout, horizontal for a row first layout.\")]\n    public Orientation Orientation\n    {\n        get => (Orientation)GetValue(OrientationProperty)!;\n        set => SetValue(OrientationProperty, value);\n    }\n\n    /// <summary>\n    /// Gets or sets the number of rows\n    /// </summary>\n    [Category(\"Layout\"), Description(\"Defines a set number of rows\")]\n    public int RowCount\n    {\n        get => (int)GetValue(RowCountProperty)!;\n        set => SetValue(RowCountProperty, value);\n    }\n\n    /// <summary>\n    /// Gets or sets the fixed row height\n    /// </summary>\n    [Category(\"Layout\"), Description(\"Presets the height of all rows set using the RowCount property\")]\n    public GridLength RowHeight\n    {\n        get => (GridLength)GetValue(RowHeightProperty)!;\n        set => SetValue(RowHeightProperty, value);\n    }\n        \n    /// <summary>\n    /// Handles the column count changed event\n    /// </summary>\n    public static void ColumnCountChanged(AvaloniaPropertyChangedEventArgs e)\n    {\n        if ((int)e.NewValue! < 0)\n            return;\n\n        var grid = (AutoGrid)e.Sender;\n\n            \n        // look for an existing column definition for the height\n        var width = grid.ColumnWidth;\n        if (!grid.IsSet(ColumnWidthProperty) && grid.ColumnDefinitions.Count > 0)\n            width = grid.ColumnDefinitions[0].Width;\n\n        // clear and rebuild\n        grid.ColumnDefinitions.Clear();\n        for (var i = 0; i < (int)e.NewValue; i++)\n            grid.ColumnDefinitions.Add(\n                new ColumnDefinition() { Width = width });\n    }\n\n    /// <summary>\n    /// Handle the fixed column width changed event\n    /// </summary>\n    public static void FixedColumnWidthChanged(AvaloniaPropertyChangedEventArgs e)\n    {\n        var grid = (AutoGrid)e.Sender;\n\n        // add a default column if missing\n        if (grid.ColumnDefinitions.Count == 0)\n            grid.ColumnDefinitions.Add(new ColumnDefinition());\n\n        // set all existing columns to this width\n        foreach (var t in grid.ColumnDefinitions)\n            t.Width = (GridLength)e.NewValue!;\n    }\n\n    /// <summary>\n    /// Handle the fixed row height changed event\n    /// </summary>\n    public static void FixedRowHeightChanged(AvaloniaPropertyChangedEventArgs e)\n    {\n        var grid = (AutoGrid)e.Sender;\n\n        // add a default row if missing\n        if (grid.RowDefinitions.Count == 0)\n            grid.RowDefinitions.Add(new RowDefinition());\n\n        // set all existing rows to this height\n        foreach (var t in grid.RowDefinitions)\n            t.Height = (GridLength)e.NewValue!;\n    }\n\n    /// <summary>\n    /// Handles the row count changed event\n    /// </summary>\n    public static void RowCountChanged(AvaloniaPropertyChangedEventArgs e)\n    {\n        if ((int)e.NewValue! < 0)\n            return;\n\n        var grid = (AutoGrid)e.Sender;\n\n        // look for an existing row to get the height\n        var height = grid.RowHeight;\n        if (!grid.IsSet(RowHeightProperty) && grid.RowDefinitions.Count > 0)\n            height = grid.RowDefinitions[0].Height;\n\n        // clear and rebuild\n        grid.RowDefinitions.Clear();\n        for (var i = 0; i < (int)e.NewValue; i++)\n            grid.RowDefinitions.Add(\n                new RowDefinition() { Height = height });\n    }\n\n    /// <summary>\n    /// Called when [child horizontal alignment changed].\n    /// </summary>\n    private static void OnChildHorizontalAlignmentChanged(AvaloniaPropertyChangedEventArgs e)\n    {\n        var grid = (AutoGrid)e.Sender;\n        foreach (var child in grid.Children)\n        {\n            child.SetValue(HorizontalAlignmentProperty,\n                grid.ChildHorizontalAlignment ?? AvaloniaProperty.UnsetValue);\n        }\n    }\n\n    /// <summary>\n    /// Called when [child layout changed].\n    /// </summary>\n    private static void OnChildMarginChanged(AvaloniaPropertyChangedEventArgs e)\n    {\n        var grid = (AutoGrid)e.Sender;\n        foreach (var child in grid.Children)\n        {\n            child.SetValue(MarginProperty, grid.ChildMargin ?? AvaloniaProperty.UnsetValue);\n        }\n    }\n\n    /// <summary>\n    /// Called when [child vertical alignment changed].\n    /// </summary>\n    private static void OnChildVerticalAlignmentChanged(AvaloniaPropertyChangedEventArgs e)\n    {\n        var grid = (AutoGrid)e.Sender;\n        foreach (var child in grid.Children)\n        {\n            child.SetValue(VerticalAlignmentProperty, grid.ChildVerticalAlignment ?? AvaloniaProperty.UnsetValue);\n        }\n    }\n\n    /// <summary>\n    /// Apply child margins and layout effects such as alignment\n    /// </summary>\n    private void ApplyChildLayout(Control child)\n    {\n        if (ChildMargin != null)\n        {\n            child.SetValue(MarginProperty, ChildMargin.Value, BindingPriority.Template);\n        }\n        if (ChildHorizontalAlignment != null)\n        {\n            child.SetValue(HorizontalAlignmentProperty, ChildHorizontalAlignment.Value, BindingPriority.Template);\n        }\n        if (ChildVerticalAlignment != null)\n        {\n            child.SetValue(VerticalAlignmentProperty, ChildVerticalAlignment.Value, BindingPriority.Template);\n        }\n    }\n\n    /// <summary>\n    /// Clamp a value to its maximum.\n    /// </summary>\n    private int Clamp(int value, int max)\n    {\n        return (value > max) ? max : value;\n    }\n\n    /// <summary>\n    /// Perform the grid layout of row and column indexes\n    /// </summary>\n    private void PerformLayout()\n    {\n        var fillRowFirst = Orientation == Orientation.Horizontal;\n        var rowCount = RowDefinitions.Count;\n        var colCount = ColumnDefinitions.Count;\n\n        if (rowCount == 0 || colCount == 0)\n            return;\n\n        var position = 0;\n        var skip = new bool[rowCount, colCount];\n        foreach (var child in Children.OfType<Control>())\n        {\n            var childIsCollapsed = !child.IsVisible;\n            if (IsAutoIndexing && !childIsCollapsed)\n            {\n                if (fillRowFirst)\n                {\n                    var row = Clamp(position / colCount, rowCount - 1);\n                    var col = Clamp(position % colCount, colCount - 1);\n                    if (skip[row, col])\n                    {\n                        position++;\n                        row = (position / colCount);\n                        col = (position % colCount);\n                    }\n\n                    SetRow(child, row);\n                    SetColumn(child, col);\n                    position += GetColumnSpan(child);\n\n                    var offset = GetRowSpan(child) - 1;\n                    while (offset > 0)\n                    {\n                        skip[row + offset--, col] = true;\n                    }\n                }\n                else\n                {\n                    var row = Clamp(position % rowCount, rowCount - 1);\n                    var col = Clamp(position / rowCount, colCount - 1);\n                    if (skip[row, col])\n                    {\n                        position++;\n                        row = position % rowCount;\n                        col = position / rowCount;\n                    }\n\n                    SetRow(child, row);\n                    SetColumn(child, col);\n                    position += GetRowSpan(child);\n\n                    var offset = GetColumnSpan(child) - 1;\n                    while (offset > 0)\n                    {\n                        skip[row, col + offset--] = true;\n                    }\n                }\n            }\n\n            ApplyChildLayout(child);\n        }\n    }\n\n    public static readonly AvaloniaProperty<HorizontalAlignment?> ChildHorizontalAlignmentProperty =\n        AvaloniaProperty.Register<AutoGrid, HorizontalAlignment?>(\"ChildHorizontalAlignment\");\n\n    public static readonly AvaloniaProperty<Thickness?> ChildMarginProperty =\n        AvaloniaProperty.Register<AutoGrid, Thickness?>(\"ChildMargin\");\n\n    public static readonly AvaloniaProperty<VerticalAlignment?> ChildVerticalAlignmentProperty =\n        AvaloniaProperty.Register<AutoGrid, VerticalAlignment?>(\"ChildVerticalAlignment\");\n\n    public static readonly AvaloniaProperty<int> ColumnCountProperty =\n        AvaloniaProperty.RegisterAttached<Control, int>(\"ColumnCount\", typeof(AutoGrid), 1);\n\n    public static readonly AvaloniaProperty<GridLength> ColumnWidthProperty =\n        AvaloniaProperty.RegisterAttached<Control, GridLength>(\"ColumnWidth\", typeof(AutoGrid), GridLength.Auto);\n\n    public static readonly AvaloniaProperty<bool> IsAutoIndexingProperty =\n        AvaloniaProperty.Register<AutoGrid, bool>(\"IsAutoIndexing\", true);\n\n    public static readonly AvaloniaProperty<Orientation> OrientationProperty =\n        AvaloniaProperty.Register<AutoGrid, Orientation>(\"Orientation\", Orientation.Vertical);\n\n    public static readonly AvaloniaProperty<int> RowCountProperty =\n        AvaloniaProperty.RegisterAttached<Control, int>(\"RowCount\", typeof(AutoGrid), 1);\n\n    public static readonly AvaloniaProperty<GridLength> RowHeightProperty =\n        AvaloniaProperty.RegisterAttached<Control, GridLength>(\"RowHeight\", typeof(AutoGrid), GridLength.Auto);\n\n    static AutoGrid()\n    {\n        AffectsMeasure<AutoGrid>(ChildHorizontalAlignmentProperty, ChildMarginProperty,\n            ChildVerticalAlignmentProperty, ColumnCountProperty, ColumnWidthProperty, IsAutoIndexingProperty, OrientationProperty,\n            RowHeightProperty);\n            \n        ChildHorizontalAlignmentProperty.Changed.Subscribe(OnChildHorizontalAlignmentChanged);\n        ChildMarginProperty.Changed.Subscribe(OnChildMarginChanged);\n        ChildVerticalAlignmentProperty.Changed.Subscribe(OnChildVerticalAlignmentChanged);\n        ColumnCountProperty.Changed.Subscribe(ColumnCountChanged);\n        RowCountProperty.Changed.Subscribe(RowCountChanged);\n        ColumnWidthProperty.Changed.Subscribe(FixedColumnWidthChanged);\n        RowHeightProperty.Changed.Subscribe(FixedRowHeightChanged);\n    }\n    \n    #region Overrides\n\n    /// <summary>\n    /// Measures the children of a <see cref=\"T:System.Windows.Controls.Grid\"/> in anticipation of arranging them during the <see cref=\"M:ArrangeOverride\"/> pass.\n    /// </summary>\n    /// <param name=\"constraint\">Indicates an upper limit size that should not be exceeded.</param>\n    /// <returns>\n    /// \t<see cref=\"Size\"/> that represents the required size to arrange child content.\n    /// </returns>\n    protected override Size MeasureOverride(Size constraint)\n    {\n        PerformLayout();\n        return base.MeasureOverride(constraint);\n    }\n        \n    #endregion Overrides\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/BetterAdvancedImage.cs",
    "content": "﻿using System;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Reflection;\nusing AsyncImageLoader;\nusing Avalonia;\nusing Avalonia.Layout;\nusing Avalonia.Media;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\n[SuppressMessage(\"ReSharper\", \"UnusedMember.Local\")]\npublic class BetterAdvancedImage : AdvancedImage\n{\n    #region Reflection Shenanigans to access private parent fields\n    [NotNull]\n    private static readonly FieldInfo? IsCornerRadiusUsedField = typeof(AdvancedImage).GetField(\n        \"_isCornerRadiusUsed\",\n        BindingFlags.Instance | BindingFlags.NonPublic\n    );\n\n    [NotNull]\n    private static readonly FieldInfo? CornerRadiusClipField = typeof(AdvancedImage).GetField(\n        \"_cornerRadiusClip\",\n        BindingFlags.Instance | BindingFlags.NonPublic\n    );\n\n    private bool IsCornerRadiusUsed\n    {\n        get => IsCornerRadiusUsedField.GetValue(this) as bool? ?? false;\n        set => IsCornerRadiusUsedField.SetValue(this, value);\n    }\n\n    private RoundedRect CornerRadiusClip\n    {\n        get => (RoundedRect)CornerRadiusClipField.GetValue(this)!;\n        set => CornerRadiusClipField.SetValue(this, value);\n    }\n\n    static BetterAdvancedImage()\n    {\n        if (IsCornerRadiusUsedField is null)\n        {\n            throw new NullReferenceException(\"IsCornerRadiusUsedField was not resolved\");\n        }\n        if (CornerRadiusClipField is null)\n        {\n            throw new NullReferenceException(\"CornerRadiusClipField was not resolved\");\n        }\n    }\n    #endregion\n\n    protected override Type StyleKeyOverride { get; } = typeof(AdvancedImage);\n\n    public BetterAdvancedImage(Uri? baseUri)\n        : base(baseUri) { }\n\n    public BetterAdvancedImage(IServiceProvider serviceProvider)\n        : base(serviceProvider) { }\n\n    /// <exception cref=\"ArgumentOutOfRangeException\"></exception>\n    /// <inheritdoc />\n    public override void Render(DrawingContext context)\n    {\n        var source = CurrentImage;\n\n        if (source != null && Bounds is { Width: > 0, Height: > 0 })\n        {\n            var viewPort = new Rect(Bounds.Size);\n            var sourceSize = source.Size;\n\n            var scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);\n            var scaledSize = sourceSize * scale;\n\n            // Calculate starting points for dest\n            var destX = HorizontalContentAlignment switch\n            {\n                HorizontalAlignment.Left => 0,\n                HorizontalAlignment.Center => (int)(viewPort.Width - scaledSize.Width) / 2,\n                HorizontalAlignment.Right => (int)(viewPort.Width - scaledSize.Width),\n                // Stretch is default, use center\n                HorizontalAlignment.Stretch\n                    => (int)(viewPort.Width - scaledSize.Width) / 2,\n                _ => throw new ArgumentException(nameof(HorizontalContentAlignment))\n            };\n            var destY = VerticalContentAlignment switch\n            {\n                VerticalAlignment.Top => 0,\n                VerticalAlignment.Center => (int)(viewPort.Height - scaledSize.Height) / 2,\n                VerticalAlignment.Bottom => (int)(viewPort.Height - scaledSize.Height),\n                VerticalAlignment.Stretch => 0, // Stretch is default, use top\n                _ => throw new ArgumentException(nameof(VerticalContentAlignment))\n            };\n\n            var destRect = viewPort.CenterRect(new Rect(scaledSize)).WithX(destX).WithY(destY).Intersect(viewPort);\n            var destRectUnscaledSize = destRect.Size / scale;\n\n            // Calculate starting points for source\n            var sourceX = HorizontalContentAlignment switch\n            {\n                HorizontalAlignment.Left => 0,\n                HorizontalAlignment.Center => (int)(sourceSize - destRectUnscaledSize).Width / 2,\n                HorizontalAlignment.Right => (int)(sourceSize - destRectUnscaledSize).Width,\n                // Stretch is default, use center\n                HorizontalAlignment.Stretch\n                    => (int)(sourceSize - destRectUnscaledSize).Width / 2,\n                _ => throw new ArgumentException(nameof(HorizontalContentAlignment))\n            };\n            var sourceY = VerticalContentAlignment switch\n            {\n                VerticalAlignment.Top => 0,\n                VerticalAlignment.Center => (int)(sourceSize - destRectUnscaledSize).Height / 2,\n                VerticalAlignment.Bottom => (int)(sourceSize - destRectUnscaledSize).Height,\n                VerticalAlignment.Stretch => 0, // Stretch is default, use top\n                _ => throw new ArgumentException(nameof(VerticalContentAlignment))\n            };\n\n            var sourceRect = new Rect(sourceSize)\n                .CenterRect(new Rect(destRect.Size / scale))\n                .WithX(sourceX)\n                .WithY(sourceY);\n\n            if (IsCornerRadiusUsed)\n            {\n                using (context.PushClip(CornerRadiusClip))\n                {\n                    context.DrawImage(source, sourceRect, destRect);\n                }\n            }\n            else\n            {\n                context.DrawImage(source, sourceRect, destRect);\n            }\n        }\n        else\n        {\n            base.Render(context);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/BetterComboBox.cs",
    "content": "﻿using System.Reactive.Linq;\nusing System.Reactive.Subjects;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Presenters;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Controls.Primitives.PopupPositioning;\nusing Avalonia.Input;\nusing Avalonia.Media;\nusing Avalonia.Threading;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic class BetterComboBox : ComboBox\n{\n    private readonly Subject<string> inputSubject = new();\n    private readonly IDisposable subscription;\n    private readonly Popup inputPopup;\n    private readonly TextBlock inputTextBlock;\n    private string currentInput = string.Empty;\n\n    public BetterComboBox()\n    {\n        // Create an observable that buffers input over a short period\n        var inputObservable = inputSubject\n            .Do(text => currentInput += text)\n            .Throttle(TimeSpan.FromMilliseconds(500))\n            .Where(_ => !string.IsNullOrEmpty(currentInput))\n            .Select(_ => currentInput);\n\n        // Subscribe to the observable to filter the ComboBox items\n        subscription = inputObservable\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(OnInputReceived, _ => ResetPopupText());\n\n        // Initialize the popup\n        inputPopup = new Popup\n        {\n            IsLightDismissEnabled = true,\n            Placement = PlacementMode.AnchorAndGravity,\n            PlacementAnchor = PopupAnchor.Bottom,\n            PlacementGravity = PopupGravity.Top,\n        };\n\n        // Initialize the TextBlock with custom styling\n        inputTextBlock = new TextBlock\n        {\n            Foreground = Brushes.White, // White text color\n            Background = Brush.Parse(\"#333333\"), // Dark gray background\n            Padding = new Thickness(8), // Add padding\n            FontSize = 14 // Optional: adjust font size\n        };\n\n        inputPopup.Child = inputTextBlock;\n    }\n\n    /// <inheritdoc/>\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        // Set the Popup's anchor to the ComboBox itself\n        inputPopup.PlacementTarget = this;\n\n        if (e.NameScope.Find<ContentPresenter>(\"ContentPresenter\") is { } contentPresenter)\n        {\n            if (SelectionBoxItemTemplate is { } template)\n            {\n                contentPresenter.ContentTemplate = template;\n            }\n        }\n    }\n\n    protected override void OnTextInput(TextInputEventArgs e)\n    {\n        if (e.Handled)\n            return;\n\n        if (!string.IsNullOrWhiteSpace(e.Text))\n        {\n            // Push the input text to the subject\n            inputSubject.OnNext(e.Text);\n            UpdatePopupText(e.Text);\n            e.Handled = true;\n        }\n\n        base.OnTextInput(e);\n    }\n\n    private void OnInputReceived(string input)\n    {\n        if (Items.OfType<Enum>().ToList() is { Count: > 0 } enumItems)\n        {\n            var foundEnum = enumItems.FirstOrDefault(\n                x => x.GetStringValue().StartsWith(input, StringComparison.OrdinalIgnoreCase)\n            );\n\n            if (foundEnum is not null)\n            {\n                Dispatcher.UIThread.Post(() =>\n                {\n                    SelectedItem = foundEnum;\n                });\n            }\n        }\n        else if (Items.OfType<ISearchText>().ToList() is { } modelFiles)\n        {\n            var found = modelFiles.FirstOrDefault(\n                x => x.SearchText.StartsWith(input, StringComparison.OrdinalIgnoreCase)\n            );\n\n            if (found is not null)\n            {\n                Dispatcher.UIThread.Post(() =>\n                {\n                    SelectedItem = found;\n                });\n            }\n        }\n\n        Dispatcher.UIThread.Post(ResetPopupText);\n    }\n\n    private void UpdatePopupText(string text)\n    {\n        inputTextBlock.Text += text; // Accumulate text in the popup\n\n        if (!inputPopup.IsOpen)\n        {\n            inputPopup.IsOpen = true;\n        }\n    }\n\n    private void ResetPopupText()\n    {\n        currentInput = string.Empty;\n        inputTextBlock.Text = string.Empty;\n        inputPopup.IsOpen = false;\n    }\n\n    // Ensure proper disposal of resources\n    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)\n    {\n        base.OnDetachedFromVisualTree(e);\n        subscription.Dispose();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Reflection;\nusing AsyncAwaitBestPractices;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Input;\nusing Avalonia.Interactivity;\nusing Avalonia.Logging;\nusing Avalonia.Threading;\nusing FluentAvalonia.UI.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\n[SuppressMessage(\"ReSharper\", \"PropertyCanBeMadeInitOnly.Global\")]\npublic class BetterContentDialog : ContentDialog\n{\n    #region Reflection Shenanigans for setting content dialog result\n    [NotNull]\n    protected static readonly FieldInfo? ResultField = typeof(ContentDialog).GetField(\n        \"_result\",\n        BindingFlags.Instance | BindingFlags.NonPublic\n    );\n\n    protected ContentDialogResult Result\n    {\n        get => (ContentDialogResult)ResultField.GetValue(this)!;\n        set => ResultField.SetValue(this, value);\n    }\n\n    [NotNull]\n    protected static readonly MethodInfo? HideCoreMethod = typeof(ContentDialog).GetMethod(\n        \"HideCore\",\n        BindingFlags.Instance | BindingFlags.NonPublic\n    );\n\n    protected void HideCore()\n    {\n        HideCoreMethod.Invoke(this, null);\n    }\n\n    // Also get button properties to hide on command execution change\n    [NotNull]\n    protected static readonly FieldInfo? PrimaryButtonField = typeof(ContentDialog).GetField(\n        \"_primaryButton\",\n        BindingFlags.Instance | BindingFlags.NonPublic\n    );\n\n    protected Button? PrimaryButton\n    {\n        get => (Button?)PrimaryButtonField.GetValue(this)!;\n        set => PrimaryButtonField.SetValue(this, value);\n    }\n\n    [NotNull]\n    protected static readonly FieldInfo? SecondaryButtonField = typeof(ContentDialog).GetField(\n        \"_secondaryButton\",\n        BindingFlags.Instance | BindingFlags.NonPublic\n    );\n\n    protected Button? SecondaryButton\n    {\n        get => (Button?)SecondaryButtonField.GetValue(this)!;\n        set => SecondaryButtonField.SetValue(this, value);\n    }\n\n    [NotNull]\n    protected static readonly FieldInfo? CloseButtonField = typeof(ContentDialog).GetField(\n        \"_closeButton\",\n        BindingFlags.Instance | BindingFlags.NonPublic\n    );\n\n    protected Button? CloseButton\n    {\n        get => (Button?)CloseButtonField.GetValue(this)!;\n        set => CloseButtonField.SetValue(this, value);\n    }\n\n    static BetterContentDialog()\n    {\n        if (ResultField is null)\n        {\n            throw new NullReferenceException(\"ResultField was not resolved\");\n        }\n        if (HideCoreMethod is null)\n        {\n            throw new NullReferenceException(\"HideCoreMethod was not resolved\");\n        }\n        if (PrimaryButtonField is null || SecondaryButtonField is null || CloseButtonField is null)\n        {\n            throw new NullReferenceException(\"Button fields were not resolved\");\n        }\n    }\n    #endregion\n\n    private Border? backgroundPart;\n\n    protected override Type StyleKeyOverride { get; } = typeof(ContentDialog);\n\n    public static readonly StyledProperty<bool> IsFooterVisibleProperty = AvaloniaProperty.Register<\n        BetterContentDialog,\n        bool\n    >(\"IsFooterVisible\", true);\n\n    public bool IsFooterVisible\n    {\n        get => GetValue(IsFooterVisibleProperty);\n        set => SetValue(IsFooterVisibleProperty, value);\n    }\n\n    public static readonly StyledProperty<ScrollBarVisibility> ContentVerticalScrollBarVisibilityProperty =\n        AvaloniaProperty.Register<BetterContentDialog, ScrollBarVisibility>(\n            \"ContentScrollBarVisibility\",\n            ScrollBarVisibility.Auto\n        );\n\n    public ScrollBarVisibility ContentVerticalScrollBarVisibility\n    {\n        get => GetValue(ContentVerticalScrollBarVisibilityProperty);\n        set => SetValue(ContentVerticalScrollBarVisibilityProperty, value);\n    }\n\n    public static readonly StyledProperty<double> MinDialogWidthProperty = AvaloniaProperty.Register<\n        BetterContentDialog,\n        double\n    >(\"MinDialogWidth\");\n\n    public double MinDialogWidth\n    {\n        get => GetValue(MinDialogWidthProperty);\n        set => SetValue(MinDialogWidthProperty, value);\n    }\n\n    public static readonly StyledProperty<double> MaxDialogWidthProperty = AvaloniaProperty.Register<\n        BetterContentDialog,\n        double\n    >(\"MaxDialogWidth\");\n\n    public double MaxDialogWidth\n    {\n        get => GetValue(MaxDialogWidthProperty);\n        set => SetValue(MaxDialogWidthProperty, value);\n    }\n\n    public static readonly StyledProperty<double> MinDialogHeightProperty = AvaloniaProperty.Register<\n        BetterContentDialog,\n        double\n    >(\"MinDialogHeight\");\n\n    public double MinDialogHeight\n    {\n        get => GetValue(MaxDialogHeightProperty);\n        set => SetValue(MaxDialogHeightProperty, value);\n    }\n\n    public static readonly StyledProperty<double> MaxDialogHeightProperty = AvaloniaProperty.Register<\n        BetterContentDialog,\n        double\n    >(\"MaxDialogHeight\");\n\n    public double MaxDialogHeight\n    {\n        get => GetValue(MaxDialogHeightProperty);\n        set => SetValue(MaxDialogHeightProperty, value);\n    }\n\n    public static readonly StyledProperty<Thickness> ContentMarginProperty = AvaloniaProperty.Register<\n        BetterContentDialog,\n        Thickness\n    >(\"ContentMargin\");\n\n    public Thickness ContentMargin\n    {\n        get => GetValue(ContentMarginProperty);\n        set => SetValue(ContentMarginProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> CloseOnClickOutsideProperty = AvaloniaProperty.Register<\n        BetterContentDialog,\n        bool\n    >(\"CloseOnClickOutside\");\n\n    /// <summary>\n    /// Whether to close the dialog when clicking outside of it (on the blurred background)\n    /// </summary>\n    public bool CloseOnClickOutside\n    {\n        get => GetValue(CloseOnClickOutsideProperty);\n        set => SetValue(CloseOnClickOutsideProperty, value);\n    }\n\n    public BetterContentDialog()\n    {\n        AddHandler(LoadedEvent, OnLoaded);\n    }\n\n    /// <inheritdoc />\n    protected override void OnPointerPressed(PointerPressedEventArgs e)\n    {\n        base.OnPointerPressed(e);\n\n        if (CloseOnClickOutside)\n        {\n            if (e.Source is Popup || backgroundPart is null)\n                return;\n\n            var point = e.GetPosition(this);\n\n            if (!backgroundPart.Bounds.Contains(point))\n            {\n                // Use vm if available\n                if ((Content as Control)?.DataContext is ContentDialogViewModelBase vm)\n                {\n                    vm.OnCloseButtonClick();\n                }\n                else\n                {\n                    Hide(ContentDialogResult.None);\n                }\n            }\n        }\n    }\n\n    private void TrySetButtonCommands()\n    {\n        // If commands provided, bind OnCanExecuteChanged to hide buttons\n        // otherwise link visibility to IsEnabled\n        if (PrimaryButton is not null)\n        {\n            if (PrimaryButtonCommand is not null)\n            {\n                PrimaryButtonCommand.CanExecuteChanged += (_, _) =>\n                    PrimaryButton.IsEnabled = PrimaryButtonCommand.CanExecute(null);\n                // Also set initial state\n                PrimaryButton.IsEnabled = PrimaryButtonCommand.CanExecute(null);\n            }\n            else\n            {\n                PrimaryButton.IsVisible = IsPrimaryButtonEnabled && !string.IsNullOrEmpty(PrimaryButtonText);\n            }\n        }\n\n        if (SecondaryButton is not null)\n        {\n            if (SecondaryButtonCommand is not null)\n            {\n                SecondaryButtonCommand.CanExecuteChanged += (_, _) =>\n                    SecondaryButton.IsEnabled = SecondaryButtonCommand.CanExecute(null);\n                // Also set initial state\n                SecondaryButton.IsEnabled = SecondaryButtonCommand.CanExecute(null);\n            }\n            else\n            {\n                SecondaryButton.IsVisible =\n                    IsSecondaryButtonEnabled && !string.IsNullOrEmpty(SecondaryButtonText);\n            }\n        }\n\n        if (CloseButton is not null)\n        {\n            if (CloseButtonCommand is not null)\n            {\n                CloseButtonCommand.CanExecuteChanged += (_, _) =>\n                    CloseButton.IsEnabled = CloseButtonCommand.CanExecute(null);\n                // Also set initial state\n                CloseButton.IsEnabled = CloseButtonCommand.CanExecute(null);\n            }\n        }\n    }\n\n    private void TryBindButtonEvents()\n    {\n        if ((Content as Control)?.DataContext is ContentDialogViewModelBase viewModel)\n        {\n            viewModel.PrimaryButtonClick += OnDialogButtonClick;\n            viewModel.SecondaryButtonClick += OnDialogButtonClick;\n            viewModel.CloseButtonClick += OnDialogButtonClick;\n        }\n        else if (Content is ContentDialogViewModelBase viewModelDirect)\n        {\n            viewModelDirect.PrimaryButtonClick += OnDialogButtonClick;\n            viewModelDirect.SecondaryButtonClick += OnDialogButtonClick;\n            viewModelDirect.CloseButtonClick += OnDialogButtonClick;\n        }\n        else if ((Content as Control)?.DataContext is ContentDialogProgressViewModelBase progressViewModel)\n        {\n            progressViewModel.PrimaryButtonClick += OnDialogButtonClick;\n            progressViewModel.SecondaryButtonClick += OnDialogButtonClick;\n            progressViewModel.CloseButtonClick += OnDialogButtonClick;\n        }\n    }\n\n    protected void OnDialogButtonClick(object? sender, ContentDialogResult e)\n    {\n        Dispatcher.UIThread.Post(() =>\n        {\n            Result = e;\n            HideCore();\n        });\n    }\n\n    protected override void OnDataContextChanged(EventArgs e)\n    {\n        base.OnDataContextChanged(e);\n\n        TryBindButtonEvents();\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        backgroundPart = e.NameScope.Find<Border>(\"BackgroundElement\");\n        if (backgroundPart is not null)\n        {\n            backgroundPart.Margin = ContentMargin;\n        }\n    }\n\n    private void OnLoaded(object? sender, RoutedEventArgs? e)\n    {\n        TryBindButtonEvents();\n\n        try\n        {\n            // Find the named grid\n            // https://github.com/amwx/FluentAvalonia/blob/master/src/FluentAvalonia/Styling/\n            // ControlThemes/FAControls/ContentDialogStyles.axaml#L96\n            var containerBorder = VisualChildren[0] as Border;\n            var layoutRootPanel = containerBorder?.Child as Panel;\n            var backgroundElementBorder = (layoutRootPanel?.Children[0] as Border).Unwrap();\n\n            // Set dialog bounds\n            if (MaxDialogWidth > 0)\n            {\n                backgroundElementBorder.MaxWidth = MaxDialogWidth;\n            }\n\n            if (MinDialogWidth > 0)\n            {\n                backgroundElementBorder.MinWidth = MinDialogWidth;\n            }\n\n            // This kind of bork for some reason\n            /*if (MinDialogHeight > 0)\n            {\n                faBorder!.MinHeight = MinDialogHeight;\n            }*/\n\n            if (MaxDialogHeight > 0)\n            {\n                backgroundElementBorder!.MaxHeight = MaxDialogHeight;\n            }\n\n            var border2 = backgroundElementBorder?.Child as Border;\n            // Named Grid 'DialogSpace'\n            var dialogSpaceGrid = (border2?.Child as Grid).Unwrap();\n\n            // Get the parent border, which is what we want to hide\n            var scrollViewer = (dialogSpaceGrid.Children[0] as ScrollViewer).Unwrap();\n            var actualBorder = (dialogSpaceGrid.Children[1] as Border).Unwrap();\n\n            var subBorder = (scrollViewer.Content as Border).Unwrap();\n            var subGrid = (subBorder.Child as Grid).Unwrap();\n\n            var contentControlTitle = (subGrid.Children[0] as ContentControl).Unwrap();\n\n            // Hide title if empty\n            if (Title is null or string { Length: 0 })\n            {\n                contentControlTitle.IsVisible = false;\n            }\n\n            // Set footer and scrollbar visibility states\n            actualBorder.IsVisible = IsFooterVisible;\n            scrollViewer.VerticalScrollBarVisibility = ContentVerticalScrollBarVisibility;\n        }\n        catch (ArgumentNullException)\n        {\n            Logger\n                .TryGet(LogEventLevel.Error, nameof(BetterContentDialog))\n                ?.Log(this, \"OnLoaded - Unable to find elements\");\n\n            return;\n        }\n\n        // Also call the vm's OnLoad\n        // (UserControlBase handles this now, so we don't need to)\n        /*if (Content is Control { DataContext: ViewModelBase viewModel })\n        {\n            viewModel.OnLoaded();\n            Dispatcher.UIThread.InvokeAsync(viewModel.OnLoadedAsync).SafeFireAndForget();\n        }*/\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/BetterContextDragBehavior.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Input;\nusing Avalonia.Interactivity;\nusing Avalonia.Xaml.Interactions.DragAndDrop;\nusing Avalonia.Xaml.Interactivity;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic class BetterContextDragBehavior : Behavior<Control>\n{\n    private Point _dragStartPoint;\n    private PointerEventArgs? _triggerEvent;\n    private bool _lock;\n    private bool _captured;\n\n    public static readonly StyledProperty<object?> ContextProperty = AvaloniaProperty.Register<\n        ContextDragBehavior,\n        object?\n    >(nameof(Context));\n\n    public static readonly StyledProperty<IDragHandler?> HandlerProperty = AvaloniaProperty.Register<\n        ContextDragBehavior,\n        IDragHandler?\n    >(nameof(Handler));\n\n    public static readonly StyledProperty<double> HorizontalDragThresholdProperty = AvaloniaProperty.Register<\n        ContextDragBehavior,\n        double\n    >(nameof(HorizontalDragThreshold), 3);\n\n    public static readonly StyledProperty<double> VerticalDragThresholdProperty = AvaloniaProperty.Register<\n        ContextDragBehavior,\n        double\n    >(nameof(VerticalDragThreshold), 3);\n\n    public static readonly StyledProperty<string> DataFormatProperty = AvaloniaProperty.Register<\n        BetterContextDragBehavior,\n        string\n    >(\"DataFormat\");\n\n    public string DataFormat\n    {\n        get => GetValue(DataFormatProperty);\n        set => SetValue(DataFormatProperty, value);\n    }\n\n    public object? Context\n    {\n        get => GetValue(ContextProperty);\n        set => SetValue(ContextProperty, value);\n    }\n\n    public IDragHandler? Handler\n    {\n        get => GetValue(HandlerProperty);\n        set => SetValue(HandlerProperty, value);\n    }\n\n    public double HorizontalDragThreshold\n    {\n        get => GetValue(HorizontalDragThresholdProperty);\n        set => SetValue(HorizontalDragThresholdProperty, value);\n    }\n\n    public double VerticalDragThreshold\n    {\n        get => GetValue(VerticalDragThresholdProperty);\n        set => SetValue(VerticalDragThresholdProperty, value);\n    }\n\n    /// <inheritdoc />\n    protected override void OnAttachedToVisualTree()\n    {\n        AssociatedObject?.AddHandler(\n            InputElement.PointerPressedEvent,\n            AssociatedObject_PointerPressed,\n            RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble\n        );\n        AssociatedObject?.AddHandler(\n            InputElement.PointerReleasedEvent,\n            AssociatedObject_PointerReleased,\n            RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble\n        );\n        AssociatedObject?.AddHandler(\n            InputElement.PointerMovedEvent,\n            AssociatedObject_PointerMoved,\n            RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble\n        );\n        AssociatedObject?.AddHandler(\n            InputElement.PointerCaptureLostEvent,\n            AssociatedObject_CaptureLost,\n            RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble\n        );\n    }\n\n    /// <inheritdoc />\n    protected override void OnDetachedFromVisualTree()\n    {\n        AssociatedObject?.RemoveHandler(InputElement.PointerPressedEvent, AssociatedObject_PointerPressed);\n        AssociatedObject?.RemoveHandler(InputElement.PointerReleasedEvent, AssociatedObject_PointerReleased);\n        AssociatedObject?.RemoveHandler(InputElement.PointerMovedEvent, AssociatedObject_PointerMoved);\n        AssociatedObject?.RemoveHandler(InputElement.PointerCaptureLostEvent, AssociatedObject_CaptureLost);\n    }\n\n    private async Task DoDragDrop(PointerEventArgs triggerEvent, object? value)\n    {\n        var data = new DataObject();\n        data.Set(DataFormat, value!);\n\n        var effect = DragDropEffects.None;\n\n        if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Alt))\n        {\n            effect |= DragDropEffects.Link;\n        }\n        else if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Shift))\n        {\n            effect |= DragDropEffects.Move;\n        }\n        else if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Control))\n        {\n            effect |= DragDropEffects.Copy;\n        }\n        else\n        {\n            effect |= DragDropEffects.Move;\n        }\n\n        await DragDrop.DoDragDrop(triggerEvent, data, effect);\n    }\n\n    private void Released()\n    {\n        _triggerEvent = null;\n        _lock = false;\n    }\n\n    private void AssociatedObject_PointerPressed(object? sender, PointerPressedEventArgs e)\n    {\n        var properties = e.GetCurrentPoint(AssociatedObject).Properties;\n        if (properties.IsLeftButtonPressed)\n        {\n            if (e.Source is Control control && AssociatedObject?.DataContext == control.DataContext)\n            {\n                _dragStartPoint = e.GetPosition(null);\n                _triggerEvent = e;\n                _lock = true;\n                _captured = true;\n            }\n        }\n    }\n\n    private void AssociatedObject_PointerReleased(object? sender, PointerReleasedEventArgs e)\n    {\n        if (_captured)\n        {\n            if (e.InitialPressMouseButton == MouseButton.Left && _triggerEvent is { })\n            {\n                Released();\n            }\n\n            _captured = false;\n        }\n    }\n\n    private async void AssociatedObject_PointerMoved(object? sender, PointerEventArgs e)\n    {\n        var properties = e.GetCurrentPoint(AssociatedObject).Properties;\n        if (_captured && properties.IsLeftButtonPressed && _triggerEvent is { })\n        {\n            var point = e.GetPosition(null);\n            var diff = _dragStartPoint - point;\n            var horizontalDragThreshold = HorizontalDragThreshold;\n            var verticalDragThreshold = VerticalDragThreshold;\n\n            if (Math.Abs(diff.X) > horizontalDragThreshold || Math.Abs(diff.Y) > verticalDragThreshold)\n            {\n                if (_lock)\n                {\n                    _lock = false;\n                }\n                else\n                {\n                    return;\n                }\n\n                var context = Context ?? AssociatedObject?.DataContext;\n\n                Handler?.BeforeDragDrop(sender, _triggerEvent, context);\n\n                await DoDragDrop(_triggerEvent, context);\n\n                Handler?.AfterDragDrop(sender, _triggerEvent, context);\n\n                _triggerEvent = null;\n            }\n        }\n    }\n\n    private void AssociatedObject_CaptureLost(object? sender, PointerCaptureLostEventArgs e)\n    {\n        Released();\n        _captured = false;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/BetterDownloadableComboBox.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Microsoft.Extensions.DependencyInjection;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic partial class BetterDownloadableComboBox : BetterComboBox\n{\n    protected override Type StyleKeyOverride => typeof(BetterComboBox);\n\n    static BetterDownloadableComboBox()\n    {\n        SelectionChangedEvent.AddClassHandler<BetterDownloadableComboBox>(\n            (comboBox, args) => comboBox.OnSelectionChanged(args)\n        );\n    }\n\n    protected virtual void OnSelectionChanged(SelectionChangedEventArgs e)\n    {\n        // On downloadable added\n        if (e.AddedItems.Count > 0 && e.AddedItems[0] is IDownloadableResource { IsDownloadable: true } item)\n        {\n            // Reset the selection\n            e.Handled = true;\n\n            if (\n                e.RemovedItems.Count > 0\n                && e.RemovedItems[0] is IDownloadableResource { IsDownloadable: false } removedItem\n            )\n            {\n                SelectedItem = removedItem;\n            }\n            else\n            {\n                SelectedItem = null;\n            }\n\n            // Show dialog to download the model\n            PromptDownloadCommand.ExecuteAsync(item).SafeFireAndForget();\n        }\n    }\n\n    [RelayCommand]\n    private static async Task PromptDownloadAsync(IDownloadableResource downloadable)\n    {\n        if (downloadable.DownloadableResource is not { } resource)\n            return;\n\n        var vmFactory = App.Services.GetRequiredService<IServiceManager<ViewModelBase>>();\n        var confirmDialog = vmFactory.Get<DownloadResourceViewModel>();\n        confirmDialog.Resource = resource;\n        confirmDialog.FileName = resource.FileName;\n\n        if (await confirmDialog.GetDialog().ShowAsync() == ContentDialogResult.Primary)\n        {\n            confirmDialog.StartDownload();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/BetterFlyout.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.VisualTree;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class BetterFlyout : Flyout\n{\n    public static readonly StyledProperty<ScrollBarVisibility> VerticalScrollBarVisibilityProperty = AvaloniaProperty.Register<BetterFlyout, ScrollBarVisibility>(\n        \"VerticalScrollBarVisibility\");\n\n    public ScrollBarVisibility VerticalScrollBarVisibility\n    {\n        get => GetValue(VerticalScrollBarVisibilityProperty);\n        set => SetValue(VerticalScrollBarVisibilityProperty, value);\n    }\n\n    public static readonly StyledProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty = AvaloniaProperty.Register<BetterFlyout, ScrollBarVisibility>(\n        \"HorizontalScrollBarVisibility\");\n\n    public ScrollBarVisibility HorizontalScrollBarVisibility\n    {\n        get => GetValue(HorizontalScrollBarVisibilityProperty);\n        set => SetValue(HorizontalScrollBarVisibilityProperty, value);\n    }\n    \n    protected override void OnOpened()\n    {\n        base.OnOpened();\n        var presenter = Popup.Child;\n        if (presenter.FindDescendantOfType<ScrollViewer>() is { } scrollViewer)\n        {\n            scrollViewer.VerticalScrollBarVisibility = VerticalScrollBarVisibility;\n            scrollViewer.HorizontalScrollBarVisibility = HorizontalScrollBarVisibility;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/BetterImage.cs",
    "content": "﻿using System;\nusing Avalonia;\nusing Avalonia.Automation;\nusing Avalonia.Automation.Peers;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Automation.Peers;\nusing Avalonia.Layout;\nusing Avalonia.Media;\nusing Avalonia.Metadata;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic class BetterImage : Control\n{\n    /// <summary>\n    /// Defines the <see cref=\"Source\"/> property.\n    /// </summary>\n    public static readonly StyledProperty<IImage?> SourceProperty =\n        AvaloniaProperty.Register<BetterImage, IImage?>(nameof(Source));\n\n    /// <summary>\n    /// Defines the <see cref=\"Stretch\"/> property.\n    /// </summary>\n    public static readonly StyledProperty<Stretch> StretchProperty =\n        AvaloniaProperty.Register<BetterImage, Stretch>(nameof(Stretch), Stretch.Uniform);\n\n    /// <summary>\n    /// Defines the <see cref=\"StretchDirection\"/> property.\n    /// </summary>\n    public static readonly StyledProperty<StretchDirection> StretchDirectionProperty =\n        AvaloniaProperty.Register<BetterImage, StretchDirection>(\n            nameof(StretchDirection),\n            StretchDirection.Both);\n\n    static BetterImage()\n    {\n        AffectsRender<BetterImage>(SourceProperty, StretchProperty, StretchDirectionProperty);\n        AffectsMeasure<BetterImage>(SourceProperty, StretchProperty, StretchDirectionProperty);\n        AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<BetterImage>(\n            AutomationControlType.Image);\n    }\n\n    /// <summary>\n    /// Gets or sets the image that will be displayed.\n    /// </summary>\n    [Content]\n    public IImage? Source\n    {\n        get { return GetValue(SourceProperty); }\n        set { SetValue(SourceProperty, value); }\n    }\n\n    /// <summary>\n    /// Gets or sets a value controlling how the image will be stretched.\n    /// </summary>\n    public Stretch Stretch\n    {\n        get { return GetValue(StretchProperty); }\n        set { SetValue(StretchProperty, value); }\n    }\n\n    /// <summary>\n    /// Gets or sets a value controlling in what direction the image will be stretched.\n    /// </summary>\n    public StretchDirection StretchDirection\n    {\n        get { return GetValue(StretchDirectionProperty); }\n        set { SetValue(StretchDirectionProperty, value); }\n    }\n\n    /// <inheritdoc />\n    protected override bool BypassFlowDirectionPolicies => true;\n\n    /// <summary>\n    /// Renders the control.\n    /// </summary>\n    /// <param name=\"context\">The drawing context.</param>\n    public sealed override void Render(DrawingContext context)\n    {\n        var source = Source;\n\n        if (source == null || Bounds is not {Width: > 0, Height: > 0}) return;\n        \n        var viewPort = new Rect(Bounds.Size);\n        var sourceSize = source.Size;\n\n        var scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);\n        var scaledSize = sourceSize * scale;\n            \n        // Calculate starting points for dest\n        var destX = HorizontalAlignment switch\n        {\n            HorizontalAlignment.Left => 0,\n            HorizontalAlignment.Center => (int) (viewPort.Width - scaledSize.Width) / 2,\n            HorizontalAlignment.Right => (int) (viewPort.Width - scaledSize.Width),\n            // Stretch is default, use center\n            HorizontalAlignment.Stretch => (int) (viewPort.Width - scaledSize.Width) / 2, \n            _ => throw new ArgumentException(nameof(HorizontalAlignment))\n        };\n            \n        var destRect = viewPort\n            .CenterRect(new Rect(scaledSize))\n            .WithX(destX)\n            .WithY(0)\n            .Intersect(viewPort);\n        var destRectUnscaledSize = destRect.Size / scale;\n            \n        var sourceX = HorizontalAlignment switch\n        {\n            HorizontalAlignment.Left => 0,\n            HorizontalAlignment.Center => (int) (sourceSize - destRectUnscaledSize).Width / 2,\n            HorizontalAlignment.Right => (int) (sourceSize - destRectUnscaledSize).Width,\n            // Stretch is default, use center\n            HorizontalAlignment.Stretch => (int) (sourceSize - destRectUnscaledSize).Width / 2, \n            _ => throw new ArgumentException(nameof(HorizontalAlignment))\n        };\n            \n        var sourceRect = new Rect(sourceSize)\n            .CenterRect(new Rect(destRect.Size / scale))\n            .WithX(sourceX)\n            .WithY(0);\n\n        context.DrawImage(source, sourceRect, destRect);\n    }\n\n    /// <summary>\n    /// Measures the control.\n    /// </summary>\n    /// <param name=\"availableSize\">The available size.</param>\n    /// <returns>The desired size of the control.</returns>\n    protected override Size MeasureOverride(Size availableSize)\n    {\n        var source = Source;\n        var result = new Size();\n\n        if (source != null)\n        {\n            result = Stretch.CalculateSize(availableSize, source.Size, StretchDirection);\n        }\n\n        return result;\n    }\n\n    /// <inheritdoc/>\n    protected override Size ArrangeOverride(Size finalSize)\n    {\n        var source = Source;\n\n        if (source != null)\n        {\n            var sourceSize = source.Size;\n            var result = Stretch.CalculateSize(finalSize, sourceSize);\n            return result;\n        }\n        else\n        {\n            return new Size();\n        }\n    }\n\n    protected override AutomationPeer OnCreateAutomationPeer()\n    {\n        return new ImageAutomationPeer(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/BetterMarkdownScrollViewer.cs",
    "content": "﻿using Markdown.Avalonia;\nusing StabilityMatrix.Avalonia.Styles.Markdown;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n/// <summary>\n/// Fix MarkdownScrollViewer IBrush errors and not working with Avalonia 11.2.0\n/// </summary>\npublic class BetterMarkdownScrollViewer : MarkdownScrollViewer\n{\n    public BetterMarkdownScrollViewer()\n    {\n        MarkdownStyleName = \"Empty\";\n        MarkdownStyle = new MarkdownStyleFluentAvalonia();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Card.cs",
    "content": "﻿using System;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic class Card : ContentControl\n{\n    protected override Type StyleKeyOverride => typeof(Card);\n\n    // ReSharper disable MemberCanBePrivate.Global\n    public static readonly StyledProperty<bool> IsCardVisualsEnabledProperty =\n        AvaloniaProperty.Register<Card, bool>(\"IsCardVisualsEnabled\", true);\n\n    /// <summary>\n    /// Whether to show card visuals.\n    /// When false, the card will have a padding of 0 and be transparent.\n    /// </summary>\n    public bool IsCardVisualsEnabled\n    {\n        get => GetValue(IsCardVisualsEnabledProperty);\n        set => SetValue(IsCardVisualsEnabledProperty, value);\n    }\n\n    // ReSharper restore MemberCanBePrivate.Global\n\n    public Card()\n    {\n        MinHeight = 8;\n        MinWidth = 8;\n    }\n\n    /// <inheritdoc />\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        // When IsCardVisualsEnabled is false, add the disabled pseudo class\n        if (change.Property == IsCardVisualsEnabledProperty)\n        {\n            PseudoClasses.Set(\"disabled\", !change.GetNewValue<bool>());\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        PseudoClasses.Set(\"disabled\", !IsCardVisualsEnabled);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/CheckerboardBorder.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Media;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class CheckerboardBorder : Control\n{\n    public static readonly StyledProperty<byte> GridCellSizeProperty = AvaloniaProperty.Register<\n        AdvancedImageBox,\n        byte\n    >(nameof(GridCellSize), 15);\n\n    public byte GridCellSize\n    {\n        get => GetValue(GridCellSizeProperty);\n        set => SetValue(GridCellSizeProperty, value);\n    }\n\n    public static readonly StyledProperty<ISolidColorBrush> GridColorProperty =\n        AvaloniaProperty.Register<AdvancedImageBox, ISolidColorBrush>(\n            nameof(GridColor),\n            SolidColorBrush.Parse(\"#181818\")\n        );\n\n    /// <summary>\n    /// Gets or sets the color used to create the checkerboard style background\n    /// </summary>\n    public ISolidColorBrush GridColor\n    {\n        get => GetValue(GridColorProperty);\n        set => SetValue(GridColorProperty, value);\n    }\n\n    public static readonly StyledProperty<ISolidColorBrush> GridColorAlternateProperty =\n        AvaloniaProperty.Register<AdvancedImageBox, ISolidColorBrush>(\n            nameof(GridColorAlternate),\n            SolidColorBrush.Parse(\"#252525\")\n        );\n\n    /// <summary>\n    /// Gets or sets the color used to create the checkerboard style background\n    /// </summary>\n    public ISolidColorBrush GridColorAlternate\n    {\n        get => GetValue(GridColorAlternateProperty);\n        set => SetValue(GridColorAlternateProperty, value);\n    }\n\n    static CheckerboardBorder()\n    {\n        AffectsRender<CheckerboardBorder>(GridCellSizeProperty);\n        AffectsRender<CheckerboardBorder>(GridColorProperty);\n        AffectsRender<CheckerboardBorder>(GridColorAlternateProperty);\n    }\n\n    /// <inheritdoc />\n    public override void Render(DrawingContext context)\n    {\n        var size = GridCellSize;\n\n        var square1Drawing = new GeometryDrawing\n        {\n            Brush = GridColorAlternate,\n            Geometry = new RectangleGeometry(new Rect(0.0, 0.0, size, size))\n        };\n\n        var square2Drawing = new GeometryDrawing\n        {\n            Brush = GridColorAlternate,\n            Geometry = new RectangleGeometry(new Rect(size, size, size, size))\n        };\n\n        var drawingGroup = new DrawingGroup { Children = { square1Drawing, square2Drawing } };\n\n        var tileBrush = new DrawingBrush(drawingGroup)\n        {\n            AlignmentX = AlignmentX.Left,\n            AlignmentY = AlignmentY.Top,\n            DestinationRect = new RelativeRect(new Size(2 * size, 2 * size), RelativeUnit.Absolute),\n            Stretch = Stretch.None,\n            TileMode = TileMode.Tile,\n        };\n\n        context.FillRectangle(GridColor, Bounds);\n        // context.DrawRectangle(new Pen(Brushes.Blue), new Rect(0.5, 0.5, Bounds.Width - 1.0, Bounds.Height - 1.0));\n\n        context.FillRectangle(tileBrush, Bounds);\n\n        // base.Render(context);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionData.cs",
    "content": "﻿using System;\nusing Avalonia.Controls.Documents;\nusing AvaloniaEdit.Document;\nusing AvaloniaEdit.Editing;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Styles;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Controls.CodeCompletion;\n\n/// <summary>\n/// Provides entries in AvaloniaEdit completion window.\n/// </summary>\npublic class CompletionData : ICompletionData\n{\n    /// <inheritdoc />\n    public string Text { get; }\n\n    /// <inheritdoc />\n    public string? Description { get; init; }\n\n    /// <inheritdoc />\n    public ImageSource? ImageSource { get; set; }\n\n    /// <summary>\n    /// Title of the image.\n    /// </summary>\n    public string? ImageTitle { get; set; }\n\n    /// <summary>\n    /// Subtitle of the image.\n    /// </summary>\n    public string? ImageSubtitle { get; set; }\n\n    /// <inheritdoc />\n    public IconData? Icon { get; init; }\n\n    private InlineCollection? _textInlines;\n\n    /// <summary>\n    /// Get the current inlines\n    /// </summary>\n    public InlineCollection TextInlines => _textInlines ??= CreateInlines();\n\n    /// <inheritdoc />\n    public double Priority { get; init; }\n\n    public CompletionData(string text)\n    {\n        Text = text;\n    }\n\n    /// <summary>\n    /// Create text block inline runs from text.\n    /// </summary>\n    private InlineCollection CreateInlines()\n    {\n        // Create a span for each character in the text.\n        var chars = Text.ToCharArray();\n        var inlines = new InlineCollection();\n\n        foreach (var c in chars)\n        {\n            var run = new Run(c.ToString());\n            inlines.Add(run);\n        }\n\n        return inlines;\n    }\n\n    /// <inheritdoc />\n    public void Complete(\n        TextArea textArea,\n        ISegment completionSegment,\n        InsertionRequestEventArgs eventArgs,\n        Func<ICompletionData, string>? prepareText = null\n    )\n    {\n        var text = Text;\n\n        if (prepareText is not null)\n        {\n            text = prepareText(this);\n        }\n\n        // Capture initial offset before replacing text, since it will change\n        var initialOffset = completionSegment.Offset;\n\n        // Replace text\n        textArea.Document.Replace(completionSegment, text);\n\n        // Append text if requested\n        if (eventArgs.AppendText is { } appendText && !string.IsNullOrEmpty(appendText))\n        {\n            var end = initialOffset + text.Length;\n            textArea.Document.Insert(end, appendText);\n            textArea.Caret.Offset = end + appendText.Length;\n        }\n    }\n\n    /// <inheritdoc />\n    public void UpdateCharHighlighting(string searchText)\n    {\n        if (TextInlines is null)\n        {\n            throw new NullReferenceException(\"TextContent is null\");\n        }\n\n        var defaultColor = ThemeColors.CompletionForegroundBrush;\n        var highlightColor = ThemeColors.CompletionSelectionForegroundBrush;\n\n        // Match characters in the text with the search text from the start\n        foreach (var (i, currentChar) in Text.Enumerate())\n        {\n            var inline = TextInlines[i];\n\n            // If longer than text, set to default color\n            if (i >= searchText.Length)\n            {\n                inline.Foreground = defaultColor;\n                continue;\n            }\n\n            // If char matches, highlight it\n            if (currentChar == searchText[i])\n            {\n                inline.Foreground = highlightColor;\n            }\n            // For mismatch, set to default color\n            else\n            {\n                inline.Foreground = defaultColor;\n            }\n        }\n    }\n\n    /// <inheritdoc />\n    public void ResetCharHighlighting()\n    {\n        // TODO: handle light theme foreground variant\n        var defaultColor = ThemeColors.CompletionForegroundBrush;\n\n        foreach (var inline in TextInlines)\n        {\n            inline.Foreground = defaultColor;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionIcons.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Avalonia.Styles;\n\nnamespace StabilityMatrix.Avalonia.Controls.CodeCompletion;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic static class CompletionIcons\n{\n    public static readonly IconData General =\n        new() { FAIcon = \"fa-solid fa-star-of-life\", Foreground = ThemeColors.LightSteelBlue, };\n\n    public static readonly IconData Artist =\n        new() { FAIcon = \"fa-solid fa-palette\", Foreground = ThemeColors.AmericanYellow, };\n\n    public static readonly IconData Character =\n        new() { FAIcon = \"fa-solid fa-user\", Foreground = ThemeColors.LuminousGreen, };\n\n    public static readonly IconData Copyright =\n        new() { FAIcon = \"fa-solid fa-copyright\", Foreground = ThemeColors.DeepMagenta, };\n\n    public static readonly IconData Species =\n        new()\n        {\n            FAIcon = \"fa-solid fa-dragon\",\n            FontSize = 14,\n            Foreground = ThemeColors.HalloweenOrange,\n        };\n\n    public static readonly IconData Invalid =\n        new()\n        {\n            FAIcon = \"fa-solid fa-question\",\n            Foreground = ThemeColors.CompletionForegroundBrush,\n        };\n\n    public static readonly IconData Keyword =\n        new() { FAIcon = \"fa-solid fa-key\", Foreground = ThemeColors.CompletionForegroundBrush, };\n\n    public static readonly IconData Model =\n        new() { FAIcon = \"fa-solid fa-cube\", Foreground = ThemeColors.CompletionForegroundBrush, };\n\n    public static readonly IconData ModelType =\n        new() { FAIcon = \"fa-solid fa-shapes\", Foreground = ThemeColors.BrilliantAzure, };\n\n    public static IconData? GetIconForTagType(TagType tagType)\n    {\n        return tagType switch\n        {\n            TagType.General => General,\n            TagType.Artist => Artist,\n            TagType.Character => Character,\n            TagType.Species => Species,\n            TagType.Invalid => Invalid,\n            TagType.Copyright => Copyright,\n            _ => null\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionList.cs",
    "content": "﻿// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this\n// software and associated documentation files (the \"Software\"), to deal in the Software\n// without restriction, including without limitation the rights to use, copy, modify, merge,\n// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons\n// to whom the Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n\nusing System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Collections.ObjectModel;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Linq;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Input;\nusing Avalonia.Interactivity;\nusing Avalonia.Markup.Xaml.Templates;\nusing AvaloniaEdit.Utils;\nusing StabilityMatrix.Core.Helper;\n\nnamespace StabilityMatrix.Avalonia.Controls.CodeCompletion;\n\n/// <summary>\n/// The listbox used inside the CompletionWindow, contains CompletionListBox.\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class CompletionList : TemplatedControlBase\n{\n    private CompletionListBox? _listBox;\n\n    public CompletionList()\n    {\n        AddHandler(DoubleTappedEvent, OnDoubleTapped);\n    }\n\n    /// <summary>\n    /// If true, the CompletionList is filtered to show only matching items. Also enables search by substring.\n    /// If false, enables the old behavior: no filtering, search by string.StartsWith.\n    /// </summary>\n    public bool IsFiltering { get; set; } = true;\n\n    /// <summary>\n    /// Dependency property for <see cref=\"EmptyTemplate\" />.\n    /// </summary>\n    public static readonly StyledProperty<ControlTemplate> EmptyTemplateProperty = AvaloniaProperty.Register<\n        CompletionList,\n        ControlTemplate\n    >(nameof(EmptyTemplate));\n\n    /// <summary>\n    /// Content of EmptyTemplate will be shown when CompletionList contains no items.\n    /// If EmptyTemplate is null, nothing will be shown.\n    /// </summary>\n    public ControlTemplate EmptyTemplate\n    {\n        get => GetValue(EmptyTemplateProperty);\n        set => SetValue(EmptyTemplateProperty, value);\n    }\n\n    /// <summary>\n    /// Dependency property for <see cref=\"FooterText\" />.\n    /// </summary>\n    public static readonly StyledProperty<string?> FooterTextProperty = AvaloniaProperty.Register<\n        CompletionList,\n        string?\n    >(\"FooterText\", \"Press Enter to insert, Tab to replace\");\n\n    /// <summary>\n    /// Gets/Sets the text displayed in the footer of the completion list.\n    /// </summary>\n    public string? FooterText\n    {\n        get => GetValue(FooterTextProperty);\n        set => SetValue(FooterTextProperty, value);\n    }\n\n    /// <summary>\n    /// Is raised when the completion list indicates that the user has chosen\n    /// an entry to be completed.\n    /// </summary>\n    public event EventHandler<InsertionRequestEventArgs>? InsertionRequested;\n\n    /// <summary>\n    /// Raised when the completion list indicates that it should be closed.\n    /// </summary>\n    public event EventHandler? CloseRequested;\n\n    /// <summary>\n    /// Raises the InsertionRequested event.\n    /// </summary>\n    public void RequestInsertion(\n        ICompletionData item,\n        RoutedEventArgs triggeringEvent,\n        string? appendText = null\n    )\n    {\n        InsertionRequested?.Invoke(\n            this,\n            new InsertionRequestEventArgs\n            {\n                Item = item,\n                TriggeringEvent = triggeringEvent,\n                AppendText = appendText\n            }\n        );\n    }\n\n    /// <summary>\n    /// Raises the CloseRequested event.\n    /// </summary>\n    public void RequestClose()\n    {\n        CloseRequested?.Invoke(this, EventArgs.Empty);\n    }\n\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        _listBox = e.NameScope.Find(\"PART_ListBox\") as CompletionListBox;\n        if (_listBox is not null)\n        {\n            // _listBox.ItemsSource = _completionData;\n            _listBox.ItemsSource = FilteredCompletionData;\n        }\n    }\n\n    /// <summary>\n    /// Gets the list box.\n    /// </summary>\n    public CompletionListBox? ListBox\n    {\n        get\n        {\n            if (_listBox == null)\n                ApplyTemplate();\n            return _listBox;\n        }\n    }\n\n    /// <summary>\n    /// Dictionary of keys that request insertion of the completion\n    /// mapped to strings that will be appended to the completion when selected.\n    /// The string may be empty.\n    /// </summary>\n    public Dictionary<Key, string> CompletionAcceptKeys { get; init; } =\n        new() { [Key.Enter] = \"\", [Key.Tab] = \"\" };\n\n    /// <summary>\n    /// Gets the scroll viewer used in this list box.\n    /// </summary>\n    public ScrollViewer? ScrollViewer => _listBox?.ScrollViewer;\n\n    private readonly ObservableCollection<ICompletionData> _completionData = new();\n\n    /// <summary>\n    /// Gets the list to which completion data can be added.\n    /// </summary>\n    public IList<ICompletionData> CompletionData => _completionData;\n\n    public ObservableCollection<ICompletionData> FilteredCompletionData { get; } = new();\n\n    /// <inheritdoc/>\n    protected override void OnKeyDown(KeyEventArgs e)\n    {\n        base.OnKeyDown(e);\n        if (!e.Handled)\n        {\n            HandleKey(e);\n        }\n    }\n\n    /// <summary>\n    /// Handles a key press. Used to let the completion list handle key presses while the\n    /// focus is still on the text editor.\n    /// </summary>\n    [SuppressMessage(\"ReSharper\", \"SwitchStatementHandlesSomeKnownEnumValuesWithDefault\")]\n    public void HandleKey(KeyEventArgs e)\n    {\n        if (_listBox == null)\n            return;\n\n        // We have to do some key handling manually, because the default doesn't work with\n        // our simulated events.\n        // Also, the default PageUp/PageDown implementation changes the focus, so we avoid it.\n        switch (e.Key)\n        {\n            case Key.Down:\n                e.Handled = true;\n                _listBox.SelectNextIndexWithLoop();\n                break;\n            case Key.Up:\n                e.Handled = true;\n                _listBox.SelectPreviousIndexWithLoop();\n                break;\n            case Key.PageDown:\n                e.Handled = true;\n                _listBox.SelectIndex(_listBox.SelectedIndex + _listBox.VisibleItemCount);\n                break;\n            case Key.PageUp:\n                e.Handled = true;\n                _listBox.SelectIndex(_listBox.SelectedIndex - _listBox.VisibleItemCount);\n                break;\n            case Key.Home:\n                e.Handled = true;\n                _listBox.SelectIndex(0);\n                break;\n            case Key.End:\n                e.Handled = true;\n                _listBox.SelectIndex(_listBox.ItemCount - 1);\n                break;\n            default:\n                // Check insertion keys\n                if (CompletionAcceptKeys.TryGetValue(e.Key, out var appendText) && CurrentList?.Count > 0)\n                {\n                    e.Handled = true;\n\n                    if (SelectedItem is { } item)\n                    {\n                        RequestInsertion(item, e, appendText);\n                    }\n                    else\n                    {\n                        RequestClose();\n                    }\n                }\n\n                break;\n        }\n    }\n\n    protected void OnDoubleTapped(object? sender, RoutedEventArgs e)\n    {\n        //TODO TEST\n        if (\n            ((AvaloniaObject?)e.Source)\n                .VisualAncestorsAndSelf()\n                .TakeWhile(obj => obj != this)\n                .Any(obj => obj is ListBoxItem)\n        )\n        {\n            e.Handled = true;\n\n            if (SelectedItem is { } item)\n            {\n                RequestInsertion(item, e);\n            }\n            else\n            {\n                RequestClose();\n            }\n        }\n    }\n\n    /// <summary>\n    /// Gets/Sets the selected item.\n    /// </summary>\n    /// <remarks>\n    /// The setter of this property does not scroll to the selected item.\n    /// You might want to also call <see cref=\"ScrollIntoView\"/>.\n    /// </remarks>\n    public ICompletionData? SelectedItem\n    {\n        get => _listBox?.SelectedItem as ICompletionData;\n        set\n        {\n            if (_listBox == null && value != null)\n                ApplyTemplate();\n            if (_listBox != null) // may still be null if ApplyTemplate fails, or if listBox and value both are null\n                _listBox.SelectedItem = value;\n        }\n    }\n\n    /// <summary>\n    /// Scrolls the specified item into view.\n    /// </summary>\n    public void ScrollIntoView(ICompletionData item)\n    {\n        if (_listBox == null)\n            ApplyTemplate();\n        _listBox?.ScrollIntoView(item);\n    }\n\n    /// <summary>\n    /// Occurs when the SelectedItem property changes.\n    /// </summary>\n    public event EventHandler<SelectionChangedEventArgs> SelectionChanged\n    {\n        add => AddHandler(SelectingItemsControl.SelectionChangedEvent, value);\n        remove => RemoveHandler(SelectingItemsControl.SelectionChangedEvent, value);\n    }\n\n    // SelectItem gets called twice for every typed character (once from FormatLine), this helps execute SelectItem only once\n    private string? _currentText;\n\n    private ObservableCollection<ICompletionData>? _currentList;\n\n    public List<ICompletionData>? CurrentList => ListBox?.Items.Cast<ICompletionData>().ToList();\n\n    /// <summary>\n    /// Selects the best match, and filter the items if turned on using <see cref=\"IsFiltering\" />.\n    /// </summary>\n    public void SelectItem(string text, bool fullUpdate = false)\n    {\n        if (text == _currentText)\n        {\n            return;\n        }\n\n        using var _ = CodeTimer.StartDebug();\n\n        if (_listBox == null)\n        {\n            ApplyTemplate();\n        }\n\n        if (IsFiltering)\n        {\n            SelectItemFilteringLive(text, fullUpdate);\n        }\n        else\n        {\n            SelectItemWithStart(text);\n        }\n\n        _currentText = text;\n    }\n\n    private IReadOnlyList<ICompletionData> FilterItems(IEnumerable<ICompletionData> items, string query)\n    {\n        using var _ = CodeTimer.StartDebug();\n\n        // Order first by quality, then by priority\n        var matchingItems = items\n            .Select(item => new { Item = item, Quality = GetMatchQuality(item.Text, query) })\n            .Where(x => x.Quality > 0)\n            .OrderByDescending(x => x.Quality)\n            .ThenByDescending(x => x.Item.Priority)\n            .Select(x => x.Item)\n            .ToList();\n\n        return matchingItems;\n    }\n\n    /// <summary>\n    /// Filters CompletionList items to show only those matching given query, and selects the best match.\n    /// </summary>\n    private void SelectItemFilteringLive(string query, bool fullUpdate = false)\n    {\n        var listToFilter = _completionData;\n\n        // if the user just typed one more character, don't filter all data but just filter what we are already displaying\n        if (\n            !fullUpdate\n            && FilteredCompletionData.Count > 0\n            && !string.IsNullOrEmpty(_currentText)\n            && !string.IsNullOrEmpty(query)\n            && query.StartsWith(_currentText, StringComparison.Ordinal)\n        )\n        {\n            listToFilter = FilteredCompletionData;\n        }\n\n        var matchingItems = FilterItems(listToFilter, query);\n\n        // Close if no items match\n        if (matchingItems.Count == 0)\n        {\n            RequestClose();\n            return;\n        }\n\n        // Fast path if both only 1 item, and item is the same\n        if (\n            FilteredCompletionData.Count == 1\n            && matchingItems.Count == 1\n            && FilteredCompletionData[0] == matchingItems[0]\n        )\n        {\n            // Just update the character highlighting\n            matchingItems[0].UpdateCharHighlighting(query);\n        }\n        else\n        {\n            // Clear current items and set new ones\n            FilteredCompletionData.Clear();\n\n            foreach (var item in matchingItems)\n            {\n                item.UpdateCharHighlighting(query);\n                FilteredCompletionData.Add(item);\n            }\n\n            // Set index to 0 if not already\n            if (_listBox != null && _listBox.SelectedIndex != 0)\n            {\n                _listBox.SelectedIndex = 0;\n            }\n        }\n    }\n\n    /// <summary>\n    /// Filters CompletionList items to show only those matching given query, and selects the best match.\n    /// </summary>\n    private void SelectItemFiltering(string query, bool fullUpdate = false)\n    {\n        if (_listBox is null)\n            throw new NullReferenceException(\"ListBox not set\");\n\n        var listToFilter = _completionData;\n\n        // if the user just typed one more character, don't filter all data but just filter what we are already displaying\n        if (\n            !fullUpdate\n            && _currentList != null\n            && !string.IsNullOrEmpty(_currentText)\n            && !string.IsNullOrEmpty(query)\n            && query.StartsWith(_currentText, StringComparison.Ordinal)\n        )\n        {\n            listToFilter = _currentList;\n        }\n\n        // Order first by quality, then by priority\n        var matchingItems = listToFilter\n            .Select(item => new { Item = item, Quality = GetMatchQuality(item.Text, query) })\n            .Where(x => x.Quality > 0)\n            .OrderByDescending(x => x.Quality)\n            .ThenByDescending(x => x.Item.Priority)\n            .ToImmutableArray();\n\n        /*var matchingItems =\n            from item in listToFilter\n            let quality = GetMatchQuality(item.Text, query)\n            where quality > 0\n            orderby quality\n            select new { Item = item, Quality = quality };*/\n\n        var suggestedItem = _listBox.SelectedIndex != -1 ? (ICompletionData)_listBox.SelectedItem! : null;\n\n        var listBoxItems = new ObservableCollection<ICompletionData>();\n        var bestIndex = -1;\n        var bestQuality = -1;\n        double bestPriority = 0;\n        var i = 0;\n        foreach (var matchingItem in matchingItems)\n        {\n            var priority =\n                matchingItem.Item == suggestedItem ? double.PositiveInfinity : matchingItem.Item.Priority;\n            var quality = matchingItem.Quality;\n            if (quality > bestQuality || quality == bestQuality && priority > bestPriority)\n            {\n                bestIndex = i;\n                bestPriority = priority;\n                bestQuality = quality;\n            }\n\n            // Add to listbox\n            listBoxItems.Add(matchingItem.Item);\n\n            // Update the character highlighting\n            matchingItem.Item.UpdateCharHighlighting(query);\n\n            i++;\n        }\n\n        _currentList = listBoxItems;\n        //_listBox.Items = null; Makes no sense? Tooltip disappeared because of this\n        _listBox.ItemsSource = listBoxItems;\n        SelectIndex(bestIndex);\n    }\n\n    /// <summary>\n    /// Selects the item that starts with the specified query.\n    /// </summary>\n    private void SelectItemWithStart(string query)\n    {\n        if (string.IsNullOrEmpty(query))\n            return;\n\n        var suggestedIndex = _listBox?.SelectedIndex ?? -1;\n        if (suggestedIndex == -1)\n        {\n            return;\n        }\n\n        var bestIndex = -1;\n        var bestQuality = -1;\n        double bestPriority = 0;\n        for (var i = 0; i < _completionData.Count; ++i)\n        {\n            var quality = GetMatchQuality(_completionData[i].Text, query);\n            if (quality < 0)\n                continue;\n\n            var priority = _completionData[i].Priority;\n            bool useThisItem;\n            if (bestQuality < quality)\n            {\n                useThisItem = true;\n            }\n            else\n            {\n                if (bestIndex == suggestedIndex)\n                {\n                    useThisItem = false;\n                }\n                else if (i == suggestedIndex)\n                {\n                    // prefer recommendedItem, regardless of its priority\n                    useThisItem = bestQuality == quality;\n                }\n                else\n                {\n                    useThisItem = bestQuality == quality && bestPriority < priority;\n                }\n            }\n            if (useThisItem)\n            {\n                bestIndex = i;\n                bestPriority = priority;\n                bestQuality = quality;\n            }\n        }\n        SelectIndexCentered(bestIndex);\n    }\n\n    private void SelectIndexCentered(int index)\n    {\n        if (_listBox is null)\n        {\n            throw new NullReferenceException(\"ListBox not set\");\n        }\n\n        if (index < 0)\n        {\n            _listBox.ClearSelection();\n        }\n        else\n        {\n            var firstItem = _listBox.FirstVisibleItem;\n            if (index < firstItem || firstItem + _listBox.VisibleItemCount <= index)\n            {\n                // CenterViewOn does nothing as CompletionListBox.ScrollViewer is null\n                _listBox.CenterViewOn(index);\n                _listBox.SelectIndex(index);\n            }\n            else\n            {\n                _listBox.SelectIndex(index);\n            }\n        }\n    }\n\n    private void SelectIndex(int index)\n    {\n        if (_listBox is null)\n        {\n            throw new NullReferenceException(\"ListBox not set\");\n        }\n\n        if (index == _listBox.SelectedIndex)\n            return;\n\n        if (index < 0)\n        {\n            _listBox.ClearSelection();\n        }\n        else\n        {\n            _listBox.SelectedIndex = index;\n        }\n    }\n\n    private int GetMatchQuality(string itemText, string query)\n    {\n        if (itemText == null)\n            throw new ArgumentNullException(nameof(itemText), \"ICompletionData.Text returned null\");\n\n        // Qualities:\n        //  \t8 = full match case sensitive\n        // \t\t7 = full match\n        // \t\t6 = match start case sensitive\n        //\t\t5 = match start\n        //\t\t4 = match CamelCase when length of query is 1 or 2 characters\n        // \t\t3 = match substring case sensitive\n        //\t\t2 = match substring\n        //\t\t1 = match CamelCase\n        //\t\t-1 = no match\n        if (query == itemText)\n            return 8;\n        if (string.Equals(itemText, query, StringComparison.CurrentCultureIgnoreCase))\n            return 7;\n\n        if (itemText.StartsWith(query, StringComparison.CurrentCulture))\n            return 6;\n        if (itemText.StartsWith(query, StringComparison.CurrentCultureIgnoreCase))\n            return 5;\n\n        bool? camelCaseMatch = null;\n        if (query.Length <= 2)\n        {\n            camelCaseMatch = CamelCaseMatch(itemText, query);\n            if (camelCaseMatch == true)\n                return 4;\n        }\n\n        // search by substring, if filtering (i.e. new behavior) turned on\n        if (IsFiltering)\n        {\n            if (itemText.Contains(query, StringComparison.CurrentCulture))\n                return 3;\n            if (itemText.Contains(query, StringComparison.CurrentCultureIgnoreCase))\n                return 2;\n        }\n\n        if (!camelCaseMatch.HasValue)\n            camelCaseMatch = CamelCaseMatch(itemText, query);\n        if (camelCaseMatch == true)\n            return 1;\n\n        return -1;\n    }\n\n    private static bool CamelCaseMatch(string text, string query)\n    {\n        // We take the first letter of the text regardless of whether or not it's upper case so we match\n        // against camelCase text as well as PascalCase text (\"cct\" matches \"camelCaseText\")\n        var theFirstLetterOfEachWord = text.AsEnumerable()\n            .Take(1)\n            .Concat(text.AsEnumerable().Skip(1).Where(char.IsUpper));\n\n        var i = 0;\n        foreach (var letter in theFirstLetterOfEachWord)\n        {\n            if (i > query.Length - 1)\n                return true; // return true here for CamelCase partial match (\"CQ\" matches \"CodeQualityAnalysis\")\n            if (char.ToUpperInvariant(query[i]) != char.ToUpperInvariant(letter))\n                return false;\n            i++;\n        }\n        if (i >= query.Length)\n            return true;\n        return false;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionListBox.cs",
    "content": "﻿// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this\n// software and associated documentation files (the \"Software\"), to deal in the Software\n// without restriction, including without limitation the rights to use, copy, modify, merge,\n// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons\n// to whom the Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n\nusing System;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing AvaloniaEdit.Utils;\n\nnamespace StabilityMatrix.Avalonia.Controls.CodeCompletion;\n\n/// <summary>\n/// The list box used inside the CompletionList.\n/// </summary>\npublic class CompletionListBox : ListBox\n{\n    internal ScrollViewer? ScrollViewer;\n\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n        ScrollViewer = e.NameScope.Find(\"PART_ScrollViewer\") as ScrollViewer;\n    }\n\n    /// <summary>\n    /// Gets the number of the first visible item.\n    /// </summary>\n    public int FirstVisibleItem\n    {\n        get\n        {\n            if (ScrollViewer == null || ScrollViewer.Extent.Height == 0)\n            {\n                return 0;\n            }\n\n            return (int)(ItemCount * ScrollViewer.Offset.Y / ScrollViewer.Extent.Height);\n        }\n        set\n        {\n            value = value.CoerceValue(0, ItemCount - VisibleItemCount);\n            if (ScrollViewer != null)\n            {\n                ScrollViewer.Offset = ScrollViewer.Offset.WithY(\n                    (double)value / ItemCount * ScrollViewer.Extent.Height\n                );\n            }\n        }\n    }\n\n    /// <summary>\n    /// Gets the number of visible items.\n    /// </summary>\n    public int VisibleItemCount\n    {\n        get\n        {\n            if (ScrollViewer == null || ScrollViewer.Extent.Height == 0)\n            {\n                return 10;\n            }\n            return Math.Max(\n                3,\n                (int)\n                    Math.Ceiling(\n                        ItemCount * ScrollViewer.Viewport.Height / ScrollViewer.Extent.Height\n                    )\n            );\n        }\n    }\n\n    /// <summary>\n    /// Removes the selection.\n    /// </summary>\n    public void ClearSelection()\n    {\n        SelectedIndex = -1;\n    }\n\n    /// <summary>\n    /// Selects the next item. If the last item is already selected, selects the first item.\n    /// </summary>\n    public void SelectNextIndexWithLoop()\n    {\n        if (ItemCount <= 0)\n            return;\n\n        SelectIndex((SelectedIndex + 1) % ItemCount);\n    }\n\n    /// <summary>\n    /// Selects the previous item. If the first item is already selected, selects the last item.\n    /// </summary>\n    public void SelectPreviousIndexWithLoop()\n    {\n        if (ItemCount <= 0)\n            return;\n\n        SelectIndex((SelectedIndex - 1 + ItemCount) % ItemCount);\n    }\n\n    /// <summary>\n    /// Selects the item with the specified index and scrolls it into view.\n    /// </summary>\n    public void SelectIndex(int index)\n    {\n        if (index >= ItemCount)\n            index = ItemCount - 1;\n        if (index < 0)\n            index = 0;\n        SelectedIndex = index;\n        if (SelectedItem is { } item)\n        {\n            ScrollIntoView(item);\n        }\n    }\n\n    /// <summary>\n    /// Centers the view on the item with the specified index.\n    /// </summary>\n    public void CenterViewOn(int index)\n    {\n        FirstVisibleItem = index - VisibleItemCount / 2;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionListThemes.axaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:cc=\"clr-namespace:StabilityMatrix.Avalonia.Controls.CodeCompletion\"\n    xmlns:icons=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\">\n\n    <Design.PreviewWith>\n        <Panel Width=\"400\" Height=\"600\">\n            <Panel Width=\"350\" Height=\"200\">\n                <ContentPresenter Content=\"{x:Static mocks:DesignData.SampleCompletionList}\" />\n            </Panel>\n        </Panel>\n\n    </Design.PreviewWith>\n\n    <models:IconData \n        x:Key=\"PlaceHolderIcon\" \n        FAIcon=\"fa-solid fa-question\"/>\n\n    <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n    <ControlTheme\n        x:Key=\"{x:Type cc:CompletionListBox}\"\n        BasedOn=\"{StaticResource {x:Type ListBox}}\"\n        TargetType=\"cc:CompletionListBox\">\n        <Setter Property=\"Margin\" Value=\"0,2\" />\n        <Setter Property=\"Padding\" Value=\"0\" />\n        <Setter Property=\"WrapSelection\" Value=\"True\" />\n        <Setter Property=\"ItemContainerTheme\">\n            <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n            <ControlTheme TargetType=\"ListBoxItem\">\n                <!--\n                    Modified from https://github.com/amwx/FluentAvalonia/blob/main/src/\n                    FluentAvalonia/Styling/ControlThemes/BasicControls/ListBoxStyles.axaml\n                -->\n\n                <Setter Property=\"Padding\" Value=\"8,0,12,0\" />\n\n                <Setter Property=\"FontFamily\" Value=\"{DynamicResource ContentControlThemeFontFamily}\" />\n                <Setter Property=\"FontSize\" Value=\"{DynamicResource ControlContentThemeFontSize}\" />\n                <Setter Property=\"Background\" Value=\"{DynamicResource ListViewItemBackground}\" />\n                <Setter Property=\"CornerRadius\" Value=\"7\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ListViewItemForeground}\" />\n\n                <Setter Property=\"HorizontalContentAlignment\" Value=\"Stretch\" />\n                <Setter Property=\"VerticalContentAlignment\" Value=\"Center\" />\n                <Setter Property=\"MinWidth\" Value=\"{DynamicResource ListViewItemMinWidth}\" />\n                <Setter Property=\"Height\" Value=\"26\" />\n\n                <Setter Property=\"Template\">\n                    <ControlTemplate>\n                        <Panel>\n                            <ContentPresenter\n                                Name=\"PART_ContentPresenter\"\n                                Margin=\"2,0\"\n                                Padding=\"{TemplateBinding Padding}\"\n                                HorizontalContentAlignment=\"{TemplateBinding HorizontalContentAlignment}\"\n                                VerticalContentAlignment=\"{TemplateBinding VerticalContentAlignment}\"\n                                Background=\"{TemplateBinding Background}\"\n                                BorderBrush=\"{TemplateBinding BorderBrush}\"\n                                BorderThickness=\"{TemplateBinding BorderThickness}\"\n                                Content=\"{TemplateBinding Content}\"\n                                ContentTemplate=\"{TemplateBinding ContentTemplate}\"\n                                CornerRadius=\"{TemplateBinding CornerRadius}\" />\n                        </Panel>\n                    </ControlTemplate>\n                </Setter>\n\n                <!--<Style Selector=\"^:pointerover\">\n                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                        <Setter Property=\"Background\" Value=\"{DynamicResource ListViewItemBackgroundPointerOver}\" />\n                    </Style>\n                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ListViewItemForegroundPointerOver}\" />\n                    </Style>\n                </Style>-->\n\n                <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                    <Setter Property=\"Background\" Value=\"{DynamicResource ListViewItemBackgroundPressed}\" />\n                </Style>\n                <Style Selector=\"^ /template/ ContentPresenter\">\n                    <Setter Property=\"Foreground\" Value=\"{DynamicResource ListViewItemForegroundPressed}\" />\n                </Style>\n\n\n                <Style Selector=\"^:selected\">\n                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                        <Setter Property=\"Background\" Value=\"{DynamicResource CompletionSelectionBackgroundBrush}\" />\n                    </Style>\n                    <Style Selector=\"^ /template/ ContentPresenter\">\n                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ListViewItemForegroundSelected}\" />\n                    </Style>\n                    <!--  Disable selection indicator from fluent theme  -->\n                    <!--<Style Selector=\"^ /template/ Rectangle#SelectionIndicator\">\n                        <Setter Property=\"IsVisible\" Value=\"False\" />\n                    </Style>-->\n                    <!--<Style Selector=\"^:not(:focus) /template/ ContentPresenter#PART_ContentPresenter\">\n                        <Setter Property=\"Background\" Value=\"{DynamicResource ListViewItemBackgroundSelected}\" />\n                    </Style>\n                    <Style Selector=\"^:not(:focus) /template/ ContentPresenter#PART_ContentPresenter\">\n                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ListViewItemForegroundSelected}\" />\n                    </Style>-->\n\n                    <!--<Style Selector=\"^:pointerover\">\n                        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                            <Setter Property=\"Background\" Value=\"{DynamicResource ListViewItemBackgroundSelectedPointerOver}\" />\n                        </Style>\n                        <Style Selector=\"^ /template/ ContentPresenter\">\n                            <Setter Property=\"Foreground\" Value=\"{DynamicResource ListViewItemForegroundSelectedPointerOver}\" />\n                        </Style>\n                    </Style>-->\n\n                    <Style Selector=\"^:pressed\">\n                        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                            <Setter Property=\"Background\" Value=\"{DynamicResource ListViewItemBackgroundSelectedPressed}\" />\n                        </Style>\n                        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                            <Setter Property=\"Foreground\" Value=\"{DynamicResource ListViewItemForegroundSelectedPressed}\" />\n                        </Style>\n                    </Style>\n\n                    <!--<Style Selector=\"^:disabled /template/ Rectangle#SelectionIndicator\">\n                        <Setter Property=\"Fill\" Value=\"{DynamicResource ListViewItemSelectionIndicatorDisabledBrush}\" />\n                    </Style>-->\n                </Style>\n\n            </ControlTheme>\n        </Setter>\n    </ControlTheme>\n\n    <ControlTheme x:Key=\"{x:Type cc:CompletionList}\" TargetType=\"cc:CompletionList\">\n\n        <Setter Property=\"Background\" Value=\"{DynamicResource CompletionBackgroundBrush}\" />\n        <Setter Property=\"BorderBrush\" Value=\"{DynamicResource CompletionBorderBrush}\" />\n        <Setter Property=\"BorderThickness\" Value=\"1.5\" />\n        <Setter Property=\"Padding\" Value=\"0\" />\n        <Setter Property=\"MinWidth\" Value=\"{DynamicResource FlyoutThemeMinWidth}\" />\n        <Setter Property=\"MaxWidth\" Value=\"{DynamicResource FlyoutThemeMaxWidth}\" />\n        <Setter Property=\"MinHeight\" Value=\"{DynamicResource FlyoutThemeMinHeight}\" />\n        <Setter Property=\"MaxHeight\" Value=\"{DynamicResource FlyoutThemeMaxHeight}\" />\n        <Setter Property=\"ScrollViewer.HorizontalScrollBarVisibility\" Value=\"Auto\" />\n        <Setter Property=\"ScrollViewer.VerticalScrollBarVisibility\" Value=\"Auto\" />\n        <Setter Property=\"CornerRadius\" Value=\"6\" />\n        <Setter Property=\"FontSize\" Value=\"{DynamicResource ControlContentThemeFontSize}\" />\n        <Setter Property=\"FontFamily\" Value=\"{DynamicResource ContentControlThemeFontFamily}\" />\n\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <!--  BoxShadow=\"0 0 10 2 #BF000000\"  -->\n                <Border\n                    Padding=\"{DynamicResource FlyoutBorderThemePadding}\"\n                    Background=\"{TemplateBinding Background}\"\n                    BorderBrush=\"{TemplateBinding BorderBrush}\"\n                    BorderThickness=\"{TemplateBinding BorderThickness}\"\n                    CornerRadius=\"{TemplateBinding CornerRadius}\">\n\n                    <Grid RowDefinitions=\"*,Auto\">\n                        <!--  ReSharper disable once InconsistentNaming  -->\n                        <cc:CompletionListBox Name=\"PART_ListBox\">\n                            <cc:CompletionListBox.ItemTemplate>\n                                <DataTemplate x:DataType=\"cc:ICompletionData\">\n                                    <Grid ColumnDefinitions=\"20,Auto,*\">\n                                        <!--<Image\n                                            Width=\"16\"\n                                            Height=\"16\"\n                                            Margin=\"0,0,2,0\"\n                                            IsVisible=\"{Binding Image, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                                            Source=\"{Binding Image}\" />-->\n\n                                        <!--  Icon  -->\n                                        <icons:Icon\n                                            Grid.Column=\"0\"\n                                            Margin=\"0,0,4,0\"\n                                            DataContext=\"{Binding Icon, TargetNullValue={StaticResource PlaceHolderIcon}}\"\n                                            FontSize=\"{Binding FontSize, TargetNullValue=12}\"\n                                            Foreground=\"{Binding Foreground, TargetNullValue=Red, FallbackValue=Red}\"\n                                            IsVisible=\"{Binding FAIcon, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                                            Value=\"{Binding FAIcon}\" />\n\n                                        <!--  Main text  -->\n                                        <TextBlock\n                                            Grid.Column=\"1\"\n                                            VerticalAlignment=\"Center\"\n                                            FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n                                            Inlines=\"{Binding TextInlines}\"\n                                            FontSize=\"13\"/>\n\n                                        <!--  Description Text  -->\n                                        <TextBlock\n                                            Grid.Column=\"2\"\n                                            VerticalAlignment=\"Center\"\n                                            FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n                                            FontSize=\"13\"\n                                            Foreground=\"{DynamicResource CompletionSecondaryForegroundBrush}\"\n                                            Text=\"{Binding Description}\"\n                                            TextAlignment=\"Right\" />\n                                        <!--<ContentPresenter Content=\"{Binding Content}\" />-->\n                                    </Grid>\n                                </DataTemplate>\n                            </cc:CompletionListBox.ItemTemplate>\n                        </cc:CompletionListBox>\n\n                        <!--  Keybinding hints panel  -->\n                        <Panel\n                            Grid.Row=\"1\"\n                            MinHeight=\"20\"\n                            Background=\"{DynamicResource CompletionSecondaryBackgroundBrush}\">\n                            <TextBlock\n                                Margin=\"10,3\"\n                                FontSize=\"12\"\n                                Foreground=\"{DynamicResource CompletionSecondaryForegroundBrush}\"\n                                Text=\"{TemplateBinding FooterText}\" />\n                        </Panel>\n                    </Grid>\n\n\n                </Border>\n\n            </ControlTemplate>\n        </Setter>\n    </ControlTheme>\n\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionWindow.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls.CodeCompletion\"\n        xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\">\n    \n    <!-- Style used for tooltip next to completion list box.  -->\n    <!--<Style Selector=\"ContentControl.ToolTip\">\n        <Setter Property=\"BorderThickness\" Value=\"{DynamicResource CompletionToolTipBorderThickness}\" />\n        <Setter Property=\"BorderBrush\" Value=\"{DynamicResource CompletionToolTipBorderBrush}\" />\n        <Setter Property=\"Background\" Value=\"{DynamicResource CompletionToolTipBackground}\" />\n        <Setter Property=\"Foreground\" Value=\"{DynamicResource CompletionToolTipForeground}\" />\n        <Setter Property=\"Padding\" Value=\"4,2\" />\n    </Style>-->\n\n    \n    <!--<Style Selector=\"controls|CompletionWindow\">\n        ~1~<Style Selector=\"^ /template/ ContentControl.ToolTip\">\n            <Setter Property=\"BorderThickness\" Value=\"{DynamicResource CompletionToolTipBorderThickness}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource CompletionToolTipBorderBrush}\" />\n            <Setter Property=\"Background\" Value=\"{DynamicResource CompletionToolTipBackground}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource CompletionToolTipForeground}\" />\n            <Setter Property=\"Padding\" Value=\"4,2\" />\n\n        </Style>@1@\n        ~1~<Setter Property=\"Template\">\n            <ControlTemplate>\n                <TextBlock Text=\"Templated Control\" />\n            </ControlTemplate>\n        </Setter>@1@\n    </Style>-->\n    \n    <!--<Style Selector=\"controls|CompletionWindow\">\n        <Setter Property=\"TransparencyLevelHint\" Value=\"Transparent\" />\n        <Setter Property=\"Background\" Value=\"{x:Null}\" />\n        <Setter Property=\"CornerRadius\" Value=\"{DynamicResource OverlayCornerRadius}\" />\n        <Setter Property=\"FontSize\" Value=\"{DynamicResource ControlContentThemeFontSize}\" />\n        <Setter Property=\"FontFamily\" Value=\"{DynamicResource ContentControlThemeFontFamily}\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <LayoutTransformControl LayoutTransform=\"{TemplateBinding Transform}\">\n                    <Panel>\n                        <Border Name=\"PART_TransparencyFallback\" IsHitTestVisible=\"False\" />\n                        <VisualLayerManager IsPopup=\"True\">\n                            <ContentPresenter Name=\"PART_ContentPresenter\"\n                                              Background=\"{TemplateBinding Background}\"\n                                              ContentTemplate=\"{TemplateBinding ContentTemplate}\"\n                                              Content=\"{TemplateBinding Content}\"\n                                              Padding=\"{TemplateBinding Padding}\" />\n                        </VisualLayerManager>\n                    </Panel>\n                </LayoutTransformControl>\n            </ControlTemplate>\n        </Setter>\n    </Style>-->\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionWindow.axaml.cs",
    "content": "﻿// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this\n// software and associated documentation files (the \"Software\"), to deal in the Software\n// without restriction, including without limitation the rights to use, copy, modify, merge,\n// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons\n// to whom the Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n\nusing System;\nusing System.Diagnostics;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Input;\nusing Avalonia.Media;\nusing Avalonia.Threading;\nusing AvaloniaEdit.Document;\nusing AvaloniaEdit.Editing;\nusing AvaloniaEdit.Utils;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\n\nnamespace StabilityMatrix.Avalonia.Controls.CodeCompletion;\n\n/// <summary>\n/// The code completion window.\n/// </summary>\npublic class CompletionWindow : CompletionWindowBase\n{\n    private readonly ICompletionProvider completionProvider;\n    private readonly ITokenizerProvider tokenizerProvider;\n\n    private PopupWithCustomPosition? _toolTip;\n    private ContentControl? _toolTipContent;\n\n    public bool ToolTipIsOpen => _toolTip is not null;\n\n    /// <summary>\n    /// Max number of items in the completion list.\n    /// </summary>\n    public int MaxListLength { get; set; } = 40;\n\n    /// <summary>\n    /// Gets the completion list used in this completion window.\n    /// </summary>\n    public CompletionList CompletionList { get; } = new();\n\n    /// <summary>\n    /// Whether selection tooltips are shown.\n    /// </summary>\n    public bool IsSelectionTooltipEnabled { get; set; }\n\n    /// <summary>\n    /// Creates a new code completion window.\n    /// </summary>\n    public CompletionWindow(\n        TextArea textArea,\n        ICompletionProvider completionProvider,\n        ITokenizerProvider tokenizerProvider\n    )\n        : base(textArea)\n    {\n        this.completionProvider = completionProvider;\n        this.tokenizerProvider = tokenizerProvider;\n\n        CloseAutomatically = true;\n        MaxHeight = 225;\n        Width = 350;\n        Child = CompletionList;\n        // prevent user from resizing window to 0x0\n        MinHeight = 15;\n        MinWidth = 30;\n\n        _toolTipContent = new ContentControl();\n        _toolTipContent.Classes.Add(\"ToolTip\");\n\n        _toolTip = new PopupWithCustomPosition\n        {\n            IsLightDismissEnabled = true,\n            PlacementTarget = this,\n            Placement = PlacementMode.RightEdgeAlignedTop,\n            Child = _toolTipContent,\n        };\n\n        LogicalChildren.Add(_toolTip);\n\n        _toolTip.Closed += (s, e) =>\n        {\n            Hide();\n            // (s as Popup)!.Child = null;\n        };\n\n        AttachEvents();\n    }\n\n    protected override void OnClosed()\n    {\n        base.OnClosed();\n\n        if (_toolTip != null)\n        {\n            _toolTip.IsOpen = false;\n            _toolTip = null;\n            _toolTipContent = null;\n        }\n    }\n\n    #region ToolTip handling\n\n    private void CompletionList_SelectionChanged(object? sender, SelectionChangedEventArgs e)\n    {\n        // Skip if tooltip not enabled\n        if (!IsSelectionTooltipEnabled)\n            return;\n\n        if (_toolTipContent == null || _toolTip == null)\n            return;\n\n        var item = CompletionList.SelectedItem;\n        if (item?.Description is { } descriptionText)\n        {\n            _toolTipContent.Content = new TextBlock\n            {\n                Text = descriptionText,\n                TextWrapping = TextWrapping.Wrap\n            };\n\n            _toolTip.IsOpen = false; // Popup needs to be closed to change position\n\n            // Calculate offset for tooltip\n            var popupRoot = Host as PopupRoot;\n            if (CompletionList.CurrentList != null)\n            {\n                double yOffset = 0;\n                var itemContainer = CompletionList.ListBox!.ContainerFromItem(item);\n                if (popupRoot != null && itemContainer != null)\n                {\n                    var position = itemContainer.TranslatePoint(new Point(0, 0), popupRoot);\n                    if (position.HasValue)\n                        yOffset = position.Value.Y;\n                }\n\n                _toolTip.Offset = new Point(2, yOffset);\n            }\n\n            _toolTip.PlacementTarget = popupRoot;\n            _toolTip.IsOpen = true;\n        }\n        else\n        {\n            _toolTip.IsOpen = false;\n        }\n    }\n\n    #endregion\n\n    private void CompletionList_InsertionRequested(object? sender, InsertionRequestEventArgs e)\n    {\n        Hide();\n\n        // The window must close before Complete() is called.\n        // If the Complete callback pushes stacked input handlers, we don't want to pop those when the CC window closes.\n        var length = EndOffset - StartOffset;\n        e.Item.Complete(\n            TextArea,\n            new AnchorSegment(TextArea.Document, StartOffset, length),\n            e,\n            completionProvider.PrepareInsertionText\n        );\n    }\n\n    private void CompletionList_CloseRequested(object? sender, EventArgs e)\n    {\n        Hide();\n    }\n\n    private void AttachEvents()\n    {\n        CompletionList.CloseRequested += CompletionList_CloseRequested;\n        CompletionList.InsertionRequested += CompletionList_InsertionRequested;\n        CompletionList.SelectionChanged += CompletionList_SelectionChanged;\n        TextArea.Caret.PositionChanged += CaretPositionChanged;\n        TextArea.PointerWheelChanged += TextArea_MouseWheel;\n        TextArea.TextInput += TextArea_PreviewTextInput;\n    }\n\n    /// <inheritdoc/>\n    protected override void DetachEvents()\n    {\n        CompletionList.CloseRequested -= CompletionList_CloseRequested;\n        CompletionList.InsertionRequested -= CompletionList_InsertionRequested;\n        CompletionList.SelectionChanged -= CompletionList_SelectionChanged;\n        TextArea.Caret.PositionChanged -= CaretPositionChanged;\n        TextArea.PointerWheelChanged -= TextArea_MouseWheel;\n        TextArea.TextInput -= TextArea_PreviewTextInput;\n        base.DetachEvents();\n    }\n\n    /// <inheritdoc/>\n    protected override void OnKeyDown(KeyEventArgs e)\n    {\n        base.OnKeyDown(e);\n        if (!e.Handled)\n        {\n            CompletionList.HandleKey(e);\n        }\n    }\n\n    private void TextArea_PreviewTextInput(object? sender, TextInputEventArgs e)\n    {\n        e.Handled = RaiseEventPair(\n            this,\n            null,\n            TextInputEvent,\n            new TextInputEventArgs { Text = e.Text }\n        );\n    }\n\n    private void TextArea_MouseWheel(object? sender, PointerWheelEventArgs e)\n    {\n        e.Handled = RaiseEventPair(GetScrollEventTarget(), null, PointerWheelChangedEvent, e);\n    }\n\n    private Control GetScrollEventTarget()\n    {\n        /*if (CompletionList == null)\n            return this;*/\n        return CompletionList.ScrollViewer ?? CompletionList.ListBox ?? (Control)CompletionList;\n    }\n\n    /// <summary>\n    /// Gets/Sets whether the completion window should close automatically.\n    /// The default value is true.\n    /// </summary>\n    public bool CloseAutomatically { get; set; }\n\n    /// <inheritdoc/>\n    protected override bool CloseOnFocusLost => CloseAutomatically;\n\n    /// <summary>\n    /// When this flag is set, code completion closes if the caret moves to the\n    /// beginning of the allowed range. This is useful in Ctrl+Space and \"complete when typing\",\n    /// but not in dot-completion.\n    /// Has no effect if CloseAutomatically is false.\n    /// </summary>\n    public bool CloseWhenCaretAtBeginning { get; set; }\n\n    private void CaretPositionChanged(object? sender, EventArgs e)\n    {\n        Debug.WriteLine($\"Caret Position changed: {e}\");\n        var offset = TextArea.Caret.Offset;\n        if (offset == StartOffset)\n        {\n            if (CloseAutomatically && CloseWhenCaretAtBeginning)\n            {\n                Hide();\n            }\n            else\n            {\n                CompletionList.SelectItem(string.Empty);\n\n                IsVisible = CompletionList.ListBox!.ItemCount != 0;\n            }\n            return;\n        }\n        if (offset < StartOffset || offset > EndOffset)\n        {\n            if (CloseAutomatically)\n            {\n                Hide();\n            }\n        }\n        else\n        {\n            var document = TextArea.Document;\n            if (document != null)\n            {\n                var newText = document.GetText(StartOffset, offset - StartOffset);\n                Debug.WriteLine(\"CaretPositionChanged newText: \" + newText);\n\n                if (lastSearchRequest is not { } lastRequest)\n                {\n                    return;\n                }\n\n                // CompletionList.SelectItem(newText);\n                Dispatcher.UIThread.Post(() => UpdateQuery(lastRequest with { Text = newText }));\n                // UpdateQuery(newText);\n\n                IsVisible = CompletionList.ListBox!.ItemCount != 0;\n            }\n        }\n    }\n\n    private TextCompletionRequest? lastSearchRequest;\n    private int lastCompletionLength;\n\n    /// <summary>\n    /// Update the completion window's current search term.\n    /// </summary>\n    public void UpdateQuery(TextCompletionRequest completionRequest)\n    {\n        var searchTerm = completionRequest.Text;\n\n        // Fast path if the search term starts with the last search term\n        // and the last completion count was less than the max list length\n        // (such we won't get new results by searching again)\n        if (\n            lastSearchRequest is not null\n            && completionRequest.Type == lastSearchRequest.Type\n            && searchTerm.StartsWith(lastSearchRequest.Text)\n            && lastCompletionLength < MaxListLength\n        )\n        {\n            CompletionList.SelectItem(searchTerm);\n            lastSearchRequest = completionRequest;\n            return;\n        }\n\n        var results = completionProvider.GetCompletions(completionRequest, MaxListLength, true);\n        CompletionList.CompletionData.Clear();\n        CompletionList.CompletionData.AddRange(results);\n\n        CompletionList.SelectItem(searchTerm, true);\n\n        lastSearchRequest = completionRequest;\n        lastCompletionLength = CompletionList.CompletionData.Count;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionWindowBase.cs",
    "content": "﻿// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team\n// \n// Permission is hereby granted, free of charge, to any person obtaining a copy of this\n// software and associated documentation files (the \"Software\"), to deal in the Software\n// without restriction, including without limitation the rights to use, copy, modify, merge,\n// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons\n// to whom the Software is furnished to do so, subject to the following conditions:\n// \n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n// \n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n\nusing System;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Linq;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Input;\nusing Avalonia.Interactivity;\nusing Avalonia.LogicalTree;\nusing Avalonia.Threading;\nusing Avalonia.VisualTree;\nusing AvaloniaEdit;\nusing AvaloniaEdit.Document;\nusing AvaloniaEdit.Editing;\nusing AvaloniaEdit.Rendering;\n\nnamespace StabilityMatrix.Avalonia.Controls.CodeCompletion;\n\n/// <summary>\n/// Base class for completion windows. Handles positioning the window at the caret.\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"MemberCanBeProtected.Global\")]\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class CompletionWindowBase : Popup\n{\n    protected override Type StyleKeyOverride => typeof(PopupRoot);\n\n    /// <summary>\n    /// Gets the parent TextArea.\n    /// </summary>\n    public TextArea TextArea { get; }\n\n    private readonly Window? _parentWindow;\n    private TextDocument? _document;\n\n    /// <summary>\n    /// Gets/Sets the start of the text range in which the completion window stays open.\n    /// This text portion is used to determine the text used to select an entry in the completion list by typing.\n    /// </summary>\n    public int StartOffset { get; set; }\n\n    /// <summary>\n    /// Gets/Sets the end of the text range in which the completion window stays open.\n    /// This text portion is used to determine the text used to select an entry in the completion list by typing.\n    /// </summary>\n    public int EndOffset { get; set; }\n\n    /// <summary>\n    /// Gets whether the window was opened above the current line.\n    /// </summary>\n    protected bool IsUp { get; private set; }\n\n    /// <summary>\n    /// Creates a new CompletionWindowBase.\n    /// </summary>\n    public CompletionWindowBase(TextArea textArea)\n    {\n        TextArea = textArea ?? throw new ArgumentNullException(nameof(textArea));\n        _parentWindow = textArea.GetVisualRoot() as Window ?? \n                        throw new InvalidOperationException(\"CompletionWindow requires a visual root.\");\n\n        AddHandler(PointerReleasedEvent, OnMouseUp, handledEventsToo: true);\n\n        StartOffset = EndOffset = TextArea.Caret.Offset;\n\n        PlacementTarget = TextArea.TextView;\n        Placement = PlacementMode.AnchorAndGravity;\n        PlacementAnchor = global::Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor.TopLeft;\n        PlacementGravity = global::Avalonia.Controls.Primitives.PopupPositioning.PopupGravity.BottomRight;\n\n        //Deactivated += OnDeactivated; //Not needed?\n\n        Closed += (_, _) => DetachEvents();\n\n        AttachEvents();\n\n        Initialize();\n    }\n\n    protected virtual void OnClosed()\n    {\n        DetachEvents();\n    }\n\n    private void Initialize()\n    {\n        if (_document != null && StartOffset != TextArea.Caret.Offset)\n        {\n            SetPosition(new TextViewPosition(_document.GetLocation(StartOffset)));\n        }\n        else\n        {\n            SetPosition(TextArea.Caret.Position);\n        }\n    }\n\n    public void Show()\n    {\n        UpdatePosition();\n\n        Open();\n        Height = double.NaN;\n        MinHeight = 0;\n    }\n\n    public void Hide()\n    {\n        Close();\n        OnClosed();\n    }\n\n    #region Event Handlers\n\n    private void AttachEvents()\n    {\n        ((ISetLogicalParent)this).SetParent(TextArea.GetVisualRoot() as ILogical);\n\n        _document = TextArea.Document;\n        if (_document != null)\n        {\n            _document.Changing += TextArea_Document_Changing;\n        }\n\n        // LostKeyboardFocus seems to be more reliable than PreviewLostKeyboardFocus - see SD-1729\n        TextArea.LostFocus += TextAreaLostFocus;\n        TextArea.TextView.ScrollOffsetChanged += TextViewScrollOffsetChanged;\n        TextArea.DocumentChanged += TextAreaDocumentChanged;\n        if (_parentWindow != null)\n        {\n            _parentWindow.PositionChanged += ParentWindow_LocationChanged;\n            _parentWindow.Deactivated += ParentWindow_Deactivated;\n        }\n\n        // close previous completion windows of same type\n        foreach (var x in TextArea.StackedInputHandlers.OfType<InputHandler>())\n        {\n            if (x.Window.GetType() == GetType())\n                TextArea.PopStackedInputHandler(x);\n        }\n\n        _myInputHandler = new InputHandler(this);\n        TextArea.PushStackedInputHandler(_myInputHandler);\n    }\n\n    /// <summary>\n    /// Detaches events from the text area.\n    /// </summary>\n    protected virtual void DetachEvents()\n    {\n        ((ISetLogicalParent)this).SetParent(null);\n\n        if (_document != null)\n        {\n            _document.Changing -= TextArea_Document_Changing;\n        }\n        TextArea.LostFocus -= TextAreaLostFocus;\n        TextArea.TextView.ScrollOffsetChanged -= TextViewScrollOffsetChanged;\n        TextArea.DocumentChanged -= TextAreaDocumentChanged;\n        if (_parentWindow != null)\n        {\n            _parentWindow.PositionChanged -= ParentWindow_LocationChanged;\n            _parentWindow.Deactivated -= ParentWindow_Deactivated;\n        }\n        TextArea.PopStackedInputHandler(_myInputHandler);\n    }\n\n    #region InputHandler\n\n    private InputHandler? _myInputHandler;\n\n    /// <summary>\n    /// A dummy input handler (that justs invokes the default input handler).\n    /// This is used to ensure the completion window closes when any other input handler\n    /// becomes active.\n    /// </summary>\n    private sealed class InputHandler : TextAreaStackedInputHandler\n    {\n        internal readonly CompletionWindowBase Window;\n\n        public InputHandler(CompletionWindowBase window)\n            : base(window.TextArea)\n        {\n            Debug.Assert(window != null);\n            Window = window;\n        }\n\n        public override void Detach()\n        {\n            base.Detach();\n            Window.Hide();\n        }\n\n        public override void OnPreviewKeyDown(KeyEventArgs e)\n        {\n            // prevents crash when typing deadchar while CC window is open\n            if (e.Key == Key.DeadCharProcessed)\n                return;\n            e.Handled = RaiseEventPair(Window, null, KeyDownEvent,\n                new KeyEventArgs { Key = e.Key });\n        }\n\n        public override void OnPreviewKeyUp(KeyEventArgs e)\n        {\n            if (e.Key == Key.DeadCharProcessed)\n                return;\n            e.Handled = RaiseEventPair(Window, null, KeyUpEvent,\n                new KeyEventArgs { Key = e.Key });\n        }\n    }\n    #endregion\n\n    private void TextViewScrollOffsetChanged(object? sender, EventArgs e)\n    {\n        ILogicalScrollable textView = TextArea;\n        var visibleRect = new Rect(textView.Offset.X, textView.Offset.Y, textView.Viewport.Width, textView.Viewport.Height);\n        //close completion window when the user scrolls so far that the anchor position is leaving the visible area\n        if (visibleRect.Contains(_visualLocation) || visibleRect.Contains(_visualLocationTop))\n        {\n            UpdatePosition();\n        }\n        else\n        {\n            Hide();\n        }\n    }\n\n    private void TextAreaDocumentChanged(object? sender, EventArgs e)\n    {\n        Hide();\n    }\n\n    private void TextAreaLostFocus(object? sender, RoutedEventArgs e)\n    {\n        Dispatcher.UIThread.Post(CloseIfFocusLost, DispatcherPriority.Background);\n    }\n\n    private void ParentWindow_Deactivated(object? sender, EventArgs e)\n    {\n        Hide();\n    }\n\n    private void ParentWindow_LocationChanged(object? sender, EventArgs e)\n    {\n        UpdatePosition();\n    }\n\n    /*\n    private void OnDeactivated(object sender, EventArgs e)\n    {\n        Dispatcher.UIThread.Post(CloseIfFocusLost, DispatcherPriority.Background);\n    }*/\n\n    #endregion\n\n    /// <summary>\n    /// Raises a tunnel/bubble event pair for a control.\n    /// </summary>\n    /// <param name=\"target\">The control for which the event should be raised.</param>\n    /// <param name=\"previewEvent\">The tunneling event.</param>\n    /// <param name=\"event\">The bubbling event.</param>\n    /// <param name=\"args\">The event args to use.</param>\n    /// <returns>The <see cref=\"RoutedEventArgs.Handled\"/> value of the event args.</returns>\n    protected static bool RaiseEventPair(Control target, RoutedEvent? previewEvent, RoutedEvent @event, RoutedEventArgs args)\n    {\n        if (target == null)\n            throw new ArgumentNullException(nameof(target));\n        if (args == null)\n            throw new ArgumentNullException(nameof(args));\n        if (previewEvent != null)\n        {\n            args.RoutedEvent = previewEvent;\n            target.RaiseEvent(args);\n        }\n        args.RoutedEvent = @event ?? throw new ArgumentNullException(nameof(@event));\n        target.RaiseEvent(args);\n        return args.Handled;\n    }\n\n    // Special handler: handledEventsToo\n    private void OnMouseUp(object? sender, PointerReleasedEventArgs e)\n    {\n        ActivateParentWindow();\n    }\n\n    /// <summary>\n    /// Activates the parent window.\n    /// </summary>\n    protected virtual void ActivateParentWindow()\n    {\n        _parentWindow?.Activate();\n    }\n\n    private void CloseIfFocusLost()\n    {\n        if (CloseOnFocusLost)\n        {\n            Debug.WriteLine(\"CloseIfFocusLost: this.IsFocues=\" + IsFocused + \" IsTextAreaFocused=\" + IsTextAreaFocused);\n            if (!IsFocused && !IsTextAreaFocused)\n            {\n                Hide();\n            }\n        }\n    }\n\n    /// <summary>\n    /// Gets whether the completion window should automatically close when the text editor looses focus.\n    /// </summary>\n    protected virtual bool CloseOnFocusLost => true;\n\n    private bool IsTextAreaFocused\n    {\n        get\n        {\n            if (_parentWindow != null && !_parentWindow.IsActive)\n                return false;\n            return TextArea.IsFocused;\n        }\n    }\n\n    /// <inheritdoc/>\n    protected override void OnKeyDown(KeyEventArgs e)\n    {\n        base.OnKeyDown(e);\n        if (!e.Handled && e.Key == Key.Escape)\n        {\n            e.Handled = true;\n            Hide();\n        }\n    }\n\n    private Point _visualLocation;\n    private Point _visualLocationTop;\n\n    /// <summary>\n    /// Positions the completion window at the specified position.\n    /// </summary>\n    protected void SetPosition(TextViewPosition position)\n    {\n        var textView = TextArea.TextView;\n\n        _visualLocation = textView.GetVisualPosition(position, VisualYPosition.LineBottom);\n        _visualLocationTop = textView.GetVisualPosition(position, VisualYPosition.LineTop);\n\n        UpdatePosition();\n    }\n\n    /// <summary>\n    /// Updates the position of the CompletionWindow based on the parent TextView position and the screen working area.\n    /// It ensures that the CompletionWindow is completely visible on the screen.\n    /// </summary>\n    protected void UpdatePosition()\n    {\n        var textView = TextArea.TextView;\n\n        var position = _visualLocation - textView.ScrollOffset;\n\n        this.HorizontalOffset = position.X;\n        this.VerticalOffset = position.Y;\n    }\n\n    // TODO: check if needed\n    //protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)\n    //{\n    //    base.OnRenderSizeChanged(sizeInfo);\n    //    if (sizeInfo.HeightChanged && IsUp)\n    //    {\n    //        this.Top += sizeInfo.PreviousSize.Height - sizeInfo.NewSize.Height;\n    //    }\n    //}\n\n    /// <summary>\n    /// Gets/sets whether the completion window should expect text insertion at the start offset,\n    /// which not go into the completion region, but before it.\n    /// </summary>\n    /// <remarks>This property allows only a single insertion, it is reset to false\n    /// when that insertion has occurred.</remarks>\n    public bool ExpectInsertionBeforeStart { get; set; }\n\n    protected virtual void TextArea_Document_Changing(object? sender, DocumentChangeEventArgs e)\n    {\n        if (e.Offset + e.RemovalLength == StartOffset && e.RemovalLength > 0)\n        {\n            Hide(); // removal immediately in front of completion segment: close the window\n            // this is necessary when pressing backspace after dot-completion\n        }\n        if (e.Offset == StartOffset && e.RemovalLength == 0 && ExpectInsertionBeforeStart)\n        {\n            StartOffset = e.GetNewOffset(StartOffset, AnchorMovementType.AfterInsertion);\n            ExpectInsertionBeforeStart = false;\n        }\n        else\n        {\n            StartOffset = e.GetNewOffset(StartOffset, AnchorMovementType.BeforeInsertion);\n        }\n        EndOffset = e.GetNewOffset(EndOffset, AnchorMovementType.AfterInsertion);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/CodeCompletion/ICompletionData.cs",
    "content": "﻿// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this\n// software and associated documentation files (the \"Software\"), to deal in the Software\n// without restriction, including without limitation the rights to use, copy, modify, merge,\n// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons\n// to whom the Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n\nusing System;\nusing System.Diagnostics.CodeAnalysis;\nusing Avalonia.Controls.Documents;\nusing AvaloniaEdit.Document;\nusing AvaloniaEdit.Editing;\nusing StabilityMatrix.Avalonia.Models;\n\nnamespace StabilityMatrix.Avalonia.Controls.CodeCompletion;\n\n/// <summary>\n/// Describes an entry in the <see cref=\"AvaloniaEdit.CodeCompletion.CompletionList\"/>.\n/// </summary>\n/// <remarks>\n/// Note that the CompletionList uses data binding against the properties in this interface.\n/// Thus, your implementation of the interface must use public properties; not explicit interface implementation.\n/// </remarks>\npublic interface ICompletionData\n{\n    /// <summary>\n    /// Gets the text. This property is used to filter the list of visible elements.\n    /// </summary>\n    string Text { get; }\n\n    /// <summary>\n    /// Gets the description.\n    /// </summary>\n    public string? Description { get; }\n\n    /// <summary>\n    /// Gets the image.\n    /// </summary>\n    ImageSource? ImageSource { get; }\n\n    /// <summary>\n    /// Title of the image.\n    /// </summary>\n    string? ImageTitle { get; }\n\n    /// <summary>\n    /// Subtitle of the image.\n    /// </summary>\n    string? ImageSubtitle { get; }\n\n    /// <summary>\n    /// Whether the image is available.\n    /// </summary>\n    [MemberNotNullWhen(true, nameof(ImageSource))]\n    bool HasImage => ImageSource != null;\n\n    /// <summary>\n    /// Gets the icon shown on the left.\n    /// </summary>\n    IconData? Icon { get; }\n\n    /// <summary>\n    /// Gets inline text fragments.\n    /// </summary>\n    InlineCollection TextInlines { get; }\n\n    /// <summary>\n    /// Gets the priority. This property is used in the selection logic. You can use it to prefer selecting those items\n    /// which the user is accessing most frequently.\n    /// </summary>\n    double Priority { get; }\n\n    /// <summary>\n    /// Perform the completion.\n    /// </summary>\n    /// <param name=\"textArea\">The text area on which completion is performed.</param>\n    /// <param name=\"completionSegment\">The text segment that was used by the completion window if\n    /// the user types (segment between CompletionWindow.StartOffset and CompletionWindow.EndOffset).</param>\n    /// <param name=\"eventArgs\">The EventArgs used for the insertion request.\n    /// These can be TextCompositionEventArgs, KeyEventArgs, MouseEventArgs, depending on how\n    /// the insertion was triggered.</param>\n    /// <param name=\"prepareText\">Optional function to transform the text to be inserted</param>\n    void Complete(\n        TextArea textArea,\n        ISegment completionSegment,\n        InsertionRequestEventArgs eventArgs,\n        Func<ICompletionData, string>? prepareText = null\n    );\n\n    /// <summary>\n    /// Update the text character highlighting\n    /// </summary>\n    void UpdateCharHighlighting(string searchText);\n\n    /// <summary>\n    /// Reset the text character highlighting\n    /// </summary>\n    void ResetCharHighlighting();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/CodeCompletion/InsertionRequestEventArgs.cs",
    "content": "﻿using System;\nusing Avalonia.Interactivity;\n\nnamespace StabilityMatrix.Avalonia.Controls.CodeCompletion;\n\npublic class InsertionRequestEventArgs : EventArgs\n{\n    public required ICompletionData Item { get; init; }\n    public required RoutedEventArgs TriggeringEvent { get; init; }\n    \n    public string? AppendText { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/CodeCompletion/PopupWithCustomPosition.cs",
    "content": "﻿using Avalonia;\nusing Avalonia.Controls.Primitives;\n\nnamespace StabilityMatrix.Avalonia.Controls.CodeCompletion;\n\ninternal class PopupWithCustomPosition : Popup\n{\n    public Point Offset\n    {\n        get => new(HorizontalOffset, VerticalOffset);\n        set\n        {\n            HorizontalOffset = value.X;\n            VerticalOffset = value.Y;\n\n            // this.Revalidate(VerticalOffsetProperty);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/ComfyUpscalerTemplateSelector.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Templates;\nusing Avalonia.Metadata;\nusing StabilityMatrix.Core.Models.Api.Comfy;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class ComfyUpscalerTemplateSelector : IDataTemplate\n{\n    // ReSharper disable once CollectionNeverUpdated.Global\n    [Content]\n    public Dictionary<ComfyUpscalerType, IDataTemplate> Templates { get; } = new();\n\n    // Check if we can accept the provided data\n    public bool Match(object? data)\n    {\n        return data is ComfyUpscaler;\n    }\n\n    // Build the DataTemplate here\n    public Control Build(object? data)\n    {\n        if (data is not ComfyUpscaler card)\n            throw new ArgumentException(null, nameof(data));\n\n        if (Templates.TryGetValue(card.Type, out var type))\n        {\n            return type.Build(card)!;\n        }\n\n        // Fallback to None\n        return Templates[ComfyUpscalerType.None].Build(card)!;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/DataTemplateSelector.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Templates;\nusing Avalonia.Metadata;\nusing JetBrains.Annotations;\nusing StabilityMatrix.Avalonia.Models;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n/// <summary>\n/// Selector for objects implementing <see cref=\"ITemplateKey{T}\"/>\n/// </summary>\n[PublicAPI]\npublic class DataTemplateSelector<TKey> : IDataTemplate\n    where TKey : notnull\n{\n    /// <summary>\n    /// Key that is used when no other key matches\n    /// </summary>\n    public TKey? DefaultKey { get; set; }\n\n    [Content]\n    public Dictionary<TKey, IDataTemplate> Templates { get; } = new();\n\n    public bool Match(object? data) => data is ITemplateKey<TKey>;\n\n    /// <inheritdoc />\n    public Control Build(object? data)\n    {\n        if (data is not ITemplateKey<TKey> key)\n            throw new ArgumentException(null, nameof(data));\n\n        if (Templates.TryGetValue(key.TemplateKey, out var template))\n        {\n            return template.Build(data)!;\n        }\n\n        if (DefaultKey is not null && Templates.TryGetValue(DefaultKey, out var defaultTemplate))\n        {\n            return defaultTemplate.Build(data)!;\n        }\n\n        throw new ArgumentException(null, nameof(data));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Dock/DockUserControlBase.cs",
    "content": "﻿using System;\nusing System.Text.Json.Nodes;\nusing System.Threading.Tasks;\nusing Avalonia;\nusing Avalonia.Collections;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Threading;\nusing Dock.Avalonia.Controls;\nusing Dock.Model;\nusing Dock.Model.Core;\nusing Dock.Serializer;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\n\nnamespace StabilityMatrix.Avalonia.Controls.Dock;\n\n/// <summary>\n/// Base for Dock controls\n/// Expects a <see cref=\"DockControl\"/> named \"Dock\" in the XAML\n/// </summary>\npublic abstract class DockUserControlBase : DropTargetUserControlBase\n{\n    private DockControl? baseDock;\n    private readonly DockSerializer dockSerializer = new(typeof(AvaloniaList<>));\n    private readonly DockState dockState = new();\n    private readonly DockState initialDockState = new();\n    private IDock? initialLayout;\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        baseDock =\n            this.FindControl<DockControl>(\"Dock\")\n            ?? throw new NullReferenceException(\"DockControl not found\");\n\n        if (baseDock.Layout is { } layout)\n        {\n            dockState.Save(layout);\n            initialDockState.Save(layout);\n            initialLayout = layout;\n            // Dispatcher.UIThread.Post(() => dockState.Save(layout), DispatcherPriority.Background);\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)\n    {\n        base.OnAttachedToVisualTree(e);\n\n        // Attach handlers for view state saving and loading\n        if (DataContext is InferenceTabViewModelBase vm)\n        {\n            vm.SaveViewStateRequested += DataContext_OnSaveViewStateRequested;\n            vm.LoadViewStateRequested += DataContext_OnLoadViewStateRequested;\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)\n    {\n        base.OnDetachedFromVisualTree(e);\n\n        // Detach handlers for view state saving and loading\n        if (DataContext is InferenceTabViewModelBase vm)\n        {\n            vm.SaveViewStateRequested -= DataContext_OnSaveViewStateRequested;\n            vm.LoadViewStateRequested -= DataContext_OnLoadViewStateRequested;\n        }\n    }\n\n    private void DataContext_OnSaveViewStateRequested(object? sender, SaveViewStateEventArgs args)\n    {\n        var saveTcs = new TaskCompletionSource<ViewState>();\n\n        Dispatcher.UIThread.Post(() =>\n        {\n            var state = new ViewState { DockLayout = SaveDockLayout() };\n            saveTcs.SetResult(state);\n        });\n\n        args.StateTask ??= saveTcs.Task;\n    }\n\n    private void DataContext_OnLoadViewStateRequested(object? sender, LoadViewStateEventArgs args)\n    {\n        if (args.State?.DockLayout is { } layout)\n        {\n            // Provided\n            LoadDockLayout(layout);\n        }\n        else\n        {\n            // Restore default\n            RestoreDockLayout();\n        }\n    }\n\n    private void LoadDockLayout(JsonObject data)\n    {\n        LoadDockLayout(data.ToJsonString());\n    }\n\n    private void LoadDockLayout(string data)\n    {\n        if (baseDock is null)\n            return;\n\n        if (dockSerializer.Deserialize<IDock?>(data) is { } layout)\n        {\n            baseDock.Layout = layout;\n            dockState.Restore(baseDock.Layout);\n        }\n    }\n\n    private void RestoreDockLayout()\n    {\n        if (baseDock != null && initialLayout != null)\n        {\n            baseDock.Layout = initialLayout;\n            initialDockState.Restore(baseDock.Layout);\n        }\n    }\n\n    protected string? SaveDockLayout()\n    {\n        return baseDock is null ? null : dockSerializer.Serialize(baseDock.Layout);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/DropTargetTemplatedControlBase.cs",
    "content": "﻿using Avalonia.Input;\nusing StabilityMatrix.Avalonia.ViewModels;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic abstract class DropTargetTemplatedControlBase : TemplatedControlBase\n{\n    protected DropTargetTemplatedControlBase()\n    {\n        AddHandler(DragDrop.DropEvent, DropHandler);\n        AddHandler(DragDrop.DragOverEvent, DragOverHandler);\n\n        DragDrop.SetAllowDrop(this, true);\n    }\n\n    protected virtual void DragOverHandler(object? sender, DragEventArgs e)\n    {\n        if (DataContext is IDropTarget dropTarget)\n        {\n            dropTarget.DragOver(sender, e);\n        }\n    }\n\n    protected virtual void DropHandler(object? sender, DragEventArgs e)\n    {\n        if (DataContext is IDropTarget dropTarget)\n        {\n            dropTarget.Drop(sender, e);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/DropTargetUserControlBase.cs",
    "content": "﻿using Avalonia.Input;\nusing StabilityMatrix.Avalonia.ViewModels;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic abstract class DropTargetUserControlBase : UserControlBase\n{\n    protected DropTargetUserControlBase()\n    {\n        AddHandler(DragDrop.DropEvent, DropHandler);\n        AddHandler(DragDrop.DragOverEvent, DragOverHandler);\n\n        DragDrop.SetAllowDrop(this, true);\n    }\n\n    private void DragOverHandler(object? sender, DragEventArgs e)\n    {\n        if (DataContext is IDropTarget dropTarget)\n        {\n            dropTarget.DragOver(sender, e);\n        }\n    }\n\n    private void DropHandler(object? sender, DragEventArgs e)\n    {\n        if (DataContext is IDropTarget dropTarget)\n        {\n            dropTarget.Drop(sender, e);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/EditorCommands.cs",
    "content": "﻿using AvaloniaEdit;\nusing CommunityToolkit.Mvvm.Input;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic static class EditorCommands\n{\n    public static RelayCommand<TextEditor> CopyCommand { get; } =\n        new(editor => editor?.Copy(), editor => editor?.CanCopy ?? false);\n\n    public static RelayCommand<TextEditor> CutCommand { get; } =\n        new(editor => editor?.Cut(), editor => editor?.CanCut ?? false);\n\n    public static RelayCommand<TextEditor> PasteCommand { get; } =\n        new(editor => editor?.Paste(), editor => editor?.CanPaste ?? false);\n\n    public static RelayCommand<TextEditor> UndoCommand { get; } =\n        new(editor => editor?.Undo(), editor => editor?.CanUndo ?? false);\n\n    public static RelayCommand<TextEditor> RedoCommand { get; } =\n        new(editor => editor?.Redo(), editor => editor?.CanRedo ?? false);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/EditorFlyouts.axaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avaloniaEdit=\"https://github.com/avaloniaui/avaloniaedit\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\">\n    <!--  Context menu for editors  -->\n    <ui:FAMenuFlyout x:Key=\"EditorContextFlyout\">\n        <ui:MenuFlyoutItem\n            Command=\"{x:Static controls:EditorCommands.PasteCommand}\"\n            CommandParameter=\"{Binding $parent[avaloniaEdit:TextEditor]}\"\n            HotKey=\"Ctrl+V\"\n            IconSource=\"Paste\"\n            Text=\"Paste\" />\n        <ui:MenuFlyoutItem\n            Command=\"{x:Static controls:EditorCommands.CopyCommand}\"\n            CommandParameter=\"{Binding $parent[avaloniaEdit:TextEditor]}\"\n            HotKey=\"Ctrl+C\"\n            IconSource=\"Copy\"\n            Text=\"Copy\" />\n        <ui:MenuFlyoutItem\n            Command=\"{x:Static controls:EditorCommands.CutCommand}\"\n            CommandParameter=\"{Binding $parent[avaloniaEdit:TextEditor]}\"\n            HotKey=\"Ctrl+X\"\n            IconSource=\"Cut\"\n            Text=\"Cut\" />\n        <ui:MenuFlyoutItem\n            Command=\"{x:Static controls:EditorCommands.UndoCommand}\"\n            CommandParameter=\"{Binding $parent[avaloniaEdit:TextEditor]}\"\n            HotKey=\"Ctrl+Z\"\n            IconSource=\"Undo\"\n            Text=\"Undo\" />\n        <ui:MenuFlyoutItem\n            Command=\"{x:Static controls:EditorCommands.RedoCommand}\"\n            CommandParameter=\"{Binding $parent[avaloniaEdit:TextEditor]}\"\n            HotKey=\"Ctrl+Y\"\n            IconSource=\"Redo\"\n            Text=\"Redo\" />\n    </ui:FAMenuFlyout>\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/FADownloadableComboBox.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Input;\nusing Avalonia.Interactivity;\nusing Avalonia.VisualTree;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Microsoft.Extensions.DependencyInjection;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n// ReSharper disable once InconsistentNaming\npublic partial class FADownloadableComboBox : FAComboBox\n{\n    protected override Type StyleKeyOverride => typeof(FAComboBox);\n\n    private Popup? dropDownPopup;\n    private IDisposable? openSubscription;\n\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        CleanupSubscription();\n        DropDownOpened -= OnDropDownOpenedHandler;\n        DropDownClosed -= OnDropDownClosedHandler;\n\n        // Template part name is \"Popup\" per FAComboBox.properties.cs (s_tpPopup = \"Popup\")\n        dropDownPopup = e.NameScope.Find<Popup>(\"Popup\");\n\n        DropDownOpened += OnDropDownOpenedHandler;\n        DropDownClosed += OnDropDownClosedHandler;\n    }\n\n    private void OnDropDownOpenedHandler(object? sender, EventArgs e)\n    {\n        CleanupSubscription();\n\n        if (dropDownPopup?.Child is not Control popupChild)\n            return;\n\n        var scrollViewer = popupChild.GetVisualDescendants().OfType<ScrollViewer>().FirstOrDefault();\n\n        if (scrollViewer == null)\n            return;\n\n        // On Unix-like systems, overlay popups share the same TopLevel visual root as the main window.\n        // FAComboBox.OnPopupOpened adds a TopLevel tunnel handler that marks all wheel eventsas handled while the dropdown is open,\n        // which inadvertently blocks scroll-wheelevents in popup menus in Inference model cards.\n        // Resetting e.Handled on the ScrollViewer's tunnel phase counters this.\n        if (!Compat.IsUnix)\n            return;\n\n        openSubscription = scrollViewer.AddDisposableHandler(\n            PointerWheelChangedEvent,\n            static (_, ev) =>\n            {\n                if (ev.Handled)\n                    ev.Handled = false;\n            },\n            RoutingStrategies.Tunnel,\n            handledEventsToo: true\n        );\n    }\n\n    private void OnDropDownClosedHandler(object? sender, EventArgs e)\n    {\n        CleanupSubscription();\n    }\n\n    private void CleanupSubscription()\n    {\n        openSubscription?.Dispose();\n        openSubscription = null;\n    }\n\n    static FADownloadableComboBox()\n    {\n        SelectionChangedEvent.AddClassHandler<FADownloadableComboBox>(\n            (comboBox, args) => comboBox.OnSelectionChanged(args)\n        );\n    }\n\n    protected virtual void OnSelectionChanged(SelectionChangedEventArgs e)\n    {\n        // On downloadable added\n        if (e.AddedItems.Count > 0 && e.AddedItems[0] is IDownloadableResource { IsDownloadable: true } item)\n        {\n            // Reset the selection\n            e.Handled = true;\n\n            if (\n                e.RemovedItems.Count > 0\n                && e.RemovedItems[0] is IDownloadableResource { IsDownloadable: false } removedItem\n            )\n            {\n                SelectedItem = removedItem;\n            }\n            else\n            {\n                SelectedItem = null;\n            }\n\n            // Show dialog to download the model\n            PromptDownloadCommand.ExecuteAsync(item).SafeFireAndForget();\n        }\n    }\n\n    [RelayCommand]\n    private static async Task PromptDownloadAsync(IDownloadableResource downloadable)\n    {\n        if (downloadable.DownloadableResource is not { } resource)\n            return;\n\n        var vmFactory = App.Services.GetRequiredService<IServiceManager<ViewModelBase>>();\n        var confirmDialog = vmFactory.Get<DownloadResourceViewModel>();\n        confirmDialog.Resource = resource;\n        confirmDialog.FileName = resource.FileName;\n\n        if (await confirmDialog.GetDialog().ShowAsync() == ContentDialogResult.Primary)\n        {\n            confirmDialog.StartDownload();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/FASymbolIconSource.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Globalization;\nusing Avalonia;\nusing Avalonia.Controls.Documents;\nusing Avalonia.Media;\nusing FluentAvalonia.UI.Controls;\nusing Projektanker.Icons.Avalonia;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[TypeConverter(typeof(FASymbolIconSourceConverter))]\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\n[SuppressMessage(\"ReSharper\", \"PropertyCanBeMadeInitOnly.Global\")]\npublic class FASymbolIconSource : PathIconSource\n{\n    public static readonly StyledProperty<string> SymbolProperty = AvaloniaProperty.Register<\n        FASymbolIconSource,\n        string\n    >(nameof(Symbol));\n\n    public static readonly StyledProperty<double> FontSizeProperty =\n        TextElement.FontSizeProperty.AddOwner<FASymbolIconSource>();\n\n    public FASymbolIconSource()\n    {\n        Stretch = Stretch.None;\n        // FontSize = 20; // Override value inherited from visual parents.\n        InvalidateData();\n    }\n\n    public string Symbol\n    {\n        get => GetValue(SymbolProperty);\n        set => SetValue(SymbolProperty, value);\n    }\n\n    public double FontSize\n    {\n        get => GetValue(FontSizeProperty);\n        set => SetValue(FontSizeProperty, value);\n    }\n\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        if (change.Property == SymbolProperty || change.Property == FontSizeProperty)\n        {\n            InvalidateData();\n        }\n    }\n\n    private void InvalidateData()\n    {\n        var path = IconProvider.Current.GetIcon(Symbol).Path;\n        var geometry = Geometry.Parse(path);\n\n        var scale = FontSize / 20;\n\n        Data = geometry;\n        // TODO: Scaling not working\n        Data.Transform = new ScaleTransform(scale, scale);\n    }\n}\n\npublic class FASymbolIconSourceConverter : TypeConverter\n{\n    public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)\n    {\n        if (sourceType == typeof(string))\n        {\n            return true;\n        }\n        return base.CanConvertFrom(context, sourceType);\n    }\n\n    public override object? ConvertFrom(\n        ITypeDescriptorContext? context,\n        CultureInfo? culture,\n        object value\n    )\n    {\n        return value switch\n        {\n            string val => new FASymbolIconSource { Symbol = val, },\n            _ => base.ConvertFrom(context, culture, value)\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/FrameCarousel.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n        xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\">\n    <Design.PreviewWith>\n        <controls:FrameCarousel />\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|FrameCarousel\">\n        <!-- Set Defaults -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Border Background=\"{TemplateBinding Background}\"\n                        BorderBrush=\"{TemplateBinding BorderBrush}\"\n                        BorderThickness=\"{TemplateBinding BorderThickness}\"\n                        CornerRadius=\"{TemplateBinding CornerRadius}\">\n                    <ui:Frame \n                        Name=\"PART_Frame\"\n                        CornerRadius=\"{TemplateBinding CornerRadius}\"\n                        ContentTemplate=\"{TemplateBinding ContentTemplate}\"/>\n                </Border>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/FrameCarousel.axaml.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Diagnostics.CodeAnalysis;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Controls.Templates;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Media.Animation;\nusing FluentAvalonia.UI.Navigation;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class FrameCarousel : SelectingItemsControl\n{\n    public static readonly StyledProperty<IDataTemplate?> ContentTemplateProperty = AvaloniaProperty.Register<FrameCarousel, IDataTemplate?>(\n        \"ContentTemplate\");\n\n    public IDataTemplate? ContentTemplate\n    {\n        get => GetValue(ContentTemplateProperty);\n        set => SetValue(ContentTemplateProperty, value);\n    }\n\n    public static readonly StyledProperty<Type> SourcePageTypeProperty = AvaloniaProperty.Register<FrameCarousel, Type>(\n        \"SourcePageType\");\n\n    public Type SourcePageType\n    {\n        get => GetValue(SourcePageTypeProperty);\n        set => SetValue(SourcePageTypeProperty, value);\n    }\n\n    private Frame? frame;\n    private int previousIndex = -1;\n\n    private static readonly FrameNavigationOptions ForwardNavigationOptions\n        = new()\n        {\n            TransitionInfoOverride = new BetterSlideNavigationTransition\n            {\n                Effect = SlideNavigationTransitionEffect.FromRight,\n                FromHorizontalOffset = 200\n            }\n        };\n    \n    private static readonly FrameNavigationOptions BackNavigationOptions\n        = new()\n        {\n            TransitionInfoOverride = new BetterSlideNavigationTransition\n            {\n                Effect = SlideNavigationTransitionEffect.FromLeft,\n                FromHorizontalOffset = 200\n            }\n        };\n    \n    private static readonly FrameNavigationOptions DirectionlessNavigationOptions\n        = new()\n        {\n            TransitionInfoOverride = new SuppressNavigationTransitionInfo()\n        };\n    \n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n        \n        frame = e.NameScope.Find<Frame>(\"PART_Frame\") \n                ?? throw new NullReferenceException(\"Frame not found\");\n        \n        frame.NavigationPageFactory = new FrameNavigationFactory(SourcePageType);\n\n        if (SelectedItem is not null)\n        {\n            frame.NavigateFromObject(SelectedItem, DirectionlessNavigationOptions);\n        }\n    }\n\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        if (frame is null) return;\n        \n        if (change.Property == SelectedItemProperty)\n        {\n            if (change.GetNewValue<object?>() is not { } value) return;\n            \n            if (SelectedIndex > previousIndex)\n            {\n                // Going forward\n                frame.NavigateFromObject(value, ForwardNavigationOptions);\n            }\n            else if (SelectedIndex < previousIndex)\n            {\n                // Going back\n                frame.NavigateFromObject(value, BackNavigationOptions);\n            }\n            else\n            {\n                frame.NavigateFromObject(value, DirectionlessNavigationOptions);\n            }\n            \n            previousIndex = SelectedIndex;\n        } \n        else if (change.Property == ItemCountProperty)\n        {\n            // On item count change to 0, clear the frame cache\n            var value = change.GetNewValue<int>();\n\n            if (value == 0)\n            {\n                var pageCache = frame.GetPrivateField<IList>(\"_pageCache\");\n                pageCache?.Clear();\n            }\n        }\n    }\n    \n    /// <summary>\n    /// Moves to the next item in the carousel.\n    /// </summary>\n    public void Next()\n    {\n        if (SelectedIndex < ItemCount - 1)\n        {\n            ++SelectedIndex;\n        }\n    }\n\n    /// <summary>\n    /// Moves to the previous item in the carousel.\n    /// </summary>\n    public void Previous()\n    {\n        if (SelectedIndex > 0)\n        {\n            --SelectedIndex;\n        }\n    }\n    \n    internal class FrameNavigationFactory : INavigationPageFactory\n    {\n        private readonly Type _sourcePageType;\n        \n        public FrameNavigationFactory(Type sourcePageType)\n        {\n            _sourcePageType = sourcePageType;\n        }\n        \n        /// <inheritdoc />\n        public Control GetPage(Type srcType)\n        {\n            return (Control) Activator.CreateInstance(srcType)!;\n        }\n\n        /// <inheritdoc />\n        public Control GetPageFromObject(object target)\n        {\n            var view = GetPage(_sourcePageType);\n            view.DataContext = target;\n            return view;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/GitVersionSelector.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controlModels=\"using:StabilityMatrix.Avalonia.Controls.Models\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\">\n    <Design.PreviewWith>\n        <DockPanel Width=\"600\" Height=\"400\">\n            <controls:Card\n                Padding=\"6\"\n                VerticalAlignment=\"Stretch\"\n                VerticalContentAlignment=\"Stretch\">\n                <controls:GitVersionSelector GitVersionProvider=\"{x:Static mocks:DesignData.MockGitVersionProvider}\" />\n            </controls:Card>\n        </DockPanel>\n\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|GitVersionSelector\">\n        <Style.Resources>\n            <converters:EnumToIntConverter x:Key=\"EnumToIntConverter\" x:TypeArguments=\"controlModels:GitVersionSelectorVersionType\" />\n        </Style.Resources>\n        <Setter Property=\"Focusable\" Value=\"True\" />\n        <Setter Property=\"FontSize\" Value=\"17\" />\n        <Setter Property=\"Margin\" Value=\"8\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Grid>\n                    <ui:ProgressRing HorizontalAlignment=\"Right\" VerticalAlignment=\"Top\">\n                        <ui:ProgressRing.IsVisible>\n                            <MultiBinding Converter=\"{x:Static BoolConverters.Or}\">\n                                <Binding Path=\"PopulateBranchesCommand.IsRunning\" RelativeSource=\"{RelativeSource TemplatedParent}\" />\n                                <Binding Path=\"PopulateCommitsForCurrentBranchCommand.IsRunning\" RelativeSource=\"{RelativeSource TemplatedParent}\" />\n                                <Binding Path=\"PopulateTagsCommand.IsRunning\" RelativeSource=\"{RelativeSource TemplatedParent}\" />\n                            </MultiBinding>\n                        </ui:ProgressRing.IsVisible>\n                    </ui:ProgressRing>\n                    <TabControl\n                        Margin=\"0,-6,0,0\"\n                        VerticalAlignment=\"{TemplateBinding VerticalAlignment}\"\n                        SelectedIndex=\"{TemplateBinding SelectedVersionType,\n                                                        Mode=TwoWay,\n                                                        Converter={StaticResource EnumToIntConverter}}\">\n                        <TabControl.Styles>\n                            <Style Selector=\"TabItem\">\n                                <Setter Property=\"Padding\" Value=\"4,4\" />\n                                <Setter Property=\"FontSize\" Value=\"{TemplateBinding FontSize}\" />\n                            </Style>\n                        </TabControl.Styles>\n\n                        <!--  Branches Tab  -->\n                        <TabItem Header=\"Branches\">\n                            <sg:SpacedGrid\n                                Margin=\"0,4,0,0\"\n                                ColumnDefinitions=\"0.4*,4,*\"\n                                RowDefinitions=\"Auto,*\"\n                                RowSpacing=\"8\">\n                                <TextBox\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"0\"\n                                    Text=\"{TemplateBinding SelectedBranch,\n                                                           Mode=TwoWay}\"\n                                    UseFloatingWatermark=\"True\"\n                                    Watermark=\"Branch\" />\n                                <ScrollViewer\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"0\"\n                                    VerticalScrollBarVisibility=\"Visible\">\n                                    <ListBox\n                                        AutoScrollToSelectedItem=\"True\"\n                                        ItemsSource=\"{TemplateBinding BranchSource}\"\n                                        SelectedItem=\"{TemplateBinding SelectedBranch,\n                                                                       Mode=TwoWay}\"\n                                        SelectionMode=\"Single\" />\n                                </ScrollViewer>\n                                <GridSplitter\n                                    Grid.Row=\"0\"\n                                    Grid.RowSpan=\"2\"\n                                    Grid.Column=\"1\" />\n                                <TextBox\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"2\"\n                                    Text=\"{TemplateBinding SelectedCommit,\n                                                           Mode=TwoWay}\"\n                                    UseFloatingWatermark=\"True\"\n                                    Watermark=\"Commit\" />\n                                <ListBox\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"2\"\n                                    AutoScrollToSelectedItem=\"True\"\n                                    ItemsSource=\"{TemplateBinding CommitSource}\"\n                                    SelectedItem=\"{TemplateBinding SelectedCommit,\n                                                                   Mode=TwoWay}\"\n                                    SelectionMode=\"Single\" />\n                                <!--<ScrollViewer\n                                Grid.Row=\"1\"\n                                Grid.Column=\"2\"\n                                VerticalScrollBarVisibility=\"Visible\">\n                                <ListBox\n                                    ItemsSource=\"{TemplateBinding CommitSource}\"\n                                    SelectedItem=\"{TemplateBinding SelectedCommit, Mode=TwoWay}\"\n                                    SelectionMode=\"Single\" AutoScrollToSelectedItem=\"True\" />\n                            </ScrollViewer>-->\n                            </sg:SpacedGrid>\n                        </TabItem>\n\n                        <!--  Tags Tab  -->\n                        <TabItem Header=\"Tags\">\n                            <sg:SpacedGrid\n                                Margin=\"0,4,0,0\"\n                                ColumnDefinitions=\"*\"\n                                RowDefinitions=\"Auto,*\"\n                                RowSpacing=\"8\">\n                                <TextBox\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"0\"\n                                    Text=\"{TemplateBinding SelectedTag,\n                                                           Mode=TwoWay}\"\n                                    UseFloatingWatermark=\"True\"\n                                    Watermark=\"Tag\" />\n                                <ScrollViewer\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"0\"\n                                    VerticalScrollBarVisibility=\"Visible\">\n                                    <ListBox\n                                        AutoScrollToSelectedItem=\"True\"\n                                        ItemsSource=\"{TemplateBinding TagSource}\"\n                                        SelectedItem=\"{TemplateBinding SelectedTag,\n                                                                       Mode=TwoWay}\"\n                                        SelectionMode=\"AlwaysSelected\" />\n                                </ScrollViewer>\n                            </sg:SpacedGrid>\n                        </TabItem>\n                    </TabControl>\n                </Grid>\n\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/GitVersionSelector.axaml.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.ComponentModel;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Avalonia;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Data;\nusing Avalonia.Interactivity;\nusing Avalonia.Logging;\nusing CommunityToolkit.Mvvm.Input;\nusing Nito.Disposables.Internals;\nusing StabilityMatrix.Avalonia.Controls.Models;\nusing StabilityMatrix.Core.Git;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[Localizable(false)]\npublic partial class GitVersionSelector : TemplatedControlBase\n{\n    public static readonly StyledProperty<IGitVersionProvider?> GitVersionProviderProperty =\n        AvaloniaProperty.Register<GitVersionSelector, IGitVersionProvider?>(nameof(GitVersionProvider));\n\n    public IGitVersionProvider? GitVersionProvider\n    {\n        get => GetValue(GitVersionProviderProperty);\n        set => SetValue(GitVersionProviderProperty, value);\n    }\n\n    public static readonly StyledProperty<SelectionMode> BranchSelectionModeProperty =\n        AvaloniaProperty.Register<GitVersionSelector, SelectionMode>(nameof(BranchSelectionMode));\n\n    public SelectionMode BranchSelectionMode\n    {\n        get => GetValue(BranchSelectionModeProperty);\n        set => SetValue(BranchSelectionModeProperty, value);\n    }\n\n    public static readonly StyledProperty<SelectionMode> CommitSelectionModeProperty =\n        AvaloniaProperty.Register<GitVersionSelector, SelectionMode>(nameof(CommitSelectionMode));\n\n    public SelectionMode CommitSelectionMode\n    {\n        get => GetValue(CommitSelectionModeProperty);\n        set => SetValue(CommitSelectionModeProperty, value);\n    }\n\n    public static readonly StyledProperty<SelectionMode> TagSelectionModeProperty = AvaloniaProperty.Register<\n        GitVersionSelector,\n        SelectionMode\n    >(nameof(TagSelectionMode));\n\n    public SelectionMode TagSelectionMode\n    {\n        get => GetValue(TagSelectionModeProperty);\n        set => SetValue(TagSelectionModeProperty, value);\n    }\n\n    public static readonly StyledProperty<string?> DefaultBranchProperty = AvaloniaProperty.Register<\n        GitVersionSelector,\n        string?\n    >(nameof(DefaultBranch), \"main\");\n\n    /// <summary>\n    /// The default branch to use when no branch is selected. Shows as a placeholder.\n    /// </summary>\n    public string? DefaultBranch\n    {\n        get => GetValue(DefaultBranchProperty);\n        set => SetValue(DefaultBranchProperty, value);\n    }\n\n    public static readonly StyledProperty<string?> DefaultCommitProperty = AvaloniaProperty.Register<\n        GitVersionSelector,\n        string?\n    >(nameof(DefaultCommit), \"latest\");\n\n    /// <summary>\n    /// The default commit to use when no commit is selected. Shows as a placeholder.\n    /// </summary>\n    public string? DefaultCommit\n    {\n        get => GetValue(DefaultCommitProperty);\n        set => SetValue(DefaultCommitProperty, value);\n    }\n\n    public static readonly StyledProperty<IReadOnlyList<string>> BranchSourceProperty =\n        AvaloniaProperty.Register<GitVersionSelector, IReadOnlyList<string>>(nameof(BranchSource), []);\n\n    public IReadOnlyList<string> BranchSource\n    {\n        get => GetValue(BranchSourceProperty);\n        set => SetValue(BranchSourceProperty, value);\n    }\n\n    public static readonly StyledProperty<IReadOnlyList<string>> CommitSourceProperty =\n        AvaloniaProperty.Register<GitVersionSelector, IReadOnlyList<string>>(nameof(CommitSource), []);\n\n    public IReadOnlyList<string> CommitSource\n    {\n        get => GetValue(CommitSourceProperty);\n        set => SetValue(CommitSourceProperty, value);\n    }\n\n    public static readonly StyledProperty<IReadOnlyList<string>> TagSourceProperty =\n        AvaloniaProperty.Register<GitVersionSelector, IReadOnlyList<string>>(nameof(TagSource), []);\n\n    public IReadOnlyList<string> TagSource\n    {\n        get => GetValue(TagSourceProperty);\n        set => SetValue(TagSourceProperty, value);\n    }\n\n    public static readonly StyledProperty<string?> SelectedBranchProperty = AvaloniaProperty.Register<\n        GitVersionSelector,\n        string?\n    >(nameof(SelectedBranch), defaultBindingMode: BindingMode.TwoWay);\n\n    public string? SelectedBranch\n    {\n        get => GetValue(SelectedBranchProperty);\n        set => SetValue(SelectedBranchProperty, value);\n    }\n\n    public static readonly StyledProperty<string?> SelectedCommitProperty = AvaloniaProperty.Register<\n        GitVersionSelector,\n        string?\n    >(nameof(SelectedCommit), defaultBindingMode: BindingMode.TwoWay);\n\n    public string? SelectedCommit\n    {\n        get => GetValue(SelectedCommitProperty);\n        set => SetValue(SelectedCommitProperty, value);\n    }\n\n    public static readonly StyledProperty<string?> SelectedTagProperty = AvaloniaProperty.Register<\n        GitVersionSelector,\n        string?\n    >(nameof(SelectedTag), defaultBindingMode: BindingMode.TwoWay);\n\n    public string? SelectedTag\n    {\n        get => GetValue(SelectedTagProperty);\n        set => SetValue(SelectedTagProperty, value);\n    }\n\n    public static readonly StyledProperty<GitVersionSelectorVersionType> SelectedVersionTypeProperty =\n        AvaloniaProperty.Register<GitVersionSelector, GitVersionSelectorVersionType>(\n            nameof(SelectedVersionType),\n            defaultBindingMode: BindingMode.TwoWay\n        );\n\n    public GitVersionSelectorVersionType SelectedVersionType\n    {\n        get => GetValue(SelectedVersionTypeProperty);\n        set => SetValue(SelectedVersionTypeProperty, value);\n    }\n\n    public static readonly DirectProperty<\n        GitVersionSelector,\n        IAsyncRelayCommand\n    > PopulateBranchesCommandProperty = AvaloniaProperty.RegisterDirect<\n        GitVersionSelector,\n        IAsyncRelayCommand\n    >(nameof(PopulateBranchesCommand), o => o.PopulateBranchesCommand);\n\n    public static readonly DirectProperty<\n        GitVersionSelector,\n        IAsyncRelayCommand\n    > PopulateCommitsForCurrentBranchCommandProperty = AvaloniaProperty.RegisterDirect<\n        GitVersionSelector,\n        IAsyncRelayCommand\n    >(nameof(PopulateCommitsForCurrentBranchCommand), o => o.PopulateCommitsForCurrentBranchCommand);\n\n    public static readonly DirectProperty<\n        GitVersionSelector,\n        IAsyncRelayCommand\n    > PopulateTagsCommandProperty = AvaloniaProperty.RegisterDirect<GitVersionSelector, IAsyncRelayCommand>(\n        nameof(PopulateTagsCommand),\n        o => o.PopulateTagsCommand\n    );\n\n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        base.OnLoaded(e);\n\n        if (GitVersionProvider is not null)\n        {\n            PopulateBranchesCommand.Execute(null);\n\n            if (SelectedBranch is not null)\n            {\n                PopulateCommitsForCurrentBranchCommand.Execute(null);\n            }\n\n            PopulateTagsCommand.Execute(null);\n        }\n    }\n\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        // On branch change, fetch commits\n        if (change.Property == SelectedBranchProperty)\n        {\n            PopulateCommitsForCurrentBranchCommand.Execute(null);\n        }\n    }\n\n    [RelayCommand]\n    public async Task PopulateBranches()\n    {\n        if (GitVersionProvider is null)\n            return;\n\n        var branches = await GitVersionProvider.FetchBranchesAsync();\n\n        BranchSource = branches.Select(v => v.Branch).WhereNotNull().ToImmutableList();\n    }\n\n    [RelayCommand]\n    public async Task PopulateCommitsForCurrentBranch()\n    {\n        if (string.IsNullOrEmpty(SelectedBranch))\n        {\n            CommitSource = [];\n            return;\n        }\n\n        if (GitVersionProvider is null)\n            return;\n\n        try\n        {\n            var commits = await GitVersionProvider.FetchCommitsAsync(SelectedBranch);\n\n            CommitSource = commits.Select(v => v.CommitSha).WhereNotNull().ToImmutableList();\n        }\n        catch (Exception e)\n        {\n            Logger\n                .TryGet(LogEventLevel.Error, nameof(GitVersionSelector))\n                ?.Log(this, \"Failed to fetch commits for branch {Branch}: {Exception}\", SelectedBranch, e);\n        }\n    }\n\n    [RelayCommand]\n    public async Task PopulateTags()\n    {\n        if (GitVersionProvider is null)\n            return;\n\n        var tags = await GitVersionProvider.FetchTagsAsync();\n\n        TagSource = tags.Select(v => v.Tag).WhereNotNull().ToImmutableList();\n    }\n\n    public enum SelectionMode\n    {\n        Disabled,\n        Required,\n        Optional\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/HybridModelTemplateSelector.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Templates;\nusing Avalonia.Metadata;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class HybridModelTemplateSelector : IDataTemplate\n{\n    // ReSharper disable once CollectionNeverUpdated.Global\n    [Content]\n    public Dictionary<HybridModelType, IDataTemplate> Templates { get; } = new();\n\n    // Check if we can accept the provided data\n    public bool Match(object? data)\n    {\n        return data is HybridModelFile;\n    }\n\n    // Build the DataTemplate here\n    public Control Build(object? data)\n    {\n        if (data is not HybridModelFile modelFile)\n            throw new ArgumentException(null, nameof(data));\n\n        if (Templates.TryGetValue(modelFile.Type, out var type))\n        {\n            return type.Build(modelFile)!;\n        }\n\n        // Fallback to Local\n        return Templates[HybridModelType.None].Build(modelFile)!;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/HyperlinkIconButton.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.IO;\nusing AsyncAwaitBestPractices;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Presenters;\nusing Avalonia.Logging;\nusing StabilityMatrix.Core.Processes;\nusing Symbol = FluentIcons.Common.Symbol;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n/// <summary>\n/// Like <see cref=\"HyperlinkButton\"/>, but with a link icon left of the text content.\n/// </summary>\npublic class HyperlinkIconButton : Button\n{\n    private Uri? _navigateUri;\n\n    /// <summary>\n    /// Defines the <see cref=\"NavigateUri\"/> property\n    /// </summary>\n    public static readonly DirectProperty<HyperlinkIconButton, Uri?> NavigateUriProperty =\n        AvaloniaProperty.RegisterDirect<HyperlinkIconButton, Uri?>(\n            nameof(NavigateUri),\n            x => x.NavigateUri,\n            (x, v) => x.NavigateUri = v\n        );\n\n    /// <summary>\n    /// Gets or sets the Uri that the button should navigate to upon clicking. In assembly paths are not supported, (e.g., avares://...)\n    /// </summary>\n    public Uri? NavigateUri\n    {\n        get => _navigateUri;\n        set => SetAndRaise(NavigateUriProperty, ref _navigateUri, value);\n    }\n\n    public static readonly StyledProperty<Symbol> IconProperty = AvaloniaProperty.Register<\n        HyperlinkIconButton,\n        Symbol\n    >(\"Icon\", Symbol.Link);\n\n    public Symbol Icon\n    {\n        get => GetValue(IconProperty);\n        set => SetValue(IconProperty, value);\n    }\n\n    protected override Type StyleKeyOverride => typeof(HyperlinkIconButton);\n\n    /// <inheritdoc />\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        // Update icon\n        if (change.Property == NavigateUriProperty)\n        {\n            var uri = change.GetNewValue<Uri?>();\n\n            if (uri is not null && uri.IsFile && Icon == Symbol.Link)\n            {\n                Icon = Symbol.Open;\n            }\n        }\n    }\n\n    protected override void OnClick()\n    {\n        base.OnClick();\n\n        if (NavigateUri is null)\n            return;\n\n        // File or Folder URIs\n        if (NavigateUri.IsFile)\n        {\n            var path = NavigateUri.LocalPath;\n\n            if (Directory.Exists(path))\n            {\n                ProcessRunner\n                    .OpenFolderBrowser(path)\n                    .SafeFireAndForget(ex =>\n                    {\n                        Logger.TryGet(LogEventLevel.Error, $\"Unable to open directory Uri {NavigateUri}\");\n                    });\n            }\n            else if (File.Exists(path))\n            {\n                ProcessRunner\n                    .OpenFileBrowser(path)\n                    .SafeFireAndForget(ex =>\n                    {\n                        Logger.TryGet(LogEventLevel.Error, $\"Unable to open file Uri {NavigateUri}\");\n                    });\n            }\n        }\n        // Web\n        else\n        {\n            try\n            {\n                Process.Start(\n                    new ProcessStartInfo(NavigateUri.ToString()) { UseShellExecute = true, Verb = \"open\" }\n                );\n            }\n            catch\n            {\n                Logger.TryGet(LogEventLevel.Error, $\"Unable to open Uri {NavigateUri}\");\n            }\n        }\n    }\n\n    protected override bool RegisterContentPresenter(ContentPresenter presenter)\n    {\n        return presenter.Name == \"ContentPresenter\";\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/ImageLoaders.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.IO;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Threading;\nusing Apizr;\nusing Fusillade;\nusing StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[Localizable(false)]\ninternal static class ImageLoaders\n{\n    private static string BaseFileCachePath => Path.Combine(Path.GetTempPath(), \"StabilityMatrix\", \"Cache\");\n\n    private static readonly Lazy<MemoryImageCache> OutputsPageImageCacheLazy =\n        new(\n            () => new MemoryImageCache { MaxMemoryCacheCount = 64 },\n            LazyThreadSafetyMode.ExecutionAndPublication\n        );\n\n    public static IImageCache OutputsPageImageCache => OutputsPageImageCacheLazy.Value;\n\n    private static readonly Lazy<ImageCache> OpenModelDbImageCacheLazy =\n        new(\n            () =>\n                new ImageCache(\n                    new CacheOptions\n                    {\n                        BaseCachePath = BaseFileCachePath,\n                        CacheFolderName = \"OpenModelDbImageCache\",\n                        CacheDuration = TimeSpan.FromDays(1),\n                        HttpClient = new HttpClient(NetCache.Background)\n                        {\n                            DefaultRequestHeaders =\n                            {\n                                UserAgent = { new ProductInfoHeaderValue(\"StabilityMatrix\", \"2.0\") },\n                                Referrer = new Uri(\"https://openmodelsdb.info/\"),\n                            }\n                        }\n                    }\n                ),\n            LazyThreadSafetyMode.ExecutionAndPublication\n        );\n\n    public static IImageCache OpenModelDbImageCache => OpenModelDbImageCacheLazy.Value;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/BatchSizeCard.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n        xmlns:vmInference=\"using:StabilityMatrix.Avalonia.ViewModels.Inference\"\n        xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n        xmlns:icons=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n        xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n        xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n        xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n        x:DataType=\"vmInference:BatchSizeCardViewModel\">\n    \n    <Design.PreviewWith>\n        <StackPanel Spacing=\"8\">\n            <controls:BatchSizeCard \n                DataContext=\"{x:Static mocks:DesignData.BatchSizeCardViewModel}\"/>\n            <controls:BatchSizeCard \n                Width=\"280\"\n                DataContext=\"{x:Static mocks:DesignData.BatchSizeCardViewModelWithIndexOption}\"/>\n        </StackPanel>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|BatchSizeCard\">\n        <Setter Property=\"Focusable\" Value=\"True\"/>\n        <Setter Property=\"ContextFlyout\">\n            <ui:FAMenuFlyout Placement=\"BottomEdgeAlignedLeft\">\n                <ui:ToggleMenuFlyoutItem\n                    IsChecked=\"{Binding IsBatchIndexEnabled}\"\n                    Text=\"{x:Static lang:Resources.Label_BatchIndex}\"/>\n            </ui:FAMenuFlyout>\n        </Setter>\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"8\">\n                    <Grid Margin=\"8\" RowDefinitions=\"Auto,Auto\" ColumnDefinitions=\"*,*\">\n                        <StackPanel>\n                            <StackPanel Orientation=\"Horizontal\" >\n                                <TextBlock \n                                    VerticalAlignment=\"Center\"\n                                    Margin=\"0,0,4,0\"\n                                    Text=\"{x:Static lang:Resources.Label_BatchSize}\"/>\n                                <icons:Icon\n                                    FontSize=\"12\"\n                                    Value=\"fa-solid fa-table-cells-large\"/>\n                            </StackPanel>\n                            <NumericUpDown \n                                HorizontalAlignment=\"Stretch\"\n                                Margin=\"0,4,4,0\"\n                                MinWidth=\"120\"\n                                Minimum=\"1\"\n                                Increment=\"1\"\n                                ParsingNumberStyle=\"Integer\"\n                                Value=\"{Binding BatchSize, Converter={x:Static converters:NullableDefaultNumericConverters.IntToDecimal}}\"\n                                ClipValueToMinMax=\"True\"/>\n                        </StackPanel>\n\n                        <StackPanel\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\">\n                            <StackPanel Orientation=\"Horizontal\" >\n                                <TextBlock \n                                    VerticalAlignment=\"Center\"\n                                    Margin=\"4,0,8,0\"\n                                    Text=\"{x:Static lang:Resources.Label_Batches}\"/>\n                                <icons:Icon\n                                    FontSize=\"14\"\n                                    Value=\"fa-regular fa-images\"/>\n                            </StackPanel>\n                            <NumericUpDown \n                                HorizontalAlignment=\"Stretch\"\n                                Margin=\"4,4,0,0\"\n                                MinWidth=\"120\"\n                                Minimum=\"1\"\n                                Increment=\"1\"\n                                ParsingNumberStyle=\"Integer\"\n                                Value=\"{Binding BatchCount, Converter={x:Static converters:NullableDefaultNumericConverters.IntToDecimal}}\"\n                                ClipValueToMinMax=\"True\"/>\n                        </StackPanel>\n                        \n                        <!-- Optional index selection -->\n                        <StackPanel \n                            Grid.Row=\"1\" \n                            Grid.Column=\"0\" \n                            Grid.ColumnSpan=\"2\"\n                            IsVisible=\"{Binding IsBatchIndexEnabled}\"\n                            Margin=\"0,8,0,0\">\n                            <StackPanel Orientation=\"Horizontal\" >\n                                <TextBlock \n                                    VerticalAlignment=\"Center\"\n                                    Margin=\"0,0,4,0\"\n                                    Text=\"{x:Static lang:Resources.Label_BatchIndex}\"/>\n                                <icons:Icon\n                                    FontSize=\"12\"\n                                    Value=\"fa-solid fa-crosshairs\"/>\n                            </StackPanel>\n                            <NumericUpDown \n                                HorizontalAlignment=\"Stretch\"\n                                Margin=\"0,4,0,0\"\n                                MinWidth=\"120\"\n                                Minimum=\"1\"\n                                Maximum=\"{Binding BatchSize}\"\n                                Increment=\"1\"\n                                ParsingNumberStyle=\"Integer\"\n                                Value=\"{Binding BatchIndex, Converter={x:Static converters:NullableDefaultNumericConverters.IntToDecimal}}\"\n                                ClipValueToMinMax=\"True\"/>\n                        </StackPanel>\n\n                    </Grid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/BatchSizeCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<BatchSizeCard>]\npublic class BatchSizeCard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/CfzCudnnToggleCard.axaml",
    "content": "<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n        xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n        xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n        x:DataType=\"vmInference:CfzCudnnToggleCardViewModel\">\n    <Design.PreviewWith>\n        <controls:CfzCudnnToggleCard Width=\"400\" />\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|CfzCudnnToggleCard\">\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card x:Name=\"PART_Card\">\n                    <StackPanel Spacing=\"8\">\n                        <TextBlock Text=\"Disable CUDNN\" FontSize=\"14\" FontWeight=\"Bold\" />\n                        <ToggleSwitch\n                            IsChecked=\"{Binding DisableCudnn}\"\n                            Margin=\"8,0,4,0\"\n                            HorizontalAlignment=\"Stretch\" />\n                    </StackPanel>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/CfzCudnnToggleCard.axaml.cs",
    "content": "using Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<CfzCudnnToggleCard>]\npublic class CfzCudnnToggleCard : TemplatedControlBase;\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:markupExtensions=\"clr-namespace:StabilityMatrix.Avalonia.MarkupExtensions\"\n    x:DataType=\"vmInference:ControlNetCardViewModel\">\n    <Design.PreviewWith>\n        <StackPanel Width=\"400\" Height=\"500\">\n            <controls:ControlNetCard DataContext=\"{x:Static mocks:DesignData.ControlNetCardViewModel}\" />\n        </StackPanel>\n    </Design.PreviewWith>\n\n    <Styles.Resources>\n        <ToolTip x:Key=\"WipFeatureToolTip\">\n            <StackPanel>\n                <TextBlock Text=\"{x:Static lang:Resources.Label_WipFeature}\" Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                <TextBlock Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\" Text=\"{x:Static lang:Resources.Label_WipFeatureDescription}\" />\n            </StackPanel>\n        </ToolTip>\n    </Styles.Resources>\n\n    <Style Selector=\"controls|ControlNetCard\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"12\">\n                    <StackPanel Spacing=\"6\">\n                        <sg:SpacedGrid\n                            ColumnDefinitions=\"0.34*,0.65*\"\n                            ColumnSpacing=\"8\"\n                            RowDefinitions=\"Auto,Auto,Auto,Auto\"\n                            RowSpacing=\"4\">\n\n                            <!--  Image Select  -->\n                            <controls:SelectImageCard\n                                Grid.RowSpan=\"3\"\n                                Padding=\"6\"\n                                VerticalAlignment=\"Stretch\"\n                                DataContext=\"{Binding SelectImageCardViewModel}\"\n                                FontSize=\"13\" />\n\n                            <!--  Preprocessor Model  -->\n                            <sg:SpacedGrid\n                                Grid.Row=\"0\"\n                                Grid.Column=\"1\"\n                                ColumnDefinitions=\"*,Auto\">\n                                <ui:FAComboBox\n                                    Margin=\"0,0,0,4\"\n                                    SelectedItem=\"{Binding SelectedPreprocessor}\"\n                                    ItemsSource=\"{Binding ClientManager.Preprocessors}\"\n                                    DisplayMemberBinding=\"{Binding DisplayName}\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    Header=\"{x:Static lang:Resources.Label_Preprocessor}\"/>\n                                \n                                <Button \n                                    ToolTip.Tip=\"{x:Static lang:Resources.Action_PreviewPreprocessor}\"\n                                    Padding=\"7\"\n                                    VerticalAlignment=\"Center\"\n                                    Margin=\"0,23,0,0\"\n                                    Command=\"{Binding PreviewPreprocessorCommand}\"\n                                    CommandParameter=\"{Binding SelectedPreprocessor}\"\n                                    Grid.Column=\"1\">\n                                    <fluentIcons:SymbolIcon\n                                        VerticalAlignment=\"Center\"\n                                        FontSize=\"15\"\n                                        IconVariant=\"Filled\"\n                                        Symbol=\"Play\" />\n                                </Button>\n                            </sg:SpacedGrid>\n\n\n                            <!--  ControlNet Model  -->\n                            <controls:FADownloadableComboBox\n                                Grid.Row=\"1\"\n                                Grid.Column=\"1\"\n                                HorizontalAlignment=\"Stretch\"\n                                Header=\"{x:Static lang:Resources.Label_Model}\"\n                                ItemContainerTheme=\"{StaticResource FAComboBoxItemHybridModelTheme}\"\n                                ItemsSource=\"{Binding ClientManager.ControlNetModels}\"\n                                SelectedItem=\"{Binding SelectedModel}\">\n\n                                <ui:FAComboBox.DataTemplates>\n                                    <controls:HybridModelTemplateSelector>\n                                        <DataTemplate x:Key=\"{x:Static models:HybridModelType.Downloadable}\" DataType=\"models:HybridModelFile\">\n                                            <Grid ColumnDefinitions=\"*,Auto\">\n                                                <TextBlock Foreground=\"{DynamicResource ThemeGreyColor}\" Text=\"{Binding ShortDisplayName}\" />\n                                                <Button\n                                                    Grid.Column=\"1\"\n                                                    Margin=\"8,0,0,0\"\n                                                    Padding=\"0\"\n                                                    Classes=\"transparent-full\">\n                                                    <fluentIcons:SymbolIcon\n                                                        VerticalAlignment=\"Center\"\n                                                        FontSize=\"18\"\n                                                        Foreground=\"{DynamicResource ThemeGreyColor}\"\n                                                        IconVariant=\"Filled\"\n                                                        Symbol=\"CloudArrowDown\" />\n                                                </Button>\n                                            </Grid>\n                                        </DataTemplate>\n                                        <DataTemplate x:Key=\"{x:Static models:HybridModelType.None}\" DataType=\"models:HybridModelFile\">\n                                            <TextBlock Text=\"{Binding ShortDisplayName}\" />\n                                        </DataTemplate>\n                                    </controls:HybridModelTemplateSelector>\n                                </ui:FAComboBox.DataTemplates>\n\n                            </controls:FADownloadableComboBox>\n                            \n                            <!-- Dimensions -->\n                            <Grid\n                                Grid.Row=\"2\"\n                                Grid.Column=\"1\"\n                                Margin=\"0,4,0,0\"\n                                ColumnDefinitions=\"*,Auto,*\" \n                                RowDefinitions=\"Auto,*\">\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"0\"\n                                    Margin=\"0,0,4,4\"\n                                    Text=\"{x:Static lang:Resources.Label_Width}\"/>\n                                <ui:NumberBox\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"0\"\n                                    Margin=\"0,0,4,0\"\n                                    PlaceholderText=\"128\"\n                                    SmallChange=\"128\"\n                                    Value=\"{Binding Width}\"\n                                    ValidationMode=\"InvalidInputOverwritten\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    SpinButtonPlacementMode=\"Compact\"/>\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"2\"\n                                    Margin=\"4,0,0,4\"\n                                    Text=\"{x:Static lang:Resources.Label_Height}\"/>\n                                <ui:NumberBox\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"2\"\n                                    Margin=\"4,0,0,0\"\n                                    PlaceholderText=\"128\"\n                                    SmallChange=\"128\"\n                                    Value=\"{Binding Height}\"\n                                    ValidationMode=\"InvalidInputOverwritten\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    SpinButtonPlacementMode=\"Compact\"/>\n                            </Grid>\n\n                        </sg:SpacedGrid>\n                        <sg:SpacedGrid\n                            ColumnDefinitions=\"*,Auto,Auto\"\n                            RowDefinitions=\"Auto,Auto,Auto,Auto\"\n                            RowSpacing=\"0\">\n                            <!--  Strength  -->\n                            <TextBlock\n                                VerticalAlignment=\"Center\"\n                                Text=\"{x:Static lang:Resources.Label_ControlWeight}\"\n                                TextAlignment=\"Left\" />\n                            <ui:NumberBox\n                                Grid.Row=\"0\"\n                                Grid.Column=\"1\"\n                                Grid.ColumnSpan=\"2\"\n                                HorizontalAlignment=\"Stretch\"\n                                Maximum=\"10\"\n                                Minimum=\"0\"\n                                SimpleNumberFormat=\"F2\"\n                                SmallChange=\"0.01\"\n                                SpinButtonPlacementMode=\"Inline\"\n                                Value=\"{Binding Strength}\" />\n                            <Slider\n                                Grid.Row=\"1\"\n                                Grid.Column=\"0\"\n                                Grid.ColumnSpan=\"3\"\n                                Margin=\"0,-8,0,0\"\n                                IsSnapToTickEnabled=\"True\"\n                                Maximum=\"1\"\n                                Minimum=\"0\"\n                                SmallChange=\"0.01\"\n                                TickFrequency=\"0.01\"\n                                Value=\"{Binding Strength}\" />\n\n                            <!--  Start end percent  -->\n                            <TextBlock\n                                Grid.Row=\"2\"\n                                Grid.Column=\"0\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"{x:Static lang:Resources.Label_ControlSteps}\"\n                                TextAlignment=\"Left\" />\n\n                            <ui:NumberBox\n                                Grid.Row=\"2\"\n                                Grid.Column=\"1\"\n                                HorizontalAlignment=\"Stretch\"\n                                Maximum=\"10\"\n                                Minimum=\"0\"\n                                SimpleNumberFormat=\"P0\"\n                                SmallChange=\"0.01\"\n                                Value=\"{Binding StartPercent}\" />\n                            <ui:NumberBox\n                                Grid.Row=\"2\"\n                                Grid.Column=\"2\"\n                                HorizontalAlignment=\"Stretch\"\n                                Maximum=\"10\"\n                                Minimum=\"0\"\n                                SimpleNumberFormat=\"P0\"\n                                SmallChange=\"0.01\"\n                                Value=\"{Binding EndPercent}\" />\n\n                            <ui:RangeSlider\n                                Grid.Row=\"3\"\n                                Grid.Column=\"0\"\n                                Grid.ColumnSpan=\"3\"\n                                Margin=\"0,4,0,0\"\n                                Maximum=\"1\"\n                                Minimum=\"0\"\n                                RangeEnd=\"{Binding EndPercent}\"\n                                RangeStart=\"{Binding StartPercent}\"\n                                ShowValueToolTip=\"False\"\n                                StepFrequency=\"0.01\"\n                                ToolTipStringFormat=\"P\" />\n                        </sg:SpacedGrid>\n                    </StackPanel>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/ControlNetCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<ControlNetCard>]\npublic class ControlNetCard : TemplatedControlBase;\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/DiscreteModelSamplingCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:inference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"inference:DiscreteModelSamplingCardViewModel\">\n    <Design.PreviewWith>\n        <Panel Width=\"400\" Height=\"200\">\n            <StackPanel Width=\"300\" VerticalAlignment=\"Center\">\n                <controls:DiscreteModelSamplingCard />\n            </StackPanel>\n        </Panel>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|DiscreteModelSamplingCard\">\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"12\">\n                    <StackPanel Spacing=\"8\">\n                        <TextBlock Text=\"Sampling Method\" />\n                        <controls:BetterComboBox\n                            HorizontalAlignment=\"Stretch\"\n                            ItemsSource=\"{Binding SamplingMethods}\"\n                            SelectedItem=\"{Binding SelectedSamplingMethod}\" />\n\n                        <ToggleSwitch\n                            Margin=\"0,12,0,0\"\n                            Content=\"ZSNR\"\n                            IsChecked=\"{Binding IsZsnrEnabled}\" />\n                    </StackPanel>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/DiscreteModelSamplingCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<DiscreteModelSamplingCard>]\npublic class DiscreteModelSamplingCard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/ExtraNetworkCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"vmInference:ExtraNetworkCardViewModel\">\n    <Design.PreviewWith>\n        <StackPanel Width=\"400\" Height=\"400\">\n            <controls:ExtraNetworkCard DataContext=\"{x:Static mocks:DesignData.ExtraNetworkCardViewModel}\" />\n        </StackPanel>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|ExtraNetworkCard\">\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"12\">\n                    <sg:SpacedGrid\n                        Margin=\"0,0,0,0\"\n                        ColumnDefinitions=\"Auto,*\"\n                        ColumnSpacing=\"8\"\n                        RowDefinitions=\"*,*,*,*,*\">\n                        <!--  Model  -->\n                        <TextBlock\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"{x:Static lang:Resources.Label_Model}\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterComboBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            ItemsSource=\"{Binding LoraModels}\"\n                            SelectedItem=\"{Binding SelectedModel}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                        <!--  Model Weight  -->\n                        <StackPanel\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            IsVisible=\"{Binding IsModelWeightEnabled}\">\n                            <Grid ColumnDefinitions=\"*,Auto\">\n                                <TextBlock VerticalAlignment=\"Center\" Text=\"{x:Static lang:Resources.Label_Strength}\" />\n                                <ui:NumberBox\n                                    Grid.Column=\"1\"\n                                    Margin=\"4,0,0,0\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    LargeChange=\"0.1\"\n                                    Maximum=\"100\"\n                                    Minimum=\"-100\"\n                                    SimpleNumberFormat=\"F2\"\n                                    SmallChange=\"0.05\"\n                                    SpinButtonPlacementMode=\"Inline\"\n                                    ValidationMode=\"InvalidInputOverwritten\"\n                                    Value=\"{Binding ModelWeight}\" />\n                            </Grid>\n                        </StackPanel>\n\n                        <!--  CLIP Strength  -->\n                        <StackPanel\n                            Grid.Row=\"2\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            IsVisible=\"{Binding IsClipWeightEnabled}\">\n                            <Grid ColumnDefinitions=\"*,Auto\">\n                                <TextBlock VerticalAlignment=\"Center\" Text=\"{x:Static lang:Resources.Label_CLIPStrength}\" />\n                                <ui:NumberBox\n                                    Grid.Column=\"1\"\n                                    Margin=\"4,0,0,0\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    LargeChange=\"0.1\"\n                                    Maximum=\"100\"\n                                    Minimum=\"-100\"\n                                    SimpleNumberFormat=\"F2\"\n                                    SmallChange=\"0.05\"\n                                    SpinButtonPlacementMode=\"Inline\"\n                                    ValidationMode=\"InvalidInputOverwritten\"\n                                    Value=\"{Binding ClipWeight}\" />\n                            </Grid>\n                        </StackPanel>\n\n                        <!--  Trigger Words  -->\n                        <TextBlock\n                            Grid.Row=\"3\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding ShowTriggerWords}\"\n                            Text=\"{x:Static lang:Resources.Label_TriggerWords}\" />\n                        <TextBox\n                            Grid.Row=\"4\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"2\"\n                            MaxHeight=\"250\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Stretch\"\n                            AcceptsReturn=\"True\"\n                            IsReadOnly=\"True\"\n                            IsVisible=\"{Binding ShowTriggerWords}\"\n                            MinLines=\"3\"\n                            ScrollViewer.VerticalScrollBarVisibility=\"Auto\"\n                            Text=\"{Binding TriggerWords}\"\n                            TextWrapping=\"Wrap\">\n                            <TextBox.InnerRightContent>\n                                <Button\n                                    Margin=\"0,4,4,4\"\n                                    HorizontalAlignment=\"Right\"\n                                    VerticalAlignment=\"Top\"\n                                    Command=\"{Binding CopyTriggerWordsCommand}\"\n                                    FontSize=\"12\"\n                                    IsVisible=\"{Binding ShowTriggerWords}\"\n                                    ToolTip.Tip=\"{x:Static lang:Resources.Action_CopyTriggerWords}\">\n                                    <avalonia:Icon Value=\"fa-solid fa-copy\" />\n                                </Button>\n                            </TextBox.InnerRightContent>\n                        </TextBox>\n                    </sg:SpacedGrid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/ExtraNetworkCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<ExtraNetworkCard>]\npublic class ExtraNetworkCard : TemplatedControlBase;\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/FaceDetailerCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:vmInference=\"using:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"vmInference:FaceDetailerViewModel\">\n    <Design.PreviewWith>\n        <controls:FaceDetailerCard Width=\"300\" />\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|FaceDetailerCard\">\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <StackPanel>\n                    <!--  Ultralytics Models  -->\n                    <TextBlock VerticalAlignment=\"Center\" Text=\"BBox Model\" />\n                    <controls:BetterComboBox\n                        Margin=\"4,4,4,8\"\n                        Padding=\"8,6,4,6\"\n                        HorizontalAlignment=\"Stretch\"\n                        ItemsSource=\"{Binding ClientManager.UltralyticsModels}\"\n                        SelectedItem=\"{Binding BboxModel}\"\n                        Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                    <TextBlock VerticalAlignment=\"Center\" Text=\"Segmentation Model (Optional)\" />\n                    <controls:BetterDownloadableComboBox\n                        Margin=\"4,4,4,8\"\n                        Padding=\"8,6,4,6\"\n                        HorizontalAlignment=\"Stretch\"\n                        ItemsSource=\"{Binding ClientManager.UltralyticsModels}\"\n                        SelectedItem=\"{Binding SegmModel}\"\n                        Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                    <!--  SAM Model  -->\n                    <TextBlock Margin=\"0,8,0,0\" Text=\"SAM Model (Optional)\" />\n                    <controls:BetterDownloadableComboBox\n                        Margin=\"4,4,4,8\"\n                        Padding=\"8,6,4,6\"\n                        HorizontalAlignment=\"Stretch\"\n                        ItemsSource=\"{Binding ClientManager.SamModels}\"\n                        SelectedItem=\"{Binding SamModel}\"\n                        Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                    <avalonia:SpacedGrid\n                        Margin=\"0,8,0,0\"\n                        ColumnDefinitions=\"Auto, *\"\n                        RowDefinitions=\"Auto, Auto, Auto, Auto\">\n                        <!--  Sampler  -->\n                        <TextBlock\n                            Margin=\"4,0,4,8\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsSamplerSelectionEnabled}\"\n                            Text=\"{x:Static lang:Resources.Label_Sampler}\" />\n                        <ui:FAComboBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,0,4,8\"\n                            HorizontalAlignment=\"Stretch\"\n                            DisplayMemberBinding=\"{Binding DisplayName}\"\n                            IsVisible=\"{Binding IsSamplerSelectionEnabled}\"\n                            ItemsSource=\"{Binding ClientManager.Samplers}\"\n                            SelectedItem=\"{Binding Sampler}\" />\n\n                        <!--  Scheduler  -->\n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Margin=\"4,0,4,8\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsSchedulerSelectionEnabled}\"\n                            Text=\"{x:Static lang:Resources.Label_Scheduler}\" />\n                        <ui:FAComboBox\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,0,4,8\"\n                            HorizontalAlignment=\"Stretch\"\n                            DisplayMemberBinding=\"{Binding DisplayName}\"\n                            IsVisible=\"{Binding IsSchedulerSelectionEnabled}\"\n                            ItemsSource=\"{Binding AvailableSchedulers}\"\n                            SelectedItem=\"{Binding Scheduler}\" />\n\n                        <!--  Steps  -->\n                        <TextBlock\n                            Grid.Row=\"2\"\n                            Grid.Column=\"0\"\n                            Margin=\"4,0,4,8\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsStepsEnabled}\"\n                            Text=\"{x:Static lang:Resources.Label_Steps}\" />\n                        <ui:NumberBox\n                            Grid.Row=\"2\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,0,4,8\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsStepsEnabled}\"\n                            Maximum=\"10000\"\n                            Minimum=\"1\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding Steps}\" />\n\n                        <!--  CFG  -->\n                        <TextBlock\n                            Grid.Row=\"3\"\n                            Grid.Column=\"0\"\n                            Margin=\"4,0,4,8\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsCfgScaleEnabled}\"\n                            Text=\"{x:Static lang:Resources.Label_CFGScale}\" />\n                        <ui:NumberBox\n                            Grid.Row=\"3\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,0,4,8\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsCfgScaleEnabled}\"\n                            Maximum=\"100\"\n                            Minimum=\"0\"\n                            SimpleNumberFormat=\"F2\"\n                            SmallChange=\"0.25\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding Cfg}\" />\n                    </avalonia:SpacedGrid>\n\n                    <controls:SeedCard\n                        Margin=\"4,0,4,8\"\n                        Background=\"Gray\"\n                        DataContext=\"{Binding SeedCardViewModel}\"\n                        IsVisible=\"{Binding !$parent[StackPanel].((vmInference:FaceDetailerViewModel)DataContext).InheritSeed, TargetNullValue=False, FallbackValue=False}\" />\n\n                    <avalonia:SpacedGrid\n                        Margin=\"0,8,0,0\"\n                        ColumnDefinitions=\"*, *\"\n                        RowDefinitions=\"Auto, Auto\">\n                        <!--  Tiled Encode  -->\n                        <TextBlock Margin=\"4,0,4,0\" Text=\"Tiled Encode\" />\n                        <ToggleSwitch\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Margin=\"8,-8,4,0\"\n                            IsChecked=\"{Binding UseTiledEncode}\" />\n\n                        <!--  Tiled Decode  -->\n                        <TextBlock\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,0,4,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Tiled Decode\" />\n                        <ToggleSwitch\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Margin=\"8,-8,4,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsChecked=\"{Binding UseTiledDecode}\" />\n                    </avalonia:SpacedGrid>\n\n                    <Separator Margin=\"8,16\" Background=\"#DDDDDD\" />\n\n                    <avalonia:SpacedGrid\n                        Margin=\"0,8,0,16\"\n                        ColumnDefinitions=\"*, *\"\n                        RowDefinitions=\"Auto, Auto\">\n                        <!--  Guide Size For  -->\n                        <TextBlock Margin=\"4,0,4,0\" Text=\"Guide Size For\" />\n                        <ToggleSwitch\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Margin=\"8,-8,4,0\"\n                            IsChecked=\"{Binding GuideSizeFor}\"\n                            OffContent=\"Crop Region\"\n                            OnContent=\"BBox\" />\n\n                        <!--  Force Inpaint  -->\n                        <TextBlock\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,0,4,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Force Inpaint\" />\n                        <ToggleSwitch\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Margin=\"8,-8,4,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsChecked=\"{Binding ForceInpaint}\" />\n                    </avalonia:SpacedGrid>\n\n                    <!--  Guide Size  -->\n                    <avalonia:SpacedGrid ColumnDefinitions=\"Auto, *\" RowDefinitions=\"Auto, Auto, Auto, Auto, Auto, Auto\">\n                        <TextBlock\n                            Margin=\"4,0,4,2\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Guide Size\" />\n                        <ui:NumberBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,4,4,4\"\n                            HorizontalAlignment=\"Stretch\"\n                            Minimum=\"64\"\n                            SmallChange=\"64\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding GuideSize}\" />\n\n                        <!--  Max Size  -->\n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Margin=\"4,0,4,2\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"{x:Static lang:Resources.Label_MaxSize}\" />\n                        <ui:NumberBox\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,4,4,4\"\n                            HorizontalAlignment=\"Stretch\"\n                            Minimum=\"64\"\n                            SmallChange=\"64\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding MaxSize}\" />\n\n                        <!--  BBox Threshold  -->\n                        <TextBlock\n                            Grid.Row=\"2\"\n                            Grid.Column=\"0\"\n                            Margin=\"4,0,4,2\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Threshold\" />\n                        <ui:NumberBox\n                            Grid.Row=\"2\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,4,4,4\"\n                            HorizontalAlignment=\"Stretch\"\n                            Maximum=\"1\"\n                            Minimum=\"0\"\n                            SimpleNumberFormat=\"F2\"\n                            SmallChange=\"0.01\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding BboxThreshold}\" />\n\n                        <!--  BBox Dilation  -->\n                        <TextBlock\n                            Grid.Row=\"3\"\n                            Grid.Column=\"0\"\n                            Margin=\"4,0,4,2\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Dilation\" />\n                        <ui:NumberBox\n                            Grid.Row=\"3\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,4,4,4\"\n                            HorizontalAlignment=\"Stretch\"\n                            Maximum=\"512\"\n                            Minimum=\"-512\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding BboxDilation}\" />\n\n                        <!--  BBox Crop Factor  -->\n                        <TextBlock\n                            Grid.Row=\"4\"\n                            Grid.Column=\"0\"\n                            Margin=\"4,0,4,2\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Crop Factor\" />\n                        <ui:NumberBox\n                            Grid.Row=\"4\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,4,4,4\"\n                            HorizontalAlignment=\"Stretch\"\n                            Maximum=\"10\"\n                            Minimum=\"1\"\n                            SimpleNumberFormat=\"F2\"\n                            SmallChange=\"0.1\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding BboxCropFactor}\" />\n                    </avalonia:SpacedGrid>\n\n                    <Separator Margin=\"8,20,8,8\" Background=\"#DDDDDD\" />\n\n                    <!--  Noise Mask  -->\n                    <TextBlock\n                        Margin=\"4,0,4,0\"\n                        VerticalAlignment=\"Center\"\n                        Text=\"Noise Mask\" />\n                    <ToggleSwitch\n                        Margin=\"8,0,4,0\"\n                        HorizontalAlignment=\"Stretch\"\n                        IsChecked=\"{Binding NoiseMask}\" />\n\n                    <avalonia:SpacedGrid ColumnDefinitions=\"Auto, *\" RowDefinitions=\"Auto, Auto, Auto, Auto\">\n\n                        <!--  Denoise  -->\n                        <TextBlock\n                            Grid.Row=\"0\"\n                            Grid.Column=\"0\"\n                            Margin=\"4,0,4,2\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Denoise\" />\n                        <ui:NumberBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,4,4,4\"\n                            HorizontalAlignment=\"Stretch\"\n                            Maximum=\"1\"\n                            Minimum=\"0\"\n                            SimpleNumberFormat=\"F2\"\n                            SmallChange=\"0.01\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding Denoise}\" />\n\n                        <Slider\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"4,-12,4,0\"\n                            IsSnapToTickEnabled=\"True\"\n                            Maximum=\"1\"\n                            Minimum=\"0\"\n                            SmallChange=\"0.01\"\n                            TickFrequency=\"0.01\"\n                            Value=\"{Binding Denoise}\" />\n\n                        <!--  Feather  -->\n                        <TextBlock\n                            Grid.Row=\"2\"\n                            Grid.Column=\"0\"\n                            Margin=\"4,0,4,4\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Feather\" />\n                        <ui:NumberBox\n                            Grid.Row=\"2\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,4,4,8\"\n                            HorizontalAlignment=\"Stretch\"\n                            Maximum=\"100\"\n                            Minimum=\"0\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding Feather}\" />\n                        <Slider\n                            Grid.Row=\"3\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"4,-12,4,0\"\n                            IsSnapToTickEnabled=\"True\"\n                            Maximum=\"100\"\n                            Minimum=\"0\"\n                            SmallChange=\"1\"\n                            TickFrequency=\"1\"\n                            Value=\"{Binding Feather}\" />\n                    </avalonia:SpacedGrid>\n\n                    <Separator Margin=\"8,16\" Background=\"#DDDDDD\" />\n\n                    <!--  SAM Detection Hint  -->\n                    <TextBlock Margin=\"4,0,4,4\" Text=\"SAM Detection Hint\" />\n                    <ComboBox\n                        Margin=\"4,4,4,8\"\n                        HorizontalAlignment=\"Stretch\"\n                        ItemsSource=\"{Binding SamDetectionHints}\"\n                        SelectedItem=\"{Binding SamDetectionHint}\" />\n\n                    <!--  SAM Dilation  -->\n                    <TextBlock Margin=\"4,0,4,4\" Text=\"SAM Dilation\" />\n                    <ui:NumberBox\n                        Margin=\"4,4,4,8\"\n                        HorizontalAlignment=\"Stretch\"\n                        Maximum=\"512\"\n                        Minimum=\"-512\"\n                        SmallChange=\"1\"\n                        SpinButtonPlacementMode=\"Inline\"\n                        Value=\"{Binding SamDilation}\" />\n\n                    <!--  SAM Threshold  -->\n                    <TextBlock Margin=\"4,0,4,4\" Text=\"SAM Threshold\" />\n                    <ui:NumberBox\n                        Margin=\"4,4,4,8\"\n                        HorizontalAlignment=\"Stretch\"\n                        Maximum=\"1\"\n                        Minimum=\"0\"\n                        SimpleNumberFormat=\"F2\"\n                        SmallChange=\"0.01\"\n                        SpinButtonPlacementMode=\"Inline\"\n                        Value=\"{Binding SamThreshold}\" />\n\n                    <!--  SAM BBox Expansion  -->\n                    <TextBlock Margin=\"4,0,4,4\" Text=\"SAM BBox Expansion\" />\n                    <ui:NumberBox\n                        Margin=\"4,4,4,8\"\n                        HorizontalAlignment=\"Stretch\"\n                        Maximum=\"1000\"\n                        Minimum=\"0\"\n                        SmallChange=\"1\"\n                        SpinButtonPlacementMode=\"Inline\"\n                        Value=\"{Binding SamBboxExpansion}\" />\n\n                    <!--  SAM Mask Hint Threshold  -->\n                    <TextBlock Margin=\"4,0,4,4\" Text=\"SAM Mask Hint Threshold\" />\n                    <ui:NumberBox\n                        Margin=\"4,4,4,8\"\n                        HorizontalAlignment=\"Stretch\"\n                        Maximum=\"1\"\n                        Minimum=\"0\"\n                        SimpleNumberFormat=\"F2\"\n                        SmallChange=\"0.01\"\n                        SpinButtonPlacementMode=\"Inline\"\n                        Value=\"{Binding SamMaskHintThreshold}\" />\n\n                    <!--  SAM Mask Hint Use Negative  -->\n                    <TextBlock Margin=\"4,0,4,4\" Text=\"SAM Mask Hint Use Negative\" />\n                    <ComboBox\n                        Margin=\"4,4,4,8\"\n                        HorizontalAlignment=\"Stretch\"\n                        ItemsSource=\"{Binding SamMaskHintUseNegatives}\"\n                        SelectedItem=\"{Binding SamMaskHintUseNegative}\" />\n\n                    <!--  Drop Size  -->\n                    <avalonia:SpacedGrid ColumnDefinitions=\"Auto, *\" RowDefinitions=\"Auto, Auto\">\n                        <TextBlock\n                            Margin=\"4,0,4,4\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Drop Size\" />\n                        <ui:NumberBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,4,4,8\"\n                            HorizontalAlignment=\"Stretch\"\n                            Maximum=\"16384\"\n                            Minimum=\"1\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding DropSize}\" />\n\n                        <!--  Cycle  -->\n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Margin=\"4,0,4,4\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Cycle\" />\n                        <ui:NumberBox\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,4,4,8\"\n                            HorizontalAlignment=\"Stretch\"\n                            Maximum=\"10\"\n                            Minimum=\"1\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding Cycle}\" />\n                    </avalonia:SpacedGrid>\n\n                    <Separator Margin=\"8,16\" Background=\"#DDDDDD\" />\n\n                    <!--  Use Separate Prompt  -->\n                    <TextBlock Margin=\"4,0,4,4\" Text=\"{x:Static lang:Resources.Label_UseSeparatePrompt}\" />\n                    <ToggleSwitch\n                        Margin=\"8,0,4,8\"\n                        HorizontalAlignment=\"Stretch\"\n                        IsChecked=\"{Binding UseSeparatePrompt}\" />\n\n                    <!--  Positive Prompt  -->\n                    <controls:PromptCard DataContext=\"{Binding PromptCardViewModel}\" IsVisible=\"{Binding $parent[StackPanel].((vmInference:FaceDetailerViewModel)DataContext).UseSeparatePrompt, TargetNullValue=False, FallbackValue=False}\" />\n\n                    <TextBlock Margin=\"4,8,4,4\" Text=\"{x:Static lang:Resources.Label_Wildcards}\" />\n                    <controls:PromptCard Margin=\"0,8,0,0\" DataContext=\"{Binding WildcardViewModel}\" />\n                </StackPanel>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/FaceDetailerCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<FaceDetailerCard>]\npublic class FaceDetailerCard : TemplatedControlBase;\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/FreeUCard.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n        xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n        xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n        xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n        x:DataType=\"vmInference:FreeUCardViewModel\">\n    <Design.PreviewWith>\n        <controls:FreeUCard DataContext=\"{x:Static mocks:DesignData.FreeUCardViewModel}\" />\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|FreeUCard\">\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card x:Name=\"PART_Card\">\n                    <controls:Card.Styles>\n                        <Style Selector=\"ui|NumberBox\">\n                            <Setter Property=\"Margin\" Value=\"12,4,0,4\"/>\n                            <Setter Property=\"MinWidth\" Value=\"70\"/>\n                            <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\"/>\n                            <Setter Property=\"ValidationMode\" Value=\"InvalidInputOverwritten\"/>\n                            <Setter Property=\"SmallChange\" Value=\"0.01\"/>\n                            <Setter Property=\"LargeChange\" Value=\"0.1\"/>\n                            <Setter Property=\"SimpleNumberFormat\" Value=\"F2\"/>\n                            <Setter Property=\"SpinButtonPlacementMode\" Value=\"Inline\"/>\n                        </Style>\n                    </controls:Card.Styles>\n                    <Grid RowDefinitions=\"*,*,*,*\" ColumnDefinitions=\"Auto,*\">\n                        <TextBlock\n                            Grid.Row=\"0\"\n                            Grid.Column=\"0\"\n                            Text=\"B₁\"\n                            VerticalAlignment=\"Center\" />\n                        <ui:NumberBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"12,0,0,4\"\n                            Value=\"{Binding B1}\"/>\n                        \n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Text=\"B₂\"\n                            VerticalAlignment=\"Center\" />\n                        <ui:NumberBox\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Value=\"{Binding B2}\"/>\n                        \n                        <TextBlock\n                            Grid.Row=\"2\"\n                            Grid.Column=\"0\"\n                            Text=\"S₁\"\n                            VerticalAlignment=\"Center\" />\n                        <ui:NumberBox\n                            Grid.Row=\"2\"\n                            Grid.Column=\"1\"\n                            Value=\"{Binding S1}\"/>\n                        \n                        <TextBlock\n                            Grid.Row=\"3\"\n                            Grid.Column=\"0\"\n                            Text=\"S₂\"\n                            VerticalAlignment=\"Center\" />\n                        <ui:NumberBox\n                            Grid.Row=\"3\"\n                            Grid.Column=\"1\"\n                            Margin=\"12,4,0,0\"\n                            Value=\"{Binding S2}\"/>\n                    </Grid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/FreeUCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<FreeUCard>]\npublic class FreeUCard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/ImageFolderCard.axaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:dbModels=\"clr-namespace:StabilityMatrix.Core.Models.Database;assembly=StabilityMatrix.Core\"\n    xmlns:input=\"using:FluentAvalonia.UI.Input\"\n    xmlns:labs=\"clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:scroll=\"clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"vmInference:ImageFolderCardViewModel\">\n\n    <Design.PreviewWith>\n        <Panel Width=\"600\" Height=\"800\">\n            <Grid MaxWidth=\"500\" MaxHeight=\"700\">\n                <controls:ImageFolderCard DataContext=\"{x:Static mocks:DesignData.ImageFolderCardViewModel}\" />\n            </Grid>\n        </Panel>\n    </Design.PreviewWith>\n\n    <converters:FileUriConverter x:Key=\"FileUriConverter\" />\n\n    <ControlTheme x:Key=\"{x:Type controls:ImageFolderCard}\" TargetType=\"controls:ImageFolderCard\">\n        <Setter Property=\"Background\" Value=\"Transparent\" />\n        <!--  Set Defaults  -->\n        <Setter Property=\"CornerRadius\" Value=\"8\" />\n        <Setter Property=\"Focusable\" Value=\"True\" />\n        <!--  Template  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card\n                    Padding=\"8\"\n                    HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\"\n                    VerticalAlignment=\"{TemplateBinding VerticalAlignment}\"\n                    HorizontalContentAlignment=\"{TemplateBinding HorizontalAlignment}\"\n                    VerticalContentAlignment=\"{TemplateBinding VerticalAlignment}\">\n\n                    <!--<controls:Card.Styles>\n                        <Style Selector=\"ItemsRepeater\">\n                            <Setter Property=\"animations:ItemsRepeaterArrangeAnimation.EnableItemsArrangeAnimation\" Value=\"True\"/>\n                        </Style>\n                    </controls:Card.Styles>-->\n\n                    <Grid RowDefinitions=\"Auto,*\">\n                        <TextBox\n                            x:Name=\"SearchBox\"\n                            Margin=\"6,4,6,8\"\n                            HorizontalAlignment=\"Stretch\"\n                            VerticalAlignment=\"Top\"\n                            Classes=\"search\"\n                            Text=\"{Binding SearchQuery}\"\n                            Watermark=\"{x:Static lang:Resources.Label_SearchEllipsis}\" />\n\n                        <scroll:BetterScrollViewer\n                            Grid.Row=\"1\"\n                            BringIntoViewOnFocusChange=\"False\"\n                            HorizontalScrollBarVisibility=\"Disabled\"\n                            IsScrollInertiaEnabled=\"True\"\n                            VerticalSnapPointsType=\"Mandatory\">\n                            <ItemsRepeater\n                                x:Name=\"ImageRepeater\"\n                                Name=\"ImageRepeater\"\n                                HorizontalAlignment=\"Center\"\n                                VerticalAlignment=\"Stretch\"\n                                ItemsSource=\"{Binding LocalImages}\"\n                                VerticalCacheLength=\"8\">\n\n                                <ItemsRepeater.Resources>\n                                    <input:StandardUICommand x:Key=\"ImageClickCommand\" Command=\"{Binding ImageClickCommand}\" />\n                                    <input:StandardUICommand\n                                        x:Key=\"ImageDeleteCommand\"\n                                        Command=\"{Binding ImageDeleteCommand}\"\n                                        IconSource=\"Delete\"\n                                        Label=\"{x:Static lang:Resources.Action_Delete}\" />\n                                    <input:StandardUICommand\n                                        x:Key=\"ImageCopyCommand\"\n                                        Command=\"{Binding ImageCopyCommand}\"\n                                        IconSource=\"Image\"\n                                        Label=\"Image\" />\n                                    <input:StandardUICommand\n                                        x:Key=\"ImageOpenCommand\"\n                                        Command=\"{Binding ImageOpenCommand}\"\n                                        IconSource=\"Folder\"\n                                        Label=\"{OnPlatform Default={x:Static lang:Resources.Action_OpenInExplorer},\n                                                           macOS={x:Static lang:Resources.Action_OpenInFinder}}\" />\n                                    <input:StandardUICommand\n                                        x:Key=\"ImageExportPngWithMetadataCommand\"\n                                        Command=\"{Binding ImageExportPngWithMetadataCommand}\"\n                                        Label=\"Png with Metadata\" />\n                                    <input:StandardUICommand\n                                        x:Key=\"ImageExportPngCommand\"\n                                        Command=\"{Binding ImageExportPngCommand}\"\n                                        Label=\"Png\" />\n                                    <input:StandardUICommand\n                                        x:Key=\"ImageExportJpegCommand\"\n                                        Command=\"{Binding ImageExportJpegCommand}\"\n                                        Label=\"Jpeg\" />\n                                    <input:StandardUICommand\n                                        x:Key=\"ImageExportWebpCommand\"\n                                        Command=\"{Binding ImageExportWebpCommand}\"\n                                        Label=\"Webp\" />\n                                    <input:StandardUICommand\n                                        x:Key=\"CopySeedCommand\"\n                                        Command=\"{Binding CopySeedToClipboardCommand}\"\n                                        Label=\"{x:Static lang:Resources.Label_Seed}\">\n                                        <input:StandardUICommand.IconSource>\n                                            <controls:FASymbolIconSource Symbol=\"fa-solid fa-dice-d20\" />\n                                        </input:StandardUICommand.IconSource>\n                                    </input:StandardUICommand>\n                                    <input:StandardUICommand\n                                        x:Key=\"CopyPromptCommand\"\n                                        Command=\"{Binding CopyPromptToClipboardCommand}\"\n                                        Label=\"{x:Static lang:Resources.Label_Prompt}\">\n                                        <input:StandardUICommand.IconSource>\n                                            <controls:FASymbolIconSource Symbol=\"fa-solid fa-comments\" />\n                                        </input:StandardUICommand.IconSource>\n                                    </input:StandardUICommand>\n                                    <input:StandardUICommand\n                                        x:Key=\"CopyNegativePromptCommand\"\n                                        Command=\"{Binding CopyNegativePromptToClipboardCommand}\"\n                                        Label=\"{x:Static lang:Resources.Label_NegativePrompt}\">\n                                        <input:StandardUICommand.IconSource>\n                                            <controls:FASymbolIconSource Symbol=\"fa-solid fa-comment-slash\" />\n                                        </input:StandardUICommand.IconSource>\n                                    </input:StandardUICommand>\n                                    <input:StandardUICommand\n                                        x:Key=\"CopyModelNameCommand\"\n                                        Command=\"{Binding CopyModelNameToClipboardCommand}\"\n                                        Label=\"Model Name\">\n                                        <input:StandardUICommand.IconSource>\n                                            <controls:FASymbolIconSource Symbol=\"fa-solid fa-font\" />\n                                        </input:StandardUICommand.IconSource>\n                                    </input:StandardUICommand>\n                                    <input:StandardUICommand\n                                        x:Key=\"CopyModelHashCommand\"\n                                        Command=\"{Binding CopyModelHashToClipboardCommand}\"\n                                        Label=\"Model Hash\">\n                                        <input:StandardUICommand.IconSource>\n                                            <controls:FASymbolIconSource Symbol=\"fa-solid fa-hashtag\" />\n                                        </input:StandardUICommand.IconSource>\n                                    </input:StandardUICommand>\n                                </ItemsRepeater.Resources>\n\n                                <ItemsRepeater.Layout>\n                                    <!--<WrapLayout HorizontalSpacing=\"2\" VerticalSpacing=\"2\" />-->\n                                    <UniformGridLayout\n                                        MinColumnSpacing=\"2\"\n                                        MinRowSpacing=\"2\"\n                                        Orientation=\"Horizontal\" />\n                                </ItemsRepeater.Layout>\n                                <ItemsRepeater.ItemTemplate>\n                                    <DataTemplate DataType=\"{x:Type dbModels:LocalImageFile}\">\n                                        <Button\n                                            Padding=\"4\"\n                                            Classes=\"transparent-full\"\n                                            Command=\"{StaticResource ImageClickCommand}\"\n                                            CommandParameter=\"{Binding $self.DataContext}\"\n                                            CornerRadius=\"8\"\n                                            HotKey=\"{x:Null}\"\n                                            ToolTip.Placement=\"LeftEdgeAlignedTop\">\n\n                                            <Interaction.Behaviors>\n                                                <BehaviorCollection>\n                                                    <ContextDragBehavior HorizontalDragThreshold=\"6\" VerticalDragThreshold=\"6\" />\n                                                </BehaviorCollection>\n                                            </Interaction.Behaviors>\n\n                                            <Button.ContextFlyout>\n                                                <ui:FAMenuFlyout>\n                                                    <ui:MenuFlyoutItem\n                                                        Command=\"{StaticResource ImageDeleteCommand}\"\n                                                        CommandParameter=\"{Binding $self.DataContext}\"\n                                                        HotKey=\"{x:Null}\" />\n                                                    <ui:MenuFlyoutSubItem IconSource=\"Copy\" Text=\"Copy\">\n                                                        <ui:MenuFlyoutItem\n                                                            Command=\"{StaticResource ImageCopyCommand}\"\n                                                            CommandParameter=\"{Binding $self.DataContext}\"\n                                                            HotKey=\"{x:Null}\" />\n                                                        <ui:MenuFlyoutItem\n                                                            Command=\"{StaticResource CopySeedCommand}\"\n                                                            CommandParameter=\"{Binding $self.DataContext}\"\n                                                            HotKey=\"{x:Null}\" />\n                                                        <ui:MenuFlyoutItem\n                                                            Command=\"{StaticResource CopyPromptCommand}\"\n                                                            CommandParameter=\"{Binding $self.DataContext}\"\n                                                            HotKey=\"{x:Null}\" />\n                                                        <ui:MenuFlyoutItem\n                                                            Command=\"{StaticResource CopyNegativePromptCommand}\"\n                                                            CommandParameter=\"{Binding $self.DataContext}\"\n                                                            HotKey=\"{x:Null}\" />\n                                                        <ui:MenuFlyoutItem\n                                                            Command=\"{StaticResource CopyModelNameCommand}\"\n                                                            CommandParameter=\"{Binding $self.DataContext}\"\n                                                            HotKey=\"{x:Null}\" />\n                                                        <ui:MenuFlyoutItem\n                                                            Command=\"{StaticResource CopyModelHashCommand}\"\n                                                            CommandParameter=\"{Binding $self.DataContext}\"\n                                                            HotKey=\"{x:Null}\" />\n                                                    </ui:MenuFlyoutSubItem>\n\n\n                                                    <ui:MenuFlyoutSeparator />\n\n                                                    <ui:MenuFlyoutItem\n                                                        Command=\"{StaticResource ImageOpenCommand}\"\n                                                        CommandParameter=\"{Binding $self.DataContext}\"\n                                                        HotKey=\"{x:Null}\" />\n                                                    <ui:MenuFlyoutSubItem IconSource=\"Share\" Text=\"Export\">\n                                                        <ui:MenuFlyoutItem\n                                                            Command=\"{StaticResource ImageExportPngWithMetadataCommand}\"\n                                                            CommandParameter=\"{Binding $self.DataContext}\"\n                                                            HotKey=\"{x:Null}\" />\n                                                        <ui:MenuFlyoutItem\n                                                            Command=\"{StaticResource ImageExportPngCommand}\"\n                                                            CommandParameter=\"{Binding $self.DataContext}\"\n                                                            HotKey=\"{x:Null}\" />\n                                                        <ui:MenuFlyoutItem\n                                                            Command=\"{StaticResource ImageExportJpegCommand}\"\n                                                            CommandParameter=\"{Binding $self.DataContext}\"\n                                                            HotKey=\"{x:Null}\" />\n                                                        <ui:MenuFlyoutItem\n                                                            Command=\"{StaticResource ImageExportWebpCommand}\"\n                                                            CommandParameter=\"{Binding $self.DataContext}\"\n                                                            HotKey=\"{x:Null}\" />\n                                                    </ui:MenuFlyoutSubItem>\n                                                </ui:FAMenuFlyout>\n                                            </Button.ContextFlyout>\n\n                                            <ToolTip.Tip>\n                                                <StackPanel Spacing=\"4\">\n                                                    <StackPanel.Styles>\n                                                        <Style Selector=\"StackPanel &gt; TextBlock\">\n                                                            <Setter Property=\"Theme\" Value=\"{DynamicResource BodyTextBlockStyle}\" />\n                                                        </Style>\n                                                        <Style Selector=\"SelectableTextBlock\">\n                                                            <Setter Property=\"FontSize\" Value=\"13\" />\n                                                            <Setter Property=\"FontFamily\" Value=\"Cascadia Code,Consolas,Menlo,Monospace\" />\n                                                        </Style>\n                                                        <Style Selector=\"controls|Card\">\n                                                            <Setter Property=\"Padding\" Value=\"4,4\" />\n                                                        </Style>\n                                                    </StackPanel.Styles>\n\n                                                    <TextBlock Text=\"{Binding FileName}\" Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n\n                                                    <controls:Card>\n                                                        <SelectableTextBlock Text=\"{Binding GenerationParameters.PositivePrompt, FallbackValue=''}\" />\n                                                    </controls:Card>\n\n                                                    <controls:Card>\n                                                        <SelectableTextBlock Text=\"{Binding GenerationParameters.NegativePrompt, FallbackValue=''}\" />\n                                                    </controls:Card>\n\n                                                    <TextBlock Text=\"{Binding GenerationParameters.ModelName, FallbackValue=''}\" />\n                                                    <TextBlock Text=\"{Binding GenerationParameters.Sampler, FallbackValue=''}\" />\n                                                    <TextBlock Text=\"{Binding GenerationParameters.Seed, FallbackValue=''}\" />\n                                                </StackPanel>\n                                            </ToolTip.Tip>\n\n                                            <Border ClipToBounds=\"True\" CornerRadius=\"8\">\n                                                <Grid RowDefinitions=\"*,Auto\">\n                                                    <vendorLabs:BetterAsyncImage\n                                                        Width=\"{Binding $parent[ItemsRepeater].((vmInference:ImageFolderCardViewModel)DataContext).ImageSize.Width}\"\n                                                        Height=\"{Binding $parent[ItemsRepeater].((vmInference:ImageFolderCardViewModel)DataContext).ImageSize.Height}\"\n                                                        Source=\"{Binding AbsolutePath, Converter={StaticResource FileUriConverter}}\"\n                                                        Stretch=\"UniformToFill\" />\n\n                                                    <Border\n                                                        Grid.Row=\"1\"\n                                                        Width=\"{Binding $parent[ItemsRepeater].((vmInference:ImageFolderCardViewModel)DataContext).ImageSize.Width}\"\n                                                        MinHeight=\"20\"\n                                                        Padding=\"4,0,0,0\"\n                                                        VerticalAlignment=\"Bottom\"\n                                                        Classes=\"theme-dark\"\n                                                        Opacity=\"0.9\">\n                                                        <TextBlock\n                                                            VerticalAlignment=\"Center\"\n                                                            FontSize=\"12\"\n                                                            Text=\"{Binding FileNameWithoutExtension}\"\n                                                            TextAlignment=\"Center\"\n                                                            TextTrimming=\"CharacterEllipsis\" />\n                                                    </Border>\n                                                </Grid>\n                                            </Border>\n                                        </Button>\n                                    </DataTemplate>\n                                </ItemsRepeater.ItemTemplate>\n                            </ItemsRepeater>\n                        </scroll:BetterScrollViewer>\n                    </Grid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </ControlTheme>\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/ImageFolderCard.axaml.cs",
    "content": "﻿using Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Core.Models.Settings;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<ImageFolderCard>]\npublic class ImageFolderCard : DropTargetTemplatedControlBase\n{\n    private ItemsRepeater? imageRepeater;\n\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        imageRepeater = e.NameScope.Find<ItemsRepeater>(\"ImageRepeater\");\n        base.OnApplyTemplate(e);\n    }\n\n    /// <inheritdoc />\n    protected override void DropHandler(object? sender, DragEventArgs e)\n    {\n        base.DropHandler(sender, e);\n        e.Handled = true;\n    }\n\n    /// <inheritdoc />\n    protected override void DragOverHandler(object? sender, DragEventArgs e)\n    {\n        base.DragOverHandler(sender, e);\n        e.Handled = true;\n    }\n\n    protected override void OnPointerWheelChanged(PointerWheelEventArgs e)\n    {\n        if (e.KeyModifiers != KeyModifiers.Control)\n            return;\n        if (DataContext is not ImageFolderCardViewModel vm)\n            return;\n\n        if (e.Delta.Y > 0)\n        {\n            if (vm.ImageSize.Height >= 500)\n                return;\n            vm.ImageSize += new Size(15, 19);\n        }\n        else\n        {\n            if (vm.ImageSize.Height <= 200)\n                return;\n            vm.ImageSize -= new Size(15, 19);\n        }\n\n        imageRepeater?.InvalidateArrange();\n        imageRepeater?.InvalidateMeasure();\n\n        e.Handled = true;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/ImageGalleryCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:labs=\"clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n    x:DataType=\"vmInference:ImageGalleryCardViewModel\">\n\n    <Design.PreviewWith>\n        <Grid Width=\"600\" Height=\"800\">\n            <controls:ImageGalleryCard DataContext=\"{x:Static mocks:DesignData.ImageGalleryCardViewModel}\" />\n        </Grid>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|ImageGalleryCard\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card\n                    Padding=\"12,12,12,8\"\n                    HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\"\n                    VerticalAlignment=\"{TemplateBinding VerticalAlignment}\"\n                    HorizontalContentAlignment=\"{TemplateBinding HorizontalAlignment}\"\n                    VerticalContentAlignment=\"{TemplateBinding VerticalAlignment}\">\n\n                    <Grid RowDefinitions=\"*,Auto\">\n                        \n                        <Grid.Styles>\n                            <Style Selector=\"ListBox /template/ VirtualizingStackPanel\">\n                                <Setter Property=\"Orientation\" Value=\"Horizontal\" />\n                            </Style>\n                        </Grid.Styles>\n                        \n                        <!--  Main image view  -->\n                        <Border\n                            Classes=\"theme-dark\"\n                            VerticalAlignment=\"Stretch\"\n                            CornerRadius=\"4\"\n                            Margin=\"0,0,0,4\">\n                            <Grid x:Name=\"MainGrid\">\n                                <Grid.Styles>\n                                    <Style Selector=\"controls|AdvancedImageBox\">\n                                        <!--  ReSharper disable once Xaml.BindingWithContextNotResolved  -->\n                                        <Setter Property=\"IsPixelGridEnabled\" Value=\"{ReflectionBinding ElementName=MainGrid, Path=DataContext.IsPixelGridEnabled, FallbackValue=True}\" />\n                                    </Style>\n                                </Grid.Styles>\n\n                                <controls:FrameCarousel\n                                    x:Name=\"PART_ImageCarousel\"\n                                    CornerRadius=\"4\"\n                                    IsVisible=\"{Binding !IsPreviewOverlayEnabled}\"\n                                    ItemsSource=\"{Binding ImageSources}\"\n                                    SelectedIndex=\"{Binding SelectedImageIndex}\"\n                                    SelectedItem=\"{Binding SelectedImage}\"\n                                    SourcePageType=\"controls:AdvancedImageBoxView\" />\n\n                                <Border\n                                    ClipToBounds=\"True\"\n                                    CornerRadius=\"4\"\n                                    IsVisible=\"{Binding IsPreviewOverlayEnabled}\">\n                                    <Image\n                                        Source=\"{Binding PreviewImage}\"\n                                        Stretch=\"Uniform\"\n                                        StretchDirection=\"Both\" />\n                                </Border>\n\n                                <!--  Left button  -->\n                                <Border\n                                    Grid.Column=\"0\"\n                                    Margin=\"4\"\n                                    IsVisible=\"{Binding HasMultipleImages}\"\n                                    HorizontalAlignment=\"Left\"\n                                    VerticalAlignment=\"Center\">\n                                    <Button\n                                        Padding=\"10,20\"\n                                        Classes=\"transparent\"\n                                        Command=\"{Binding #PART_ImageCarousel.Previous}\"\n                                        IsEnabled=\"{Binding CanNavigateBack}\">\n                                        <Path Data=\"M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z\" Fill=\"{DynamicResource ButtonForeground}\" />\n                                    </Button>\n                                </Border>\n\n                                <!--  Right button  -->\n                                <Border\n                                    Margin=\"4\"\n                                    IsVisible=\"{Binding HasMultipleImages}\"\n                                    HorizontalAlignment=\"Right\"\n                                    VerticalAlignment=\"Center\">\n                                    <Button\n                                        Padding=\"10,20\"\n                                        Classes=\"transparent\"\n                                        Command=\"{Binding #PART_ImageCarousel.Next}\"\n                                        IsEnabled=\"{Binding CanNavigateForward}\">\n                                        <Path Data=\"M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z\" Fill=\"{DynamicResource ButtonForeground}\" />\n                                    </Button>\n                                </Border>\n\n                            </Grid>\n                        </Border>\n\n                        <!--  Thumbnails  -->\n                        <ListBox\n                            Grid.Row=\"1\"\n                            HorizontalAlignment=\"Center\"\n                            VerticalAlignment=\"Stretch\"\n                            MinHeight=\"65\"\n                            ItemsSource=\"{Binding ImageSources}\"\n                            SelectedItem=\"{Binding SelectedImage}\"\n                            SelectionMode=\"AlwaysSelected\">\n                            <ListBox.Styles>\n                                <Style Selector=\"ListBox /template/ VirtualizingStackPanel\">\n                                    <Setter Property=\"Orientation\" Value=\"Horizontal\" />\n                                </Style>\n                            </ListBox.Styles>\n                            <ListBox.ItemTemplate>\n                                <DataTemplate DataType=\"{x:Type models:ImageSource}\">\n                                    <vendorLabs:BetterAsyncImage\n                                        MaxHeight=\"65\"\n                                        CornerRadius=\"4\"\n                                        Source=\"{Binding Uri}\"\n                                        Stretch=\"Uniform\" />\n                                </DataTemplate>\n                            </ListBox.ItemTemplate>\n                        </ListBox>\n                    </Grid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/ImageGalleryCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<ImageGalleryCard>]\npublic class ImageGalleryCard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/LayerDiffuseCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    x:DataType=\"vmInference:LayerDiffuseCardViewModel\">\n    <Design.PreviewWith>\n        <Panel Width=\"400\" Height=\"200\">\n            <StackPanel Width=\"300\" VerticalAlignment=\"Center\">\n                <controls:LayerDiffuseCard DataContext=\"{x:Static mocks:DesignData.LayerDiffuseCardViewModel}\"/>\n            </StackPanel>\n        </Panel>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|LayerDiffuseCard\">\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"12\">\n                    <sg:SpacedGrid\n                        ColumnDefinitions=\"Auto,*\"\n                        ColumnSpacing=\"8\"\n                        RowDefinitions=\"*,*,*,*\"\n                        RowSpacing=\"0\">\n                        <!--  Mode Selection  -->\n                        <TextBlock\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Mode\"\n                            TextAlignment=\"Left\" />\n\n                        <ui:FAComboBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            HorizontalAlignment=\"Stretch\"\n                            DisplayMemberBinding=\"{Binding Converter={x:Static converters:EnumAttributeConverters.DisplayName}}\"\n                            ItemsSource=\"{Binding AvailableModes}\"\n                            SelectedItem=\"{Binding SelectedMode}\" />\n                    </sg:SpacedGrid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/LayerDiffuseCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<LayerDiffuseCard>]\npublic class LayerDiffuseCard : TemplatedControlBase;\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:fluent=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:inference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    x:DataType=\"inference:ModelCardViewModel\">\n    <Design.PreviewWith>\n        <Panel Width=\"400\" Height=\"400\">\n            <StackPanel\n                Width=\"400\"\n                VerticalAlignment=\"Center\"\n                Spacing=\"32\">\n                <controls:ModelCard DataContext=\"{x:Static mocks:DesignData.ModelCardViewModel}\" />\n                <controls:ModelCard DataContext=\"{x:Static mocks:DesignData.ImgToVidModelCardViewModel}\" />\n            </StackPanel>\n        </Panel>\n    </Design.PreviewWith>\n\n    <Styles.Resources>\n        <fluent:SymbolIconSource\n            x:Key=\"IconCube\"\n            IconVariant=\"Filled\"\n            Symbol=\"Cube\" />\n        <fluent:SymbolIconSource\n            x:Key=\"IconQuestionCircle\"\n            IconVariant=\"Filled\"\n            Symbol=\"QuestionCircle\" />\n        <fluent:SymbolIconSource\n            x:Key=\"IconTableCellEdit\"\n            IconVariant=\"Filled\"\n            Symbol=\"TableCellEdit\" />\n    </Styles.Resources>\n\n    <Style Selector=\"controls|ModelCard\">\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"12\">\n                    <sg:SpacedGrid\n                        ColumnDefinitions=\"Auto,*,Auto\"\n                        ColumnSpacing=\"8\"\n                        RowDefinitions=\"*,*,*,*,*,*,*,*,*,*,*,*,*\"\n                        RowSpacing=\"0\">\n                        <!--  Model  -->\n                        <TextBlock\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"{x:Static lang:Resources.Label_Model}\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterComboBox\n                            Name=\"PART_ModelComboBox\"\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding !IsStandaloneModelLoader}\"\n                            ItemsSource=\"{Binding ClientManager.Models}\"\n                            SelectedItem=\"{Binding SelectedModel}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                        <controls:BetterComboBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsStandaloneModelLoader}\"\n                            ItemsSource=\"{Binding ClientManager.UnetModels}\"\n                            SelectedItem=\"{Binding SelectedUnetModel}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                        <Button\n                            Grid.Row=\"0\"\n                            Grid.Column=\"2\"\n                            Margin=\"0,0,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            VerticalAlignment=\"Stretch\">\n                            <ui:SymbolIcon FontSize=\"16\" Symbol=\"Setting\" />\n                            <Button.Flyout>\n                                <ui:FAMenuFlyout Placement=\"BottomEdgeAlignedLeft\">\n                                    <ui:ToggleMenuFlyoutItem\n                                        IconSource=\"{StaticResource IconCube}\"\n                                        IsChecked=\"{Binding IsRefinerSelectionEnabled}\"\n                                        IsVisible=\"{Binding ShowRefinerOption}\"\n                                        Text=\"{x:Static lang:Resources.Label_Refiner}\" />\n                                    <ui:ToggleMenuFlyoutItem\n                                        IconSource=\"{StaticResource IconCube}\"\n                                        IsChecked=\"{Binding IsVaeSelectionEnabled}\"\n                                        Text=\"{x:Static lang:Resources.Label_VAE}\" />\n                                    <ui:ToggleMenuFlyoutItem\n                                        IconSource=\"{StaticResource IconCube}\"\n                                        IsChecked=\"{Binding IsExtraNetworksEnabled}\"\n                                        Text=\"{x:Static lang:Resources.Label_ExtraNetworks}\" />\n                                    <ui:ToggleMenuFlyoutItem\n                                        IconSource=\"{StaticResource IconTableCellEdit}\"\n                                        IsChecked=\"{Binding IsClipSkipEnabled}\"\n                                        Text=\"{x:Static lang:Resources.Label_CLIPSkip}\" />\n                                    <ui:ToggleMenuFlyoutItem\n                                        IconSource=\"{StaticResource IconTableCellEdit}\"\n                                        IsChecked=\"{Binding IsClipModelSelectionEnabled}\"\n                                        Text=\"Text Encoders\" />\n                                    <ui:ToggleMenuFlyoutItem\n                                        IconSource=\"{StaticResource IconCube}\"\n                                        IsChecked=\"{Binding IsModelLoaderSelectionEnabled}\"\n                                        IsVisible=\"{Binding EnableModelLoaderSelection}\"\n                                        Text=\"Model Loader\" />\n                                    <ui:MenuFlyoutSeparator />\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding ConfigClickCommand}\"\n                                        IconSource=\"{StaticResource IconQuestionCircle}\"\n                                        Text=\"{x:Static lang:Resources.Label_Config}\" />\n                                </ui:FAMenuFlyout>\n                            </Button.Flyout>\n                        </Button>\n\n                        <!--  Refiner  -->\n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsRefinerSelectionEnabled}\"\n                            Text=\"{x:Static lang:Resources.Label_Refiner}\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterComboBox\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsRefinerSelectionEnabled}\"\n                            ItemsSource=\"{Binding ClientManager.Models}\"\n                            SelectedItem=\"{Binding SelectedRefiner}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                        <!--  model precision  -->\n                        <TextBlock\n                            Grid.Row=\"2\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding ShowPrecisionSelection}\"\n                            Text=\"Precision\"\n                            TextAlignment=\"Left\" />\n\n                        <ui:FAComboBox\n                            Grid.Row=\"2\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsTextSearchEnabled=\"True\"\n                            IsVisible=\"{Binding ShowPrecisionSelection}\"\n                            ItemsSource=\"{Binding WeightDTypes}\"\n                            SelectedItem=\"{Binding SelectedDType}\" />\n\n                        <!--  VAE  -->\n                        <TextBlock\n                            Grid.Row=\"3\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsVaeSelectionEnabled}\"\n                            Text=\"{x:Static lang:Resources.Label_VAE}\"\n                            TextAlignment=\"Left\" />\n\n                        <ui:FAComboBox\n                            Grid.Row=\"3\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            DisplayMemberBinding=\"{Binding ShortDisplayName}\"\n                            IsTextSearchEnabled=\"True\"\n                            IsVisible=\"{Binding IsVaeSelectionEnabled}\"\n                            ItemsSource=\"{Binding ClientManager.VaeModels}\"\n                            SelectedItem=\"{Binding SelectedVae}\" />\n\n                        <!--  CLIP Skip  -->\n                        <TextBlock\n                            Grid.Row=\"4\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsClipSkipEnabled}\"\n                            Text=\"{x:Static lang:Resources.Label_CLIPSkip}\"\n                            TextAlignment=\"Left\" />\n\n                        <NumericUpDown\n                            Grid.Row=\"4\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            ClipValueToMinMax=\"True\"\n                            Increment=\"1\"\n                            IsVisible=\"{Binding IsClipSkipEnabled}\"\n                            Maximum=\"24\"\n                            Minimum=\"1\"\n                            ParsingNumberStyle=\"Integer\"\n                            Watermark=\"1\"\n                            Value=\"{Binding ClipSkip, Converter={x:Static converters:NullableDefaultNumericConverters.IntToDecimal}}\" />\n\n                        <!--  Model Loader Selection  -->\n                        <TextBlock\n                            Grid.Row=\"5\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsModelLoaderSelectionEnabled}\"\n                            Text=\"Model Loader\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterComboBox\n                            Name=\"PART_ModelLoaderComboBox\"\n                            Grid.Row=\"5\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsModelLoaderSelectionEnabled}\"\n                            ItemsSource=\"{Binding ModelLoaders}\"\n                            SelectedItem=\"{Binding SelectedModelLoader}\">\n                            <controls:BetterComboBox.Resources>\n                                <converters:EnumStringConverter x:Key=\"EnumStringConverter\" />\n                            </controls:BetterComboBox.Resources>\n                            <controls:BetterComboBox.ItemTemplate>\n                                <DataTemplate>\n                                    <TextBlock Text=\"{Binding ., Converter={StaticResource EnumStringConverter}}\" />\n                                </DataTemplate>\n                            </controls:BetterComboBox.ItemTemplate>\n                            <controls:BetterComboBox.SelectionBoxItemTemplate>\n                                <DataTemplate>\n                                    <TextBlock Text=\"{Binding ., Converter={StaticResource EnumStringConverter}}\" />\n                                </DataTemplate>\n                            </controls:BetterComboBox.SelectionBoxItemTemplate>\n                        </controls:BetterComboBox>\n\n                        <!--  Encooder Type  -->\n                        <TextBlock\n                            Grid.Row=\"6\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsClipModelSelectionEnabled}\"\n                            Text=\"Encoder Type\"\n                            TextAlignment=\"Left\" />\n                        <ui:FAComboBox\n                            Grid.Row=\"6\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsTextSearchEnabled=\"True\"\n                            IsVisible=\"{Binding IsClipModelSelectionEnabled}\"\n                            ItemsSource=\"{Binding ClipTypes}\"\n                            SelectedItem=\"{Binding SelectedClipType}\" />\n\n                        <!--  CLIP 1  -->\n                        <TextBlock\n                            Grid.Row=\"7\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsClipModelSelectionEnabled}\"\n                            Text=\"Encoder 1\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterDownloadableComboBox\n                            Grid.Row=\"7\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsClipModelSelectionEnabled}\"\n                            ItemsSource=\"{Binding ClientManager.ClipModels}\"\n                            SelectedItem=\"{Binding SelectedClip1}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                        <!--  CLIP 2  -->\n                        <TextBlock\n                            Grid.Row=\"8\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsClipModelSelectionEnabled}\"\n                            Text=\"Encoder 2\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterDownloadableComboBox\n                            Grid.Row=\"8\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsClipModelSelectionEnabled}\"\n                            ItemsSource=\"{Binding ClientManager.ClipModels}\"\n                            SelectedItem=\"{Binding SelectedClip2}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                        <!--  CLIP 3  -->\n                        <TextBlock\n                            Grid.Row=\"9\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Encoder 3\"\n                            TextAlignment=\"Left\">\n                            <TextBlock.IsVisible>\n                                <MultiBinding Converter=\"{x:Static BoolConverters.Or}\">\n                                    <Binding Path=\"IsSd3Clip\" />\n                                    <Binding Path=\"IsHiDreamClip\" />\n                                </MultiBinding>\n                            </TextBlock.IsVisible>\n                        </TextBlock>\n\n                        <controls:BetterDownloadableComboBox\n                            Grid.Row=\"9\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            ItemsSource=\"{Binding ClientManager.ClipModels}\"\n                            SelectedItem=\"{Binding SelectedClip3}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\">\n                            <controls:BetterDownloadableComboBox.IsVisible>\n                                <MultiBinding Converter=\"{x:Static BoolConverters.Or}\">\n                                    <Binding Path=\"IsSd3Clip\" />\n                                    <Binding Path=\"IsHiDreamClip\" />\n                                </MultiBinding>\n                            </controls:BetterDownloadableComboBox.IsVisible>\n                        </controls:BetterDownloadableComboBox>\n\n                        <!--  CLIP 4  -->\n                        <TextBlock\n                            Grid.Row=\"10\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsHiDreamClip}\"\n                            Text=\"Encoder 4\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterDownloadableComboBox\n                            Grid.Row=\"10\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsHiDreamClip}\"\n                            ItemsSource=\"{Binding ClientManager.ClipModels}\"\n                            SelectedItem=\"{Binding SelectedClip4}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                        <TextBlock\n                            Grid.Row=\"11\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsHiDreamClip}\"\n                            Text=\"Shift\" />\n                        <ui:NumberBox\n                            Grid.Row=\"11\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsHiDreamClip}\"\n                            SelectionHighlightColor=\"Transparent\"\n                            SimpleNumberFormat=\"F2\"\n                            SmallChange=\"0.25\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding Shift}\" />\n\n                        <!--  Extra Networks  -->\n                        <Panel\n                            Grid.Row=\"12\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"3\"\n                            IsVisible=\"{Binding IsExtraNetworksEnabled}\">\n                            <controls:StackEditableCard Margin=\"0,8,0,0\" DataContext=\"{Binding ExtraNetworksStackCardViewModel}\" />\n                        </Panel>\n\n                    </sg:SpacedGrid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<ModelCard>]\npublic class ModelCard : TemplatedControlBase;\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/NrsCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"vmInference:NrsCardViewModel\">\n    <Design.PreviewWith>\n        <controls:NrsCard DataContext=\"{x:Static mocks:DesignData.NrsCardViewModel}\" />\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|NrsCard\">\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card x:Name=\"PART_Card\">\n                    <controls:Card.Styles>\n                        <Style Selector=\"ui|NumberBox\">\n                            <Setter Property=\"Margin\" Value=\"12,4,0,4\" />\n                            <Setter Property=\"MinWidth\" Value=\"70\" />\n                            <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n                            <Setter Property=\"ValidationMode\" Value=\"InvalidInputOverwritten\" />\n                            <Setter Property=\"SimpleNumberFormat\" Value=\"F2\" />\n                            <Setter Property=\"SpinButtonPlacementMode\" Value=\"Inline\" />\n                        </Style>\n                    </controls:Card.Styles>\n                    <Grid ColumnDefinitions=\"Auto,*\" RowDefinitions=\"*,*,*,*\">\n                        <TextBlock\n                            Grid.Row=\"0\"\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Skew\" />\n                        <ui:NumberBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"12,0,0,4\"\n                            SmallChange=\"0.25\"\n                            Value=\"{Binding Skew}\" />\n\n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Stretch\" />\n                        <ui:NumberBox\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            SmallChange=\"0.25\"\n                            Value=\"{Binding Stretch}\" />\n\n                        <TextBlock\n                            Grid.Row=\"2\"\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Squash\" />\n                        <ui:NumberBox\n                            Grid.Row=\"2\"\n                            Grid.Column=\"1\"\n                            SmallChange=\"0.01\"\n                            Value=\"{Binding Squash}\" />\n\n                    </Grid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/NrsCard.axaml.cs",
    "content": "﻿using Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<NrsCard>]\npublic class NrsCard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/PlasmaNoiseCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:controls1=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:inference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    x:DataType=\"inference:PlasmaNoiseCardViewModel\">\n\n    <Design.PreviewWith>\n        <Panel Width=\"400\" Height=\"400\">\n            <StackPanel\n                Width=\"400\"\n                VerticalAlignment=\"Center\"\n                Spacing=\"32\">\n                <controls:PlasmaNoiseCard DataContext=\"{x:Static mocks:DesignData.PlasmaNoiseCardViewModel}\" />\n            </StackPanel>\n        </Panel>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|PlasmaNoiseCard\">\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"12\">\n                    <avalonia:SpacedGrid\n                        ColumnDefinitions=\"Auto,*,Auto,*\"\n                        ColumnSpacing=\"8\"\n                        RowDefinitions=\"*,*,*,*,*,*,*,*,*,*,*\"\n                        RowSpacing=\"0\">\n\n                        <!--  Noise Type Selector  -->\n                        <TextBlock\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Noise Type\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterComboBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"3\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            ItemsSource=\"{Binding NoiseTypes}\"\n                            SelectedItem=\"{Binding SelectedNoiseType}\" />\n\n                        <!--  Plasma Turbulence  -->\n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding ShowPlasmaTurbulence}\"\n                            Text=\"Turbulence\"\n                            TextAlignment=\"Left\" />\n                        <NumericUpDown\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"3\"\n                            Margin=\"0,8,0,0\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            FormatString=\"F2\"\n                            Increment=\"0.05\"\n                            IsVisible=\"{Binding ShowPlasmaTurbulence}\"\n                            Maximum=\"32.0\"\n                            Minimum=\"0.5\"\n                            Value=\"{Binding PlasmaTurbulence}\" />\n\n                        <!--  Value Clamping  -->\n                        <TextBlock\n                            Grid.Row=\"2\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Brightness Min\"\n                            TextAlignment=\"Left\" />\n                        <controls1:NumberBox\n                            Grid.Row=\"2\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            Maximum=\"255\"\n                            Minimum=\"-1\"\n                            SimpleNumberFormat=\"F0\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Compact\"\n                            Value=\"{Binding ValueMin}\" />\n                        <TextBlock\n                            Grid.Row=\"2\"\n                            Grid.Column=\"2\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Max\"\n                            TextAlignment=\"Left\" />\n                        <controls1:NumberBox\n                            Grid.Row=\"2\"\n                            Grid.Column=\"3\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            Maximum=\"255\"\n                            Minimum=\"-1\"\n                            SimpleNumberFormat=\"F0\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Compact\"\n                            Value=\"{Binding ValueMax}\" />\n\n                        <!--  Per-Channel Toggle  -->\n                        <ToggleSwitch\n                            Grid.Row=\"3\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,16,0,0\"\n                            Content=\"Per-Channel Clamping\"\n                            IsChecked=\"{Binding IsPerChannelClampingEnabled}\"\n                            ToolTip.Tip=\"Allows you to select brightness levels for R/G/B individually\" />\n\n                        <!--  Plasma Sampler Toggle  -->\n                        <ToggleSwitch\n                            Grid.Row=\"3\"\n                            Grid.Column=\"2\"\n                            Grid.ColumnSpan=\"2\"\n                            Margin=\"0,16,0,0\"\n                            Content=\"Plasma Sampler\"\n                            IsChecked=\"{Binding IsPlasmaSamplerEnabled}\"\n                            ToolTip.Tip=\"Replaces the KSampler with a more random sampler\" />\n\n                        <Separator\n                            Grid.Row=\"4\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"4\"\n                            Margin=\"0,8\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\" />\n\n                        <!--  Red Clamping  -->\n                        <TextBlock\n                            Grid.Row=\"5\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\"\n                            Text=\"Red Brightness Min\"\n                            TextAlignment=\"Left\" />\n                        <controls1:NumberBox\n                            Grid.Row=\"5\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\"\n                            Maximum=\"255\"\n                            Minimum=\"-1\"\n                            SimpleNumberFormat=\"F0\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Compact\"\n                            Value=\"{Binding RedMin}\" />\n                        <TextBlock\n                            Grid.Row=\"5\"\n                            Grid.Column=\"2\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\"\n                            Text=\"Max\"\n                            TextAlignment=\"Left\" />\n                        <controls1:NumberBox\n                            Grid.Row=\"5\"\n                            Grid.Column=\"3\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\"\n                            Maximum=\"255\"\n                            Minimum=\"-1\"\n                            SimpleNumberFormat=\"F0\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Compact\"\n                            Value=\"{Binding RedMax}\" />\n\n                        <!--  Green Clamping  -->\n                        <TextBlock\n                            Grid.Row=\"6\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\"\n                            Text=\"Green Brightness Min\"\n                            TextAlignment=\"Left\" />\n                        <controls1:NumberBox\n                            Grid.Row=\"6\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\"\n                            Maximum=\"255\"\n                            Minimum=\"-1\"\n                            SimpleNumberFormat=\"F0\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Compact\"\n                            Value=\"{Binding GreenMin}\" />\n                        <TextBlock\n                            Grid.Row=\"6\"\n                            Grid.Column=\"2\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\"\n                            Text=\"Max\"\n                            TextAlignment=\"Left\" />\n                        <controls1:NumberBox\n                            Grid.Row=\"6\"\n                            Grid.Column=\"3\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\"\n                            Maximum=\"255\"\n                            Minimum=\"-1\"\n                            SimpleNumberFormat=\"F0\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Compact\"\n                            Value=\"{Binding GreenMax}\" />\n\n                        <!--  Blue Clamping  -->\n                        <TextBlock\n                            Grid.Row=\"7\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\"\n                            Text=\"Blue Brightness Min\"\n                            TextAlignment=\"Left\" />\n                        <controls1:NumberBox\n                            Grid.Row=\"7\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\"\n                            Maximum=\"255\"\n                            Minimum=\"-1\"\n                            SimpleNumberFormat=\"F0\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Compact\"\n                            Value=\"{Binding BlueMin}\" />\n                        <TextBlock\n                            Grid.Row=\"7\"\n                            Grid.Column=\"2\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\"\n                            Text=\"Max\"\n                            TextAlignment=\"Left\" />\n                        <controls1:NumberBox\n                            Grid.Row=\"7\"\n                            Grid.Column=\"3\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsPerChannelClampingEnabled}\"\n                            Maximum=\"255\"\n                            Minimum=\"-1\"\n                            SimpleNumberFormat=\"F0\"\n                            SmallChange=\"1\"\n                            SpinButtonPlacementMode=\"Compact\"\n                            Value=\"{Binding BlueMax}\" />\n\n                        <Separator\n                            Grid.Row=\"8\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"4\"\n                            Margin=\"0,16,0,8\"\n                            IsVisible=\"{Binding IsPlasmaSamplerEnabled}\" />\n\n                        <TextBlock\n                            Grid.Row=\"9\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsPlasmaSamplerEnabled}\"\n                            Text=\"Latent Noise\"\n                            TextAlignment=\"Left\"\n                            ToolTip.Tip=\"How strong should the built in noise be. 0 means no noise is used, and 1 means it's adding all the noise\" />\n                        <controls1:NumberBox\n                            Grid.Row=\"9\"\n                            Grid.Column=\"1\"\n                            Grid.ColumnSpan=\"3\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsPlasmaSamplerEnabled}\"\n                            Maximum=\"1\"\n                            Minimum=\"0\"\n                            SimpleNumberFormat=\"F2\"\n                            SmallChange=\"0.01\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            ToolTip.Tip=\"How strong should the built in noise be. 0 means no noise is used, and 1 means it's adding all the noise\"\n                            Value=\"{Binding PlasmaSamplerLatentNoise}\" />\n                    </avalonia:SpacedGrid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/PlasmaNoiseCard.axaml.cs",
    "content": "﻿using Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<PlasmaNoiseCard>]\npublic partial class PlasmaNoiseCard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:avaloniaEdit=\"https://github.com/avaloniaui/avaloniaedit\"\n    xmlns:behaviors=\"clr-namespace:StabilityMatrix.Avalonia.Behaviors\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:fluent=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:i=\"clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity\"\n    xmlns:iconExt=\"clr-namespace:FluentIcons.Avalonia.Fluent.MarkupExtensions;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:icons=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"vmInference:PromptCardViewModel\">\n    <Design.PreviewWith>\n        <Grid Width=\"460\" Height=\"600\">\n            <controls:PromptCard DataContext=\"{x:Static mocks:DesignData.PromptCardViewModel}\" />\n        </Grid>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|PromptCard\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card\n                    Padding=\"12\"\n                    HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\"\n                    VerticalAlignment=\"{TemplateBinding VerticalAlignment}\"\n                    VerticalContentAlignment=\"Stretch\">\n                    <controls:Card.Styles>\n                        <Style Selector=\"avaloniaEdit|TextEditor\">\n                            <Setter Property=\"Margin\" Value=\"0,8,0,8\" />\n                            <Setter Property=\"CornerRadius\" Value=\"8\" />\n                            <Setter Property=\"BorderThickness\" Value=\"4\" />\n                            <Setter Property=\"LineNumbersForeground\" Value=\"DarkSlateGray\" />\n                            <Setter Property=\"ShowLineNumbers\" Value=\"True\" />\n                            <Setter Property=\"WordWrap\" Value=\"True\" />\n                            <Setter Property=\"VerticalScrollBarVisibility\" Value=\"Auto\" />\n                            <Setter Property=\"ContextFlyout\" Value=\"{StaticResource EditorContextFlyout}\" />\n                        </Style>\n                    </controls:Card.Styles>\n\n                    <Grid Name=\"PART_RootGrid\" RowDefinitions=\"*,16,*,16,Auto\">\n                        <!--  Prompt  -->\n                        <Grid ColumnDefinitions=\"*,Auto\" RowDefinitions=\"Auto,*\">\n                            <StackPanel\n                                Grid.Row=\"0\"\n                                Grid.Column=\"0\"\n                                Grid.ColumnSpan=\"2\"\n                                Margin=\"4,0,4,4\"\n                                VerticalAlignment=\"Top\"\n                                Orientation=\"Horizontal\">\n                                <TextBlock FontSize=\"14\" Text=\"Prompt\" />\n                                <icons:Icon\n                                    Margin=\"8,0\"\n                                    FontSize=\"10\"\n                                    Value=\"fa-solid fa-caret-up\" />\n                            </StackPanel>\n\n                            <StackPanel\n                                Grid.Row=\"0\"\n                                Grid.Column=\"1\"\n                                Margin=\"0,-2,0,4\"\n                                HorizontalAlignment=\"Right\"\n                                VerticalAlignment=\"Top\"\n                                Orientation=\"Horizontal\"\n                                Spacing=\"0\">\n                                <StackPanel.Styles>\n                                    <!--  Set Button Stuff  -->\n                                    <Style Selector=\"StackPanel &gt; Button\">\n                                        <Setter Property=\"Padding\" Value=\"6,4\" />\n                                        <Setter Property=\"Height\" Value=\"24\" />\n                                        <Setter Property=\"VerticalAlignment\" Value=\"Top\" />\n                                    </Style>\n                                </StackPanel.Styles>\n                                <Button\n                                    x:Name=\"PART_HelpButton\"\n                                    VerticalAlignment=\"Top\"\n                                    VerticalContentAlignment=\"Top\"\n                                    icons:Attached.Icon=\"fa-solid fa-question\"\n                                    Classes=\"transparent-full\"\n                                    Command=\"{Binding ShowHelpDialogCommand}\" />\n                                <ui:TeachingTip\n                                    Title=\"{x:Static lang:Resources.TeachingTip_InferencePromptHelpButton}\"\n                                    IsOpen=\"{Binding IsHelpButtonTeachingTipOpen, Mode=TwoWay}\"\n                                    Target=\"{Binding #PART_HelpButton}\" />\n                                <Button\n                                    x:Name=\"PART_AmplifyButton\"\n                                    Padding=\"6,2,6,1\"\n                                    VerticalAlignment=\"Top\"\n                                    VerticalContentAlignment=\"Top\"\n                                    Classes=\"transparent-full\"\n                                    Content=\"{iconExt:FluentIcon Icon=SlideTextSparkle,\n                                                                 IconVariant=Regular,\n                                                                 IconSize=Size20,\n                                                                 FontSize=19}\"\n                                    ToolTip.Tip=\"Prompt Amplifier\">\n                                    <Button.Flyout>\n                                        <controls:BetterFlyout>\n                                            <Grid ColumnDefinitions=\"*, Auto\" RowDefinitions=\"Auto, Auto, Auto, Auto, Auto, Auto, Auto, Auto\">\n                                                <TextBlock\n                                                    Grid.Row=\"0\"\n                                                    Grid.Column=\"0\"\n                                                    Grid.ColumnSpan=\"2\"\n                                                    Margin=\"0,4,0,8\"\n                                                    FontSize=\"24\"\n                                                    FontWeight=\"Light\"\n                                                    Text=\"Prompt Amplifier\"\n                                                    TextAlignment=\"Center\" />\n                                                <Button\n                                                    Grid.Row=\"0\"\n                                                    Grid.Column=\"1\"\n                                                    Margin=\"16,0,0,0\"\n                                                    HorizontalAlignment=\"Right\"\n                                                    icons:Attached.Icon=\"fa-solid fa-circle-info\"\n                                                    Classes=\"transparent-full\"\n                                                    Command=\"{Binding ShowAmplifierDisclaimerCommand}\" />\n\n                                                <Separator\n                                                    Grid.Row=\"1\"\n                                                    Grid.Column=\"0\"\n                                                    Grid.ColumnSpan=\"2\"\n                                                    Margin=\"16,0,16,16\"\n                                                    Background=\"#777777\" />\n\n                                                <TextBlock\n                                                    Grid.Row=\"2\"\n                                                    Grid.Column=\"0\"\n                                                    Grid.ColumnSpan=\"2\"\n                                                    Margin=\"4,8,0,4\"\n                                                    FontSize=\"18\"\n                                                    FontWeight=\"Light\"\n                                                    Text=\"Model\"\n                                                    TextAlignment=\"Left\" />\n\n                                                <ToggleButton\n                                                    Grid.Row=\"3\"\n                                                    Grid.Column=\"1\"\n                                                    HorizontalAlignment=\"Center\"\n                                                    IsChecked=\"{Binding IsThinkingEnabled}\">\n                                                    <StackPanel Orientation=\"Horizontal\" Spacing=\"8\">\n                                                        <fluent:SymbolIcon Symbol=\"BrainCircuit\" />\n                                                        <TextBlock Text=\"Think\" />\n                                                    </StackPanel>\n                                                </ToggleButton>\n\n                                                <controls:BetterComboBox\n                                                    Grid.Row=\"3\"\n                                                    Grid.Column=\"0\"\n                                                    Height=\"34\"\n                                                    Margin=\"0,0,8,0\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    SelectedIndex=\"0\">\n                                                    <controls:BetterComboBox.Items>\n                                                        <ComboBoxItem Content=\"Spark\" />\n                                                    </controls:BetterComboBox.Items>\n                                                </controls:BetterComboBox>\n\n                                                <TextBlock\n                                                    Grid.Row=\"4\"\n                                                    Grid.Column=\"0\"\n                                                    Grid.ColumnSpan=\"2\"\n                                                    Margin=\"4,16,0,4\"\n                                                    FontSize=\"18\"\n                                                    FontWeight=\"Light\"\n                                                    Text=\"Feel\"\n                                                    TextAlignment=\"Left\" />\n\n                                                <StackPanel\n                                                    Grid.Row=\"5\"\n                                                    Grid.Column=\"0\"\n                                                    Grid.ColumnSpan=\"2\"\n                                                    Orientation=\"Horizontal\">\n                                                    <ToggleButton\n                                                        Width=\"100\"\n                                                        Content=\"Focused\"\n                                                        CornerRadius=\"8,0,0,8\"\n                                                        IsChecked=\"{Binding IsFocused}\" />\n                                                    <ToggleButton\n                                                        Width=\"100\"\n                                                        Content=\"Balanced\"\n                                                        CornerRadius=\"0\"\n                                                        IsChecked=\"{Binding IsBalanced}\" />\n                                                    <ToggleButton\n                                                        Width=\"100\"\n                                                        Content=\"Imaginative\"\n                                                        CornerRadius=\"0,8,8,0\"\n                                                        IsChecked=\"{Binding IsImaginative}\" />\n                                                </StackPanel>\n\n                                                <Button\n                                                    Grid.Row=\"6\"\n                                                    Grid.Column=\"0\"\n                                                    Grid.ColumnSpan=\"2\"\n                                                    Margin=\"0,24,0,12\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    Classes=\"accent\"\n                                                    Command=\"{Binding AmplifyPromptCommand}\">\n                                                    <StackPanel Orientation=\"Horizontal\" Spacing=\"4\">\n                                                        <Grid\n                                                            Width=\"24\"\n                                                            Height=\"24\"\n                                                            VerticalAlignment=\"Center\">\n                                                            <!--  Main wand icon  -->\n                                                            <icons:Icon\n                                                                x:Name=\"AmplifyIcon\"\n                                                                Classes.running=\"{Binding AmplifyPromptCommand.IsRunning}\"\n                                                                RenderTransformOrigin=\"50% 50%\"\n                                                                Value=\"fa-solid fa-wand-magic-sparkles\">\n                                                                <icons:Icon.RenderTransform>\n                                                                    <TransformGroup>\n                                                                        <RotateTransform Angle=\"0\" />\n                                                                        <ScaleTransform ScaleX=\"1\" ScaleY=\"1\" />\n                                                                    </TransformGroup>\n                                                                </icons:Icon.RenderTransform>\n                                                                <icons:Icon.Styles>\n                                                                    <Style Selector=\"icons|Icon.running\">\n                                                                        <Style.Animations>\n                                                                            <Animation IterationCount=\"Infinite\" Duration=\"0:0:1.2\">\n                                                                                <KeyFrame Cue=\"0%\">\n                                                                                    <Setter Property=\"RotateTransform.Angle\" Value=\"-15\" />\n                                                                                    <Setter Property=\"ScaleTransform.ScaleX\" Value=\"1\" />\n                                                                                    <Setter Property=\"ScaleTransform.ScaleY\" Value=\"1\" />\n                                                                                    <Setter Property=\"Foreground\" Value=\"#FFFFFFFF\" />\n                                                                                </KeyFrame>\n                                                                                <KeyFrame Cue=\"25%\">\n                                                                                    <Setter Property=\"RotateTransform.Angle\" Value=\"10\" />\n                                                                                    <Setter Property=\"ScaleTransform.ScaleX\" Value=\"1.15\" />\n                                                                                    <Setter Property=\"ScaleTransform.ScaleY\" Value=\"1.15\" />\n                                                                                    <Setter Property=\"Foreground\" Value=\"#FFFFD700\" />\n                                                                                </KeyFrame>\n                                                                                <KeyFrame Cue=\"50%\">\n                                                                                    <Setter Property=\"RotateTransform.Angle\" Value=\"15\" />\n                                                                                    <Setter Property=\"ScaleTransform.ScaleX\" Value=\"1\" />\n                                                                                    <Setter Property=\"ScaleTransform.ScaleY\" Value=\"1\" />\n                                                                                    <Setter Property=\"Foreground\" Value=\"#FFFFFFFF\" />\n                                                                                </KeyFrame>\n                                                                                <KeyFrame Cue=\"75%\">\n                                                                                    <Setter Property=\"RotateTransform.Angle\" Value=\"-5\" />\n                                                                                    <Setter Property=\"ScaleTransform.ScaleX\" Value=\"1.15\" />\n                                                                                    <Setter Property=\"ScaleTransform.ScaleY\" Value=\"1.15\" />\n                                                                                    <Setter Property=\"Foreground\" Value=\"#FF9370DB\" />\n                                                                                </KeyFrame>\n                                                                                <KeyFrame Cue=\"100%\">\n                                                                                    <Setter Property=\"RotateTransform.Angle\" Value=\"-15\" />\n                                                                                    <Setter Property=\"ScaleTransform.ScaleX\" Value=\"1\" />\n                                                                                    <Setter Property=\"ScaleTransform.ScaleY\" Value=\"1\" />\n                                                                                    <Setter Property=\"Foreground\" Value=\"#FFFFFFFF\" />\n                                                                                </KeyFrame>\n                                                                            </Animation>\n                                                                        </Style.Animations>\n                                                                    </Style>\n                                                                </icons:Icon.Styles>\n                                                            </icons:Icon>\n\n                                                            <!--  Sparkle 1  -->\n                                                            <icons:Icon\n                                                                HorizontalAlignment=\"Right\"\n                                                                VerticalAlignment=\"Top\"\n                                                                Classes.running=\"{Binding AmplifyPromptCommand.IsRunning}\"\n                                                                FontSize=\"8\"\n                                                                Foreground=\"#FFFFD700\"\n                                                                Opacity=\"0\"\n                                                                Value=\"fa-solid fa-star\">\n                                                                <icons:Icon.Styles>\n                                                                    <Style Selector=\"icons|Icon.running\">\n                                                                        <Style.Animations>\n                                                                            <Animation IterationCount=\"Infinite\" Duration=\"0:0:1.5\">\n                                                                                <KeyFrame Cue=\"0%\">\n                                                                                    <Setter Property=\"Opacity\" Value=\"0\" />\n                                                                                </KeyFrame>\n                                                                                <KeyFrame Cue=\"20%\">\n                                                                                    <Setter Property=\"Opacity\" Value=\"1\" />\n                                                                                </KeyFrame>\n                                                                                <KeyFrame Cue=\"40%\">\n                                                                                    <Setter Property=\"Opacity\" Value=\"0\" />\n                                                                                </KeyFrame>\n                                                                            </Animation>\n                                                                        </Style.Animations>\n                                                                    </Style>\n                                                                </icons:Icon.Styles>\n                                                            </icons:Icon>\n\n                                                            <!--  Sparkle 2  -->\n                                                            <icons:Icon\n                                                                HorizontalAlignment=\"Left\"\n                                                                VerticalAlignment=\"Bottom\"\n                                                                Classes.running=\"{Binding AmplifyPromptCommand.IsRunning}\"\n                                                                FontSize=\"6\"\n                                                                Foreground=\"#FF9370DB\"\n                                                                Opacity=\"0\"\n                                                                Value=\"fa-solid fa-star\">\n                                                                <icons:Icon.Styles>\n                                                                    <Style Selector=\"icons|Icon.running\">\n                                                                        <Style.Animations>\n                                                                            <Animation\n                                                                                Delay=\"0:0:0.4\"\n                                                                                IterationCount=\"Infinite\"\n                                                                                Duration=\"0:0:1.5\">\n                                                                                <KeyFrame Cue=\"0%\">\n                                                                                    <Setter Property=\"Opacity\" Value=\"0\" />\n                                                                                </KeyFrame>\n                                                                                <KeyFrame Cue=\"20%\">\n                                                                                    <Setter Property=\"Opacity\" Value=\"1\" />\n                                                                                </KeyFrame>\n                                                                                <KeyFrame Cue=\"40%\">\n                                                                                    <Setter Property=\"Opacity\" Value=\"0\" />\n                                                                                </KeyFrame>\n                                                                            </Animation>\n                                                                        </Style.Animations>\n                                                                    </Style>\n                                                                </icons:Icon.Styles>\n                                                            </icons:Icon>\n\n                                                            <!--  Sparkle 3  -->\n                                                            <icons:Icon\n                                                                Margin=\"0,0,2,1\"\n                                                                HorizontalAlignment=\"Right\"\n                                                                VerticalAlignment=\"Bottom\"\n                                                                Classes.running=\"{Binding AmplifyPromptCommand.IsRunning}\"\n                                                                FontSize=\"7\"\n                                                                Foreground=\"#FF00BFFF\"\n                                                                Opacity=\"0\"\n                                                                Value=\"fa-solid fa-star\">\n                                                                <icons:Icon.Styles>\n                                                                    <Style Selector=\"icons|Icon.running\">\n                                                                        <Style.Animations>\n                                                                            <Animation\n                                                                                Delay=\"0:0:0.8\"\n                                                                                IterationCount=\"Infinite\"\n                                                                                Duration=\"0:0:1.5\">\n                                                                                <KeyFrame Cue=\"0%\">\n                                                                                    <Setter Property=\"Opacity\" Value=\"0\" />\n                                                                                </KeyFrame>\n                                                                                <KeyFrame Cue=\"20%\">\n                                                                                    <Setter Property=\"Opacity\" Value=\"1\" />\n                                                                                </KeyFrame>\n                                                                                <KeyFrame Cue=\"40%\">\n                                                                                    <Setter Property=\"Opacity\" Value=\"0\" />\n                                                                                </KeyFrame>\n                                                                            </Animation>\n                                                                        </Style.Animations>\n                                                                    </Style>\n                                                                </icons:Icon.Styles>\n                                                            </icons:Icon>\n                                                        </Grid>\n\n                                                        <TextBlock\n                                                            Margin=\"0,-1,0,0\"\n                                                            VerticalAlignment=\"Center\"\n                                                            IsVisible=\"{Binding !AmplifyPromptCommand.IsRunning}\"\n                                                            Text=\"Amplify\" />\n                                                        <TextBlock\n                                                            Margin=\"0,-1,0,0\"\n                                                            VerticalAlignment=\"Center\"\n                                                            IsVisible=\"{Binding AmplifyPromptCommand.IsRunning}\"\n                                                            Text=\"Amplifying...\" />\n                                                    </StackPanel>\n                                                </Button>\n\n                                                <TextBlock\n                                                    Grid.Row=\"7\"\n                                                    Grid.Column=\"0\"\n                                                    Grid.ColumnSpan=\"2\"\n                                                    FontSize=\"12\"\n                                                    Foreground=\"#999999\"\n                                                    IsVisible=\"{Binding ShowLowTokenWarning}\"\n                                                    Text=\"{Binding LowTokenWarningText}\"\n                                                    TextAlignment=\"Center\" />\n                                            </Grid>\n                                        </controls:BetterFlyout>\n                                    </Button.Flyout>\n                                </Button>\n                                <ui:TeachingTip\n                                    Title=\"{x:Static lang:Resources.TeachingTip_PromptAmplifier}\"\n                                    IsOpen=\"{Binding IsPromptAmplifyTeachingTipOpen, Mode=TwoWay}\"\n                                    PreferredPlacement=\"Bottom\"\n                                    Target=\"{Binding #PART_AmplifyButton}\">\n                                    <StackPanel Orientation=\"Vertical\">\n                                        <TextBlock\n                                            FontSize=\"13\"\n                                            Foreground=\"#AAAAAA\"\n                                            Text=\"All users get 60 free amplifications per month. Supporters get 1,000+ based on their support tier.\"\n                                            TextWrapping=\"Wrap\" />\n                                        <HyperlinkButton\n                                            Margin=\"0,4,0,0\"\n                                            Padding=\"4,8,4,8\"\n                                            Content=\"More Info\"\n                                            FontSize=\"13\"\n                                            NavigateUri=\"https://lykos.ai/prompt-amplifier/help\" />\n                                    </StackPanel>\n                                </ui:TeachingTip>\n                                <Button Classes=\"transparent-full\" Content=\"{iconExt:FluentIcon Icon=MoreVertical, FontSize=16, IconVariant=Regular}\">\n                                    <Button.Flyout>\n                                        <MenuFlyout>\n                                            <!--  ReSharper disable once Xaml.RedundantResource  -->\n                                            <MenuItem Command=\"{Binding DebugShowTokensCommand}\" Header=\"(Debug) Show Tokens\" />\n                                        </MenuFlyout>\n                                    </Button.Flyout>\n                                </Button>\n\n                            </StackPanel>\n\n                            <Border\n                                Grid.Row=\"1\"\n                                Grid.Column=\"0\"\n                                Grid.ColumnSpan=\"2\"\n                                VerticalAlignment=\"Stretch\"\n                                Classes=\"theme-dark\"\n                                CornerRadius=\"4\">\n\n                                <avaloniaEdit:TextEditor\n                                    x:Name=\"PromptEditor\"\n                                    VerticalAlignment=\"Stretch\"\n                                    Document=\"{Binding PromptDocument}\"\n                                    FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\">\n                                    <i:Interaction.Behaviors>\n                                        <behaviors:TextEditorCompletionBehavior\n                                            CompletionProvider=\"{Binding CompletionProvider}\"\n                                            IsEnabled=\"{Binding IsAutoCompletionEnabled}\"\n                                            TokenizerProvider=\"{Binding TokenizerProvider}\" />\n                                        <behaviors:TextEditorWeightAdjustmentBehavior TokenizerProvider=\"{Binding TokenizerProvider}\" />\n                                        <behaviors:TextEditorToolTipBehavior IsEnabled=\"False\" TokenizerProvider=\"{Binding TokenizerProvider}\" />\n                                    </i:Interaction.Behaviors>\n                                </avaloniaEdit:TextEditor>\n\n                            </Border>\n                        </Grid>\n\n                        <GridSplitter\n                            Grid.Row=\"1\"\n                            MaxWidth=\"45\"\n                            VerticalAlignment=\"Center\"\n                            BorderThickness=\"1\"\n                            CornerRadius=\"4\"\n                            Opacity=\"0.3\" />\n\n                        <!--  Negative Prompt  -->\n                        <Grid\n                            Grid.Row=\"2\"\n                            IsVisible=\"{Binding IsNegativePromptEnabled, FallbackValue=True, TargetNullValue=True}\"\n                            RowDefinitions=\"Auto,*\">\n                            <StackPanel Margin=\"4,0,4,8\" Orientation=\"Horizontal\">\n                                <TextBlock FontSize=\"14\" Text=\"Negative Prompt\" />\n                                <icons:Icon\n                                    Margin=\"8,0\"\n                                    FontSize=\"10\"\n                                    Value=\"fa-solid fa-caret-down\" />\n                            </StackPanel>\n\n                            <Border\n                                Grid.Row=\"1\"\n                                VerticalAlignment=\"Stretch\"\n                                Classes=\"theme-dark\"\n                                CornerRadius=\"4\">\n\n                                <avaloniaEdit:TextEditor\n                                    x:Name=\"NegativePromptEditor\"\n                                    VerticalAlignment=\"Stretch\"\n                                    Document=\"{Binding NegativePromptDocument}\"\n                                    FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\">\n                                    <i:Interaction.Behaviors>\n                                        <behaviors:TextEditorCompletionBehavior\n                                            CompletionProvider=\"{Binding CompletionProvider}\"\n                                            IsEnabled=\"{Binding IsAutoCompletionEnabled}\"\n                                            TokenizerProvider=\"{Binding TokenizerProvider}\" />\n                                        <behaviors:TextEditorWeightAdjustmentBehavior TokenizerProvider=\"{Binding TokenizerProvider}\" />\n                                        <behaviors:TextEditorToolTipBehavior IsEnabled=\"False\" TokenizerProvider=\"{Binding TokenizerProvider}\" />\n                                    </i:Interaction.Behaviors>\n                                </avaloniaEdit:TextEditor>\n\n\n                            </Border>\n                        </Grid>\n\n                        <GridSplitter\n                            Grid.Row=\"3\"\n                            MaxWidth=\"45\"\n                            VerticalAlignment=\"Center\"\n                            BorderThickness=\"1\"\n                            CornerRadius=\"4\"\n                            Opacity=\"0.3\" />\n\n                        <controls:StackEditableCard\n                            Grid.Row=\"4\"\n                            Margin=\"2,0,0,0\"\n                            DataContext=\"{Binding ModulesCardViewModel}\"\n                            IsVisible=\"{Binding $parent[Grid].((vmInference:PromptCardViewModel)DataContext).IsStackCardEnabled}\" />\n\n                        <!--  Styles and Prompt Expansions  -->\n                        <!--<Grid Grid.Row=\"4\" RowDefinitions=\"Auto,*\">\n                            <StackPanel Margin=\"4,0,4,8\" Orientation=\"Horizontal\">\n                                <TextBlock FontSize=\"14\" Text=\"Styles\" />\n                                <icons:Icon\n                                    Margin=\"8,0\"\n                                    FontSize=\"10\"\n                                    Value=\"fa-solid fa-caret-down\" />\n                            </StackPanel>\n\n                            <Border\n                                Grid.Row=\"1\"\n                                Classes=\"theme-dark\"\n                                VerticalAlignment=\"Stretch\"\n                                CornerRadius=\"4\">\n\n                                <avaloniaEdit:TextEditor\n                                    x:Name=\"ExtraPromptEditor\"\n                                    Document=\"{Binding NegativePromptDocument}\"\n                                    FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\">\n                                    <i:Interaction.Behaviors>\n                                        <behaviors:TextEditorCompletionBehavior\n                                            CompletionProvider=\"{Binding CompletionProvider}\"\n                                            IsEnabled=\"{Binding IsAutoCompletionEnabled}\"\n                                            TokenizerProvider=\"{Binding TokenizerProvider}\" />\n                                        <behaviors:TextEditorToolTipBehavior IsEnabled=\"False\" TokenizerProvider=\"{Binding TokenizerProvider}\" />\n                                    </i:Interaction.Behaviors>\n                                </avaloniaEdit:TextEditor>\n\n\n                            </Border>\n                        </Grid>-->\n                    </Grid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/PromptCard.axaml.cs",
    "content": "﻿using Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Input;\nusing AvaloniaEdit;\nusing AvaloniaEdit.Editing;\nusing AvaloniaEdit.Utils;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<PromptCard>]\npublic class PromptCard : TemplatedControlBase\n{\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        FixGrids(e);\n        InitializeEditors(e);\n    }\n\n    private static void InitializeEditors(TemplateAppliedEventArgs e)\n    {\n        foreach (\n            var editor in new[]\n            {\n                e.NameScope.Find<TextEditor>(\"PromptEditor\"),\n                e.NameScope.Find<TextEditor>(\"NegativePromptEditor\")\n            }\n        )\n        {\n            if (editor is not null)\n            {\n                TextEditorConfigs.Configure(editor, TextEditorPreset.Prompt);\n                editor.TextArea.Margin = new Thickness(0, 0, 4, 0);\n\n                if (editor.TextArea.ActiveInputHandler is TextAreaInputHandler inputHandler)\n                {\n                    // Add some aliases for editor shortcuts\n                    inputHandler.KeyBindings.AddRange(\n                        new KeyBinding[]\n                        {\n                            new()\n                            {\n                                Command = ApplicationCommands.Cut,\n                                Gesture = new KeyGesture(Key.Delete, KeyModifiers.Shift)\n                            },\n                            new()\n                            {\n                                Command = ApplicationCommands.Paste,\n                                Gesture = new KeyGesture(Key.Insert, KeyModifiers.Shift)\n                            }\n                        }\n                    );\n                }\n            }\n        }\n    }\n\n    private void FixGrids(TemplateAppliedEventArgs e)\n    {\n        if (DataContext is not PromptCardViewModel { IsNegativePromptEnabled: false })\n        {\n            return;\n        }\n\n        // When negative prompt disabled, rearrange grid\n        if (e.NameScope.Find<Grid>(\"PART_RootGrid\") is not { } rootGrid)\n            return;\n\n        // Change `*,16,*,16,Auto` to `*,16,Auto` (Remove index 2 and 3)\n        rootGrid.RowDefinitions.RemoveRange(2, 2);\n\n        // Set the last children to row 2\n        rootGrid.Children[4].SetValue(Grid.RowProperty, 2);\n\n        // Remove the negative prompt row and the separator row (index 2 and 3)\n        rootGrid.Children.RemoveRange(2, 2);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:inference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:local=\"clr-namespace:StabilityMatrix.Avalonia\"\n    x:DataType=\"inference:PromptExpansionCardViewModel\">\n    \n    <Design.PreviewWith>\n        <Panel Width=\"400\" Height=\"200\">\n            <StackPanel Width=\"300\" VerticalAlignment=\"Center\">\n                <controls:PromptExpansionCard />\n            </StackPanel>\n        </Panel>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|PromptExpansionCard\">\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"12\">\n                    <sg:SpacedGrid\n                        ColumnDefinitions=\"Auto,*\"\n                        ColumnSpacing=\"8\"\n                        RowDefinitions=\"*,*,*,*\"\n                        RowSpacing=\"0\">\n                        <!--  Model  -->\n                        <TextBlock\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"{x:Static lang:Resources.Label_Model}\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:FADownloadableComboBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                                HorizontalAlignment=\"Stretch\"\n                                ItemContainerTheme=\"{StaticResource FAComboBoxItemHybridModelTheme}\"\n                                ItemsSource=\"{Binding ClientManager.PromptExpansionModels}\"\n                                SelectedItem=\"{Binding SelectedModel}\">\n\n                                <ui:FAComboBox.DataTemplates>\n                                    <controls:HybridModelTemplateSelector>\n                                        <DataTemplate x:Key=\"{x:Static models:HybridModelType.Downloadable}\" DataType=\"models:HybridModelFile\">\n                                            <Grid ColumnDefinitions=\"*,Auto\">\n                                                <TextBlock Foreground=\"{DynamicResource ThemeGreyColor}\" Text=\"{Binding ShortDisplayName}\" />\n                                                <Button\n                                                    Grid.Column=\"1\"\n                                                    Margin=\"8,0,0,0\"\n                                                    Padding=\"0\"\n                                                    Classes=\"transparent-full\">\n                                                    <fluentIcons:SymbolIcon\n                                                        VerticalAlignment=\"Center\"\n                                                        FontSize=\"18\"\n                                                        Foreground=\"{DynamicResource ThemeGreyColor}\"\n                                                        IconVariant=\"Filled\"\n                                                        Symbol=\"CloudArrowDown\" />\n                                                </Button>\n                                            </Grid>\n                                        </DataTemplate>\n                                        <DataTemplate x:Key=\"{x:Static models:HybridModelType.None}\" DataType=\"models:HybridModelFile\">\n                                            <TextBlock Text=\"{Binding ShortDisplayName}\" />\n                                        </DataTemplate>\n                                    </controls:HybridModelTemplateSelector>\n                                </ui:FAComboBox.DataTemplates>\n\n                            </controls:FADownloadableComboBox>\n                        \n                        <!--<controls:BetterComboBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            ItemsSource=\"{Binding ClientManager.Upscalers}\"\n                            SelectedItem=\"{Binding SelectedModel}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />-->\n                    </sg:SpacedGrid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/PromptExpansionCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<PromptExpansionCard>]\npublic class PromptExpansionCard : TemplatedControlBase;\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/RescaleCfgCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"vmInference:RescaleCfgCardViewModel\">\n    <Design.PreviewWith>\n        <controls:RescaleCfgCard />\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|RescaleCfgCard\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card x:Name=\"PART_Card\">\n                    <controls:Card.Styles>\n                        <Style Selector=\"ui|NumberBox\">\n                            <Setter Property=\"Margin\" Value=\"12,4,0,4\" />\n                            <Setter Property=\"MinWidth\" Value=\"70\" />\n                            <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n                            <Setter Property=\"ValidationMode\" Value=\"InvalidInputOverwritten\" />\n                            <Setter Property=\"SmallChange\" Value=\"0.01\" />\n                            <Setter Property=\"LargeChange\" Value=\"0.1\" />\n                            <Setter Property=\"SimpleNumberFormat\" Value=\"F2\" />\n                            <Setter Property=\"SpinButtonPlacementMode\" Value=\"Inline\" />\n                        </Style>\n                    </controls:Card.Styles>\n                    <Grid ColumnDefinitions=\"Auto,*\" RowDefinitions=\"*,*\">\n                        <TextBlock\n                            Grid.Row=\"0\"\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Multiplier\" />\n                        <ui:NumberBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"12,0,0,4\"\n                            Value=\"{Binding Multiplier, Mode=TwoWay}\" />\n\n                        <Slider\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"2\"\n                            LargeChange=\"0.1\"\n                            Maximum=\"1.0\"\n                            Minimum=\"0.0\"\n                            SmallChange=\"0.01\"\n                            Value=\"{Binding Multiplier, Mode=TwoWay}\" />\n                    </Grid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/RescaleCfgCard.axaml.cs",
    "content": "﻿using Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<RescaleCfgCard>]\npublic class RescaleCfgCard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:generic=\"clr-namespace:System.Collections.Generic;assembly=System.Runtime\"\n    xmlns:generic1=\"clr-namespace:System.Collections.Generic;assembly=System.Collections\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmInference=\"using:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"vmInference:SamplerCardViewModel\">\n    <Design.PreviewWith>\n        <StackPanel MinWidth=\"350\" Spacing=\"16\">\n            <controls:SamplerCard DataContext=\"{x:Static mocks:DesignData.SamplerCardViewModel}\" />\n            <controls:SamplerCard DataContext=\"{x:Static mocks:DesignData.SamplerCardViewModelScaleMode}\" />\n            <controls:SamplerCard DataContext=\"{x:Static mocks:DesignData.SamplerCardViewModelRefinerMode}\" />\n        </StackPanel>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|SamplerCard\">\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\">\n                    <controls:Card.Resources>\n                        <input:StandardUICommand x:Key=\"SetResolutionCommand\" Command=\"{Binding SetResolutionCommand}\" />\n                    </controls:Card.Resources>\n                    <StackPanel HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\" Spacing=\"8\">\n                        <Grid ColumnDefinitions=\"Auto,5,*\" RowDefinitions=\"*,*,*,*,*,*,*\">\n                            <!--  Sampler  -->\n                            <TextBlock\n                                Grid.Row=\"0\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,0,0,8\"\n                                VerticalAlignment=\"Center\"\n                                IsVisible=\"{Binding IsSamplerSelectionEnabled}\"\n                                Text=\"{x:Static lang:Resources.Label_Sampler}\" />\n                            <controls:BetterComboBox\n                                Grid.Row=\"0\"\n                                Grid.Column=\"2\"\n                                Margin=\"8,0,0,8\"\n                                HorizontalAlignment=\"Stretch\"\n                                DisplayMemberBinding=\"{Binding DisplayName}\"\n                                IsVisible=\"{Binding IsSamplerSelectionEnabled}\"\n                                ItemsSource=\"{Binding ClientManager.Samplers}\"\n                                SelectedItem=\"{Binding SelectedSampler}\" />\n                            <!--  Scheduler  -->\n                            <TextBlock\n                                Grid.Row=\"1\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,0,0,8\"\n                                VerticalAlignment=\"Center\"\n                                IsVisible=\"{Binding IsSchedulerSelectionEnabled}\"\n                                Text=\"{x:Static lang:Resources.Label_Scheduler}\" />\n                            <controls:BetterComboBox\n                                Grid.Row=\"1\"\n                                Grid.Column=\"2\"\n                                Margin=\"8,0,0,8\"\n                                HorizontalAlignment=\"Stretch\"\n                                DisplayMemberBinding=\"{Binding DisplayName}\"\n                                IsVisible=\"{Binding IsSchedulerSelectionEnabled}\"\n                                ItemsSource=\"{Binding ClientManager.Schedulers}\"\n                                SelectedItem=\"{Binding SelectedScheduler}\" />\n\n                            <TextBlock\n                                Grid.Row=\"2\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,0,0,8\"\n                                VerticalAlignment=\"Center\"\n                                IsVisible=\"{Binding IsModelTypeSelectionEnabled}\"\n                                Text=\"{x:Static lang:Resources.Label_ModelType}\" />\n                            <ui:FAComboBox\n                                Grid.Row=\"2\"\n                                Grid.Column=\"2\"\n                                Margin=\"8,0,0,8\"\n                                HorizontalAlignment=\"Stretch\"\n                                IsVisible=\"{Binding IsModelTypeSelectionEnabled}\"\n                                ItemsSource=\"{Binding ModelTypes}\"\n                                SelectedItem=\"{Binding SelectedModelType}\" />\n\n                            <!--  Steps (Normal)  -->\n                            <TextBlock\n                                Grid.Row=\"3\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,0,0,8\"\n                                VerticalAlignment=\"Center\"\n                                IsVisible=\"{Binding !IsRefinerStepsEnabled}\"\n                                Text=\"{x:Static lang:Resources.Label_Steps}\" />\n                            <TextBlock\n                                Grid.Row=\"3\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,0,0,8\"\n                                VerticalAlignment=\"Center\"\n                                IsVisible=\"{Binding IsRefinerStepsEnabled}\"\n                                Text=\"{x:Static lang:Resources.Label_StepsBase}\" />\n                            <ui:NumberBox\n                                Grid.Row=\"3\"\n                                Grid.Column=\"2\"\n                                Margin=\"8,0,0,8\"\n                                HorizontalAlignment=\"Stretch\"\n                                SelectionHighlightColor=\"Transparent\"\n                                SpinButtonPlacementMode=\"Inline\"\n                                Value=\"{Binding Steps}\" />\n\n                            <!--  Steps (Refiner)  -->\n                            <TextBlock\n                                Grid.Row=\"4\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,0,0,8\"\n                                VerticalAlignment=\"Center\"\n                                IsVisible=\"{Binding IsRefinerStepsEnabled}\"\n                                Text=\"{x:Static lang:Resources.Label_StepsRefiner}\" />\n                            <ui:NumberBox\n                                Grid.Row=\"4\"\n                                Grid.Column=\"2\"\n                                Margin=\"8,0,0,8\"\n                                HorizontalAlignment=\"Stretch\"\n                                IsVisible=\"{Binding IsRefinerStepsEnabled}\"\n                                SelectionHighlightColor=\"Transparent\"\n                                SpinButtonPlacementMode=\"Inline\"\n                                Value=\"{Binding RefinerSteps}\" />\n\n                            <!--  CFG Scale  -->\n                            <TextBlock\n                                Grid.Row=\"5\"\n                                Grid.Column=\"0\"\n                                VerticalAlignment=\"Center\"\n                                IsVisible=\"{Binding IsCfgScaleEnabled}\"\n                                Text=\"{x:Static lang:Resources.Label_CFGScale}\" />\n                            <ui:NumberBox\n                                Grid.Row=\"5\"\n                                Grid.Column=\"2\"\n                                Margin=\"8,0,0,0\"\n                                HorizontalAlignment=\"Stretch\"\n                                IsVisible=\"{Binding IsCfgScaleEnabled}\"\n                                SelectionHighlightColor=\"Transparent\"\n                                SimpleNumberFormat=\"F2\"\n                                SmallChange=\"0.25\"\n                                SpinButtonPlacementMode=\"Inline\"\n                                Value=\"{Binding CfgScale}\" />\n\n                            <!--  Length (Frame Count)  -->\n                            <TextBlock\n                                Grid.Row=\"6\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,8,0,0\"\n                                VerticalAlignment=\"Center\"\n                                IsVisible=\"{Binding IsLengthEnabled}\"\n                                Text=\"{x:Static lang:Resources.Label_Frames}\" />\n                            <ui:NumberBox\n                                Grid.Row=\"6\"\n                                Grid.Column=\"2\"\n                                Margin=\"8,8,0,0\"\n                                HorizontalAlignment=\"Stretch\"\n                                IsVisible=\"{Binding IsLengthEnabled}\"\n                                SelectionHighlightColor=\"Transparent\"\n                                SmallChange=\"4\"\n                                SpinButtonPlacementMode=\"Inline\"\n                                Value=\"{Binding Length}\" />\n                        </Grid>\n\n                        <StackPanel>\n                            <!--  Denoise Strength  -->\n                            <StackPanel>\n                                <StackPanel.IsVisible>\n                                    <MultiBinding Converter=\"{x:Static BoolConverters.Or}\">\n                                        <Binding Path=\"IsDenoiseStrengthEnabled\" />\n                                        <Binding Path=\"IsDenoiseStrengthTempEnabled\" />\n                                    </MultiBinding>\n                                </StackPanel.IsVisible>\n                                <Grid ColumnDefinitions=\"*,Auto\">\n                                    <TextBlock VerticalAlignment=\"Center\" Text=\"{x:Static lang:Resources.Label_DenoisingStrength}\" />\n                                    <ui:NumberBox\n                                        Grid.Column=\"1\"\n                                        MinWidth=\"70\"\n                                        Margin=\"4,0,0,0\"\n                                        HorizontalAlignment=\"Stretch\"\n                                        SimpleNumberFormat=\"F2\"\n                                        SmallChange=\"0.01\"\n                                        SpinButtonPlacementMode=\"Compact\"\n                                        ValidationMode=\"InvalidInputOverwritten\"\n                                        Value=\"{Binding DenoiseStrength}\" />\n                                </Grid>\n                                <Slider\n                                    Margin=\"0,0,0,-4\"\n                                    Maximum=\"1\"\n                                    Minimum=\"0\"\n                                    TickFrequency=\"1\"\n                                    TickPlacement=\"BottomRight\"\n                                    Value=\"{Binding DenoiseStrength}\" />\n                            </StackPanel>\n\n                            <!--  Dimensions (Absolute)  -->\n                            <Grid\n                                Margin=\"0,8,0,0\"\n                                ColumnDefinitions=\"*,Auto,*,Auto\"\n                                IsVisible=\"{Binding IsDimensionsEnabled}\"\n                                RowDefinitions=\"Auto,*,Auto\">\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"0\"\n                                    Margin=\"0,0,4,4\"\n                                    Text=\"{x:Static lang:Resources.Label_Width}\" />\n                                <ui:NumberBox\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"0\"\n                                    Margin=\"0,0,4,0\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    PlaceholderText=\"128\"\n                                    SmallChange=\"{Binding DimensionStepChange}\"\n                                    SpinButtonPlacementMode=\"Compact\"\n                                    ValidationMode=\"InvalidInputOverwritten\"\n                                    Value=\"{Binding Width}\" />\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"2\"\n                                    Margin=\"4,0,0,4\"\n                                    Text=\"{x:Static lang:Resources.Label_Height}\" />\n                                <ui:NumberBox\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"2\"\n                                    Margin=\"4,0,0,0\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    PlaceholderText=\"128\"\n                                    SmallChange=\"{Binding DimensionStepChange}\"\n                                    SpinButtonPlacementMode=\"Compact\"\n                                    ValidationMode=\"InvalidInputOverwritten\"\n                                    Value=\"{Binding Height}\" />\n                                <Button\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"1\"\n                                    Padding=\"4\"\n                                    VerticalAlignment=\"Stretch\"\n                                    VerticalContentAlignment=\"Center\"\n                                    Command=\"{Binding SwapDimensionsCommand}\"\n                                    ToolTip.Tip=\"Swap Image Dimensions\">\n                                    <ui:SymbolIcon\n                                        HorizontalAlignment=\"Center\"\n                                        VerticalAlignment=\"Center\"\n                                        FontSize=\"18\"\n                                        Symbol=\"RepeatAll\" />\n                                </Button>\n\n                                <Button\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"3\"\n                                    Margin=\"4,0,0,0\"\n                                    Padding=\"4\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    VerticalAlignment=\"Stretch\"\n                                    VerticalContentAlignment=\"Center\"\n                                    ToolTip.Tip=\"Select from Favorite Dimensions\">\n                                    <avalonia:Icon\n                                        Margin=\"2,0\"\n                                        FontSize=\"18\"\n                                        Value=\"fa-solid fa-table-cells-large\" />\n                                    <Button.Flyout>\n                                        <controls:BetterFlyout Placement=\"Bottom\">\n                                            <Grid RowDefinitions=\"*,Auto\">\n                                                <Button\n                                                    Grid.Row=\"1\"\n                                                    Margin=\"0,16,0,0\"\n                                                    Padding=\"4\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    VerticalAlignment=\"Stretch\"\n                                                    VerticalContentAlignment=\"Center\"\n                                                    Command=\"{Binding SaveDimensionsToFavoritesCommand}\"\n                                                    ToolTip.Tip=\"Save Dimensions to Favorites\">\n                                                    <StackPanel Orientation=\"Horizontal\">\n                                                        <avalonia:Icon\n                                                            Margin=\"2,0\"\n                                                            FontSize=\"18\"\n                                                            Value=\"fa-solid fa-floppy-disk\" />\n                                                        <TextBlock\n                                                            Margin=\"4,0,0,0\"\n                                                            HorizontalAlignment=\"Center\"\n                                                            FontSize=\"14\"\n                                                            Text=\"Save Current Dimensions\"\n                                                            TextAlignment=\"Center\" />\n                                                    </StackPanel>\n                                                </Button>\n                                                <ItemsRepeater\n                                                    Grid.Row=\"0\"\n                                                    MaxWidth=\"300\"\n                                                    ItemsSource=\"{Binding GroupedResolutionsByAspectRatio}\">\n                                                    <ItemsRepeater.Layout>\n                                                        <StackLayout Orientation=\"Vertical\" />\n                                                    </ItemsRepeater.Layout>\n                                                    <ItemsRepeater.ItemTemplate>\n                                                        <DataTemplate>\n                                                            <DataTemplate.DataType>\n                                                                <x:Type x:TypeArguments=\"x:String, generic1:List(system:String)\" TypeName=\"generic:KeyValuePair\" />\n                                                            </DataTemplate.DataType>\n                                                            <Grid MaxWidth=\"300\" RowDefinitions=\"Auto,*\">\n                                                                <TextBlock\n                                                                    Margin=\"0,8,0,4\"\n                                                                    FontSize=\"16\"\n                                                                    FontWeight=\"Light\"\n                                                                    Text=\"{Binding Key}\" />\n                                                                <ItemsRepeater Grid.Row=\"1\" ItemsSource=\"{Binding Value}\">\n                                                                    <ItemsRepeater.Layout>\n                                                                        <WrapLayout\n                                                                            HorizontalSpacing=\"8\"\n                                                                            Orientation=\"Horizontal\"\n                                                                            VerticalSpacing=\"8\" />\n                                                                    </ItemsRepeater.Layout>\n                                                                    <ItemsRepeater.ItemTemplate>\n                                                                        <DataTemplate DataType=\"{x:Type system:String}\">\n                                                                            <Button\n                                                                                HorizontalAlignment=\"Stretch\"\n                                                                                Classes=\"transparent\"\n                                                                                Command=\"{StaticResource SetResolutionCommand}\"\n                                                                                CommandParameter=\"{Binding}\"\n                                                                                Content=\"{Binding}\" />\n                                                                        </DataTemplate>\n                                                                    </ItemsRepeater.ItemTemplate>\n                                                                </ItemsRepeater>\n                                                            </Grid>\n                                                        </DataTemplate>\n                                                    </ItemsRepeater.ItemTemplate>\n                                                </ItemsRepeater>\n                                            </Grid>\n\n                                        </controls:BetterFlyout>\n                                    </Button.Flyout>\n                                </Button>\n                            </Grid>\n\n                            <!--  Conditioning Modules  -->\n                            <Grid\n                                Margin=\"0,8,0,0\"\n                                ColumnDefinitions=\"*\"\n                                IsVisible=\"{Binding EnableAddons}\"\n                                RowDefinitions=\"*\">\n                                <controls:StackEditableCard DataContext=\"{Binding ModulesCardViewModel}\" />\n                            </Grid>\n                        </StackPanel>\n                    </StackPanel>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/SamplerCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<SamplerCard>]\npublic class SamplerCard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/SeedCard.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n        xmlns:vmInference=\"using:StabilityMatrix.Avalonia.ViewModels.Inference\"\n        xmlns:icons=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n        x:DataType=\"vmInference:SeedCardViewModel\"\n        xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n        xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\">\n    <Design.PreviewWith>\n        <Grid MinWidth=\"400\">\n            <controls:SeedCard DataContext=\"{x:Static mocks:DesignData.SeedCardViewModel}\"/>\n        </Grid>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|SeedCard\">\n        <!-- Set Defaults -->\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\"/>\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"8\" HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\">\n                    <Grid Margin=\"4\" RowDefinitions=\"Auto,*\">\n                        <Grid Grid.Row=\"1\" ColumnDefinitions=\"Auto,*,Auto,Auto\">\n                            <TextBlock\n                                Grid.Column=\"0\"\n                                Margin=\"0,0,8,0\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"{x:Static lang:Resources.Label_Seed}\" />\n                            <NumericUpDown\n                                Grid.Column=\"1\"\n                                Name=\"SeedNumberBox\"\n                                ShowButtonSpinner=\"False\"\n                                IsReadOnly=\"{Binding !#RandomizeToggleButton.IsChecked}\"\n                                FormatString=\"F0\"\n                                ParsingNumberStyle=\"Integer\"\n                                Value=\"{Binding Seed, Mode=TwoWay}\"\n                                HorizontalAlignment=\"Stretch\" />\n                            <RepeatButton \n                                Grid.Column=\"2\"\n                                Name=\"GenerateRandomSeedButton\"\n                                Command=\"{Binding GenerateNewSeedCommand}\"\n                                IsVisible=\"{Binding #RandomizeToggleButton.IsChecked}\"\n                                Margin=\"8,0,0,0\">\n                                <icons:Icon Margin=\"1\" Value=\"fa-solid fa-rotate\" />\n                            </RepeatButton>\n                            <ToggleButton\n                                Name=\"RandomizeToggleButton\"\n                                ToolTip.Tip=\"{Binding RandomizeButtonToolTip}\"\n                                IsChecked=\"{Binding !IsRandomizeEnabled}\"\n                                Grid.Column=\"3\" Margin=\"8,0,0,0\">\n                                <Panel>\n                                    <icons:Icon \n                                        Margin=\"1\" \n                                        Value=\"fa-solid fa-lock\"\n                                        IsVisible=\"{Binding #RandomizeToggleButton.IsChecked}\"/>\n                                    <icons:Icon \n                                        Margin=\"1\" \n                                        Value=\"fa-solid fa-dice-d20\"\n                                        IsVisible=\"{Binding !#RandomizeToggleButton.IsChecked}\"/>\n                                </Panel>\n                            </ToggleButton>\n                        </Grid>\n                    </Grid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/SeedCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<SeedCard>]\npublic class SeedCard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mocks=\"using:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"vmInference:SelectImageCardViewModel\">\n\n    <Design.PreviewWith>\n        <Panel Width=\"600\" Height=\"500\">\n            <Grid MaxHeight=\"400\" ColumnDefinitions=\"*,*\">\n                <controls:SelectImageCard Margin=\"4\" DataContext=\"{x:Static mocks:DesignData.SelectImageCardViewModel}\" />\n                <controls:SelectImageCard\n                    Grid.Column=\"1\"\n                    Margin=\"4\"\n                    DataContext=\"{x:Static mocks:DesignData.SelectImageCardViewModel_WithImage}\" />\n            </Grid>\n        </Panel>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|SelectImageCard\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Padding\" Value=\"12\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card\n                    Padding=\"{TemplateBinding Padding}\"\n                    HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\"\n                    VerticalAlignment=\"{TemplateBinding VerticalAlignment}\"\n                    VerticalContentAlignment=\"{TemplateBinding VerticalAlignment}\">\n\n                    <!--  Background frame  -->\n                    <ExperimentalAcrylicBorder\n                        VerticalAlignment=\"Stretch\"\n                        CornerRadius=\"4\"\n                        Material=\"{StaticResource OpaqueDarkAcrylicMaterial}\">\n                        <Panel>\n                            <!--  Image  -->\n                            <controls:BetterAdvancedImage\n                                x:Name=\"PART_BetterAdvancedImage\"\n                                VerticalAlignment=\"Stretch\"\n                                VerticalContentAlignment=\"Stretch\"\n                                CornerRadius=\"4\"\n                                IsVisible=\"{Binding !IsSelectionAvailable}\"\n                                RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n                                Source=\"{Binding ImageSource}\"\n                                Stretch=\"Uniform\"\n                                StretchDirection=\"Both\" />\n\n                            <!--  Overlay Image  -->\n                            <Panel>\n                                <Panel.IsVisible>\n                                    <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                        <CompiledBinding Path=\"!IsSelectionAvailable\" />\n                                        <CompiledBinding Path=\"IsMaskOverlayEnabled\" />\n                                    </MultiBinding>\n                                </Panel.IsVisible>\n                                <controls:BetterImage\n                                    VerticalAlignment=\"Stretch\"\n                                    DataContext=\"{Binding MaskEditorViewModel}\"\n                                    RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n                                    Source=\"{Binding CachedOrNewMaskRenderImage.Bitmap}\"\n                                    Stretch=\"Uniform\"\n                                    StretchDirection=\"Both\" />\n                            </Panel>\n\n                            <!--  Missing image  -->\n                            <Border\n                                BorderBrush=\"{StaticResource ThemeCoralRedColor}\"\n                                BorderThickness=\"3\"\n                                BoxShadow=\"inset 1.2 0 20 1.8 #66000000\"\n                                CornerRadius=\"4\"\n                                IsVisible=\"{Binding IsImageFileNotFound}\">\n                                <Grid\n                                    HorizontalAlignment=\"Center\"\n                                    VerticalAlignment=\"Center\"\n                                    RowDefinitions=\"Auto,Auto,Auto\">\n\n                                    <fluentIcons:SymbolIcon\n                                        FontSize=\"28\"\n                                        IconVariant=\"Filled\"\n                                        Symbol=\"DocumentQuestionMark\" />\n                                    <TextBlock\n                                        Grid.Row=\"1\"\n                                        FontSize=\"{TemplateBinding FontSize}\"\n                                        Text=\"{x:Static lang:Resources.Label_MissingImageFile}\"\n                                        TextAlignment=\"Center\"\n                                        TextWrapping=\"WrapWithOverflow\" />\n                                    <SelectableTextBlock\n                                        Grid.Row=\"2\"\n                                        Margin=\"0,4,0,0\"\n                                        FontSize=\"10\"\n                                        Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                        Text=\"{Binding NotFoundImagePath}\"\n                                        TextAlignment=\"Center\"\n                                        TextWrapping=\"WrapWithOverflow\" />\n                                </Grid>\n                            </Border>\n\n                            <!--  Active Selection Prompt  -->\n                            <StackPanel\n                                Margin=\"4\"\n                                HorizontalAlignment=\"Right\"\n                                VerticalAlignment=\"Top\"\n                                IsVisible=\"{Binding !IsSelectionAvailable}\"\n                                Orientation=\"Horizontal\"\n                                Spacing=\"4\">\n                                <!--  Mask Overlay Toggle  -->\n                                <Border\n                                    Width=\"40\"\n                                    Height=\"40\"\n                                    BoxShadow=\"inset 1.2 0 80 1.8 #66000000\"\n                                    CornerRadius=\"10\"\n                                    IsVisible=\"{Binding IsMaskEditorEnabled}\">\n                                    <Border.Resources>\n                                        <DropShadowEffect\n                                            x:Key=\"TextDropShadowEffect\"\n                                            BlurRadius=\"12\"\n                                            Opacity=\"0.9\"\n                                            Color=\"#FF000000\" />\n                                        <DropShadowEffect\n                                            x:Key=\"ImageDropShadowEffect\"\n                                            BlurRadius=\"12\"\n                                            Opacity=\"0.2\"\n                                            Color=\"#FF000000\" />\n                                    </Border.Resources>\n                                    <ToggleButton\n                                        Padding=\"2\"\n                                        CornerRadius=\"10\"\n                                        FontSize=\"{TemplateBinding FontSize}\"\n                                        IsChecked=\"{Binding IsMaskOverlayEnabled}\">\n                                        <ToolTip.Tip>\n                                            <MultiBinding StringFormat=\"{}{0} - {1}\">\n                                                <CompiledBinding Source=\"{x:Static lang:Resources.Label_ClippingMask}\" />\n                                                <CompiledBinding Source=\"{x:Static lang:Resources.Action_ToggleVisibility}\" />\n                                            </MultiBinding>\n                                        </ToolTip.Tip>\n                                        <fluentIcons:SymbolIcon\n                                            Effect=\"{StaticResource ImageDropShadowEffect}\"\n                                            FontSize=\"28\"\n                                            IconVariant=\"Filled\"\n                                            Symbol=\"Eye\" />\n                                    </ToggleButton>\n                                </Border>\n                                <!--  Mask Editor  -->\n                                <Border\n                                    Width=\"40\"\n                                    Height=\"40\"\n                                    BoxShadow=\"inset 1.2 0 80 1.8 #66000000\"\n                                    CornerRadius=\"10\"\n                                    IsVisible=\"{Binding IsMaskEditorEnabled}\">\n                                    <Border.Resources>\n                                        <DropShadowEffect\n                                            x:Key=\"TextDropShadowEffect\"\n                                            BlurRadius=\"12\"\n                                            Opacity=\"0.9\"\n                                            Color=\"#FF000000\" />\n                                        <DropShadowEffect\n                                            x:Key=\"ImageDropShadowEffect\"\n                                            BlurRadius=\"12\"\n                                            Opacity=\"0.2\"\n                                            Color=\"#FF000000\" />\n                                    </Border.Resources>\n                                    <Button\n                                        Padding=\"2\"\n                                        Classes=\"transparent\"\n                                        Command=\"{Binding OpenEditMaskDialogCommand}\"\n                                        CornerRadius=\"10\"\n                                        FontSize=\"{TemplateBinding FontSize}\">\n                                        <ToolTip.Tip>\n                                            <MultiBinding StringFormat=\"{}{0} - {1}\">\n                                                <CompiledBinding Source=\"{x:Static lang:Resources.Label_ClippingMask}\" />\n                                                <CompiledBinding Source=\"{x:Static lang:Resources.Action_Edit}\" />\n                                            </MultiBinding>\n                                        </ToolTip.Tip>\n                                        <fluentIcons:SymbolIcon\n                                            Effect=\"{StaticResource ImageDropShadowEffect}\"\n                                            FontSize=\"28\"\n                                            IconVariant=\"Filled\"\n                                            Symbol=\"Layer\" />\n                                    </Button>\n                                </Border>\n                                <!--  Replace Contents  -->\n                                <Border\n                                    Width=\"40\"\n                                    Height=\"40\"\n                                    BoxShadow=\"inset 1.2 0 80 1.8 #66000000\"\n                                    CornerRadius=\"10\">\n                                    <Border.Resources>\n                                        <DropShadowEffect\n                                            x:Key=\"TextDropShadowEffect\"\n                                            BlurRadius=\"12\"\n                                            Opacity=\"0.9\"\n                                            Color=\"#FF000000\" />\n                                        <DropShadowEffect\n                                            x:Key=\"ImageDropShadowEffect\"\n                                            BlurRadius=\"12\"\n                                            Opacity=\"0.2\"\n                                            Color=\"#FF000000\" />\n                                    </Border.Resources>\n                                    <Button\n                                        Padding=\"2\"\n                                        Classes=\"transparent\"\n                                        Command=\"{Binding SelectImageFromFilePickerCommand}\"\n                                        CornerRadius=\"10\"\n                                        FontSize=\"{TemplateBinding FontSize}\"\n                                        ToolTip.Tip=\"{x:Static lang:Resources.Action_ReplaceContents}\">\n                                        <fluentIcons:SymbolIcon\n                                            Effect=\"{StaticResource ImageDropShadowEffect}\"\n                                            FontSize=\"28\"\n                                            IconVariant=\"Filled\"\n                                            Symbol=\"ImageArrowCounterclockwise\" />\n                                    </Button>\n                                </Border>\n                            </StackPanel>\n\n\n                            <!--  No Image Selection Prompt  -->\n                            <controls:LineDashFrame\n                                Padding=\"8,4\"\n                                CornerRadius=\"8\"\n                                IsVisible=\"{Binding IsSelectionAvailable}\"\n                                Stroke=\"DimGray\"\n                                StrokeDashLine=\"6\"\n                                StrokeDashSpace=\"6\"\n                                StrokeThickness=\"3\">\n\n                                <Grid\n                                    HorizontalAlignment=\"Center\"\n                                    VerticalAlignment=\"Center\"\n                                    RowDefinitions=\"*,Auto,Auto\">\n\n                                    <fluentIcons:SymbolIcon\n                                        FontSize=\"28\"\n                                        IconVariant=\"Filled\"\n                                        Symbol=\"ImageCopy\" />\n                                    <TextBlock\n                                        Grid.Row=\"1\"\n                                        FontSize=\"{TemplateBinding FontSize}\"\n                                        Foreground=\"DarkGray\"\n                                        Text=\"Drag an image here\"\n                                        TextAlignment=\"Center\"\n                                        TextWrapping=\"WrapWithOverflow\" />\n\n                                    <StackPanel\n                                        Grid.Row=\"2\"\n                                        Margin=\"0,4,0,0\"\n                                        HorizontalAlignment=\"Center\"\n                                        Orientation=\"Horizontal\">\n\n                                        <TextBlock\n                                            VerticalAlignment=\"Center\"\n                                            FontSize=\"{TemplateBinding FontSize}\"\n                                            Foreground=\"DarkGray\"\n                                            Text=\"or\" />\n\n                                        <Button\n                                            Margin=\"4,0\"\n                                            Padding=\"4\"\n                                            Command=\"{Binding SelectImageFromFilePickerCommand}\"\n                                            Content=\"Browse\"\n                                            FontSize=\"{TemplateBinding FontSize}\" />\n                                    </StackPanel>\n                                </Grid>\n                            </controls:LineDashFrame>\n                        </Panel>\n                    </ExperimentalAcrylicBorder>\n\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml.cs",
    "content": "﻿using System;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing DynamicData.Binding;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing Size = Avalonia.Size;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<SelectImageCard>]\npublic class SelectImageCard : DropTargetTemplatedControlBase\n{\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        if (DataContext is not SelectImageCardViewModel vm)\n            return;\n\n        if (e.NameScope.Find<BetterAdvancedImage>(\"PART_BetterAdvancedImage\") is not { } imageControl)\n            return;\n\n        imageControl\n            .WhenPropertyChanged(x => x.CurrentImage)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(propertyValue =>\n            {\n                if (propertyValue.Value is { } image)\n                {\n                    // Sometimes Avalonia Bitmap.Size getter throws a NullReferenceException depending on skia lifetimes (probably)\n                    // so just catch it and ignore it\n                    Size? size = null;\n                    try\n                    {\n                        size = image.Size;\n                    }\n                    catch (NullReferenceException) { }\n\n                    if (size is not null)\n                    {\n                        vm.CurrentBitmapSize = new System.Drawing.Size(\n                            Convert.ToInt32(size.Value.Width),\n                            Convert.ToInt32(size.Value.Height)\n                        );\n                        return;\n                    }\n                }\n\n                vm.CurrentBitmapSize = System.Drawing.Size.Empty;\n            });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/SharpenCard.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n        xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n        xmlns:inference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n        xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n        xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n        x:DataType=\"inference:SharpenCardViewModel\">\n    <Design.PreviewWith>\n        <Grid Width=\"600\" Height=\"400\">\n            <StackPanel>\n                <controls:SharpenCard DataContext=\"{x:Static mocks:DesignData.SharpenCardViewModel}\" />\n            </StackPanel>\n        </Grid>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|SharpenCard\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Focusable\" Value=\"True\"/>\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card x:Name=\"PART_Card\">\n                    <StackPanel Spacing=\"8\">\n                        <!-- Sharpen Radius -->\n                        <StackPanel>\n                            <Grid ColumnDefinitions=\"*,Auto\">\n                                <TextBlock \n                                    VerticalAlignment=\"Center\"\n                                    Text=\"Sharpen Radius\"/>\n                                <ui:NumberBox\n                                    Grid.Column=\"1\"\n                                    Margin=\"4,0,0,0\"\n                                    ValidationMode=\"InvalidInputOverwritten\"\n                                    SmallChange=\"1\"\n                                    SimpleNumberFormat=\"F0\"\n                                    Minimum=\"1\"\n                                    Maximum=\"31\"\n                                    Value=\"{Binding SharpenRadius}\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    MinWidth=\"70\"\n                                    SpinButtonPlacementMode=\"Compact\"/>\n                            </Grid>\n                            <Slider \n                                Minimum=\"1\"\n                                Maximum=\"31\"\n                                Value=\"{Binding SharpenRadius}\"\n                                TickFrequency=\"1\"\n                                Margin=\"0,0,0,-4\"\n                                TickPlacement=\"BottomRight\"/>\n                        </StackPanel>\n                        \n                        <!-- Sigma -->\n                        <StackPanel>\n                            <Grid ColumnDefinitions=\"*,Auto\">\n                                <TextBlock \n                                    VerticalAlignment=\"Center\"\n                                    Text=\"Sigma\"/>\n                                <ui:NumberBox\n                                    Grid.Column=\"1\"\n                                    Margin=\"4,0,0,0\"\n                                    ValidationMode=\"InvalidInputOverwritten\"\n                                    SmallChange=\"0.1\"\n                                    SimpleNumberFormat=\"F1\"\n                                    Minimum=\"0.1\"\n                                    Maximum=\"10\"\n                                    Value=\"{Binding Sigma}\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    MinWidth=\"70\"\n                                    SpinButtonPlacementMode=\"Compact\"/>\n                            </Grid>\n                            <Slider \n                                Minimum=\"0.1\"\n                                Maximum=\"10\"\n                                SmallChange=\"0.1\"\n                                Value=\"{Binding Sigma}\"\n                                Margin=\"0,0,0,-4\"\n                                TickPlacement=\"BottomRight\"/>\n                        </StackPanel>\n                        \n                        <!-- Alpha -->\n                        <StackPanel>\n                            <Grid ColumnDefinitions=\"*,Auto\">\n                                <TextBlock \n                                    VerticalAlignment=\"Center\"\n                                    Text=\"Alpha\"/>\n                                <ui:NumberBox\n                                    Grid.Column=\"1\"\n                                    Margin=\"4,0,0,0\"\n                                    ValidationMode=\"InvalidInputOverwritten\"\n                                    SmallChange=\"0.1\"\n                                    SimpleNumberFormat=\"F1\"\n                                    Minimum=\"0\"\n                                    Maximum=\"5\"\n                                    Value=\"{Binding Alpha}\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    MinWidth=\"70\"\n                                    SpinButtonPlacementMode=\"Compact\"/>\n                            </Grid>\n                            <Slider \n                                Minimum=\"0\"\n                                Maximum=\"5\"\n                                SmallChange=\"0.1\"\n                                TickFrequency=\"0.5\"\n                                IsSnapToTickEnabled=\"True\"\n                                Value=\"{Binding Alpha}\"\n                                Margin=\"0,0,0,-4\"\n                                TickPlacement=\"BottomRight\"/>\n                        </StackPanel>\n                    </StackPanel>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/SharpenCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<SharpenCard>]\npublic class SharpenCard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/StackCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:local=\"clr-namespace:StabilityMatrix.Avalonia\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"vmInference:StackCardViewModel\">\n\n    <Design.PreviewWith>\n        <StackPanel Width=\"600\" Height=\"800\">\n            <controls:StackCard DataContext=\"{x:Static mocks:DesignData.StackCardViewModel}\" Spacing=\"16\" />\n            <controls:StackCard DataContext=\"{x:Static mocks:DesignData.StackCardViewModel}\" Spacing=\"4\" />\n        </StackPanel>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|StackCard\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <ScrollViewer Padding=\"4,0,4,0\">\n                    <ItemsControl VerticalAlignment=\"Top\" ItemsSource=\"{Binding Cards}\">\n\n                        <ItemsControl.DataTemplates>\n                            <local:ViewLocator />\n                        </ItemsControl.DataTemplates>\n\n                        <ItemsControl.ItemsPanel>\n                            <ItemsPanelTemplate>\n                                <StackPanel VerticalAlignment=\"Top\" Spacing=\"{Binding $parent[controls:StackCard].Spacing}\" />\n                            </ItemsPanelTemplate>\n                        </ItemsControl.ItemsPanel>\n\n                    </ItemsControl>\n                </ScrollViewer>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/StackCard.axaml.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing Avalonia;\nusing Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\n[RegisterTransient<StackCard>]\npublic class StackCard : TemplatedControlBase\n{\n    public static readonly StyledProperty<int> SpacingProperty = AvaloniaProperty.Register<StackCard, int>(\n        \"Spacing\",\n        4\n    );\n\n    public int Spacing\n    {\n        get => GetValue(SpacingProperty);\n        set => SetValue(SpacingProperty, value);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:local=\"clr-namespace:StabilityMatrix.Avalonia\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:modules=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference.Modules\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"vmInference:StackEditableCardViewModel\">\n\n    <Design.PreviewWith>\n        <Grid Width=\"250\" Height=\"600\">\n            <controls:StackEditableCard DataContext=\"{x:Static mocks:DesignData.StackEditableCardViewModel}\" />\n        </Grid>\n    </Design.PreviewWith>\n\n    <!--  Like StackCard but draggable items  -->\n    <Style Selector=\"controls|StackEditableCard\">\n        <Setter Property=\"IsListBoxEditEnabled\" Value=\"{Binding IsEditEnabled, Mode=TwoWay}\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <ScrollViewer>\n                    <Grid RowDefinitions=\"Auto,*\">\n                        <controls:Card\n                            Margin=\"0,0,0,3\"\n                            Padding=\"0,4\"\n                            Classes=\"transparent\">\n                            <Grid ColumnDefinitions=\"*,Auto,Auto\">\n                                <TextBlock\n                                    VerticalAlignment=\"Center\"\n                                    Text=\"{Binding Title}\"\n                                    TextWrapping=\"Wrap\" />\n\n                                <!--  Edit button  -->\n                                <ToggleButton\n                                    Name=\"PART_EditButton\"\n                                    Grid.Column=\"1\"\n                                    Margin=\"4,0\"\n                                    Padding=\"12,4\"\n                                    VerticalAlignment=\"Bottom\"\n                                    CornerRadius=\"4\"\n                                    IsChecked=\"{Binding IsEditEnabled}\"\n                                    IsEnabled=\"{Binding !!Cards.Count}\">\n                                    <fluentIcons:SymbolIcon FontSize=\"14\" Symbol=\"Edit\" />\n                                </ToggleButton>\n                                <!--  Add button  -->\n                                <Button\n                                    Name=\"PART_AddButton\"\n                                    Grid.Column=\"2\"\n                                    Margin=\"4,0\"\n                                    Padding=\"12,4\"\n                                    VerticalAlignment=\"Bottom\"\n                                    CornerRadius=\"4\">\n                                    <fluentIcons:SymbolIcon\n                                        FontSize=\"14\"\n                                        IconVariant=\"Filled\"\n                                        Symbol=\"Add\" />\n                                </Button>\n                            </Grid>\n\n                        </controls:Card>\n\n                        <ListBox\n                            x:Name=\"PART_ListBox\"\n                            Grid.Row=\"1\"\n                            ItemsSource=\"{Binding Cards}\"\n                            Theme=\"{StaticResource DraggableListBoxTheme}\">\n                            <ListBox.DataTemplates>\n                                <local:ViewLocator />\n                            </ListBox.DataTemplates>\n\n                            <ListBox.ItemsPanel>\n                                <ItemsPanelTemplate>\n                                    <StackPanel Spacing=\"0\" />\n                                </ItemsPanelTemplate>\n                            </ListBox.ItemsPanel>\n                        </ListBox>\n                    </Grid>\n\n\n                </ScrollViewer>\n            </ControlTemplate>\n        </Setter>\n\n        <!--<Style Selector=\"^:editEnabled\">\n            <Style Selector=\"^ /template/ Expander\">\n                <Setter Property=\"IsEnabled\" Value=\"False\" />\n                <Setter Property=\"Background\" Value=\"Magenta\"/>\n            </Style>\n            <Style Selector=\"^ /template/ controls|DraggableListBox#PART_ListBox Expander\">\n                <Setter Property=\"IsEnabled\" Value=\"False\" />\n            </Style>\n        </Style>-->\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/StackEditableCard.axaml.cs",
    "content": "﻿using System;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Metadata;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Interactivity;\nusing Avalonia.LogicalTree;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Core.Extensions;\n#pragma warning disable CS0657 // Not a valid attribute location for this declaration\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[PseudoClasses(\":editEnabled\")]\n[RegisterTransient<StackEditableCard>]\npublic class StackEditableCard : TemplatedControlBase\n{\n    private ListBox? listBoxPart;\n\n    // ReSharper disable once MemberCanBePrivate.Global\n    public static readonly StyledProperty<bool> IsListBoxEditEnabledProperty = AvaloniaProperty.Register<\n        StackEditableCard,\n        bool\n    >(\"IsListBoxEditEnabled\");\n\n    public bool IsListBoxEditEnabled\n    {\n        get => GetValue(IsListBoxEditEnabledProperty);\n        set => SetValue(IsListBoxEditEnabledProperty, value);\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        listBoxPart = e.NameScope.Find<ListBox>(\"PART_ListBox\");\n        if (listBoxPart != null)\n        {\n            // Register handlers to attach container behavior\n\n            // Forward container index changes to view model\n            ((IChildIndexProvider)listBoxPart).ChildIndexChanged += (_, args) =>\n            {\n                if (args.Child is Control { DataContext: StackExpanderViewModel vm })\n                {\n                    vm.OnContainerIndexChanged(args.Index);\n                }\n            };\n        }\n\n        if (e.NameScope.Find<Button>(\"PART_AddButton\") is { } addButton)\n        {\n            addButton.Flyout = GetAddButtonFlyout();\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        base.OnLoaded(e);\n\n        UpdatePseudoClasses(IsListBoxEditEnabled);\n    }\n\n    private void UpdatePseudoClasses(bool editEnabled)\n    {\n        PseudoClasses.Set(\":editEnabled\", IsListBoxEditEnabled);\n\n        listBoxPart?.Classes.Set(\"draggableVirtualizing\", IsListBoxEditEnabled);\n    }\n\n    /// <inheritdoc />\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        if (change.Property == IsListBoxEditEnabledProperty)\n        {\n            UpdatePseudoClasses(change.GetNewValue<bool>());\n        }\n    }\n\n    private FAMenuFlyout GetAddButtonFlyout()\n    {\n        var vm = (DataContext as StackEditableCardViewModel)!;\n        var flyout = new FAMenuFlyout();\n\n        foreach (var moduleType in vm.AvailableModules)\n        {\n            var menuItem = new MenuFlyoutItem\n            {\n                Text = GetModuleDisplayName(moduleType),\n                Command = vm.AddModuleCommand,\n                CommandParameter = moduleType,\n            };\n            flyout.Items.Add(menuItem);\n        }\n\n        return flyout;\n    }\n\n    private static string GetModuleDisplayName(Type moduleType)\n    {\n        var name = moduleType.Name;\n        name = name.StripEnd(\"Module\");\n\n        // Add a space between lower and upper case letters, unless one part is 1 letter long\n        /*name = Regex.Replace(name, @\"(\\P{Ll})(\\P{Lu})\", \"$1 $2\");*/\n\n        return name;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/StackExpander.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:fluent=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:local=\"clr-namespace:StabilityMatrix.Avalonia\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"vmInference:StackExpanderViewModel\">\n\n    <Design.PreviewWith>\n        <Grid Width=\"500\" Height=\"800\">\n            <StackPanel>\n                <controls:StackExpander DataContext=\"{x:Static mocks:DesignData.StackExpanderViewModel}\" />\n\n                <controls:StackExpander DataContext=\"{x:Static mocks:DesignData.StackExpanderViewModel2}\" />\n            </StackPanel>\n        </Grid>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|StackExpander\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"CornerRadius\" Value=\"8\" />\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Expander\n                    MinHeight=\"0\"\n                    HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\"\n                    VerticalAlignment=\"{TemplateBinding VerticalAlignment}\"\n                    HorizontalContentAlignment=\"Stretch\"\n                    VerticalContentAlignment=\"Top\"\n                    CornerRadius=\"{TemplateBinding CornerRadius}\"\n                    ExpandDirection=\"{TemplateBinding ExpandDirection}\"\n                    IsExpanded=\"{TemplateBinding IsExpanded}\">\n\n                    <Expander.Styles>\n                        <Style Selector=\"Expander /template/ ToggleButton#PART_toggle\">\n                            <Setter Property=\"HorizontalContentAlignment\" Value=\"Stretch\" />\n                        </Style>\n                    </Expander.Styles>\n\n                    <Expander.Header>\n                        <Grid Margin=\"0,-4\" ColumnDefinitions=\"Auto,Auto,*,Auto,Auto\">\n                            <ToggleSwitch\n                                Margin=\"2,0,0,4\"\n                                VerticalContentAlignment=\"Center\"\n                                IsChecked=\"{Binding IsEnabled}\"\n                                OffContent=\"\"\n                                OnContent=\"\" />\n                            <TextBlock\n                                Grid.Column=\"1\"\n                                VerticalAlignment=\"Center\"\n                                IsVisible=\"{Binding TitleExtra, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                                Text=\"{Binding TitleExtra}\" />\n                            <TextBlock\n                                Grid.Column=\"2\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"{Binding Title}\" />\n                            <!--  Settings button  -->\n                            <Button\n                                Grid.Column=\"3\"\n                                Padding=\"12,6\"\n                                HorizontalAlignment=\"Right\"\n                                Classes=\"transparent-full\"\n                                Command=\"{Binding SettingsCommand}\"\n                                IsVisible=\"{Binding IsSettingsEnabled}\">\n                                <fluent:SymbolIcon FontSize=\"17\" Symbol=\"Settings\" />\n                            </Button>\n                            <!--  Delete button for StackEditableCard  -->\n                            <Button\n                                Grid.Column=\"4\"\n                                Padding=\"12,6\"\n                                HorizontalAlignment=\"Right\"\n                                Classes=\"transparent-red\"\n                                Command=\"{Binding RemoveFromParentListCommand}\"\n                                IsVisible=\"{Binding IsEditEnabled}\">\n                                <fluent:SymbolIcon FontSize=\"16\" Symbol=\"Delete\" />\n                            </Button>\n                        </Grid>\n                    </Expander.Header>\n\n                    <Panel>\n                        <ItemsControl VerticalAlignment=\"Top\" ItemsSource=\"{Binding Cards}\">\n\n                            <ItemsControl.Styles>\n                                <Style Selector=\"controls|Card\">\n                                    <Setter Property=\"IsCardVisualsEnabled\" Value=\"False\" />\n                                </Style>\n                            </ItemsControl.Styles>\n\n                            <ItemsControl.DataTemplates>\n                                <local:ViewLocator />\n                            </ItemsControl.DataTemplates>\n\n                            <ItemsControl.ItemsPanel>\n                                <ItemsPanelTemplate>\n                                    <StackPanel\n                                        x:Name=\"PART_ItemsPanel\"\n                                        VerticalAlignment=\"Top\"\n                                        Spacing=\"{Binding $parent[controls:StackExpander].Spacing}\" />\n                                </ItemsPanelTemplate>\n                            </ItemsControl.ItemsPanel>\n                        </ItemsControl>\n                    </Panel>\n\n\n                </Expander>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/StackExpander.axaml.cs",
    "content": "﻿using Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<StackExpander>]\npublic class StackExpander : TemplatedControlBase\n{\n    public static readonly StyledProperty<bool> IsExpandedProperty =\n        Expander.IsExpandedProperty.AddOwner<StackExpander>();\n\n    public static readonly StyledProperty<ExpandDirection> ExpandDirectionProperty =\n        Expander.ExpandDirectionProperty.AddOwner<StackExpander>();\n\n    public static readonly StyledProperty<int> SpacingProperty = AvaloniaProperty.Register<StackCard, int>(\n        \"Spacing\",\n        8\n    );\n\n    public ExpandDirection ExpandDirection\n    {\n        get => GetValue(ExpandDirectionProperty);\n        set => SetValue(ExpandDirectionProperty, value);\n    }\n\n    public bool IsExpanded\n    {\n        get => GetValue(IsExpandedProperty);\n        set => SetValue(IsExpandedProperty, value);\n    }\n\n    public int Spacing\n    {\n        get => GetValue(SpacingProperty);\n        set => SetValue(SpacingProperty, value);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/TiledVAECard.axaml",
    "content": "<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    x:DataType=\"vmInference:TiledVAECardViewModel\">\n    <Design.PreviewWith>\n        <controls:TiledVAECard />\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|TiledVAECard\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card x:Name=\"PART_Card\">\n                    <controls:Card.Styles>\n                        <Style Selector=\"ui|NumberBox\">\n                            <Setter Property=\"Margin\" Value=\"12,0,0,0\" />\n                            <Setter Property=\"MinWidth\" Value=\"70\" />\n                            <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n                            <Setter Property=\"ValidationMode\" Value=\"InvalidInputOverwritten\" />\n                            <Setter Property=\"SmallChange\" Value=\"32\" />\n                            <Setter Property=\"LargeChange\" Value=\"128\" />\n                            <Setter Property=\"SpinButtonPlacementMode\" Value=\"Inline\" />\n                        </Style>\n                    </controls:Card.Styles>\n                    <StackPanel Spacing=\"8\">\n                        <!--  Tile Size  -->\n                        <Grid ColumnDefinitions=\"Auto,*\">\n                            <TextBlock\n                                Grid.Column=\"0\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"Tile Size\" />\n                            <ui:NumberBox\n                                Grid.Column=\"1\"\n                                Value=\"{Binding TileSize, Mode=TwoWay}\" />\n                        </Grid>\n\n                        <!--  Overlap  -->\n                        <Grid ColumnDefinitions=\"Auto,*\">\n                            <TextBlock\n                                Grid.Column=\"0\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"Overlap\" />\n                            <ui:NumberBox\n                                Grid.Column=\"1\"\n                                Value=\"{Binding Overlap, Mode=TwoWay}\" />\n                        </Grid>\n\n                        <!--  Temporal Size (for Video VAEs)  -->\n                        <Grid ColumnDefinitions=\"Auto,*\">\n                            <TextBlock\n                                Grid.Column=\"0\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"Temporal Size\" />\n                            <ui:NumberBox\n                                Grid.Column=\"1\"\n                                Value=\"{Binding TemporalSize, Mode=TwoWay}\" />\n                        </Grid>\n\n                        <!--  Temporal Overlap (for Video VAEs)  -->\n                        <Grid ColumnDefinitions=\"Auto,*\">\n                            <TextBlock\n                                Grid.Column=\"0\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"Temporal Overlap\" />\n                            <ui:NumberBox\n                                Grid.Column=\"1\"\n                                Value=\"{Binding TemporalOverlap, Mode=TwoWay}\"\n                                SmallChange=\"4\"\n                                LargeChange=\"16\" />\n                        </Grid>\n                    </StackPanel>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/TiledVAECard.axaml.cs",
    "content": "using Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<TiledVAECard>]\npublic class TiledVAECard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/UnetModelCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:inference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    x:DataType=\"inference:UnetModelCardViewModel\">\n    <Design.PreviewWith>\n        <Panel Width=\"400\" Height=\"400\">\n            <StackPanel Width=\"300\" VerticalAlignment=\"Center\" Spacing=\"32\">\n                <controls:UnetModelCard />\n            </StackPanel>\n        </Panel>\n    </Design.PreviewWith>\n    \n    <Style Selector=\"controls|UnetModelCard\">\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"8\">\n                    <Grid\n                        ColumnDefinitions=\"Auto,*\"\n                        RowDefinitions=\"*,*,*,*,*\">\n                        <!--  Model  -->\n                        <TextBlock\n                            Grid.Row=\"0\"\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Margin=\"4\"\n                            Text=\"{x:Static lang:Resources.Label_Model}\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterComboBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"4\"\n                            HorizontalAlignment=\"Stretch\"\n                            ItemsSource=\"{Binding ClientManager.UnetModels}\"\n                            SelectedItem=\"{Binding SelectedModel}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\"/>\n                        \n                        <!--  Model  -->\n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Margin=\"4\"\n                            Text=\"Precision\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterComboBox\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Margin=\"4\"\n                            HorizontalAlignment=\"Stretch\"\n                            ItemsSource=\"{Binding WeightDTypes}\"\n                            SelectedItem=\"{Binding SelectedDType}\" />\n\n                        <!--  VAE  -->\n                        <TextBlock\n                            Grid.Row=\"2\"\n                            Grid.Column=\"0\"\n                            Margin=\"4\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"{x:Static lang:Resources.Label_VAE}\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterComboBox\n                            Grid.Row=\"2\"\n                            Grid.Column=\"1\"\n                            Margin=\"4\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsTextSearchEnabled=\"True\"\n                            ItemsSource=\"{Binding ClientManager.VaeModels}\"\n                            SelectedItem=\"{Binding SelectedVae}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\"/>\n\n                        <!--  CLIP Selection 1  -->\n                        <TextBlock\n                            Grid.Row=\"3\"\n                            Grid.Column=\"0\"\n                            Margin=\"4\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"CLIP 1\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterDownloadableComboBox\n                            Grid.Row=\"3\"\n                            Grid.Column=\"1\"\n                            Margin=\"4\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsTextSearchEnabled=\"True\"\n                            ItemsSource=\"{Binding ClientManager.ClipModels}\"\n                            SelectedItem=\"{Binding SelectedClip1}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\"/>\n\n                        <!--  CLIP Selection 2  -->\n                        <TextBlock\n                            Grid.Row=\"4\"\n                            Grid.Column=\"0\"\n                            Margin=\"4\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"CLIP 2\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterDownloadableComboBox\n                            Grid.Row=\"4\"\n                            Grid.Column=\"1\"\n                            Margin=\"4\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsTextSearchEnabled=\"True\"\n                            ItemsSource=\"{Binding ClientManager.ClipModels}\"\n                            SelectedItem=\"{Binding SelectedClip2}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\"/>\n                    </Grid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/UnetModelCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<UnetModelCard>]\npublic class UnetModelCard : TemplatedControlBase;\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/UpscalerCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:comfy=\"clr-namespace:StabilityMatrix.Core.Models.Api.Comfy;assembly=StabilityMatrix.Core\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:data=\"clr-namespace:FluentAvalonia.UI.Data;assembly=FluentAvalonia\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmInference=\"using:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    x:DataType=\"vmInference:UpscalerCardViewModel\">\n    <Design.PreviewWith>\n        <Grid MinWidth=\"300\">\n            <StackPanel Spacing=\"16\">\n                <controls:UpscalerCard DataContext=\"{x:Static mocks:DesignData.UpscalerCardViewModel}\" />\n                <controls:UpscalerCard \n                    DataContext=\"{x:Static mocks:DesignData.UpscalerCardViewModel}\" />\n            </StackPanel>\n        </Grid>\n    </Design.PreviewWith>\n    \n    <Style Selector=\"controls|UpscalerCard\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card x:Name=\"PART_Card\">\n                    <StackPanel Spacing=\"8\">\n                        <Grid ColumnDefinitions=\"Auto,*\" RowDefinitions=\"*,*,*\">\n                            <!-- Upscaler -->\n                            <TextBlock\n                                Grid.Row=\"0\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,0,4,8\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"Upscaler\" />\n                            <controls:FADownloadableComboBox\n                                Grid.Row=\"0\"\n                                Grid.Column=\"1\"\n                                ItemsSource=\"{Binding ClientManager.Upscalers}\"\n                                SelectedItem=\"{Binding SelectedUpscaler}\"\n                                Margin=\"8,0,0,8\"\n                                HorizontalAlignment=\"Stretch\">\n                                \n                                <ui:FAComboBox.DataTemplates>\n                                    <controls:ComfyUpscalerTemplateSelector>\n                                        <DataTemplate DataType=\"comfy:ComfyUpscaler\" x:Key=\"{x:Static comfy:ComfyUpscalerType.DownloadableModel}\">\n                                            <Grid ColumnDefinitions=\"*,Auto\">\n                                                <TextBlock\n                                                    Text=\"{Binding DisplayName}\"\n                                                    Foreground=\"{DynamicResource ThemeGreyColor}\"/>\n                                                <Button \n                                                    Grid.Column=\"1\" \n                                                    Classes=\"transparent-full\"\n                                                    Margin=\"8,0,0,0\"\n                                                    Padding=\"0\">\n                                                    <fluentIcons:SymbolIcon\n                                                        VerticalAlignment=\"Center\"\n                                                        FontSize=\"18\"\n                                                        Foreground=\"{DynamicResource ThemeGreyColor}\"\n                                                        IconVariant=\"Filled\"\n                                                        Symbol=\"CloudArrowDown\" />\n                                                </Button>\n                                            </Grid>\n                                        </DataTemplate>\n                                        <DataTemplate DataType=\"comfy:ComfyUpscaler\" x:Key=\"{x:Static comfy:ComfyUpscalerType.None}\">\n                                            <TextBlock Text=\"{Binding DisplayName}\"/>\n                                        </DataTemplate>\n                                    </controls:ComfyUpscalerTemplateSelector>\n                                </ui:FAComboBox.DataTemplates>\n                            </controls:FADownloadableComboBox>\n                        </Grid>\n                        <!-- Dimensions (Scale) -->\n                        <StackPanel>\n                            <Grid ColumnDefinitions=\"*,Auto\">\n                                <TextBlock\n                                    VerticalAlignment=\"Center\"\n                                    Text=\"Upscale by\"/>\n                                <ui:NumberBox\n                                    Grid.Column=\"1\"\n                                    Margin=\"4,0,0,0\"\n                                    ValidationMode=\"InvalidInputOverwritten\"\n                                    SmallChange=\"0.5\"\n                                    SimpleNumberFormat=\"F2\"\n                                    Value=\"{Binding Scale}\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    SpinButtonPlacementMode=\"Compact\"/>\n                            </Grid>\n                            <Slider\n                                Minimum=\"1\"\n                                Maximum=\"4\"\n                                Value=\"{Binding Scale}\"\n                                IsSnapToTickEnabled=\"True\"\n                                TickFrequency=\"0.5\"\n                                TickPlacement=\"BottomRight\"/>\n                        </StackPanel>\n\n                    </StackPanel>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/UpscalerCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<UpscalerCard>]\npublic class UpscalerCard : TemplatedControlBase;\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/WanModelCard.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:fluent=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:inference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    x:DataType=\"inference:WanModelCardViewModel\">\n    <Design.PreviewWith>\n        <Panel Width=\"400\" Height=\"400\">\n            <controls:WanModelCard DataContext=\"{x:Static mocks:DesignData.WanModelCardViewModel}\" />\n        </Panel>\n    </Design.PreviewWith>\n\n    <Styles.Resources>\n        <fluent:SymbolIconSource\n            x:Key=\"IconCube\"\n            IconVariant=\"Filled\"\n            Symbol=\"Cube\" />\n        <fluent:SymbolIconSource\n            x:Key=\"IconQuestionCircle\"\n            IconVariant=\"Filled\"\n            Symbol=\"QuestionCircle\" />\n        <fluent:SymbolIconSource\n            x:Key=\"IconTableCellEdit\"\n            IconVariant=\"Filled\"\n            Symbol=\"TableCellEdit\" />\n    </Styles.Resources>\n\n    <Style Selector=\"controls|WanModelCard\">\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"12\">\n                    <sg:SpacedGrid\n                        ColumnDefinitions=\"Auto,*\"\n                        ColumnSpacing=\"8\"\n                        RowDefinitions=\"*,*,*,*,*,*,*\"\n                        RowSpacing=\"0\">\n                        <!--  Model  -->\n                        <TextBlock\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"{x:Static lang:Resources.Label_Model}\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterComboBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            ItemsSource=\"{Binding ClientManager.UnetModels}\"\n                            SelectedItem=\"{Binding SelectedModel}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                        <!--  model precision  -->\n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Precision\"\n                            TextAlignment=\"Left\" />\n\n                        <ui:FAComboBox\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsTextSearchEnabled=\"True\"\n                            ItemsSource=\"{Binding WeightDTypes}\"\n                            SelectedItem=\"{Binding SelectedDType}\" />\n\n                        <!--  VAE  -->\n                        <TextBlock\n                            Grid.Row=\"2\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"{x:Static lang:Resources.Label_VAE}\"\n                            TextAlignment=\"Left\" />\n\n                        <ui:FAComboBox\n                            Grid.Row=\"2\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            DisplayMemberBinding=\"{Binding ShortDisplayName}\"\n                            IsTextSearchEnabled=\"True\"\n                            ItemsSource=\"{Binding ClientManager.VaeModels}\"\n                            SelectedItem=\"{Binding SelectedVae}\" />\n\n                        <!--  Text Encoder  -->\n                        <TextBlock\n                            Grid.Row=\"3\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Text Encoder\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterDownloadableComboBox\n                            Grid.Row=\"3\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,8,0,0\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            ItemsSource=\"{Binding ClientManager.ClipModels}\"\n                            SelectedItem=\"{Binding SelectedClipModel}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                        <!--  CLIP Vision  -->\n                        <TextBlock\n                            Grid.Row=\"4\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsClipVisionEnabled}\"\n                            Text=\"CLIP Vision\"\n                            TextAlignment=\"Left\" />\n\n                        <controls:BetterDownloadableComboBox\n                            Grid.Row=\"4\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,8,0,0\"\n                            Padding=\"8,6,4,6\"\n                            HorizontalAlignment=\"Stretch\"\n                            IsVisible=\"{Binding IsClipVisionEnabled}\"\n                            ItemsSource=\"{Binding ClientManager.ClipVisionModels}\"\n                            SelectedItem=\"{Binding SelectedClipVisionModel}\"\n                            Theme=\"{StaticResource BetterComboBoxHybridModelTheme}\" />\n\n                        <!--  Shift  -->\n                        <TextBlock\n                            Grid.Row=\"5\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,0,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Shift\" />\n                        <ui:NumberBox\n                            Grid.Row=\"5\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            SelectionHighlightColor=\"Transparent\"\n                            SimpleNumberFormat=\"F2\"\n                            SmallChange=\"0.25\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            Value=\"{Binding Shift}\" />\n\n                        <!--  Extra Networks  -->\n                        <Panel\n                            Grid.Row=\"6\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"2\">\n                            <controls:StackEditableCard Margin=\"0,8,0,0\" DataContext=\"{Binding ExtraNetworksStackCardViewModel}\" />\n                        </Panel>\n                    </sg:SpacedGrid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Inference/WanModelCard.axaml.cs",
    "content": "﻿using Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<WanModelCard>]\npublic class WanModelCard : TemplatedControlBase;\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/LaunchOptionCardTemplateSelector.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Templates;\nusing Avalonia.Metadata;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class LaunchOptionCardTemplateSelector : IDataTemplate\n{\n    // public bool SupportsRecycling => false;\n    \n    // ReSharper disable once CollectionNeverUpdated.Global\n    [Content]\n    public Dictionary<LaunchOptionType, IDataTemplate> Templates { get; } = new();\n\n    // Check if we can accept the provided data\n    public bool Match(object? data)\n    {\n        return data is LaunchOptionCard;\n    }\n\n    // Build the DataTemplate here\n    public Control Build(object? data)\n    {\n        if (data is not LaunchOptionCard card) throw new ArgumentException(null, nameof(data));\n        return Templates[card.Type].Build(card)!;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/LineDashFrame.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing Avalonia;\nusing Avalonia.Media;\nusing FluentAvalonia.UI.Controls;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class LineDashFrame : Frame\n{\n    protected override Type StyleKeyOverride { get; } = typeof(Frame);\n\n    public static readonly StyledProperty<ISolidColorBrush> StrokeProperty = AvaloniaProperty.Register<\n        LineDashFrame,\n        ISolidColorBrush\n    >(\"Stroke\");\n\n    public ISolidColorBrush Stroke\n    {\n        get => GetValue(StrokeProperty);\n        set => SetValue(StrokeProperty, value);\n    }\n\n    public static readonly StyledProperty<double> StrokeThicknessProperty = AvaloniaProperty.Register<\n        LineDashFrame,\n        double\n    >(\"StrokeThickness\");\n\n    public double StrokeThickness\n    {\n        get => GetValue(StrokeThicknessProperty);\n        set => SetValue(StrokeThicknessProperty, value);\n    }\n\n    public static readonly StyledProperty<double> StrokeDashLineProperty = AvaloniaProperty.Register<\n        LineDashFrame,\n        double\n    >(\"StrokeDashLine\");\n\n    public double StrokeDashLine\n    {\n        get => GetValue(StrokeDashLineProperty);\n        set => SetValue(StrokeDashLineProperty, value);\n    }\n\n    public static readonly StyledProperty<double> StrokeDashSpaceProperty = AvaloniaProperty.Register<\n        LineDashFrame,\n        double\n    >(\"StrokeDashSpace\");\n\n    public double StrokeDashSpace\n    {\n        get => GetValue(StrokeDashSpaceProperty);\n        set => SetValue(StrokeDashSpaceProperty, value);\n    }\n\n    public static readonly StyledProperty<ISolidColorBrush> FillProperty = AvaloniaProperty.Register<\n        LineDashFrame,\n        ISolidColorBrush\n    >(\"Fill\");\n\n    public ISolidColorBrush Fill\n    {\n        get => GetValue(FillProperty);\n        set => SetValue(FillProperty, value);\n    }\n\n    public LineDashFrame()\n    {\n        UseLayoutRounding = true;\n    }\n\n    /// <inheritdoc />\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        if (\n            change.Property == StrokeProperty\n            || change.Property == StrokeThicknessProperty\n            || change.Property == StrokeDashLineProperty\n            || change.Property == StrokeDashSpaceProperty\n            || change.Property == FillProperty\n        )\n        {\n            InvalidateVisual();\n        }\n    }\n\n    /// <inheritdoc />\n    public override void Render(DrawingContext context)\n    {\n        var width = Bounds.Width;\n        var height = Bounds.Height;\n\n        context.DrawRectangle(Fill, null, new Rect(0, 0, width, height));\n\n        var dashPen = new Pen(Stroke, StrokeThickness) { DashStyle = new DashStyle(GetDashArray(width), 0) };\n\n        context.DrawLine(dashPen, new Point(0, 0), new Point(width, 0));\n        context.DrawLine(dashPen, new Point(0, height), new Point(width, height));\n        context.DrawLine(dashPen, new Point(0, 0), new Point(0, height));\n        context.DrawLine(dashPen, new Point(width, 0), new Point(width, height));\n    }\n\n    private IEnumerable<double> GetDashArray(double length)\n    {\n        var availableLength = length - StrokeDashLine;\n        var lines = (int)Math.Round(availableLength / (StrokeDashLine + StrokeDashSpace));\n        availableLength -= lines * StrokeDashLine;\n        var actualSpacing = availableLength / lines;\n\n        yield return StrokeDashLine / StrokeThickness;\n        yield return actualSpacing / StrokeThickness;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/MarkdownViewer.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"clr-namespace:TheArtOfDev.HtmlRenderer.Avalonia;assembly=Avalonia.HtmlRenderer\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\">\n    <Design.PreviewWith>\n        <controls:MarkdownViewer\n            MaxWidth=\"800\"\n            MaxHeight=\"800\"\n            Padding=\"8\"\n            Text=\"## Hello World\" />\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|MarkdownViewer\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <avalonia:HtmlPanel\n                    x:Name=\"PART_HtmlPanel\"\n                    Background=\"Transparent\"\n                    Text=\"{TemplateBinding Html}\" />\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/MarkdownViewer.axaml.cs",
    "content": "﻿using System.IO;\nusing Avalonia;\nusing Avalonia.Controls.Primitives;\nusing Markdig;\nusing TheArtOfDev.HtmlRenderer.Avalonia;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic class MarkdownViewer : TemplatedControlBase\n{\n    public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<\n        MarkdownViewer,\n        string\n    >(nameof(Text));\n\n    public string Text\n    {\n        get => GetValue(TextProperty);\n        set\n        {\n            SetValue(TextProperty, value);\n            ParseText(value);\n        }\n    }\n\n    public static readonly StyledProperty<string> HtmlProperty = AvaloniaProperty.Register<\n        MarkdownViewer,\n        string\n    >(nameof(Html));\n\n    private string Html\n    {\n        get => GetValue(HtmlProperty);\n        set => SetValue(HtmlProperty, value);\n    }\n\n    public static readonly StyledProperty<string> CustomCssProperty = AvaloniaProperty.Register<\n        MarkdownViewer,\n        string\n    >(nameof(CustomCss));\n\n    public string CustomCss\n    {\n        get => GetValue(CustomCssProperty);\n        set => SetValue(CustomCssProperty, value);\n    }\n\n    private void ParseText(string value)\n    {\n        if (string.IsNullOrWhiteSpace(value))\n            return;\n\n        var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();\n        var html =\n            $\"\"\"<html><body class=\"markdown-body\">{Markdig.Markdown.ToHtml(value, pipeline)}</body></html>\"\"\";\n        Html = html;\n    }\n\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        if (e.NameScope.Find(\"PART_HtmlPanel\") is not HtmlPanel htmlPanel)\n            return;\n\n        using var cssFile = Assets.MarkdownCss.Open();\n        using var reader = new StreamReader(cssFile);\n        var css = reader.ReadToEnd();\n\n        htmlPanel.BaseStylesheet = $\"{css}\\n{CustomCss}\";\n\n        if (string.IsNullOrWhiteSpace(Html) && !string.IsNullOrWhiteSpace(Text))\n        {\n            ParseText(Text);\n        }\n    }\n\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        if (change.Property == TextProperty && change.NewValue != null)\n        {\n            ParseText(change.NewValue.ToString());\n        }\n\n        base.OnPropertyChanged(change);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Models/GitVersionSelectorVersionType.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Controls.Models;\n\npublic enum GitVersionSelectorVersionType\n{\n    BranchCommit,\n    Tag\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Models/PenPath.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Text.Json.Serialization;\nusing SkiaSharp;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Avalonia.Controls.Models;\n\npublic readonly record struct PenPath()\n{\n    [JsonConverter(typeof(SKColorJsonConverter))]\n    public SKColor FillColor { get; init; }\n\n    public bool IsErase { get; init; }\n\n    public List<PenPoint> Points { get; init; } = [];\n\n    public SKPath ToSKPath()\n    {\n        var skPath = new SKPath();\n\n        if (Points.Count <= 0)\n        {\n            return skPath;\n        }\n\n        // First move to the first point\n        skPath.MoveTo(Points[0].X, Points[0].Y);\n\n        // Add the rest of the points\n        for (var i = 1; i < Points.Count; i++)\n        {\n            skPath.LineTo(Points[i].X, Points[i].Y);\n        }\n\n        return skPath;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Models/PenPoint.cs",
    "content": "﻿using System;\nusing SkiaSharp;\n\nnamespace StabilityMatrix.Avalonia.Controls.Models;\n\npublic readonly record struct PenPoint(ulong X, ulong Y)\n{\n    public PenPoint(double x, double y)\n        : this(Convert.ToUInt64(x), Convert.ToUInt64(y)) { }\n\n    public PenPoint(SKPoint skPoint)\n        : this(Convert.ToUInt64(skPoint.X), Convert.ToUInt64(skPoint.Y)) { }\n\n    /// <summary>\n    /// Radius of the point.\n    /// </summary>\n    public double Radius { get; init; } = 1;\n\n    /// <summary>\n    /// Optional pressure of the point. If null, the pressure is unknown.\n    /// </summary>\n    public double? Pressure { get; init; }\n\n    /// <summary>\n    /// True if the point was created by a pen, false if it was created by a mouse.\n    /// </summary>\n    public bool IsPen { get; init; }\n\n    public SKPoint ToSKPoint() => new(X, Y);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Models/SKLayer.cs",
    "content": "﻿using System.Collections.Immutable;\nusing SkiaSharp;\n\nnamespace StabilityMatrix.Avalonia.Controls.Models;\n\npublic class SKLayer\n{\n    /// <summary>\n    /// Surface from Canvas that contains the layer.\n    /// </summary>\n    public SKSurface? Surface { get; set; }\n\n    /// <summary>\n    /// Optional bitmaps that will be drawn on the layer, in order.\n    /// (Last index will be drawn on top over previous ones)\n    /// </summary>\n    public ImmutableList<SKBitmap> Bitmaps { get; set; } = [];\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Paginator.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:icons=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\">\n    <Design.PreviewWith>\n        <controls:Paginator TotalPages=\"5\" CurrentPageNumber=\"1\" />\n    </Design.PreviewWith>\n    \n    <Style Selector=\"controls|Paginator\">\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <StackPanel\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Orientation=\"Vertical\">\n                    <TextBlock Margin=\"0,0,4,4\" TextAlignment=\"Center\">\n                        <Run Text=\"{x:Static lang:Resources.Label_Page}\" />\n                        <Run Text=\"{TemplateBinding CurrentPageNumber, Converter={x:Static converters:StringFormatConverters.Decimal}}\" />\n                        <Run Text=\"/\" />\n                        <Run Text=\"{TemplateBinding TotalPages, Converter={x:Static converters:StringFormatConverters.Decimal}}\" />\n                    </TextBlock>\n                    <StackPanel HorizontalAlignment=\"Center\" Orientation=\"Horizontal\">\n                        <Button\n                            Margin=\"0,0,8,0\"\n                            IsEnabled=\"{TemplateBinding CanNavBack}\"\n                            Command=\"{TemplateBinding FirstPageCommand}\"\n                            ToolTip.ShowDelay=\"1000\"\n                            ToolTip.Tip=\"{x:Static lang:Resources.Label_FirstPage}\">\n                            <icons:Icon Value=\"fa-solid fa-backward-fast\" />\n                        </Button>\n                        <Button\n                            Margin=\"0,0,8,0\"\n                            IsEnabled=\"{TemplateBinding CanNavBack}\"\n                            Command=\"{TemplateBinding PreviousPageCommand}\"\n                            ToolTip.ShowDelay=\"1000\"\n                            ToolTip.Tip=\"{x:Static lang:Resources.Label_PreviousPage}\">\n                            <icons:Icon Value=\"fa-solid fa-caret-left\" />\n                        </Button>\n                        <Button\n                            Margin=\"0,0,8,0\"\n                            IsEnabled=\"{TemplateBinding CanNavForward}\"\n                            Command=\"{TemplateBinding NextPageCommand}\"\n                            ToolTip.ShowDelay=\"1000\"\n                            ToolTip.Tip=\"{x:Static lang:Resources.Label_NextPage}\">\n                            <icons:Icon Value=\"fa-solid fa-caret-right\" />\n                        </Button>\n                        <Button \n                            Command=\"{TemplateBinding LastPageCommand}\" \n                            IsEnabled=\"{TemplateBinding CanNavForward}\"\n                            ToolTip.ShowDelay=\"1000\"\n                            ToolTip.Tip=\"{x:Static lang:Resources.Label_LastPage}\">\n                            <icons:Icon Value=\"fa-solid fa-forward-fast\" />\n                        </Button>\n                    </StackPanel>\n                </StackPanel>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Paginator.axaml.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Windows.Input;\nusing Avalonia;\nusing Avalonia.Controls.Primitives;\nusing CommunityToolkit.Mvvm.Input;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class Paginator : TemplatedControlBase\n{\n    private bool isFirstTemplateApplied;\n    private ICommand? firstPageCommandBinding;\n    private ICommand? previousPageCommandBinding;\n    private ICommand? nextPageCommandBinding;\n    private ICommand? lastPageCommandBinding;\n\n    public static readonly StyledProperty<int> CurrentPageNumberProperty = AvaloniaProperty.Register<\n        Paginator,\n        int\n    >(\"CurrentPageNumber\", 1);\n\n    public int CurrentPageNumber\n    {\n        get => GetValue(CurrentPageNumberProperty);\n        set => SetValue(CurrentPageNumberProperty, value);\n    }\n\n    public static readonly StyledProperty<int> TotalPagesProperty = AvaloniaProperty.Register<Paginator, int>(\n        \"TotalPages\",\n        1\n    );\n\n    public int TotalPages\n    {\n        get => GetValue(TotalPagesProperty);\n        set => SetValue(TotalPagesProperty, value);\n    }\n\n    public static readonly StyledProperty<ICommand?> FirstPageCommandProperty = AvaloniaProperty.Register<\n        Paginator,\n        ICommand?\n    >(\"FirstPageCommand\");\n\n    public ICommand? FirstPageCommand\n    {\n        get => GetValue(FirstPageCommandProperty);\n        set => SetValue(FirstPageCommandProperty, value);\n    }\n\n    public static readonly StyledProperty<ICommand?> PreviousPageCommandProperty = AvaloniaProperty.Register<\n        Paginator,\n        ICommand?\n    >(\"PreviousPageCommand\");\n\n    public ICommand? PreviousPageCommand\n    {\n        get => GetValue(PreviousPageCommandProperty);\n        set => SetValue(PreviousPageCommandProperty, value);\n    }\n\n    public static readonly StyledProperty<ICommand?> NextPageCommandProperty = AvaloniaProperty.Register<\n        Paginator,\n        ICommand?\n    >(\"NextPageCommand\");\n\n    public ICommand? NextPageCommand\n    {\n        get => GetValue(NextPageCommandProperty);\n        set => SetValue(NextPageCommandProperty, value);\n    }\n\n    public static readonly StyledProperty<ICommand?> LastPageCommandProperty = AvaloniaProperty.Register<\n        Paginator,\n        ICommand?\n    >(\"LastPageCommand\");\n\n    public ICommand? LastPageCommand\n    {\n        get => GetValue(LastPageCommandProperty);\n        set => SetValue(LastPageCommandProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> CanNavForwardProperty = AvaloniaProperty.Register<\n        Paginator,\n        bool\n    >(\"CanNavForward\");\n\n    public bool CanNavForward\n    {\n        get => GetValue(CanNavForwardProperty);\n        set => SetValue(CanNavForwardProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> CanNavBackProperty = AvaloniaProperty.Register<\n        Paginator,\n        bool\n    >(\"CanNavBack\");\n\n    public bool CanNavBack\n    {\n        get => GetValue(CanNavBackProperty);\n        set => SetValue(CanNavBackProperty, value);\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        if (!isFirstTemplateApplied)\n        {\n            firstPageCommandBinding = FirstPageCommand;\n            previousPageCommandBinding = PreviousPageCommand;\n            nextPageCommandBinding = NextPageCommand;\n            lastPageCommandBinding = LastPageCommand;\n            isFirstTemplateApplied = true;\n        }\n\n        // Wrap the commands\n        FirstPageCommand = new RelayCommand(() =>\n        {\n            if (CurrentPageNumber > 1)\n            {\n                CurrentPageNumber = 1;\n            }\n            firstPageCommandBinding?.Execute(null);\n        });\n\n        PreviousPageCommand = new RelayCommand(() =>\n        {\n            if (CurrentPageNumber > 1)\n            {\n                CurrentPageNumber--;\n            }\n            previousPageCommandBinding?.Execute(null);\n        });\n\n        NextPageCommand = new RelayCommand(() =>\n        {\n            if (CurrentPageNumber < TotalPages)\n            {\n                CurrentPageNumber++;\n            }\n            nextPageCommandBinding?.Execute(null);\n        });\n\n        LastPageCommand = new RelayCommand(() =>\n        {\n            if (CurrentPageNumber < TotalPages)\n            {\n                CurrentPageNumber = TotalPages;\n            }\n            lastPageCommandBinding?.Execute(null);\n        });\n    }\n\n    /// <inheritdoc />\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        // Update the CanNavForward and CanNavBack properties\n        if (change.Property == CurrentPageNumberProperty && change.NewValue is int)\n        {\n            CanNavForward = (int)change.NewValue < TotalPages;\n            CanNavBack = (int)change.NewValue > 1;\n        }\n        else if (change.Property == TotalPagesProperty && change.NewValue is int)\n        {\n            CanNavForward = CurrentPageNumber < (int)change.NewValue;\n            CanNavBack = CurrentPageNumber > 1;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Painting/PaintCanvas.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmControls=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Controls\"\n    xmlns:faIcons=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    x:DataType=\"vmControls:PaintCanvasViewModel\">\n    <Design.PreviewWith>\n        <controls:PaintCanvas DataContext=\"{x:Static mocks:DesignData.PaintCanvasViewModel}\" />\n    </Design.PreviewWith>\n    \n    <Styles.Resources>\n        <converters:EnumToBooleanConverter x:Key=\"EnumBoolConverter\" />\n    </Styles.Resources>\n\n    <Style Selector=\"controls|PaintCanvas\">\n        <Setter Property=\"Background\" Value=\"Transparent\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Grid RowDefinitions=\"Auto,Auto\">\n                    <Grid.Resources>\n                        <input:StandardUICommand\n                            x:Key=\"ClearCanvasCommand\"\n                            Command=\"{Binding ClearCanvasCommand, RelativeSource={RelativeSource TemplatedParent}}\"\n                            IconSource=\"Clear\"\n                            Label=\"Clear\" />\n                        <input:StandardUICommand\n                            x:Key=\"UndoCommand\"\n                            Command=\"{Binding UndoCommand}\"\n                            HotKey=\"Ctrl+Z\"\n                            IconSource=\"Undo\"\n                            Label=\"Undo\" />\n                    </Grid.Resources>\n                    <Grid.Styles>\n                        <!--  Change cursor to crosshair when mouse is over the canvas  -->\n                        <!--<Style Selector=\"controls|SkiaCustomCanvas:pointerover\">\n                            <Setter Property=\"Cursor\" Value=\"Cross\"/>\n                        </Style>\n                        <Style Selector=\"controls|SkiaCustomCanvas:pointerleave\">\n                            <Setter Property=\"Cursor\" Value=\"Arrow\"/>\n                        </Style>-->\n                    </Grid.Styles>\n                    \n                    <!-- Top bar -->\n                    <StackPanel\n                        Orientation=\"Horizontal\"\n                        Spacing=\"6\">\n                        \n                        <!-- Tools -->\n                        <controls:Card Padding=\"4\">\n                            <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                                <ToggleButton IsChecked=\"{Binding SelectedTool, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static models:PaintCanvasTool.PaintBrush}}\">\n                                    <faIcons:Icon\n                                        FontSize=\"18\"\n                                        Value=\"fa-solid fa-paintbrush\" />\n                                </ToggleButton>\n                                <ToggleButton IsChecked=\"{Binding SelectedTool, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static models:PaintCanvasTool.Eraser}}\">\n                                    <faIcons:Icon\n                                        FontSize=\"18\"\n                                        Value=\"fa-solid fa-eraser\" />\n                                </ToggleButton>\n                            </StackPanel>\n                        </controls:Card>\n                        \n                        <GridSplitter BorderThickness=\"1\" Margin=\"0,16\"/>\n\n                        <ui:ColorPickerButton VerticalAlignment=\"Center\" Color=\"{Binding PaintBrushColor}\" />\n                        <Label\n                            Margin=\"8,0,0,0\"\n                            VerticalAlignment=\"Center\"\n                            Content=\"Pressure:\" />\n                        <Label\n                            Margin=\"-4,0,0,0\"\n                            VerticalAlignment=\"Center\"\n                            Content=\"{Binding CurrentPenPressure}\" />\n\n                        <ui:CommandBar Margin=\"0,4\" HorizontalAlignment=\"Right\">\n                            <ui:CommandBar.PrimaryCommands>\n                                <!--  Brush Size  -->\n                                <ui:CommandBarButton>\n                                    <ui:CommandBarButton.IconSource>\n                                        <fluentIcons:SymbolIconSource FontSize=\"12\" Symbol=\"ArrowAutofitWidth\" />\n                                    </ui:CommandBarButton.IconSource>\n                                    <ui:CommandBarButton.Flyout>\n                                        <Flyout>\n                                            <StackPanel>\n                                                <Label Content=\"Brush Size\" />\n                                                <ui:NumberBox\n                                                    MinWidth=\"120\"\n                                                    LargeChange=\"1\"\n                                                    Maximum=\"100\"\n                                                    Minimum=\"0\"\n                                                    SmallChange=\"1\"\n                                                    SpinButtonPlacementMode=\"Inline\"\n                                                    Value=\"{Binding PaintBrushSize}\" />\n                                            </StackPanel>\n                                        </Flyout>\n                                    </ui:CommandBarButton.Flyout>\n                                </ui:CommandBarButton>\n                                <!--  Opacity  -->\n                                <ui:CommandBarButton>\n                                    <ui:CommandBarButton.IconSource>\n                                        <fluentIcons:SymbolIconSource FontSize=\"12\" Symbol=\"CircleHalfFill\" />\n                                    </ui:CommandBarButton.IconSource>\n                                    <ui:CommandBarButton.Flyout>\n                                        <Flyout>\n                                            <StackPanel>\n                                                <Label Content=\"Brush Opacity\" />\n                                                <Slider\n                                                    MinWidth=\"200\"\n                                                    IsSnapToTickEnabled=\"True\"\n                                                    Maximum=\"1\"\n                                                    Minimum=\"0\"\n                                                    SmallChange=\"0.01\"\n                                                    TickFrequency=\"0.01\"\n                                                    Value=\"{Binding PaintBrushAlpha}\" />\n                                            </StackPanel>\n                                        </Flyout>\n                                    </ui:CommandBarButton.Flyout>\n                                </ui:CommandBarButton>\n\n                                <ui:CommandBarButton Command=\"{Binding UndoCommand}\" IconSource=\"Undo\" />\n                                <ui:CommandBarButton Command=\"{StaticResource ClearCanvasCommand}\" />\n\n                            </ui:CommandBar.PrimaryCommands>\n\n                            <!--<ui:CommandBar.SecondaryCommands>\n                                <ui:CommandBarButton Command=\"{StaticResource UndoCommand}\" />\n                                <ui:CommandBarButton Command=\"{StaticResource ClearCanvasCommand}\" />\n                            </ui:CommandBar.SecondaryCommands>-->\n                        </ui:CommandBar>\n                    </StackPanel>\n                    \n                    <ZoomBorder\n                        x:Name=\"PART_ZoomBorder\"\n                        Grid.Row=\"1\"\n                        Margin=\"0,2,0,0\"\n                        HorizontalAlignment=\"Stretch\"\n                        VerticalAlignment=\"Stretch\"\n                        Background=\"SlateBlue\"\n                        ClipToBounds=\"True\"\n                        Focusable=\"True\"\n                        Stretch=\"None\"\n                        ZoomSpeed=\"1.2\">\n                        <controls:SkiaCustomCanvas\n                            x:Name=\"PART_MainCanvas\"\n                            Width=\"1000\"\n                            Height=\"1000\"\n                            HorizontalAlignment=\"Stretch\"\n                            VerticalAlignment=\"Stretch\"\n                            Background=\"Transparent\" />\n                    </ZoomBorder>\n\n                </Grid>\n            </ControlTemplate>\n        </Setter>\n\n\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Painting/PaintCanvas.axaml.cs",
    "content": "﻿using System;\nusing System.Collections.Concurrent;\nusing System.Collections.Immutable;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.PanAndZoom;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Input;\nusing Avalonia.Interactivity;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData.Binding;\nusing SkiaSharp;\nusing StabilityMatrix.Avalonia.Controls.Models;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels.Controls;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic class PaintCanvas : TemplatedControlBase\n{\n    private ConcurrentDictionary<long, PenPath> TemporaryPaths => ViewModel!.TemporaryPaths;\n\n    private ImmutableList<PenPath> Paths\n    {\n        get => ViewModel!.Paths;\n        set => ViewModel!.Paths = value;\n    }\n\n    private IDisposable? viewModelSubscription;\n\n    private bool isPenDown;\n\n    private PaintCanvasViewModel? ViewModel { get; set; }\n\n    private SkiaCustomCanvas? MainCanvas { get; set; }\n\n    static PaintCanvas()\n    {\n        AffectsRender<PaintCanvas>(BoundsProperty);\n    }\n\n    public void RefreshCanvas()\n    {\n        Dispatcher.UIThread.Post(() => MainCanvas?.InvalidateVisual(), DispatcherPriority.Render);\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        MainCanvas = e.NameScope.Find<SkiaCustomCanvas>(\"PART_MainCanvas\");\n\n        Debug.Assert(MainCanvas != null);\n\n        if (MainCanvas is not null)\n        {\n            if (DataContext is PaintCanvasViewModel { CanvasSize: var canvasSize })\n            {\n                MainCanvas.Width = canvasSize.Width;\n                MainCanvas.Height = canvasSize.Height;\n            }\n\n            MainCanvas.RenderSkia += OnRenderSkia;\n            MainCanvas.PointerEntered += MainCanvas_OnPointerEntered;\n            MainCanvas.PointerExited += MainCanvas_OnPointerExited;\n        }\n\n        var zoomBorder = e.NameScope.Find<ZoomBorder>(\"PART_ZoomBorder\");\n        if (zoomBorder is not null)\n        {\n            zoomBorder.ZoomChanged += (_, zoomEventArgs) =>\n            {\n                if (ViewModel is not null)\n                {\n                    ViewModel.CurrentZoom = zoomEventArgs.ZoomX;\n\n                    UpdateCanvasCursor();\n                }\n            };\n\n            if (ViewModel is not null)\n            {\n                ViewModel.CurrentZoom = zoomBorder.ZoomX;\n\n                UpdateCanvasCursor();\n            }\n        }\n\n        OnDataContextChanged(EventArgs.Empty);\n    }\n\n    /// <inheritdoc />\n    protected override void OnDataContextChanged(EventArgs e)\n    {\n        base.OnDataContextChanged(e);\n\n        if (DataContext is PaintCanvasViewModel viewModel)\n        {\n            // Set the remote actions\n            viewModel.RefreshCanvas = RefreshCanvas;\n\n            viewModelSubscription?.Dispose();\n            viewModelSubscription = viewModel\n                .WhenPropertyChanged(vm => vm.CanvasSize)\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe(change =>\n                {\n                    if (MainCanvas is not null && !change.Value.IsEmpty)\n                    {\n                        MainCanvas.Width = change.Value.Width;\n                        MainCanvas.Height = change.Value.Height;\n                        MainCanvas.InvalidateVisual();\n                    }\n                });\n\n            ViewModel = viewModel;\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        if (change.Property == IsEnabledProperty)\n        {\n            var newIsEnabled = change.GetNewValue<bool>();\n\n            if (!newIsEnabled)\n            {\n                isPenDown = false;\n            }\n\n            // On any enabled change, flush temporary paths\n            if (!TemporaryPaths.IsEmpty)\n            {\n                Paths = Paths.AddRange(TemporaryPaths.Values);\n                TemporaryPaths.Clear();\n            }\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        base.OnLoaded(e);\n\n        UpdateMainCanvasBounds();\n    }\n\n    private void HandlePointerEvent(PointerEventArgs e)\n    {\n        // Ignore if disabled\n        if (!IsEnabled)\n        {\n            return;\n        }\n\n        if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch)\n        {\n            TemporaryPaths.TryRemove(e.Pointer.Id, out _);\n            return;\n        }\n\n        e.Handled = true;\n\n        // Must have this or stylus inputs lost after a while\n        // https://github.com/AvaloniaUI/Avalonia/issues/12289#issuecomment-1695620412\n        e.PreventGestureRecognition();\n\n        if (DataContext is not PaintCanvasViewModel viewModel)\n        {\n            return;\n        }\n\n        var currentPoint = e.GetCurrentPoint(this);\n\n        if (e.RoutedEvent == PointerPressedEvent)\n        {\n            // Ignore if mouse and not left button\n            if (e.Pointer.Type == PointerType.Mouse && !currentPoint.Properties.IsLeftButtonPressed)\n            {\n                return;\n            }\n\n            isPenDown = true;\n\n            HandlePointerMoved(e);\n        }\n        else if (e.RoutedEvent == PointerReleasedEvent)\n        {\n            if (isPenDown)\n            {\n                HandlePointerMoved(e);\n\n                isPenDown = false;\n            }\n\n            if (TemporaryPaths.TryGetValue(e.Pointer.Id, out var path))\n            {\n                Paths = Paths.Add(path);\n            }\n\n            TemporaryPaths.TryRemove(e.Pointer.Id, out _);\n        }\n        else\n        {\n            // Moved event\n            if (!isPenDown || currentPoint.Properties.Pressure == 0)\n            {\n                return;\n            }\n\n            HandlePointerMoved(e);\n        }\n\n        Dispatcher.UIThread.Post(() => MainCanvas?.InvalidateVisual(), DispatcherPriority.Render);\n    }\n\n    private void HandlePointerMoved(PointerEventArgs e)\n    {\n        if (DataContext is not PaintCanvasViewModel viewModel)\n        {\n            return;\n        }\n\n        // Use intermediate points to include past events we missed\n        var points = e.GetIntermediatePoints(MainCanvas);\n\n        Debug.WriteLine($\"Points: {string.Join(\",\", points.Select(p => p.Position.ToString()))}\");\n\n        if (points.Count == 0)\n        {\n            return;\n        }\n\n        viewModel.CurrentPenPressure = points.FirstOrDefault().Properties.Pressure;\n\n        // Get or create a temp path\n        if (!TemporaryPaths.TryGetValue(e.Pointer.Id, out var penPath))\n        {\n            penPath = new PenPath\n            {\n                FillColor = viewModel.PaintBrushSKColor.WithAlpha((byte)(viewModel.PaintBrushAlpha * 255)),\n                IsErase = viewModel.SelectedTool == PaintCanvasTool.Eraser\n            };\n            TemporaryPaths[e.Pointer.Id] = penPath;\n        }\n\n        // Add line for path\n        // var cursorPosition = e.GetPosition(MainCanvas);\n        // penPath.Path.LineTo(cursorPosition.ToSKPoint());\n\n        // Get bounds for discarding invalid points\n        var canvasBounds = new Rect(0, 0, MainCanvas?.Bounds.Width ?? 0, MainCanvas?.Bounds.Height ?? 0);\n\n        // Add points\n        foreach (var point in points)\n        {\n            // Discard invalid points\n            if (!canvasBounds.Contains(point.Position) || point.Position.X < 0 || point.Position.Y < 0)\n            {\n                continue;\n            }\n\n            var penPoint = new PenPoint(point.Position.X, point.Position.Y)\n            {\n                Pressure = point.Pointer.Type == PointerType.Mouse ? null : point.Properties.Pressure,\n                Radius = viewModel.PaintBrushSize,\n                IsPen = point.Pointer.Type == PointerType.Pen\n            };\n\n            penPath.Points.Add(penPoint);\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void OnPointerPressed(PointerPressedEventArgs e)\n    {\n        HandlePointerEvent(e);\n        base.OnPointerPressed(e);\n    }\n\n    /// <inheritdoc />\n    protected override void OnPointerReleased(PointerReleasedEventArgs e)\n    {\n        HandlePointerEvent(e);\n        base.OnPointerReleased(e);\n    }\n\n    /// <inheritdoc />\n    protected override void OnPointerMoved(PointerEventArgs e)\n    {\n        HandlePointerEvent(e);\n        base.OnPointerMoved(e);\n    }\n\n    /// <inheritdoc />\n    protected override void OnKeyDown(KeyEventArgs e)\n    {\n        base.OnKeyDown(e);\n\n        if (e.Key == Key.Escape)\n        {\n            e.Handled = true;\n        }\n    }\n\n    /// <summary>\n    /// Update the bounds of the main canvas to match the background image\n    /// </summary>\n    private void UpdateMainCanvasBounds()\n    {\n        if (MainCanvas is null || DataContext is not PaintCanvasViewModel vm)\n        {\n            return;\n        }\n\n        var canvasSize = vm.CanvasSize;\n\n        // Set size if mismatch\n        if (\n            ((int)Math.Round(MainCanvas.Width) != canvasSize.Width)\n            || ((int)Math.Round(MainCanvas.Height) != canvasSize.Height)\n        )\n        {\n            MainCanvas.Width = vm.CanvasSize.Width;\n            MainCanvas.Height = vm.CanvasSize.Height;\n            MainCanvas.InvalidateVisual();\n        }\n    }\n\n    private int lastCanvasCursorRadius;\n    private Cursor? lastCanvasCursor;\n\n    private void UpdateCanvasCursor()\n    {\n        if (MainCanvas is not { } canvas)\n        {\n            return;\n        }\n\n        var currentZoom = ViewModel?.CurrentZoom ?? 1;\n\n        // Get brush size\n        var currentBrushSize = Math.Max((ViewModel?.PaintBrushSize ?? 1) - 2, 1);\n        var brushRadius = (int)Math.Ceiling(currentBrushSize * 2 * currentZoom);\n\n        // Only update cursor if brush size has changed\n        if (brushRadius == lastCanvasCursorRadius)\n        {\n            canvas.Cursor = lastCanvasCursor;\n            return;\n        }\n\n        lastCanvasCursorRadius = brushRadius;\n\n        var brushDiameter = brushRadius * 2;\n\n        const int padding = 4;\n\n        var canvasCenter = brushRadius + padding;\n        var canvasSize = brushDiameter + padding * 2;\n\n        using var cursorBitmap = new SKBitmap(canvasSize, canvasSize);\n\n        using var cursorCanvas = new SKCanvas(cursorBitmap);\n        cursorCanvas.Clear(SKColors.Transparent);\n        cursorCanvas.DrawCircle(\n            brushRadius + padding,\n            brushRadius + padding,\n            brushRadius,\n            new SKPaint\n            {\n                Color = SKColors.Black,\n                Style = SKPaintStyle.Stroke,\n                StrokeWidth = 1.5f,\n                StrokeCap = SKStrokeCap.Round,\n                StrokeJoin = SKStrokeJoin.Round,\n                IsDither = true,\n                IsAntialias = true\n            }\n        );\n        cursorCanvas.Flush();\n\n        using var data = cursorBitmap.Encode(SKEncodedImageFormat.Png, 100);\n        using var stream = data.AsStream();\n\n        var bitmap = WriteableBitmap.Decode(stream);\n\n        canvas.Cursor = new Cursor(bitmap, new PixelPoint(canvasCenter, canvasCenter));\n\n        lastCanvasCursor?.Dispose();\n        lastCanvasCursor = canvas.Cursor;\n    }\n\n    private void MainCanvas_OnPointerEntered(object? sender, PointerEventArgs e)\n    {\n        UpdateCanvasCursor();\n    }\n\n    private void MainCanvas_OnPointerExited(object? sender, PointerEventArgs e)\n    {\n        if (sender is SkiaCustomCanvas canvas)\n        {\n            canvas.Cursor = new Cursor(StandardCursorType.Arrow);\n        }\n    }\n\n    private Point GetRelativePosition(Point pt, Visual? relativeTo)\n    {\n        if (VisualRoot is not Visual visualRoot)\n            return default;\n        if (relativeTo == null)\n            return pt;\n\n        return pt * visualRoot.TransformToVisual(relativeTo) ?? default;\n    }\n\n    public AsyncRelayCommand ClearCanvasCommand => new(ClearCanvasAsync);\n\n    public async Task ClearCanvasAsync()\n    {\n        Paths = ImmutableList<PenPath>.Empty;\n        TemporaryPaths.Clear();\n\n        await Dispatcher.UIThread.InvokeAsync(() => MainCanvas?.InvalidateVisual());\n    }\n\n    private void OnRenderSkia(SKSurface surface)\n    {\n        ViewModel?.RenderToSurface(surface, renderBackgroundFill: true, renderBackgroundImage: true);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/ProgressRing.cs",
    "content": "﻿using System;\nusing System.Diagnostics.CodeAnalysis;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Metadata;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Controls.Shapes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n/// <summary>\n/// A control used to indicate the progress of an operation.\n/// </summary>\n[PseudoClasses(\":preserveaspect\", \":indeterminate\")]\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class ProgressRing : RangeBase\n{\n    private Arc? fillArc;\n\n    public static readonly StyledProperty<bool> IsIndeterminateProperty = ProgressBar\n        .IsIndeterminateProperty\n        .AddOwner<ProgressRing>();\n\n    public bool IsIndeterminate\n    {\n        get => GetValue(IsIndeterminateProperty);\n        set => SetValue(IsIndeterminateProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> PreserveAspectProperty = AvaloniaProperty.Register<ProgressRing, bool>(\n        nameof(PreserveAspect),\n        true\n    );\n\n    public bool PreserveAspect\n    {\n        get => GetValue(PreserveAspectProperty);\n        set => SetValue(PreserveAspectProperty, value);\n    }\n\n    public static readonly StyledProperty<double> StrokeThicknessProperty = Shape\n        .StrokeThicknessProperty\n        .AddOwner<ProgressRing>();\n\n    public double StrokeThickness\n    {\n        get => GetValue(StrokeThicknessProperty);\n        set => SetValue(StrokeThicknessProperty, value);\n    }\n\n    public static readonly StyledProperty<double> StartAngleProperty = AvaloniaProperty.Register<ProgressRing, double>(\n        nameof(StartAngle)\n    );\n\n    public double StartAngle\n    {\n        get => GetValue(StartAngleProperty);\n        set => SetValue(StartAngleProperty, value);\n    }\n\n    public static readonly StyledProperty<double> SweepAngleProperty = AvaloniaProperty.Register<ProgressRing, double>(\n        nameof(SweepAngle)\n    );\n\n    public double SweepAngle\n    {\n        get => GetValue(SweepAngleProperty);\n        set => SetValue(SweepAngleProperty, value);\n    }\n\n    public static readonly StyledProperty<double> EndAngleProperty = AvaloniaProperty.Register<ProgressRing, double>(\n        nameof(EndAngle),\n        360\n    );\n\n    public double EndAngle\n    {\n        get => GetValue(EndAngleProperty);\n        set => SetValue(EndAngleProperty, value);\n    }\n\n    static ProgressRing()\n    {\n        AffectsRender<ProgressRing>(SweepAngleProperty, StartAngleProperty, EndAngleProperty);\n\n        ValueProperty.Changed.AddClassHandler<ProgressRing>(OnValuePropertyChanged);\n        SweepAngleProperty.Changed.AddClassHandler<ProgressRing>(OnSweepAnglePropertyChanged);\n    }\n\n    public ProgressRing()\n    {\n        UpdatePseudoClasses(IsIndeterminate, PreserveAspect);\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        fillArc = e.NameScope.Find<Arc>(\"PART_Fill\");\n        if (fillArc is not null)\n        {\n            fillArc.StartAngle = StartAngle;\n            fillArc.SweepAngle = SweepAngle;\n        }\n    }\n\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n        var e = change as AvaloniaPropertyChangedEventArgs<bool>;\n        if (e is null)\n            return;\n\n        if (e.Property == IsIndeterminateProperty)\n        {\n            UpdatePseudoClasses(e.NewValue.GetValueOrDefault(), null);\n        }\n        else if (e.Property == PreserveAspectProperty)\n        {\n            UpdatePseudoClasses(null, e.NewValue.GetValueOrDefault());\n        }\n    }\n\n    private void UpdatePseudoClasses(bool? isIndeterminate, bool? preserveAspect)\n    {\n        if (isIndeterminate.HasValue)\n        {\n            PseudoClasses.Set(\":indeterminate\", isIndeterminate.Value);\n        }\n\n        if (preserveAspect.HasValue)\n        {\n            PseudoClasses.Set(\":preserveaspect\", preserveAspect.Value);\n        }\n    }\n\n    private static void OnValuePropertyChanged(ProgressRing sender, AvaloniaPropertyChangedEventArgs e)\n    {\n        sender.SweepAngle =\n            ((double)e.NewValue! - sender.Minimum)\n            * (sender.EndAngle - sender.StartAngle)\n            / (sender.Maximum - sender.Minimum);\n    }\n\n    private static void OnSweepAnglePropertyChanged(ProgressRing sender, AvaloniaPropertyChangedEventArgs e)\n    {\n        if (sender.fillArc is { } arc)\n        {\n            arc.SweepAngle = Math.Round(e.GetNewValue<double>());\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/PropertyGrid/BetterPropertyGrid.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing Avalonia;\nusing Avalonia.PropertyGrid.Services;\nusing JetBrains.Annotations;\nusing PropertyModels.ComponentModel;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n/// <inheritdoc />\n[PublicAPI]\npublic class BetterPropertyGrid : global::Avalonia.PropertyGrid.Controls.PropertyGrid\n{\n    protected override Type StyleKeyOverride => typeof(global::Avalonia.PropertyGrid.Controls.PropertyGrid);\n\n    public static readonly StyledProperty<IEnumerable<string>> ExcludedCategoriesProperty =\n        AvaloniaProperty.Register<BetterPropertyGrid, IEnumerable<string>>(\"ExcludedCategories\");\n\n    public IEnumerable<string> ExcludedCategories\n    {\n        get => GetValue(ExcludedCategoriesProperty);\n        set => SetValue(ExcludedCategoriesProperty, value);\n    }\n\n    public static readonly StyledProperty<IEnumerable<string>> IncludedCategoriesProperty =\n        AvaloniaProperty.Register<BetterPropertyGrid, IEnumerable<string>>(\"IncludedCategories\");\n\n    public IEnumerable<string> IncludedCategories\n    {\n        get => GetValue(IncludedCategoriesProperty);\n        set => SetValue(IncludedCategoriesProperty, value);\n    }\n\n    static BetterPropertyGrid()\n    {\n        // Register factories\n        CellEditFactoryService.Default.AddFactory(new ToggleSwitchCellEditFactory());\n\n        // Initialize localization and name resolver\n        LocalizationService.Default.AddExtraService(new PropertyGridLocalizationService());\n\n        ExcludedCategoriesProperty.Changed.AddClassHandler<BetterPropertyGrid>(\n            (grid, args) =>\n            {\n                if (args.NewValue is IEnumerable<string> excludedCategories)\n                {\n                    grid.FilterExcludeCategories(excludedCategories);\n                }\n            }\n        );\n\n        IncludedCategoriesProperty.Changed.AddClassHandler<BetterPropertyGrid>(\n            (grid, args) =>\n            {\n                if (args.NewValue is IEnumerable<string> includedCategories)\n                {\n                    grid.FilterIncludeCategories(includedCategories);\n                }\n            }\n        );\n    }\n\n    protected override void OnDataContextChanged(EventArgs e)\n    {\n        base.OnDataContextChanged(e);\n\n        if (DataContext is null)\n            return;\n\n        SetViewModelContext(DataContext);\n\n        // Apply filters again\n        FilterExcludeCategories(ExcludedCategories);\n        FilterIncludeCategories(IncludedCategories);\n    }\n\n    public void FilterExcludeCategories(IEnumerable<string>? excludedCategories)\n    {\n        excludedCategories ??= [];\n\n        if (DataContext is null)\n            return;\n\n        var categoryFilter = GetCategoryFilter();\n\n        categoryFilter.BeginUpdate();\n\n        // Uncheck All, then check all except All\n        categoryFilter.UnCheck(categoryFilter.All);\n\n        foreach (var mask in categoryFilter.Masks.Where(m => m != categoryFilter.All))\n        {\n            categoryFilter.Check(mask);\n        }\n\n        // Uncheck excluded categories\n        foreach (var mask in excludedCategories)\n        {\n            categoryFilter.UnCheck(mask);\n        }\n\n        categoryFilter.EndUpdate();\n    }\n\n    public void FilterIncludeCategories(IEnumerable<string>? includeCategories)\n    {\n        includeCategories ??= [];\n\n        if (DataContext is null)\n            return;\n\n        var categoryFilter = GetCategoryFilter();\n\n        categoryFilter.BeginUpdate();\n\n        // Uncheck non-included categories\n        foreach (var mask in categoryFilter.Masks.Where(m => !includeCategories.Contains(m)))\n        {\n            categoryFilter.UnCheck(mask);\n        }\n\n        categoryFilter.UnCheck(categoryFilter.All);\n\n        // Check included categories\n        foreach (var mask in includeCategories)\n        {\n            categoryFilter.Check(mask);\n        }\n\n        categoryFilter.EndUpdate();\n    }\n\n    private void SetViewModelContext(object? context)\n    {\n        // Get internal property `ViewModel` of internal type `PropertyGridViewModel`\n        var propertyGridViewModelType =\n            typeof(global::Avalonia.PropertyGrid.Controls.PropertyGrid).Assembly.GetType(\n                \"Avalonia.PropertyGrid.ViewModels.PropertyGridViewModel\",\n                true\n            )!;\n\n        var gridVm = this.GetProtectedProperty(\"ViewModel\").Unwrap();\n\n        // Set `Context` public property\n        var contextProperty = propertyGridViewModelType\n            .GetProperty(\"Context\", BindingFlags.Instance | BindingFlags.Public)\n            .Unwrap();\n        contextProperty.SetValue(gridVm, context);\n\n        // Trigger update that builds some stuff from `Context` and maybe initializes `Context` and `CategoryFilter`\n        var buildPropertiesViewMethod = typeof(global::Avalonia.PropertyGrid.Controls.PropertyGrid)\n            .GetMethod(\"BuildPropertiesView\", BindingFlags.Instance | BindingFlags.NonPublic)\n            .Unwrap();\n        buildPropertiesViewMethod.Invoke(this, [DataContext, ShowStyle]);\n\n        // Call this to ensure `CategoryFilter` is initialized\n        var method = propertyGridViewModelType\n            .GetMethod(\"RefreshProperties\", BindingFlags.Instance | BindingFlags.Public)\n            .Unwrap();\n        method.Invoke(gridVm, null);\n    }\n\n    private CheckedMaskModel GetCategoryFilter()\n    {\n        // Get internal property `ViewModel` of internal type `PropertyGridViewModel`\n        var propertyGridViewModelType =\n            typeof(global::Avalonia.PropertyGrid.Controls.PropertyGrid).Assembly.GetType(\n                \"Avalonia.PropertyGrid.ViewModels.PropertyGridViewModel\",\n                true\n            )!;\n\n        var gridVm = this.GetProtectedProperty(\"ViewModel\").Unwrap();\n\n        // Call this to ensure `CategoryFilter` is initialized\n        var method = propertyGridViewModelType\n            .GetMethod(\"RefreshProperties\", BindingFlags.Instance | BindingFlags.Public)\n            .Unwrap();\n\n        method.Invoke(gridVm, null);\n\n        // Get public property `CategoryFilter`\n        return gridVm.GetProtectedProperty<CheckedMaskModel>(\"CategoryFilter\").Unwrap();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/PropertyGrid/PropertyGridCultureData.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing PropertyModels.Localilzation;\nusing StabilityMatrix.Avalonia.Languages;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\ninternal class PropertyGridCultureData : ICultureData\n{\n    /// <inheritdoc />\n    public bool Reload() => false;\n\n    /// <inheritdoc />\n    public CultureInfo Culture => Cultures.Current ?? Cultures.Default;\n\n    /// <inheritdoc />\n    public Uri Path => new(\"\");\n\n    /// <inheritdoc />\n    public string this[string key]\n    {\n        get\n        {\n            if (Resources.ResourceManager.GetString(key) is { } result)\n            {\n                return result;\n            }\n\n            return key;\n        }\n    }\n\n    /// <inheritdoc />\n    public bool IsLoaded => true;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/PropertyGrid/PropertyGridLocalizationService.cs",
    "content": "﻿using System;\nusing PropertyModels.ComponentModel;\nusing PropertyModels.Localilzation;\nusing StabilityMatrix.Avalonia.Languages;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n/// <summary>\n/// Implements <see cref=\"ILocalizationService\"/> using static <see cref=\"Cultures\"/>.\n/// </summary>\ninternal class PropertyGridLocalizationService : MiniReactiveObject, ILocalizationService\n{\n    /// <inheritdoc />\n    public ICultureData CultureData { get; } = new PropertyGridCultureData();\n\n    /// <inheritdoc />\n    public string this[string key] => CultureData[key];\n\n    /// <inheritdoc />\n    public event EventHandler? OnCultureChanged;\n\n    /// <inheritdoc />\n    public ILocalizationService[] GetExtraServices() => Array.Empty<ILocalizationService>();\n\n    /// <inheritdoc />\n    public void AddExtraService(ILocalizationService service) { }\n\n    /// <inheritdoc />\n    public void RemoveExtraService(ILocalizationService service) { }\n\n    /// <inheritdoc />\n    public ICultureData[] GetCultures() => new[] { CultureData };\n\n    /// <inheritdoc />\n    public void SelectCulture(string cultureName) { }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/PropertyGrid/ToggleSwitchCellEditFactory.cs",
    "content": "﻿using Avalonia.Controls;\nusing Avalonia.PropertyGrid.Controls;\nusing Avalonia.PropertyGrid.Controls.Factories;\nusing Avalonia.PropertyGrid.Localization;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\ninternal class ToggleSwitchCellEditFactory : AbstractCellEditFactory\n{\n    // make this extend factor only effect on TestExtendPropertyGrid\n    public override bool Accept(object accessToken)\n    {\n        return accessToken is BetterPropertyGrid;\n    }\n\n    public override Control? HandleNewProperty(PropertyCellContext context)\n    {\n        var propertyDescriptor = context.Property;\n        var target = context.Target;\n\n        if (propertyDescriptor.PropertyType != typeof(bool))\n        {\n            return null;\n        }\n\n        var control = new ToggleSwitch();\n        control.SetLocalizeBinding(ToggleSwitch.OnContentProperty, \"On\");\n        control.SetLocalizeBinding(ToggleSwitch.OffContentProperty, \"Off\");\n\n        control.IsCheckedChanged += (s, e) =>\n        {\n            SetAndRaise(context, control, control.IsChecked);\n        };\n\n        return control;\n    }\n\n    public override bool HandlePropertyChanged(PropertyCellContext context)\n    {\n        var propertyDescriptor = context.Property;\n        var target = context.Target;\n        var control = context.CellEdit;\n\n        if (propertyDescriptor.PropertyType != typeof(bool))\n        {\n            return false;\n        }\n\n        ValidateProperty(control, propertyDescriptor, target);\n\n        if (control is ToggleSwitch ts)\n        {\n            ts.IsChecked = (bool)(propertyDescriptor.GetValue(target) ?? false);\n\n            return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/RefreshBadge.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n                          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                          xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n                          xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n                          xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n                          xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n                          xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n                          xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n                          Background=\"Transparent\"\n                          d:DataContext=\"{x:Static mocks:DesignData.RefreshBadgeViewModel}\"\n                          x:DataType=\"vm:RefreshBadgeViewModel\"\n                          mc:Ignorable=\"d\" d:DesignWidth=\"800\" d:DesignHeight=\"450\"\n                          x:Class=\"StabilityMatrix.Avalonia.Controls.RefreshBadge\">\n    <Grid Background=\"Transparent\">\n        <Button\n            BorderThickness=\"0\"\n            Command=\"{Binding RefreshCommand}\"\n            FontSize=\"26\"\n            Foreground=\"{Binding ColorBrush}\"\n            Margin=\"4\"\n            Padding=\"2\"\n            Background=\"Transparent\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            IsEnabled=\"{Binding !IsWorking}\">\n            <ui:SymbolIcon Symbol=\"{Binding Icon}\"/>\n        </Button>\n        <controls:ProgressRing\n            FontSize=\"14\"\n            Grid.Row=\"0\"\n            Height=\"20\"\n            BorderThickness=\"2\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            IsEnabled=\"{Binding IsWorking}\"\n            IsIndeterminate=\"True\"\n            IsVisible=\"{Binding IsWorking}\"\n            Width=\"20\" \n            ToolTip.Tip=\"{Binding CurrentToolTip}\" />\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/RefreshBadge.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<RefreshBadge>]\npublic partial class RefreshBadge : UserControlBase\n{\n    public RefreshBadge()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Scroll/BetterScrollContentPresenter.cs",
    "content": "﻿using Avalonia.Controls.Presenters;\nusing Avalonia.Input;\n\nnamespace StabilityMatrix.Avalonia.Controls.Scroll;\n\npublic class BetterScrollContentPresenter : ScrollContentPresenter\n{\n    protected override void OnPointerWheelChanged(PointerWheelEventArgs e)\n    {\n        if (e.KeyModifiers == KeyModifiers.Control)\n            return;\n        base.OnPointerWheelChanged(e);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Scroll/BetterScrollViewer.axaml",
    "content": "﻿<ResourceDictionary xmlns=\"https://github.com/avaloniaui\"\n                    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                    xmlns:scroll=\"clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll\">\n    <Design.PreviewWith>\n        <scroll:BetterScrollViewer Width=\"200\" Height=\"200\"\n                                   HorizontalScrollBarVisibility=\"Auto\">\n            <StackPanel Spacing=\"20\" Width=\"210\">\n                <TextBlock>Item 1</TextBlock>\n                <TextBlock>Item 2</TextBlock>\n                <TextBlock>Item 3</TextBlock>\n                <TextBlock>Item 4</TextBlock>\n                <TextBlock>Item 5</TextBlock>\n                <TextBlock>Item 6</TextBlock>\n                <TextBlock>Item 7</TextBlock>\n                <TextBlock>Item 8</TextBlock>\n                <TextBlock>Item 9</TextBlock>\n            </StackPanel>\n        </scroll:BetterScrollViewer>\n    </Design.PreviewWith>\n\n    <ControlTheme x:Key=\"{x:Type scroll:BetterScrollViewer}\" TargetType=\"scroll:BetterScrollViewer\">\n        <Setter Property=\"Background\" Value=\"Transparent\" />\n        <Setter Property=\"BringIntoViewOnFocusChange\" Value=\"False\"/>\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Grid ColumnDefinitions=\"*,Auto\" RowDefinitions=\"*,Auto\">\n                    <scroll:BetterScrollContentPresenter x:Name=\"PART_ContentPresenter\"\n                                                         Grid.Row=\"0\"\n                                                         Grid.Column=\"0\"\n                                                         Grid.RowSpan=\"2\"\n                                                         Grid.ColumnSpan=\"2\"\n                                                         Background=\"{TemplateBinding Background}\"\n                                                         HorizontalSnapPointsType=\"{TemplateBinding HorizontalSnapPointsType}\"\n                                                         VerticalSnapPointsType=\"{TemplateBinding VerticalSnapPointsType}\"\n                                                         HorizontalSnapPointsAlignment=\"{TemplateBinding HorizontalSnapPointsAlignment}\"\n                                                         VerticalSnapPointsAlignment=\"{TemplateBinding VerticalSnapPointsAlignment}\"\n                                                         Padding=\"{TemplateBinding Padding}\"\n                                                         ScrollViewer.IsScrollInertiaEnabled=\"{TemplateBinding IsScrollInertiaEnabled}\">\n                        <scroll:BetterScrollContentPresenter.GestureRecognizers>\n                            <ScrollGestureRecognizer\n                                CanHorizontallyScroll=\"{Binding CanHorizontallyScroll, ElementName=PART_ContentPresenter}\"\n                                CanVerticallyScroll=\"{Binding CanVerticallyScroll, ElementName=PART_ContentPresenter}\"\n                                IsScrollInertiaEnabled=\"{Binding (ScrollViewer.IsScrollInertiaEnabled), ElementName=PART_ContentPresenter}\" />\n                        </scroll:BetterScrollContentPresenter.GestureRecognizers>\n                    </scroll:BetterScrollContentPresenter>\n                    <ScrollBar Name=\"PART_HorizontalScrollBar\"\n                               Orientation=\"Horizontal\"\n                               Grid.Row=\"1\" />\n                    <ScrollBar Name=\"PART_VerticalScrollBar\"\n                               Orientation=\"Vertical\"\n                               Grid.Column=\"1\" />\n                    <Panel x:Name=\"PART_ScrollBarsSeparator\"\n                           Grid.Row=\"1\"\n                           Grid.Column=\"1\"\n                           Background=\"{DynamicResource ScrollViewerScrollBarsSeparatorBackground}\"\n                           Opacity=\"0\">\n                        <Panel.Transitions>\n                            <Transitions>\n                                <DoubleTransition Property=\"Opacity\" Duration=\"0:0:0.1\" />\n                            </Transitions>\n                        </Panel.Transitions>\n                    </Panel>\n                </Grid>\n            </ControlTemplate>\n        </Setter>\n        <Style Selector=\"^[IsExpanded=true] /template/ Panel#PART_ScrollBarsSeparator\">\n            <Setter Property=\"Opacity\" Value=\"1\" />\n        </Style>\n    </ControlTheme>\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/Scroll/BetterScrollViewer.cs",
    "content": "﻿using Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Controls.Scroll;\n\npublic class BetterScrollViewer : ScrollViewer { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:selectableImageCard=\"clr-namespace:StabilityMatrix.Avalonia.Controls.SelectableImageCard\"\n        xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n        xmlns:controls1=\"clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls\"\n        xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\">\n    <Design.PreviewWith>\n        <Border Padding=\"20\">\n            <!-- Add Controls for Previewer Here -->\n            <selectableImageCard:SelectableImageButton \n                Source=\"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg\" />\n        </Border>\n    </Design.PreviewWith>\n\n    <!-- Add Styles Here -->\n    <Style Selector=\"selectableImageCard|SelectableImageButton\">\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Grid\n                    Width=\"{Binding ImageWidth, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}\"\n                    Height=\"{Binding ImageHeight, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}\">\n                    <CheckBox VerticalAlignment=\"Top\"\n                              HorizontalAlignment=\"Right\"\n                              Margin=\"14,8\"\n                              Padding=\"0\"\n                              IsChecked=\"{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}\"\n                              ZIndex=\"100\">\n                        <CheckBox.RenderTransform>\n                            <ScaleTransform ScaleX=\"1.5\" ScaleY=\"1.5\" />\n                        </CheckBox.RenderTransform>\n                        <CheckBox.Styles>\n                            <Style Selector=\"CheckBox\">\n                                <Setter Property=\"CornerRadius\" Value=\"16\" />\n                            </Style>\n                        </CheckBox.Styles>\n                    </CheckBox>\n                    <Button\n                        Margin=\"0\"\n                        Padding=\"4\"\n                        CornerRadius=\"12\"\n                        Command=\"{TemplateBinding Command}\"\n                        CommandParameter=\"{TemplateBinding CommandParameter}\">\n                        <vendorLabs:BetterAsyncImage\n                            IsCacheEnabled=\"True\"\n                            Stretch=\"UniformToFill\"\n                            CornerRadius=\"8\"\n                            ContextFlyout=\"{TemplateBinding ContextFlyout}\"\n                            Source=\"{TemplateBinding Source}\" />\n                    </Button>\n                </Grid>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.cs",
    "content": "﻿using System;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\n\nnamespace StabilityMatrix.Avalonia.Controls.SelectableImageCard;\n\npublic class SelectableImageButton : Button\n{\n    public static readonly StyledProperty<bool?> IsSelectedProperty =\n        ToggleButton.IsCheckedProperty.AddOwner<SelectableImageButton>();\n\n    public static readonly StyledProperty<Uri?> SourceProperty = AvaloniaProperty.Register<\n        SelectableImageButton,\n        Uri?\n    >(\"Source\");\n\n    public static readonly StyledProperty<double> ImageWidthProperty = AvaloniaProperty.Register<\n        SelectableImageButton,\n        double\n    >(\"ImageWidth\", 300);\n\n    public static readonly StyledProperty<double> ImageHeightProperty = AvaloniaProperty.Register<\n        SelectableImageButton,\n        double\n    >(\"ImageHeight\", 300);\n\n    static SelectableImageButton()\n    {\n        AffectsRender<SelectableImageButton>(ImageWidthProperty, ImageHeightProperty);\n        AffectsArrange<SelectableImageButton>(ImageWidthProperty, ImageHeightProperty);\n    }\n\n    public double ImageHeight\n    {\n        get => GetValue(ImageHeightProperty);\n        set => SetValue(ImageHeightProperty, value);\n    }\n\n    public double ImageWidth\n    {\n        get => GetValue(ImageWidthProperty);\n        set => SetValue(ImageWidthProperty, value);\n    }\n\n    public bool? IsSelected\n    {\n        get => GetValue(IsSelectedProperty);\n        set => SetValue(IsSelectedProperty, value);\n    }\n\n    public Uri? Source\n    {\n        get => GetValue(SourceProperty);\n        set => SetValue(SourceProperty, value);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/SettingsAccountLinkExpander.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\">\n    <Design.PreviewWith>\n        <StackPanel Width=\"600\" Height=\"400\">\n            <controls:SettingsAccountLinkExpander\n                Header=\"Service 1\"\n                IconSource=\"OtherUser\"\n                OffDescription=\"Manage account services like A, B, and C\" />\n\n            <controls:SettingsAccountLinkExpander\n                Header=\"Service 1 (Loading)\"\n                IconSource=\"OtherUser\"\n                IsLoading=\"True\"\n                OffDescription=\"Manage account services like A, B, and C\" />\n\n            <controls:SettingsAccountLinkExpander\n                Header=\"Service 2\"\n                IconSource=\"CloudFilled\"\n                IsConnected=\"True\"\n                OffDescription=\"Manage account services like A, B, and C\" />\n\n            <controls:SettingsAccountLinkExpander\n                Header=\"Service 3\"\n                IconSource=\"CloudFilled\"\n                IsConnected=\"True\"\n                OnDescriptionExtra=\"(account)\"\n                OffDescription=\"Manage account services like A, B, and C\" />\n        </StackPanel>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|SettingsAccountLinkExpander\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <ui:SettingsExpander x:Name=\"PART_SettingsExpander\" IconSource=\"{TemplateBinding IconSource}\">\n                    <ui:SettingsExpander.Header>\n                        <StackPanel>\n                            <TextBlock x:Name=\"PART_HeaderTextBlock\" Text=\"{TemplateBinding Header}\" />\n\n                            <TextBlock\n                                Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                IsVisible=\"{TemplateBinding IsConnected,\n                                                            Mode=OneWay,\n                                                            Converter={x:Static BoolConverters.Not}}\"\n                                Text=\"{TemplateBinding OffDescription}\"\n                                TextWrapping=\"Wrap\"\n                                Theme=\"{DynamicResource CaptionTextBlockStyle}\" />\n\n\n                            <StackPanel\n                                x:Name=\"PART_OnDescriptionPanel\"\n                                IsVisible=\"{TemplateBinding IsConnected,\n                                                            Mode=OneWay}\"\n                                Orientation=\"Horizontal\"\n                                Spacing=\"4\">\n                                <Ellipse\n                                    Width=\"5\"\n                                    Height=\"5\"\n                                    Fill=\"{StaticResource ThemeMediumSeaGreenColor}\" />\n                                <TextBlock\n                                    Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                    Text=\"{TemplateBinding OnDescription}\"\n                                    TextWrapping=\"Wrap\"\n                                    Theme=\"{DynamicResource CaptionTextBlockStyle}\" />\n                                <TextBlock\n                                    Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                    Text=\"{TemplateBinding OnDescriptionExtra}\"\n                                    TextWrapping=\"Wrap\"\n                                    Theme=\"{DynamicResource CaptionTextBlockStyle}\" />\n                            </StackPanel>\n                        </StackPanel>\n\n                    </ui:SettingsExpander.Header>\n\n                    <ui:SettingsExpander.Footer>\n                        <StackPanel Margin=\"0,0,12,0\" Orientation=\"Horizontal\">\n                            <!--  for some reason direct bind to IsConnected doesn't work here  -->\n\n                            <controls:ProgressRing\n                                Margin=\"0,0,24,0\"\n                                BorderThickness=\"3\"\n                                IsIndeterminate=\"{Binding $parent[controls:SettingsAccountLinkExpander].IsLoading}\"\n                                IsVisible=\"{Binding $parent[controls:SettingsAccountLinkExpander].IsLoading}\" />\n\n\n                            <!--  Connect button  -->\n                            <Button\n                                x:Name=\"PART_ConnectButton\"\n                                Padding=\"32,6\"\n                                Command=\"{TemplateBinding ConnectCommand}\"\n                                Content=\"{x:Static lang:Resources.Action_Connect}\"\n                                IsVisible=\"{Binding !IsVisible, ElementName=PART_OnDescriptionPanel}\" />\n\n                            <!--  Disconnect button  -->\n                            <Button\n                                x:Name=\"PART_DisconnectButton\"\n                                Padding=\"6,8\"\n                                HorizontalAlignment=\"Right\"\n                                BorderThickness=\"0\"\n                                Classes=\"transparent\"\n                                IsVisible=\"{Binding IsVisible, ElementName=PART_OnDescriptionPanel}\">\n                                <ui:SymbolIcon FontSize=\"20\" Symbol=\"More\" />\n                                <Button.Flyout>\n                                    <ui:FAMenuFlyout Placement=\"BottomEdgeAlignedLeft\">\n                                        <ui:MenuFlyoutItem\n                                            x:Name=\"PART_DisconnectMenuItem\"\n                                            Command=\"{TemplateBinding DisconnectCommand}\"\n                                            Text=\"{x:Static lang:Resources.Action_Disconnect}\" />\n                                    </ui:FAMenuFlyout>\n                                </Button.Flyout>\n                            </Button>\n                        </StackPanel>\n\n                    </ui:SettingsExpander.Footer>\n\n                </ui:SettingsExpander>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/SettingsAccountLinkExpander.axaml.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Windows.Input;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Metadata;\nusing Avalonia.VisualTree;\nusing FluentAvalonia.UI.Controls;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic class SettingsAccountLinkExpander : TemplatedControlBase\n{\n    private readonly List<object?> _items = new();\n\n    [Content]\n    public List<object?> Items => _items;\n\n    // ReSharper disable MemberCanBePrivate.Global\n    public static readonly StyledProperty<object?> HeaderProperty =\n        HeaderedItemsControl.HeaderProperty.AddOwner<SettingsAccountLinkExpander>();\n\n    public object? Header\n    {\n        get => GetValue(HeaderProperty);\n        set => SetValue(HeaderProperty, value);\n    }\n\n    public static readonly StyledProperty<Uri?> HeaderTargetUriProperty = AvaloniaProperty.Register<\n        SettingsAccountLinkExpander,\n        Uri?\n    >(\"HeaderTargetUri\");\n\n    public Uri? HeaderTargetUri\n    {\n        get => GetValue(HeaderTargetUriProperty);\n        set => SetValue(HeaderTargetUriProperty, value);\n    }\n\n    public static readonly StyledProperty<IconSource?> IconSourceProperty =\n        SettingsExpander.IconSourceProperty.AddOwner<SettingsAccountLinkExpander>();\n\n    public IconSource? IconSource\n    {\n        get => GetValue(IconSourceProperty);\n        set => SetValue(IconSourceProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> IsConnectedProperty = AvaloniaProperty.Register<\n        SettingsAccountLinkExpander,\n        bool\n    >(\"IsConnected\");\n\n    public bool IsConnected\n    {\n        get => GetValue(IsConnectedProperty);\n        set => SetValue(IsConnectedProperty, value);\n    }\n\n    public static readonly StyledProperty<object?> OnDescriptionProperty = AvaloniaProperty.Register<\n        SettingsAccountLinkExpander,\n        object?\n    >(\"OnDescription\", Languages.Resources.Label_Connected);\n\n    public object? OnDescription\n    {\n        get => GetValue(OnDescriptionProperty);\n        set => SetValue(OnDescriptionProperty, value);\n    }\n\n    public static readonly StyledProperty<object?> OnDescriptionExtraProperty = AvaloniaProperty.Register<\n        SettingsAccountLinkExpander,\n        object?\n    >(\"OnDescriptionExtra\");\n\n    public object? OnDescriptionExtra\n    {\n        get => GetValue(OnDescriptionExtraProperty);\n        set => SetValue(OnDescriptionExtraProperty, value);\n    }\n\n    public static readonly StyledProperty<object?> OffDescriptionProperty = AvaloniaProperty.Register<\n        SettingsAccountLinkExpander,\n        object?\n    >(\"OffDescription\");\n\n    public object? OffDescription\n    {\n        get => GetValue(OffDescriptionProperty);\n        set => SetValue(OffDescriptionProperty, value);\n    }\n\n    public static readonly StyledProperty<ICommand?> ConnectCommandProperty = AvaloniaProperty.Register<\n        SettingsAccountLinkExpander,\n        ICommand?\n    >(nameof(ConnectCommand), enableDataValidation: true);\n\n    public ICommand? ConnectCommand\n    {\n        get => GetValue(ConnectCommandProperty);\n        set => SetValue(ConnectCommandProperty, value);\n    }\n\n    public static readonly StyledProperty<ICommand?> DisconnectCommandProperty = AvaloniaProperty.Register<\n        SettingsAccountLinkExpander,\n        ICommand?\n    >(nameof(DisconnectCommand), enableDataValidation: true);\n\n    public ICommand? DisconnectCommand\n    {\n        get => GetValue(DisconnectCommandProperty);\n        set => SetValue(DisconnectCommandProperty, value);\n    }\n\n    /*public static readonly StyledProperty<bool> IsLoading2Property = AvaloniaProperty.Register<SettingsAccountLinkExpander, bool>(\n        nameof(IsLoading2));\n\n    public bool IsLoading2\n    {\n        get => GetValue(IsLoading2Property);\n        set => SetValue(IsLoading2Property, value);\n    }*/\n\n    private bool _isLoading;\n\n    public static readonly DirectProperty<SettingsAccountLinkExpander, bool> IsLoadingProperty =\n        AvaloniaProperty.RegisterDirect<SettingsAccountLinkExpander, bool>(\n            \"IsLoading\",\n            o => o.IsLoading,\n            (o, v) => o.IsLoading = v\n        );\n\n    public bool IsLoading\n    {\n        get => _isLoading;\n        set => SetAndRaise(IsLoadingProperty, ref _isLoading, value);\n    }\n\n    // ReSharper restore MemberCanBePrivate.Global\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        // Bind tapped event on header\n        if (\n            HeaderTargetUri is { } headerTargetUri\n            && e.NameScope.Find<TextBlock>(\"PART_HeaderTextBlock\") is { } headerTextBlock\n        )\n        {\n            headerTextBlock.Tapped += (_, _) =>\n            {\n                ProcessRunner.OpenUrl(headerTargetUri.ToString());\n            };\n        }\n\n        if (e.NameScope.Find<SettingsExpander>(\"PART_SettingsExpander\") is { } expander)\n        {\n            expander.ItemsSource = Items;\n        }\n\n        if (ConnectCommand is { } command)\n        {\n            var connectButton = e.NameScope.Get<Button>(\"PART_ConnectButton\");\n            connectButton.Command = command;\n        }\n\n        if (DisconnectCommand is { } disconnectCommand)\n        {\n            var disconnectMenuItem = e.NameScope.Get<MenuFlyoutItem>(\"PART_DisconnectMenuItem\");\n            disconnectMenuItem.Command = disconnectCommand;\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        if (!this.IsAttachedToVisualTree())\n        {\n            return;\n        }\n\n        if (change.Property == ConnectCommandProperty)\n        {\n            var button = this.GetControl<Button>(\"PART_ConnectButton\");\n            button.Command = ConnectCommand;\n        }\n\n        if (change.Property == DisconnectCommandProperty)\n        {\n            var button = this.GetControl<Button>(\"PART_DisconnectButton\");\n            button.Command = DisconnectCommand;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/SkiaCustomCanvas.axaml",
    "content": "﻿<UserControl xmlns=\"https://github.com/avaloniaui\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             mc:Ignorable=\"d\" d:DesignWidth=\"800\" d:DesignHeight=\"450\"\n             x:Class=\"StabilityMatrix.Avalonia.Controls.SkiaCustomCanvas\">\n</UserControl>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/SkiaCustomCanvas.axaml.cs",
    "content": "﻿using System;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Media;\nusing Avalonia.Platform;\nusing Avalonia.Rendering.SceneGraph;\nusing Avalonia.Skia;\nusing SkiaSharp;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic partial class SkiaCustomCanvas : UserControl\n{\n    private readonly RenderingLogic renderingLogic = new();\n\n    public event Action<SKSurface>? RenderSkia;\n\n    public SkiaCustomCanvas()\n    {\n        InitializeComponent();\n\n        Background = Brushes.Transparent;\n\n        renderingLogic.RenderCall += surface => RenderSkia?.Invoke(surface);\n    }\n\n    public override void Render(DrawingContext context)\n    {\n        renderingLogic.Bounds = new Rect(0, 0, Bounds.Width, Bounds.Height);\n\n        context.Custom(renderingLogic);\n    }\n\n    private class RenderingLogic : ICustomDrawOperation\n    {\n        public Action<SKSurface>? RenderCall;\n\n        public Rect Bounds { get; set; }\n\n        public void Dispose() { }\n\n        public bool Equals(ICustomDrawOperation? other)\n        {\n            return other == this;\n        }\n\n        /// <inheritdoc />\n        public bool HitTest(Point p)\n        {\n            return false;\n        }\n\n        /// <inheritdoc />\n        public void Render(ImmediateDrawingContext context)\n        {\n            var skia = context.TryGetFeature<ISkiaSharpApiLeaseFeature>();\n\n            using var lease = skia?.Lease();\n\n            if (lease?.SkSurface is { } skSurface)\n            {\n                Render(skSurface);\n            }\n        }\n\n        private void Render(SKSurface surface)\n        {\n            RenderCall?.Invoke(surface);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/StarsRating.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\">\n    <Design.PreviewWith>\n        <StackPanel Width=\"400\" Height=\"400\" Spacing=\"4\">\n            <StackPanel.Styles>\n                <Style Selector=\"controls|Card\">\n                    <Setter Property=\"Padding\" Value=\"6,4\"/>\n                    <Setter Property=\"HorizontalAlignment\" Value=\"Left\"/>\n                </Style>\n            </StackPanel.Styles>\n            \n            <controls:Card>\n                <controls:StarsRating Value=\"1\" />\n            </controls:Card>\n            \n            <controls:Card Classes=\"transparent\">\n                <controls:StarsRating Value=\"2\" />\n            </controls:Card>\n            \n            <controls:Card Classes=\"transparent\">\n                <controls:StarsRating Value=\"2.5\" />\n            </controls:Card>\n            \n        </StackPanel>\n    </Design.PreviewWith>\n    \n    <!--<Styles.Resources>\n        <icons:SymbolIcon \n            x:Key=\"StarFilledIcon\"\n            FontSize=\"15\"\n            Margin=\"8,0\"\n            VerticalAlignment=\"Center\"\n            Symbol=\"Star\" \n            IconVariant=\"Filled\"/>\n    </Styles.Resources>-->\n\n    <Style Selector=\"controls|StarsRating\">\n        <!-- Set Defaults -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <ItemsControl\n                    x:Name=\"PART_StarsItemsControl\">\n                    <ItemsControl.ItemsPanel>\n                        <ItemsPanelTemplate>\n                            <StackPanel Spacing=\"2\" Orientation=\"Horizontal\" />\n                        </ItemsPanelTemplate>\n                    </ItemsControl.ItemsPanel>\n                </ItemsControl>\n                <!--<sg:SpacedGrid\n                    x:Name=\"PART_Grid\">\n                    ~1~ Stars filled dynamically @1@\n                </sg:SpacedGrid>-->\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/StarsRating.axaml.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Layout;\nusing Avalonia.Media;\nusing Avalonia.VisualTree;\nusing FluentIcons.Avalonia.Fluent;\nusing FluentIcons.Common;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic class StarsRating : TemplatedControlBase\n{\n    private SymbolIcon? StarFilledIcon => Resources[\"StarFilledIcon\"] as SymbolIcon;\n\n    private ItemsControl? itemsControl;\n\n    private IEnumerable<SymbolIcon> StarItems => itemsControl!.ItemsSource!.Cast<SymbolIcon>();\n\n    public static readonly StyledProperty<bool> IsEditableProperty = AvaloniaProperty.Register<\n        StarsRating,\n        bool\n    >(\"IsEditable\");\n\n    public bool IsEditable\n    {\n        get => GetValue(IsEditableProperty);\n        set => SetValue(IsEditableProperty, value);\n    }\n\n    public static readonly StyledProperty<int> MaximumProperty = AvaloniaProperty.Register<StarsRating, int>(\n        nameof(Maximum),\n        5\n    );\n\n    public int Maximum\n    {\n        get => GetValue(MaximumProperty);\n        set => SetValue(MaximumProperty, value);\n    }\n\n    public static readonly StyledProperty<double> ValueProperty = AvaloniaProperty.Register<\n        StarsRating,\n        double\n    >(nameof(Value));\n\n    public double Value\n    {\n        get => GetValue(ValueProperty);\n        set => SetValue(ValueProperty, value);\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        itemsControl = e.NameScope.Find<ItemsControl>(\"PART_StarsItemsControl\")!;\n\n        CreateStars();\n    }\n\n    /// <inheritdoc />\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        if (!this.IsAttachedToVisualTree())\n        {\n            return;\n        }\n\n        if (change.Property == ValueProperty || change.Property == MaximumProperty)\n        {\n            SyncStarState();\n        }\n    }\n\n    private void CreateStars()\n    {\n        if (itemsControl is null)\n        {\n            return;\n        }\n\n        // Fill stars\n        var stars = new List<Control>();\n\n        for (var i = 0; i < Maximum; i++)\n        {\n            var star = new SymbolIcon\n            {\n                FontSize = FontSize,\n                Margin = new Thickness(0, 0),\n                Symbol = Symbol.Star,\n                HorizontalAlignment = HorizontalAlignment.Center,\n                VerticalAlignment = VerticalAlignment.Center,\n                Tag = i\n            };\n\n            stars.Add(star);\n            OnStarAdded(star);\n        }\n\n        itemsControl.ItemsSource = stars;\n        SyncStarState();\n    }\n\n    private void OnStarAdded(SymbolIcon item)\n    {\n        if (IsEditable)\n        {\n            item.Tapped += (sender, args) =>\n            {\n                var star = (SymbolIcon)sender!;\n                Value = (int)star.Tag! + 1;\n            };\n        }\n    }\n\n    /// <summary>\n    /// Round a number to the nearest 0.5\n    /// </summary>\n    private static double RoundToHalf(double value)\n    {\n        return Math.Round(value * 2, MidpointRounding.AwayFromZero) / 2;\n    }\n\n    private void SyncStarState()\n    {\n        // Set star to filled when Value is greater than or equal to the star index\n        foreach (var star in StarItems)\n        {\n            // Add 1 to tag since its index is 0-based\n            var tag = (int)star.Tag! + 1;\n\n            // Fill if current is equal or lower than floor of Value\n            if (tag <= Math.Floor(RoundToHalf(Value)))\n            {\n                star.Symbol = Symbol.Star;\n                star.IconVariant = IconVariant.Filled;\n                star.Foreground = Foreground;\n            }\n            // If current is between floor and ceil of value, use half-star\n            else if (tag <= Math.Ceiling(RoundToHalf(Value)))\n            {\n                star.Symbol = Symbol.StarHalf;\n                star.IconVariant = IconVariant.Filled;\n                star.Foreground = Foreground;\n            }\n            // Otherwise no fill and gray disabled color\n            else\n            {\n                star.Symbol = Symbol.Star;\n                star.IconVariant = IconVariant.Regular;\n                star.Foreground = new SolidColorBrush(Colors.DarkSlateGray);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/TemplatedControlBase.cs",
    "content": "﻿using AsyncAwaitBestPractices;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Interactivity;\nusing Avalonia.Threading;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic abstract class TemplatedControlBase : TemplatedControl\n{\n    /// <inheritdoc />\n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        base.OnLoaded(e);\n\n        if (DataContext is not ViewModelBase viewModel)\n            return;\n\n        // Run synchronous load then async load\n        viewModel.OnLoaded();\n\n        // Can't block here so we'll run as async on UI thread\n        Dispatcher.UIThread.InvokeAsync(viewModel.OnLoadedAsync).SafeFireAndForget();\n    }\n\n    /// <inheritdoc />\n    protected override void OnUnloaded(RoutedEventArgs e)\n    {\n        base.OnUnloaded(e);\n\n        if (DataContext is not ViewModelBase viewModel)\n            return;\n\n        // Run synchronous load then async load\n        viewModel.OnUnloaded();\n\n        // Can't block here so we'll run as async on UI thread\n        Dispatcher.UIThread.InvokeAsync(viewModel.OnUnloadedAsync).SafeFireAndForget();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/TextMarkers/TextMarker.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Avalonia.Media;\nusing AvaloniaEdit.Document;\n\nnamespace StabilityMatrix.Avalonia.Controls.TextMarkers;\n\npublic sealed class TextMarker : TextSegment\n{\n    private readonly TextMarkerService _service;\n\n    public TextMarker(TextMarkerService service, int startOffset, int length)\n    {\n        _service = service ?? throw new ArgumentNullException(nameof(service));\n        StartOffset = startOffset;\n        Length = length;\n    }\n\n    public event EventHandler? Deleted;\n\n    public bool IsDeleted => !IsConnectedToCollection;\n\n    public void Delete()\n    {\n        _service.Remove(this);\n    }\n\n    internal void OnDeleted()\n    {\n        Deleted?.Invoke(this, EventArgs.Empty);\n    }\n\n    private void Redraw()\n    {\n        _service.Redraw(this);\n    }\n\n    private Color? _backgroundColor;\n\n    public Color? BackgroundColor\n    {\n        get => _backgroundColor; set\n        {\n            if (!EqualityComparer<Color?>.Default.Equals(_backgroundColor, value))\n            {\n                _backgroundColor = value;\n                Redraw();\n            }\n        }\n    }\n\n    private Color? _foregroundColor;\n\n    public Color? ForegroundColor\n    {\n        get => _foregroundColor; set\n        {\n            if (!EqualityComparer<Color?>.Default.Equals(_foregroundColor, value))\n            {\n                _foregroundColor = value;\n                Redraw();\n            }\n        }\n    }\n\n    private FontWeight? _fontWeight;\n\n    public FontWeight? FontWeight\n    {\n        get => _fontWeight; set\n        {\n            if (_fontWeight != value)\n            {\n                _fontWeight = value;\n                Redraw();\n            }\n        }\n    }\n\n    private FontStyle? _fontStyle;\n\n    public FontStyle? FontStyle\n    {\n        get => _fontStyle; set\n        {\n            if (_fontStyle != value)\n            {\n                _fontStyle = value;\n                Redraw();\n            }\n        }\n    }\n\n    public object? Tag { get; set; }\n\n    private Color _markerColor;\n\n    public Color MarkerColor\n    {\n        get => _markerColor; set\n        {\n            if (!EqualityComparer<Color>.Default.Equals(_markerColor, value))\n            {\n                _markerColor = value;\n                Redraw();\n            }\n        }\n    }\n\n    public object? ToolTip { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/TextMarkers/TextMarkerService.cs",
    "content": "﻿// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team\n// \n// Permission is hereby granted, free of charge, to any person obtaining a copy of this\n// software and associated documentation files (the \"Software\"), to deal in the Software\n// without restriction, including without limitation the rights to use, copy, modify, merge,\n// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons\n// to whom the Software is furnished to do so, subject to the following conditions:\n// \n// The above copyright notice and this permission notice shall be included in all copies or\n// substantial portions of the Software.\n// \n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n// DEALINGS IN THE SOFTWARE.\n\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing Avalonia;\nusing Avalonia.Media;\nusing AvaloniaEdit;\nusing AvaloniaEdit.Document;\nusing AvaloniaEdit.Rendering;\nusing CommonBrush = Avalonia.Media.IBrush;\n\nnamespace StabilityMatrix.Avalonia.Controls.TextMarkers;\n\npublic sealed class TextMarkerService : DocumentColorizingTransformer, IBackgroundRenderer, ITextViewConnect\n{\n    private readonly TextSegmentCollection<TextMarker> _markers;\n    private readonly TextDocument _document;\n    private readonly List<TextView> _textViews;\n\n    public TextMarkerService(TextEditor editor)\n    {\n        if (editor == null) throw new ArgumentNullException(nameof(editor));\n        _document = editor.Document;\n        _markers = new TextSegmentCollection<TextMarker>(_document);\n        _textViews = new List<TextView>();\n        // editor.ToolTipRequest += EditorOnToolTipRequest;\n    }\n\n    /*private void EditorOnToolTipRequest(object? sender, ToolTipRequestEventArgs args)\n    {\n        var offset = _document.GetOffset(args.LogicalPosition);\n\n        //FoldingManager foldings = _editor.GetService(typeof(FoldingManager)) as FoldingManager;\n        //if (foldings != null)\n        //{\n        //    var foldingsAtOffset = foldings.GetFoldingsAt(offset);\n        //    FoldingSection collapsedSection = foldingsAtOffset.FirstOrDefault(section => section.IsFolded);\n\n        //    if (collapsedSection != null)\n        //    {\n        //        args.SetToolTip(GetTooltipTextForCollapsedSection(args, collapsedSection));\n        //    }\n        //}\n\n        var markersAtOffset = GetMarkersAtOffset(offset);\n        var markerWithToolTip = markersAtOffset.FirstOrDefault(marker => marker.ToolTip != null);\n        if (markerWithToolTip != null && markerWithToolTip.ToolTip != null)\n        {\n            args.SetToolTip(markerWithToolTip.ToolTip);\n        }\n    }*/\n\n    #region TextMarkerService\n\n    public TextMarker? TryCreate(int startOffset, int length)\n    {\n        if (_markers == null)\n            throw new InvalidOperationException(\"Cannot create a marker when not attached to a document\");\n\n        var textLength = _document.TextLength;\n        if (startOffset < 0 || startOffset > textLength) return null;\n        //throw new ArgumentOutOfRangeException(nameof(startOffset), startOffset, \"Value must be between 0 and \" + textLength);\n        if (length < 0 || startOffset + length > textLength) return null;\n        //throw new ArgumentOutOfRangeException(nameof(length), length, \"length must not be negative and startOffset+length must not be after the end of the document\");\n\n        var marker = new TextMarker(this, startOffset, length);\n        _markers.Add(marker);\n        return marker;\n    }\n\n    public IEnumerable<TextMarker> GetMarkersAtOffset(int offset)\n    {\n        return _markers.FindSegmentsContaining(offset);\n    }\n\n    public IEnumerable<TextMarker> TextMarkers => _markers ?? Enumerable.Empty<TextMarker>();\n\n    public void RemoveAll(Predicate<TextMarker> predicate)\n    {\n        if (predicate == null)\n            throw new ArgumentNullException(nameof(predicate));\n        \n        foreach (var m in _markers.ToArray())\n        {\n            if (predicate(m))\n                Remove(m);\n        }\n    }\n\n    public void Remove(TextMarker? marker)\n    {\n        if (marker == null) throw new ArgumentNullException(nameof(marker));\n\n        if (_markers.Remove(marker))\n        {\n            Redraw(marker);\n            marker.OnDeleted();\n        }\n    }\n\n    internal void Redraw(ISegment segment)\n    {\n        foreach (var view in _textViews)\n        {\n            view.Redraw(segment);\n        }\n        RedrawRequested?.Invoke(this, EventArgs.Empty);\n    }\n\n    public event EventHandler? RedrawRequested;\n\n    #endregion\n\n    #region DocumentColorizingTransformer\n\n    protected override void ColorizeLine(DocumentLine line)\n    {\n        var lineStart = line.Offset;\n        var lineEnd = lineStart + line.Length;\n        foreach (var marker in _markers.FindOverlappingSegments(lineStart, line.Length))\n        {\n            CommonBrush? foregroundBrush = null;\n            if (marker.ForegroundColor != null)\n            {\n                foregroundBrush = new SolidColorBrush(marker.ForegroundColor.Value).ToImmutable();\n            }\n            ChangeLinePart(\n                Math.Max(marker.StartOffset, lineStart),\n                Math.Min(marker.EndOffset, lineEnd),\n                element =>\n                {\n                    if (foregroundBrush != null)\n                    {\n                        element.TextRunProperties.SetForegroundBrush(foregroundBrush);\n                    }\n                    var tf = element.TextRunProperties.Typeface;\n                    element.TextRunProperties.SetTypeface(new Typeface(\n                        tf.FontFamily,\n                        marker.FontStyle ?? tf.Style,\n                        marker.FontWeight ?? tf.Weight,\n                        tf.Stretch\n                    ));\n                }\n            );\n        }\n    }\n\n    #endregion\n\n    #region IBackgroundRenderer\n\n    public KnownLayer Layer => KnownLayer.Selection;\n\n    public void Draw(TextView textView, DrawingContext drawingContext)\n    {\n        if (textView == null)\n            throw new ArgumentNullException(nameof(textView));\n        if (drawingContext == null)\n            throw new ArgumentNullException(nameof(drawingContext));\n        if (!textView.VisualLinesValid)\n            return;\n        var visualLines = textView.VisualLines;\n        if (visualLines.Count == 0)\n            return;\n        var viewStart = visualLines.First().FirstDocumentLine.Offset;\n        var viewEnd = visualLines.Last().LastDocumentLine.EndOffset;\n        foreach (var marker in _markers.FindOverlappingSegments(viewStart, viewEnd - viewStart))\n        {\n            if (marker.BackgroundColor != null)\n            {\n                var geoBuilder = new BackgroundGeometryBuilder\n                {\n                    AlignToWholePixels = true,\n                    CornerRadius = 3\n                };\n                geoBuilder.AddSegment(textView, marker);\n                var geometry = geoBuilder.CreateGeometry();\n                if (geometry != null)\n                {\n                    var color = marker.BackgroundColor.Value;\n                    var brush = new SolidColorBrush(color).ToImmutable();\n                    drawingContext.DrawGeometry(brush, null, geometry);\n                }\n            }\n            foreach (var r in BackgroundGeometryBuilder.GetRectsForSegment(textView, marker))\n            {\n                var startPoint = r.BottomLeft;\n                var endPoint = r.BottomRight;\n\n                var usedBrush = new SolidColorBrush(marker.MarkerColor).ToImmutable();\n                var offset = 2.5;\n\n                var count = Math.Max((int)((endPoint.X - startPoint.X) / offset) + 1, 4);\n\n                /*var geometry = new StreamGeometry();\n\n                using (var ctx = geometry.Open())\n                {\n                    ctx.BeginFigure(startPoint, false);\n                    // ctx.PolyLineTo(CreatePoints(startPoint, offset, count).ToArray(), true, false);\n                    ctx.LineTo(CreatePoints(startPoint, offset, count).ToArray());\n                }*/\n\n                var geometry = new PolylineGeometry(CreatePoints(startPoint, offset, count), false);\n\n                // geometry.Freeze();\n\n                var usedPen = new Pen(usedBrush, 1);\n                // usedPen.Freeze();\n                drawingContext.DrawGeometry(Brushes.Transparent, usedPen, geometry);\n            }\n        }\n    }\n\n    private static IEnumerable<Point> CreatePoints(Point start, double offset, int count)\n    {\n        for (var i = 0; i < count; i++)\n            yield return new Point(start.X + i * offset, start.Y - ((i + 1) % 2 == 0 ? offset : 0));\n    }\n\n    #endregion\n\n    #region ITextViewConnect\n\n    void ITextViewConnect.AddToTextView(TextView textView)\n    {\n        if (textView != null && !_textViews.Contains(textView))\n        {\n            Debug.Assert(textView.Document == _document);\n            _textViews.Add(textView);\n        }\n    }\n\n    void ITextViewConnect.RemoveFromTextView(TextView textView)\n    {\n        if (textView != null)\n        {\n            Debug.Assert(textView.Document == _document);\n            _textViews.Remove(textView);\n        }\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/TextMarkers/TextMarkerValidationEventArgs.cs",
    "content": "﻿using System;\n\nnamespace StabilityMatrix.Avalonia.Controls.TextMarkers;\n\npublic class TextMarkerValidationEventArgs : EventArgs\n{\n    \n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/TextMarkers/TextMarkerValidatorService.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\n\nnamespace StabilityMatrix.Avalonia.Controls.TextMarkers;\n\npublic class TextMarkerValidatorService\n{\n    private string? currentText;\n    private Task? currentTask;\n    \n    private TimeSpan updateInterval;\n\n    public EventHandler<TextMarkerValidationEventArgs>? ValidationUpdate;\n    \n    private void OnValidationUpdate(TextMarkerValidationEventArgs e)\n    {\n        ValidationUpdate?.Invoke(this, e);\n    }\n    \n    public TextMarkerValidatorService(TimeSpan updateInterval)\n    {\n        this.updateInterval = updateInterval;\n    }\n    \n    public void UpdateText(string text)\n    {\n        // Ignore if text is the same\n        if (currentText == text) return;\n        \n        // If previous task is not null, ignore\n        if (currentTask != null) return;\n        \n        // Start a task to validate the text, and delay it by the update interval after the last update\n        currentTask = Task.Run(async () =>\n        {\n            await ValidateWithDelayAsync();\n        }).ContinueWith(_ =>\n        {\n            // Set task to null\n            currentTask = null;\n            // Set current text\n            currentText = text;\n        });\n\n        currentTask.SafeFireAndForget();\n    }\n    \n    private void Validate()\n    {\n        \n    }\n    \n    private async Task ValidateWithDelayAsync(CancellationToken cancellationToken = default)\n    {\n        // Validate the text\n        Validate();\n        \n        // Wait for the update interval\n        await Task.Delay(updateInterval, cancellationToken);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n    xmlns:system=\"using:System\"\n    xmlns:treeFileExplorer=\"clr-namespace:StabilityMatrix.Avalonia.Models.TreeFileExplorer\"\n    xmlns:mock=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\">\n    <Design.PreviewWith>\n        <StackPanel Spacing=\"16\">\n            <Panel Height=\"300\" Margin=\"4\">\n                <controls:TreeFileExplorer \n                    RootPath=\"{x:Static mock:DesignData.CurrentDirectory}\" />\n            </Panel>\n        \n            <Panel Height=\"300\" Margin=\"4\">\n                <controls:TreeFileExplorer \n                    IndexFiles=\"False\"\n                    CanSelectFiles=\"False\"\n                    RootPath=\"{x:Static mock:DesignData.CurrentDirectory}\" />\n            </Panel>\n        </StackPanel>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|TreeFileExplorer\">\n        <!--  Set Defaults  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Grid>\n                    <TreeView \n                        ScrollViewer.VerticalScrollBarVisibility=\"Auto\"\n                        ItemsSource=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RootItem.Children}\">\n                        <TreeView.DataTemplates>\n                            <DataTemplate \n                                DataType=\"treeFileExplorer:TreeFileExplorerFile\">\n                                <sg:SpacedGrid ColumnDefinitions=\"Auto,*\" RowSpacing=\"0\" ColumnSpacing=\"4\">\n                                    <fluentIcons:SymbolIcon\n                                        Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                        Symbol=\"Document\" />\n                                    <TextBlock\n                                        Grid.Column=\"1\"\n                                        Text=\"{Binding Path.Name}\" />   \n                                </sg:SpacedGrid>\n                            </DataTemplate>\n                            <TreeDataTemplate \n                                DataType=\"treeFileExplorer:TreeFileExplorerDirectory\" \n                                ItemsSource=\"{Binding Children}\">\n                                <sg:SpacedGrid ColumnDefinitions=\"Auto,*\" RowSpacing=\"0\" ColumnSpacing=\"4\">\n                                    <fluentIcons:SymbolIcon\n                                        Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                        IconVariant=\"Filled\"\n                                        Symbol=\"Folder\" />\n                                    <TextBlock\n                                        Grid.Column=\"1\"\n                                        Text=\"{Binding Path.Name}\" />   \n                                </sg:SpacedGrid>\n                            </TreeDataTemplate>\n                        </TreeView.DataTemplates>\n                    </TreeView>\n                </Grid>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/TreeFileExplorer.axaml.cs",
    "content": "﻿using Avalonia;\nusing Avalonia.Controls.Primitives;\nusing StabilityMatrix.Avalonia.Models.TreeFileExplorer;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\npublic class TreeFileExplorer : TemplatedControlBase\n{\n    public static readonly StyledProperty<TreeFileExplorerDirectory?> RootItemProperty =\n        AvaloniaProperty.Register<TreeFileExplorer, TreeFileExplorerDirectory?>(\"RootItem\");\n\n    public TreeFileExplorerDirectory? RootItem\n    {\n        get => GetValue(RootItemProperty);\n        set => SetValue(RootItemProperty, value);\n    }\n\n    public static readonly StyledProperty<string?> RootPathProperty = AvaloniaProperty.Register<\n        TreeFileExplorer,\n        string?\n    >(\"RootPath\");\n\n    public string? RootPath\n    {\n        get => GetValue(RootPathProperty);\n        set => SetValue(RootPathProperty, value);\n    }\n\n    public static readonly StyledProperty<IPathObject?> SelectedPathProperty = AvaloniaProperty.Register<\n        TreeFileExplorer,\n        IPathObject?\n    >(\"SelectedPath\");\n\n    public IPathObject? SelectedPath\n    {\n        get => GetValue(SelectedPathProperty);\n        set => SetValue(SelectedPathProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> CanSelectFilesProperty = AvaloniaProperty.Register<\n        TreeFileExplorer,\n        bool\n    >(\"CanSelectFiles\", true);\n\n    public bool CanSelectFiles\n    {\n        get => GetValue(CanSelectFilesProperty);\n        set => SetValue(CanSelectFilesProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> CanSelectFoldersProperty = AvaloniaProperty.Register<\n        TreeFileExplorer,\n        bool\n    >(\"CanSelectFolders\", true);\n\n    public bool CanSelectFolders\n    {\n        get => GetValue(CanSelectFoldersProperty);\n        set => SetValue(CanSelectFoldersProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> IndexFilesProperty = AvaloniaProperty.Register<\n        TreeFileExplorer,\n        bool\n    >(\"IndexFiles\", true);\n\n    public bool IndexFiles\n    {\n        get => GetValue(IndexFilesProperty);\n        set => SetValue(IndexFilesProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> IndexFoldersProperty = AvaloniaProperty.Register<\n        TreeFileExplorer,\n        bool\n    >(\"IndexFolders\", true);\n\n    public bool IndexFolders\n    {\n        get => GetValue(IndexFoldersProperty);\n        set => SetValue(IndexFoldersProperty, value);\n    }\n\n    private TreeFileExplorerOptions GetOptions()\n    {\n        var options = TreeFileExplorerOptions.None;\n\n        if (CanSelectFiles)\n        {\n            options |= TreeFileExplorerOptions.CanSelectFiles;\n        }\n        if (CanSelectFolders)\n        {\n            options |= TreeFileExplorerOptions.CanSelectFolders;\n        }\n        if (IndexFiles)\n        {\n            options |= TreeFileExplorerOptions.IndexFiles;\n        }\n        if (IndexFolders)\n        {\n            options |= TreeFileExplorerOptions.IndexFolders;\n        }\n\n        return options;\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        if (RootItem is null)\n        {\n            RootItem = RootPath is null\n                ? null\n                : new TreeFileExplorerDirectory(new DirectoryPath(RootPath), GetOptions());\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        if (change.Property == RootPathProperty)\n        {\n            var path = change.GetNewValue<string?>();\n            RootItem = path is null\n                ? null\n                : new TreeFileExplorerDirectory(new DirectoryPath(path), GetOptions());\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/UserControlBase.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Interactivity;\nusing Avalonia.Threading;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[SuppressMessage(\"ReSharper\", \"VirtualMemberNeverOverridden.Global\")]\npublic class UserControlBase : UserControl\n{\n    static UserControlBase()\n    {\n        LoadedEvent.AddClassHandler<UserControlBase>((cls, args) => cls.OnLoadedEvent(args));\n\n        UnloadedEvent.AddClassHandler<UserControlBase>((cls, args) => cls.OnUnloadedEvent(args));\n    }\n\n    // ReSharper disable once UnusedParameter.Global\n    protected virtual void OnLoadedEvent(RoutedEventArgs? e)\n    {\n        if (DataContext is not ViewModelBase viewModel)\n            return;\n\n        // Run synchronous load then async load\n        viewModel.OnLoaded();\n\n        // Can't block here so we'll run as async on UI thread\n        Dispatcher.UIThread.InvokeAsync(viewModel.OnLoadedAsync).SafeFireAndForget();\n    }\n\n    // ReSharper disable once UnusedParameter.Global\n    protected virtual void OnUnloadedEvent(RoutedEventArgs? e)\n    {\n        if (DataContext is not ViewModelBase viewModel)\n            return;\n\n        // Run synchronous load then async load\n        viewModel.OnUnloaded();\n\n        // Can't block here so we'll run as async on UI thread\n        Dispatcher.UIThread.InvokeAsync(viewModel.OnUnloadedAsync).SafeFireAndForget();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/AsyncImageFailedEventArgs.cs",
    "content": "﻿using System;\nusing Avalonia.Interactivity;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs;\n\npublic partial class BetterAsyncImage\n{\n    public class AsyncImageFailedEventArgs : RoutedEventArgs\n    {\n        internal AsyncImageFailedEventArgs(Exception? errorException = null, string errorMessage = \"\")\n            : base(FailedEvent)\n        {\n            ErrorException = errorException;\n            ErrorMessage = errorMessage;\n        }\n\n        public Exception? ErrorException { get; private set; }\n        public string ErrorMessage { get; private set; }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImage.Events.cs",
    "content": "﻿using System;\nusing Avalonia.Interactivity;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs;\n\npublic partial class BetterAsyncImage\n{\n    /// <summary>\n    /// Deines the <see cref=\"Opened\"/> event\n    /// </summary>\n    public static readonly RoutedEvent<RoutedEventArgs> OpenedEvent = RoutedEvent.Register<\n        BetterAsyncImage,\n        RoutedEventArgs\n    >(nameof(Opened), RoutingStrategies.Bubble);\n\n    /// <summary>\n    /// Deines the <see cref=\"Failed\"/> event\n    /// </summary>\n    public static readonly RoutedEvent<global::Avalonia.Labs.Controls.AsyncImage.AsyncImageFailedEventArgs> FailedEvent =\n        RoutedEvent.Register<\n            BetterAsyncImage,\n            global::Avalonia.Labs.Controls.AsyncImage.AsyncImageFailedEventArgs\n        >(nameof(Failed), RoutingStrategies.Bubble);\n\n    /// <summary>\n    /// Occurs when the image is successfully loaded.\n    /// </summary>\n    public event EventHandler<RoutedEventArgs>? Opened\n    {\n        add => AddHandler(OpenedEvent, value);\n        remove => RemoveHandler(OpenedEvent, value);\n    }\n\n    /// <summary>\n    /// Occurs when the image fails to load the uri provided.\n    /// </summary>\n    public event EventHandler<global::Avalonia.Labs.Controls.AsyncImage.AsyncImageFailedEventArgs>? Failed\n    {\n        add => AddHandler(FailedEvent, value);\n        remove => RemoveHandler(FailedEvent, value);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImage.Properties.cs",
    "content": "﻿using System;\nusing Avalonia;\nusing Avalonia.Animation;\nusing Avalonia.Controls;\nusing Avalonia.Labs.Controls;\nusing Avalonia.Media;\nusing StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs;\n\npublic partial class BetterAsyncImage\n{\n    /// <summary>\n    /// Defines the <see cref=\"PlaceholderSource\"/> property.\n    /// </summary>\n    public static readonly StyledProperty<IImage?> PlaceholderSourceProperty = AvaloniaProperty.Register<\n        BetterAsyncImage,\n        IImage?\n    >(nameof(PlaceholderSource));\n\n    /// <summary>\n    /// Defines the <see cref=\"Source\"/> property.\n    /// </summary>\n    public static readonly StyledProperty<Uri?> SourceProperty = AvaloniaProperty.Register<\n        BetterAsyncImage,\n        Uri?\n    >(nameof(Source));\n\n    /// <summary>\n    /// Defines the <see cref=\"Stretch\"/> property.\n    /// </summary>\n    public static readonly StyledProperty<Stretch> StretchProperty = AvaloniaProperty.Register<\n        BetterAsyncImage,\n        Stretch\n    >(nameof(Stretch), Stretch.Uniform);\n\n    /// <summary>\n    /// Defines the <see cref=\"PlaceholderStretch\"/> property.\n    /// </summary>\n    public static readonly StyledProperty<Stretch> PlaceholderStretchProperty = AvaloniaProperty.Register<\n        BetterAsyncImage,\n        Stretch\n    >(nameof(PlaceholderStretch), Stretch.Uniform);\n\n    /// <summary>\n    /// Defines the <see cref=\"State\"/> property.\n    /// </summary>\n    public static readonly DirectProperty<BetterAsyncImage, AsyncImageState> StateProperty =\n        AvaloniaProperty.RegisterDirect<BetterAsyncImage, AsyncImageState>(\n            nameof(State),\n            o => o.State,\n            (o, v) => o.State = v\n        );\n\n    /// <summary>\n    /// Defines the <see cref=\"ImageTransition\"/> property.\n    /// </summary>\n    public static readonly StyledProperty<IPageTransition?> ImageTransitionProperty =\n        AvaloniaProperty.Register<BetterAsyncImage, IPageTransition?>(\n            nameof(ImageTransition),\n            new CrossFade(TimeSpan.FromSeconds(0.25))\n        );\n\n    /// <summary>\n    /// Defines the <see cref=\"IsCacheEnabled\"/> property.\n    /// </summary>\n    public static readonly DirectProperty<BetterAsyncImage, bool> IsCacheEnabledProperty =\n        AvaloniaProperty.RegisterDirect<BetterAsyncImage, bool>(\n            nameof(IsCacheEnabled),\n            o => o.IsCacheEnabled,\n            (o, v) => o.IsCacheEnabled = v\n        );\n    private bool _isCacheEnabled;\n\n    /// <summary>\n    /// Gets or sets the placeholder image.\n    /// </summary>\n    public IImage? PlaceholderSource\n    {\n        get => GetValue(PlaceholderSourceProperty);\n        set => SetValue(PlaceholderSourceProperty, value);\n    }\n\n    /// <summary>\n    /// Gets or sets the uri pointing to the image resource\n    /// </summary>\n    public Uri? Source\n    {\n        get => GetValue(SourceProperty);\n        set => SetValue(SourceProperty, value);\n    }\n\n    /// <summary>\n    /// Gets or sets a value controlling how the image will be stretched.\n    /// </summary>\n    public Stretch Stretch\n    {\n        get { return GetValue(StretchProperty); }\n        set { SetValue(StretchProperty, value); }\n    }\n\n    /// <summary>\n    /// Gets or sets a value controlling how the placeholder will be stretched.\n    /// </summary>\n    public Stretch PlaceholderStretch\n    {\n        get { return GetValue(StretchProperty); }\n        set { SetValue(StretchProperty, value); }\n    }\n\n    /// <summary>\n    /// Gets the current loading state of the image.\n    /// </summary>\n    public AsyncImageState State\n    {\n        get => _state;\n        private set => SetAndRaise(StateProperty, ref _state, value);\n    }\n\n    /// <summary>\n    /// Gets or sets the transition to run when the image is loaded.\n    /// </summary>\n    public IPageTransition? ImageTransition\n    {\n        get => GetValue(ImageTransitionProperty);\n        set => SetValue(ImageTransitionProperty, value);\n    }\n\n    /// <summary>\n    /// Gets or sets whether to use cache for retrieved images\n    /// </summary>\n    public bool IsCacheEnabled\n    {\n        get => _isCacheEnabled;\n        set => SetAndRaise(IsCacheEnabledProperty, ref _isCacheEnabled, value);\n    }\n\n    public static readonly AttachedProperty<IImageCache?> ImageCacheProperty =\n        AvaloniaProperty.RegisterAttached<BetterAsyncImage, Control, IImageCache?>(\"ImageCache\", null, true);\n\n    public IImageCache? ImageCache\n    {\n        get => GetValue(ImageCacheProperty);\n        set => SetValue(ImageCacheProperty, value);\n    }\n\n    public static void SetImageCache(Control control, IImageCache? value)\n    {\n        control.SetValue(ImageCacheProperty, value);\n    }\n\n    public static IImageCache? GetImageCache(Control control)\n    {\n        return control.GetValue(ImageCacheProperty);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImage.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Metadata;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Interactivity;\nusing Avalonia.Labs.Controls;\nusing Avalonia.Media;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Platform;\nusing Avalonia.Threading;\nusing StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs;\n\n/// <summary>\n/// An image control that asynchronously retrieves an image using a <see cref=\"Uri\"/>.\n/// </summary>\n[TemplatePart(\"PART_Image\", typeof(Image))]\n[TemplatePart(\"PART_PlaceholderImage\", typeof(Image))]\npublic partial class BetterAsyncImage : TemplatedControlBase\n{\n    private static NLog.Logger Logger { get; } = NLog.LogManager.GetCurrentClassLogger();\n\n    protected Image? ImagePart { get; private set; }\n    protected Image? PlaceholderPart { get; private set; }\n\n    private bool _isInitialized;\n    private CancellationTokenSource? _setSourceCts;\n    private CancellationTokenSource? _attachSourceAnimationCts;\n    private AsyncImageState _state;\n\n    private readonly Lazy<IImageCache?> _instanceImageCache;\n    public IImageCache? InstanceImageCache => _instanceImageCache.Value;\n\n    public BetterAsyncImage()\n    {\n        // Need to run on UI thread to get our attached property, so we cache the result\n        _instanceImageCache = new Lazy<IImageCache?>(\n            () => Dispatcher.UIThread.Invoke(() => GetImageCache(this))\n        );\n    }\n\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        ImagePart = e.NameScope.Get<Image>(\"PART_Image\");\n        PlaceholderPart = e.NameScope.Get<Image>(\"PART_PlaceholderImage\");\n\n        _isInitialized = true;\n\n        // Skip loading the image if we're disabled\n        if (!IsEffectivelyEnabled)\n            return;\n\n        // In case property change didn't trigger the initial load, do it now\n        if (State == AsyncImageState.Unloaded && Source is not null)\n        {\n            SetSource(Source);\n        }\n    }\n\n    /// <summary>\n    /// Cancels the current <see cref=\"_setSourceCts\"/> and sets and returns a new <see cref=\"CancellationTokenSource\"/>.\n    /// </summary>\n    private CancellationTokenSource CancelAndSetNewTokenSource(\n        ref CancellationTokenSource? cancellationTokenSource\n    )\n    {\n        var newTokenSource = new CancellationTokenSource();\n\n        var oldTokenSource = Interlocked.Exchange(ref cancellationTokenSource, newTokenSource);\n\n        if (oldTokenSource is not null)\n        {\n            try\n            {\n                oldTokenSource.Cancel();\n            }\n            catch (ObjectDisposedException) { }\n        }\n\n        return newTokenSource;\n    }\n\n    private async void SetSource(object? source)\n    {\n        if (!_isInitialized)\n        {\n            return;\n        }\n\n        var newTokenSource = CancelAndSetNewTokenSource(ref _setSourceCts);\n\n        // AttachSource(null, newTokenSource.Token);\n\n        if (source == null)\n        {\n            return;\n        }\n\n        State = AsyncImageState.Loading;\n\n        if (Source is IImage image)\n        {\n            AttachSource(image, newTokenSource.Token);\n\n            return;\n        }\n\n        if (Source == null)\n        {\n            return;\n        }\n\n        var uri = Source;\n\n        if (!uri.IsAbsoluteUri)\n        {\n            State = AsyncImageState.Failed;\n\n            RaiseEvent(\n                new AsyncImageFailedEventArgs(\n                    new UriFormatException($\"Relative paths aren't supported. Uri:{source}\")\n                )\n            );\n\n            return;\n        }\n\n        try\n        {\n            var bitmap = await Task.Run(\n                async () =>\n                {\n                    // A small delay allows to cancel early if the image goes out of screen too fast (e.g. scrolling)\n                    // The Bitmap constructor is expensive and cannot be cancelled\n                    await Task.Delay(10, newTokenSource.Token);\n\n                    if (uri.Scheme is \"http\" or \"https\")\n                    {\n                        return await LoadImageAsync(uri, newTokenSource.Token);\n                    }\n\n                    if (uri.Scheme == \"file\" && File.Exists(uri.LocalPath))\n                    {\n                        if (!IsCacheEnabled)\n                        {\n                            return new Bitmap(uri.LocalPath);\n                        }\n\n                        return await LoadImageAsync(uri, newTokenSource.Token);\n                    }\n\n                    if (uri.Scheme == \"avares\")\n                    {\n                        return new Bitmap(AssetLoader.Open(uri));\n                    }\n\n                    throw new UriFormatException($\"Uri has unsupported scheme. Uri:{source}\");\n                },\n                CancellationToken.None\n            );\n\n            newTokenSource.Token.ThrowIfCancellationRequested();\n\n            AttachSource(bitmap, newTokenSource.Token);\n        }\n        catch (OperationCanceledException ex)\n        {\n            State = AsyncImageState.Unloaded;\n\n            // Logger.Info(ex, \"Canceled loading image from {Uri}\", uri);\n        }\n        catch (Exception ex)\n        {\n            State = AsyncImageState.Failed;\n\n            // Logger.Warn(ex, \"Failed to load image from {Uri} ({Ex})\", uri, ex.ToString());\n\n            RaiseEvent(new AsyncImageFailedEventArgs(ex));\n        }\n        finally\n        {\n            newTokenSource.Dispose();\n        }\n    }\n\n    private void AttachSource(IImage? image, CancellationToken cancellationToken)\n    {\n        if (ImagePart != null)\n        {\n            ImagePart.Source = image;\n        }\n\n        // Get new animation token source, cancel previous ones\n        var newAnimationCts = CancelAndSetNewTokenSource(ref _attachSourceAnimationCts);\n\n        if (image == null)\n        {\n            State = AsyncImageState.Unloaded;\n\n            ImageTransition\n                ?.Start(ImagePart, PlaceholderPart, true, newAnimationCts.Token)\n                .ContinueWith(_ => newAnimationCts.Dispose(), CancellationToken.None);\n        }\n        else if (image.Size != default)\n        {\n            State = AsyncImageState.Loaded;\n\n            ImageTransition\n                ?.Start(PlaceholderPart, ImagePart, true, newAnimationCts.Token)\n                .ContinueWith(_ => newAnimationCts.Dispose(), CancellationToken.None);\n\n            RaiseEvent(new RoutedEventArgs(OpenedEvent));\n        }\n    }\n\n    private async Task<IImage?> LoadImageAsync(Uri url, CancellationToken cancellationToken)\n    {\n        // Get specific cache for this control or use the default one\n        var cache = InstanceImageCache;\n\n        cache ??= BetterAsyncImageCacheProvider.DefaultCache;\n\n        // Logger.Trace(\"Using ImageCache: <{Type}:{Id}>\", cache.GetType().Name, cache.GetHashCode());\n\n        if (IsCacheEnabled)\n        {\n            return await cache.GetWithCacheAsync(url, cancellationToken);\n        }\n\n        return await cache.GetAsync(url, cancellationToken);\n    }\n\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        // If we're disabled, don't load the image\n        if (!IsEffectivelyEnabled)\n            return;\n\n        if (change.Property == SourceProperty)\n        {\n            SetSource(Source);\n        }\n        else if (change.Property == IsEffectivelyEnabledProperty)\n        {\n            // When we become enabled, reload the image since it was skipped at apply template\n            if (change.GetNewValue<bool>() && State == AsyncImageState.Unloaded)\n            {\n                SetSource(Source);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/AsyncImage/BetterAsyncImageCacheProvider.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Reflection;\nusing System.Threading;\nusing Fusillade;\nusing StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs;\n\npublic static class BetterAsyncImageCacheProvider\n{\n    private static readonly Lazy<ImageCache> DefaultCacheLazy =\n        new(\n            () =>\n                new ImageCache(\n                    new CacheOptions\n                    {\n                        // ReSharper disable twice LocalizableElement\n                        BaseCachePath =\n                            Assembly.GetExecutingAssembly().FullName is { } assemblyName\n                            && !string.IsNullOrEmpty(assemblyName)\n                                ? Path.Combine(Path.GetTempPath(), assemblyName, \"Cache\")\n                                : Path.Combine(Path.GetTempPath(), \"Cache\"),\n                        CacheDuration = TimeSpan.FromDays(1),\n                        HttpMessageHandler = NetCache.UserInitiated\n                    }\n                ),\n            LazyThreadSafetyMode.ExecutionAndPublication\n        );\n\n    private static IImageCache? _defaultCache;\n\n    public static IImageCache DefaultCache\n    {\n        get => _defaultCache ?? DefaultCacheLazy.Value;\n        set => _defaultCache = value;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/CacheBase.cs",
    "content": "﻿// Parts of this file was taken from Windows Community Toolkit CacheBase implementation\n// https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp.UI/Cache/ImageCache.cs\n\n// Licensed to the .NET Foundation under one or more agreements.\n// The .NET Foundation licenses this file to you under the MIT license.\n// See the LICENSE file in the project root for more information.\n\nusing System.Collections.Concurrent;\nusing System.ComponentModel;\nusing System.Diagnostics;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\n[Localizable(false)]\ninternal abstract class CacheBase<T>\n{\n    private class ConcurrentRequest(Func<Task<T?>> factory)\n    {\n        private readonly Lazy<Task<T?>> _factory = new(factory, LazyThreadSafetyMode.ExecutionAndPublication);\n\n        public Task<T?> Task => _factory.Value;\n\n        public bool EnsureCachedCopy { get; set; }\n\n        public bool IsCompletedSuccessfully =>\n            _factory is { IsValueCreated: true, Value.IsCompletedSuccessfully: true };\n    }\n\n    private readonly SemaphoreSlim _cacheFolderSemaphore = new SemaphoreSlim(1);\n    private string? _baseFolder = null;\n    private string? _cacheFolderName = null;\n\n    private string? _cacheFolder = null;\n    protected InMemoryStorage<T>? InMemoryFileStorage = new();\n\n    private readonly ConcurrentDictionary<string, ConcurrentRequest> _concurrentTasks = new();\n\n    private HttpMessageHandler? _httpMessageHandler;\n    private HttpClient? _httpClient = null;\n\n    /// <summary>\n    /// Initializes a new instance of the <see cref=\"CacheBase{T}\"/> class.\n    /// </summary>\n    protected CacheBase(CacheOptions? options = null)\n    {\n        options ??= CacheOptions.Default;\n        _baseFolder = options.BaseCachePath ?? null;\n        _cacheFolderName = options.CacheFolderName ?? null;\n\n        CacheDuration = options.CacheDuration ?? TimeSpan.FromDays(1);\n        MaxMemoryCacheCount = options.MaxMemoryCacheCount ?? 0;\n        RetryCount = 1;\n\n        _httpMessageHandler = options.HttpMessageHandler;\n        _httpClient = options.HttpClient;\n    }\n\n    /// <summary>\n    /// Gets or sets the life duration of every cache entry.\n    /// </summary>\n    public TimeSpan CacheDuration { get; set; }\n\n    /// <summary>\n    /// Gets or sets the number of retries trying to ensure the file is cached.\n    /// </summary>\n    public uint RetryCount { get; set; }\n\n    /// <summary>\n    /// Gets or sets max in-memory item storage count\n    /// </summary>\n    public int MaxMemoryCacheCount\n    {\n        get { return InMemoryFileStorage?.MaxItemCount ?? 0; }\n        set\n        {\n            if (InMemoryFileStorage != null)\n                InMemoryFileStorage.MaxItemCount = value;\n        }\n    }\n\n    /// <summary>\n    /// Gets instance of <see cref=\"HttpClient\"/>\n    /// </summary>\n    protected HttpClient HttpClient\n    {\n        get\n        {\n            if (_httpClient == null)\n            {\n                _httpClient = new HttpClient(_httpMessageHandler ?? new HttpClientHandler());\n            }\n\n            return _httpClient;\n        }\n    }\n\n    /// <summary>\n    /// Initializes FileCache and provides root folder and cache folder name\n    /// </summary>\n    /// <param name=\"folder\">Folder that is used as root for cache</param>\n    /// <param name=\"folderName\">Cache folder name</param>\n    /// <param name=\"httpMessageHandler\">instance of <see cref=\"HttpMessageHandler\"/></param>\n    /// <returns>awaitable task</returns>\n    public virtual async Task InitializeAsync(\n        string? folder = null,\n        string? folderName = null,\n        HttpMessageHandler? httpMessageHandler = null\n    )\n    {\n        _baseFolder = folder;\n        _cacheFolderName = folderName;\n\n        _cacheFolder = await GetCacheFolderAsync().ConfigureAwait(false);\n\n        if (httpMessageHandler != null)\n        {\n            _httpClient = new HttpClient(httpMessageHandler);\n        }\n    }\n\n    /// <summary>\n    /// Clears all files in the cache\n    /// </summary>\n    /// <returns>awaitable task</returns>\n    public async Task ClearAsync()\n    {\n        var folder = await GetCacheFolderAsync().ConfigureAwait(false);\n        var files = Directory.EnumerateFiles(folder!);\n\n        await InternalClearAsync(files.Select(x => x as string)).ConfigureAwait(false);\n\n        InMemoryFileStorage?.Clear();\n    }\n\n    /// <summary>\n    /// Clears file if it has expired\n    /// </summary>\n    /// <param name=\"duration\">timespan to compute whether file has expired or not</param>\n    /// <returns>awaitable task</returns>\n    public Task ClearAsync(TimeSpan duration)\n    {\n        return RemoveExpiredAsync(duration);\n    }\n\n    /// <summary>\n    /// Removes cached files that have expired\n    /// </summary>\n    /// <param name=\"duration\">Optional timespan to compute whether file has expired or not. If no value is supplied, <see cref=\"CacheDuration\"/> is used.</param>\n    /// <returns>awaitable task</returns>\n    public async Task RemoveExpiredAsync(TimeSpan? duration = null)\n    {\n        var expiryDuration = duration ?? CacheDuration;\n\n        var folder = await GetCacheFolderAsync().ConfigureAwait(false);\n        var files = Directory.EnumerateFiles(folder!);\n\n        var filesToDelete = new List<string>();\n\n        foreach (var file in files)\n        {\n            if (file == null)\n            {\n                continue;\n            }\n\n            if (await IsFileOutOfDateAsync(file, expiryDuration, false).ConfigureAwait(false))\n            {\n                filesToDelete.Add(file);\n            }\n        }\n\n        await InternalClearAsync(filesToDelete).ConfigureAwait(false);\n\n        InMemoryFileStorage?.Clear(expiryDuration);\n    }\n\n    /// <summary>\n    /// Removed items based on uri list passed\n    /// </summary>\n    /// <param name=\"uriForCachedItems\">Enumerable uri list</param>\n    /// <returns>awaitable Task</returns>\n    public async Task RemoveAsync(IEnumerable<Uri> uriForCachedItems)\n    {\n        if (uriForCachedItems == null || !uriForCachedItems.Any())\n        {\n            return;\n        }\n\n        var folder = await GetCacheFolderAsync().ConfigureAwait(false);\n        var files = Directory.EnumerateFiles(folder!);\n        var filesToDelete = new List<string>();\n        var keys = new List<string>();\n\n        var hashDictionary = new Dictionary<string, string>();\n\n        foreach (var file in files)\n        {\n            hashDictionary.Add(Path.GetFileName(file), file);\n        }\n\n        foreach (var uri in uriForCachedItems)\n        {\n            var fileName = GetCacheFileName(uri);\n            if (hashDictionary.TryGetValue(fileName, out var file))\n            {\n                filesToDelete.Add(file);\n                keys.Add(fileName);\n            }\n        }\n\n        await InternalClearAsync(filesToDelete).ConfigureAwait(false);\n\n        InMemoryFileStorage?.Remove(keys);\n    }\n\n    /// <summary>\n    /// Assures that item represented by Uri is cached.\n    /// </summary>\n    /// <param name=\"uri\">Uri of the item</param>\n    /// <param name=\"throwOnError\">Indicates whether or not exception should be thrown if item cannot be cached</param>\n    /// <param name=\"storeToMemoryCache\">Indicates if item should be loaded into the in-memory storage</param>\n    /// <param name=\"cancellationToken\">instance of <see cref=\"CancellationToken\"/></param>\n    /// <returns>Awaitable Task</returns>\n    public Task PreCacheAsync(\n        Uri uri,\n        bool throwOnError = false,\n        bool storeToMemoryCache = false,\n        CancellationToken cancellationToken = default\n    )\n    {\n        return GetItemAsync(uri, throwOnError, !storeToMemoryCache, cancellationToken);\n    }\n\n    /// <summary>\n    /// Retrieves item represented by Uri from the cache. If the item is not found in the cache, it will try to downloaded and saved before returning it to the caller.\n    /// </summary>\n    /// <param name=\"uri\">Uri of the item.</param>\n    /// <param name=\"throwOnError\">Indicates whether or not exception should be thrown if item cannot be found / downloaded.</param>\n    /// <param name=\"cancellationToken\">instance of <see cref=\"CancellationToken\"/></param>\n    /// <returns>an instance of Generic type</returns>\n    public Task<T?> GetFromCacheAsync(\n        Uri uri,\n        bool throwOnError = false,\n        CancellationToken cancellationToken = default\n    )\n    {\n        return GetItemAsync(uri, throwOnError, false, cancellationToken);\n    }\n\n    /// <summary>\n    /// Gets the string containing cached item for given Uri\n    /// </summary>\n    /// <param name=\"uri\">Uri of the item.</param>\n    /// <returns>a string</returns>\n    public async Task<string> GetFileFromCacheAsync(Uri uri)\n    {\n        var folder = await GetCacheFolderAsync().ConfigureAwait(false);\n\n        return Path.Combine(folder!, GetCacheFileName(uri));\n    }\n\n    /// <summary>\n    /// Retrieves item represented by Uri from the in-memory cache if it exists and is not out of date. If item is not found or is out of date, default instance of the generic type is returned.\n    /// </summary>\n    /// <param name=\"uri\">Uri of the item.</param>\n    /// <returns>an instance of Generic type</returns>\n    public T? GetFromMemoryCache(Uri uri)\n    {\n        var instance = default(T);\n\n        var fileName = GetCacheFileName(uri);\n\n        if (InMemoryFileStorage?.MaxItemCount > 0)\n        {\n            var msi = InMemoryFileStorage?.GetItem(fileName, CacheDuration);\n            if (msi != null)\n            {\n                instance = msi.Item;\n            }\n        }\n\n        return instance;\n    }\n\n    /// <summary>\n    /// Cache specific hooks to process items from HTTP response\n    /// </summary>\n    /// <param name=\"stream\">input stream</param>\n    /// <returns>awaitable task</returns>\n    protected abstract Task<T> ConvertFromAsync(Stream stream);\n\n    /// <summary>\n    /// Cache specific hooks to process items from HTTP response\n    /// </summary>\n    /// <param name=\"baseFile\">storage file</param>\n    /// <returns>awaitable task</returns>\n    protected abstract Task<T> ConvertFromAsync(string baseFile);\n\n    /// <summary>\n    /// Override-able method that checks whether file is valid or not.\n    /// </summary>\n    /// <param name=\"file\">storage file</param>\n    /// <param name=\"duration\">cache duration</param>\n    /// <param name=\"treatNullFileAsOutOfDate\">option to mark uninitialized file as expired</param>\n    /// <returns>bool indicate whether file has expired or not</returns>\n    protected virtual async Task<bool> IsFileOutOfDateAsync(\n        string file,\n        TimeSpan duration,\n        bool treatNullFileAsOutOfDate = true\n    )\n    {\n        if (file == null)\n        {\n            return treatNullFileAsOutOfDate;\n        }\n\n        var info = new FileInfo(file);\n\n        return info.Length == 0 || DateTime.Now.Subtract(info.LastWriteTime) > duration;\n    }\n\n    private static string GetCacheFileName(Uri uri)\n    {\n        return CreateHash64(uri.ToString()).ToString();\n    }\n\n    private static ulong CreateHash64(string str)\n    {\n        var utf8 = System.Text.Encoding.UTF8.GetBytes(str);\n\n        var value = (ulong)utf8.Length;\n        for (var n = 0; n < utf8.Length; n++)\n        {\n            value += (ulong)utf8[n] << ((n * 5) % 56);\n        }\n\n        return value;\n    }\n\n    [DebuggerDisableUserUnhandledExceptions]\n    private async Task<T?> GetItemAsync(\n        Uri uri,\n        bool throwOnError,\n        bool preCacheOnly,\n        CancellationToken cancellationToken\n    )\n    {\n        var fileName = GetCacheFileName(uri);\n\n        // Check if already in memory cache\n        if (InMemoryFileStorage?.MaxItemCount > 0)\n        {\n            var msi = InMemoryFileStorage?.GetItem(fileName, CacheDuration);\n            if (msi != null)\n            {\n                return msi.Item;\n            }\n        }\n\n        // Atomically get or add\n        var request = _concurrentTasks.GetOrAdd(\n            fileName,\n            key =>\n            {\n                return new ConcurrentRequest(\n                    () => GetFromCacheOrDownloadAsync(uri, key, preCacheOnly, cancellationToken)\n                );\n            }\n        );\n\n        try\n        {\n            // Wait for the task to complete\n            var itemTask = request.Task;\n            var instance = await itemTask.ConfigureAwait(false);\n\n            // --- Handle In-Memory Caching ---\n            // If the current request is not preCacheOnly, and the instance was successfully retrieved,\n            // ensure it's in the memory cache.\n            if (!preCacheOnly && instance != null && InMemoryFileStorage is { MaxItemCount: > 0 })\n            {\n                var memItem = InMemoryFileStorage.GetItem(fileName, CacheDuration);\n                if (memItem == null || memItem.Item == null) // Check if not already in memory or expired\n                {\n                    var folder = await GetCacheFolderAsync().ConfigureAwait(false);\n                    var lastWriteTime = DateTime.Now;\n                    if (folder != null)\n                    {\n                        var baseFile = Path.Combine(folder, fileName);\n                        try\n                        {\n                            if (File.Exists(baseFile)) // Check existence before FileInfo\n                                lastWriteTime = new FileInfo(baseFile).LastWriteTime;\n                        }\n                        catch (IOException ioEx)\n                        {\n                            Debug.WriteLine(\n                                $\"CacheBase: Error getting FileInfo for memory cache update on {fileName}: {ioEx.Message}\"\n                            );\n                        }\n                        catch (Exception ex)\n                        {\n                            Debug.WriteLine(\n                                $\"CacheBase: Error getting FileInfo for memory cache update on {fileName}: {ex.Message}\"\n                            );\n                        }\n                    }\n\n                    var msi = new InMemoryStorageItem<T>(fileName, lastWriteTime, instance);\n                    InMemoryFileStorage.SetItem(msi);\n                }\n            }\n\n            return instance;\n        }\n        catch (Exception ex)\n        {\n            Debug.WriteLine($\"CacheBase: Exception during GetItemAsync for {fileName} (URI: {uri}): {ex}\");\n\n            // Attempt to remove the entry associated with the failed task.\n            _concurrentTasks.TryRemove(new KeyValuePair<string, ConcurrentRequest>(fileName, request));\n\n            if (throwOnError)\n            {\n                throw;\n            }\n            return default;\n        }\n        finally\n        {\n            // If the request was created and its underlying task completed successfully, remove the entry.\n            // Ensure we don't remove entries for tasks still running.\n            if (request.IsCompletedSuccessfully)\n            {\n                // Remove the entry only if it still contains the Lazy instance we worked with.\n                _concurrentTasks.TryRemove(new KeyValuePair<string, ConcurrentRequest>(fileName, request));\n            }\n        }\n    }\n\n    private async Task<T?> GetFromCacheOrDownloadAsync(\n        Uri uri,\n        string fileName,\n        bool preCacheOnly,\n        CancellationToken cancellationToken\n    )\n    {\n        var instance = default(T);\n\n        if (InMemoryFileStorage?.MaxItemCount > 0)\n        {\n            var msi = InMemoryFileStorage?.GetItem(fileName, CacheDuration);\n            if (msi != null)\n            {\n                instance = msi.Item;\n            }\n        }\n\n        if (instance != null)\n        {\n            return instance;\n        }\n\n        var isLocal = File.Exists(uri.LocalPath);\n        var isRemote = uri.Scheme is \"http\" or \"https\";\n\n        // Only cache to file for remote\n        if (isRemote)\n        {\n            var folder = await GetCacheFolderAsync().ConfigureAwait(false);\n            var baseFile = Path.Combine(folder!, fileName);\n\n            var downloadDataFile =\n                !File.Exists(baseFile)\n                || await IsFileOutOfDateAsync(baseFile, CacheDuration).ConfigureAwait(false);\n\n            if (!File.Exists(baseFile))\n            {\n                File.Create(baseFile).Dispose();\n            }\n\n            if (downloadDataFile)\n            {\n                uint retries = 0;\n                try\n                {\n                    while (retries < RetryCount)\n                    {\n                        try\n                        {\n                            instance = await DownloadFileAsync(uri, baseFile, preCacheOnly, cancellationToken)\n                                .ConfigureAwait(false);\n\n                            if (instance != null)\n                            {\n                                break;\n                            }\n                        }\n                        catch (FileNotFoundException) { }\n\n                        retries++;\n                    }\n                }\n                catch (Exception)\n                {\n                    File.Delete(baseFile);\n                    throw; // re-throwing the exception changes the stack trace. just throw\n                }\n            }\n\n            // Cache\n            if (EqualityComparer<T>.Default.Equals(instance, default) && !preCacheOnly)\n            {\n                instance = await ConvertFromAsync(baseFile).ConfigureAwait(false);\n\n                if (InMemoryFileStorage?.MaxItemCount > 0)\n                {\n                    var properties = new FileInfo(baseFile);\n\n                    var msi = new InMemoryStorageItem<T>(fileName, properties.LastWriteTime, instance);\n                    InMemoryFileStorage?.SetItem(msi);\n                }\n            }\n        }\n        else if (isLocal)\n        {\n            for (var i = 0; i < RetryCount; i++)\n            {\n                cancellationToken.ThrowIfCancellationRequested();\n\n                try\n                {\n                    instance = await LoadLocalFileAsync(uri, cancellationToken).ConfigureAwait(false);\n\n                    if (instance != null)\n                    {\n                        break;\n                    }\n                }\n                catch (FileNotFoundException) { }\n            }\n\n            // Cache\n            if (instance != null && InMemoryFileStorage?.MaxItemCount > 0)\n            {\n                var msi = new InMemoryStorageItem<T>(fileName, DateTime.Now, instance);\n                InMemoryFileStorage?.SetItem(msi);\n            }\n        }\n        else\n        {\n            throw new ArgumentException(\"Uri scheme is not supported\", nameof(uri));\n        }\n\n        return instance;\n    }\n\n    [DebuggerDisableUserUnhandledExceptions]\n    private async Task<T?> DownloadFileAsync(\n        Uri uri,\n        string baseFile,\n        bool preCacheOnly,\n        CancellationToken cancellationToken\n    )\n    {\n        var instance = default(T);\n\n        Debug.WriteLine($\"CacheBase Getting: {uri}\");\n\n        using var ms = new MemoryStream();\n        await using (var stream = await HttpClient.GetStreamAsync(uri, cancellationToken))\n        {\n            await stream.CopyToAsync(ms, cancellationToken);\n            await ms.FlushAsync(cancellationToken);\n\n            ms.Position = 0;\n\n            await using (var fs = File.Open(baseFile, FileMode.OpenOrCreate, FileAccess.Write))\n            {\n                await ms.CopyToAsync(fs, cancellationToken);\n\n                await fs.FlushAsync(cancellationToken);\n\n                ms.Position = 0;\n            }\n        }\n\n        // if its pre-cache we aren't looking to load items in memory\n        if (!preCacheOnly)\n        {\n            instance = await ConvertFromAsync(ms).ConfigureAwait(false);\n        }\n\n        return instance;\n    }\n\n    private async Task<T?> LoadLocalFileAsync(Uri uri, CancellationToken cancellationToken)\n    {\n        using var ms = new MemoryStream();\n\n        await using (var stream = File.Open(uri.LocalPath, FileMode.Open, FileAccess.Read, FileShare.Read))\n        {\n            await stream.CopyToAsync(ms, cancellationToken);\n            await ms.FlushAsync(cancellationToken);\n        }\n\n        ms.Position = 0;\n\n        return await ConvertFromAsync(ms).ConfigureAwait(false);\n    }\n\n    private async Task InternalClearAsync(IEnumerable<string?> files)\n    {\n        foreach (var file in files)\n        {\n            try\n            {\n                File.Delete(file!);\n            }\n            catch\n            {\n                // Just ignore errors for now}\n            }\n        }\n    }\n\n    /// <summary>\n    /// Initializes with default values if user has not initialized explicitly\n    /// </summary>\n    /// <returns>awaitable task</returns>\n    private async Task ForceInitialiseAsync()\n    {\n        if (_cacheFolder != null)\n        {\n            return;\n        }\n\n        await _cacheFolderSemaphore.WaitAsync().ConfigureAwait(false);\n\n        var currentMaxItemCount = InMemoryFileStorage?.MaxItemCount ?? 0;\n\n        InMemoryFileStorage = new InMemoryStorage<T> { MaxItemCount = currentMaxItemCount };\n\n        if (_baseFolder == null)\n        {\n            _baseFolder = Path.GetTempPath();\n        }\n\n        if (string.IsNullOrWhiteSpace(_cacheFolderName))\n        {\n            _cacheFolderName = GetType().Name;\n        }\n\n        try\n        {\n            _cacheFolder = Path.Combine(_baseFolder, _cacheFolderName);\n            Directory.CreateDirectory(_cacheFolder);\n        }\n        finally\n        {\n            _cacheFolderSemaphore.Release();\n        }\n    }\n\n    private async Task<string?> GetCacheFolderAsync()\n    {\n        if (_cacheFolder == null)\n        {\n            await ForceInitialiseAsync().ConfigureAwait(false);\n        }\n\n        return _cacheFolder;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/CacheOptions.cs",
    "content": "﻿using System;\nusing System.Net.Http;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\npublic class CacheOptions\n{\n    private static CacheOptions? _cacheOptions;\n\n    public static CacheOptions Default => _cacheOptions ??= new CacheOptions();\n\n    public static void SetDefault(CacheOptions defaultCacheOptions)\n    {\n        _cacheOptions = defaultCacheOptions;\n    }\n\n    public string? BaseCachePath { get; set; }\n    public string? CacheFolderName { get; set; }\n    public TimeSpan? CacheDuration { get; set; }\n    public int? MaxMemoryCacheCount { get; set; }\n\n    public HttpMessageHandler? HttpMessageHandler { get; set; }\n    public HttpClient? HttpClient { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/FileCache.cs",
    "content": "using System.IO;\nusing System.Threading.Tasks;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\n/// <summary>\n/// Provides methods and tools to cache files in a folder\n/// </summary>\ninternal class FileCache : CacheBase<string>\n{\n    /// <summary>\n    /// Private singleton field.\n    /// </summary>\n    private static FileCache? _instance;\n\n    /// <summary>\n    /// Gets public singleton property.\n    /// </summary>\n    public static FileCache Instance => _instance ?? (_instance = new FileCache());\n\n    protected override Task<string> ConvertFromAsync(Stream stream)\n    {\n        // nothing to do in this instance;\n        return Task.FromResult<string>(\"\");\n    }\n\n    /// <summary>\n    /// Returns a cached path\n    /// </summary>\n    /// <param name=\"baseFile\">storage file</param>\n    /// <returns>awaitable task</returns>\n    protected override Task<string> ConvertFromAsync(string baseFile)\n    {\n        return Task.FromResult(baseFile);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/IImageCache.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Avalonia.Media;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\npublic interface IImageCache\n{\n    /// <summary>\n    /// Assures that item represented by Uri is cached.\n    /// </summary>\n    /// <param name=\"uri\">Uri of the item</param>\n    /// <param name=\"cancellationToken\">instance of <see cref=\"CancellationToken\"/></param>\n    /// <returns>Awaitable Task</returns>\n    Task PreCacheAsync(Uri uri, CancellationToken cancellationToken = default);\n\n    /// <summary>\n    /// Retrieves item represented by Uri locally or by downloading, does not cache the item.\n    /// </summary>\n    /// <param name=\"uri\">Uri of the item.</param>\n    /// <param name=\"cancellationToken\">instance of <see cref=\"CancellationToken\"/></param>\n    /// <returns>an instance of Generic type</returns>\n    Task<IImage?> GetAsync(Uri uri, CancellationToken cancellationToken = default);\n\n    /// <summary>\n    /// Retrieves item represented by Uri from the cache.\n    /// If the item is not found in the cache, it downloads and saves before returning it to the caller.\n    /// </summary>\n    /// <param name=\"uri\">Uri of the item.</param>\n    /// <param name=\"cancellationToken\">instance of <see cref=\"CancellationToken\"/></param>\n    /// <returns>an instance of Generic type</returns>\n    Task<IImage?> GetWithCacheAsync(Uri uri, CancellationToken cancellationToken = default);\n\n    int ClearMemoryCache();\n\n    int ClearMemoryCache(DateTime olderThan);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/ImageCache.cs",
    "content": "using System;\nusing System.IO;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Avalonia.Media;\nusing Avalonia.Media.Imaging;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\n/// <summary>\n/// Provides methods and tools to cache images in a folder\n/// </summary>\ninternal class ImageCache(CacheOptions? options = null) : CacheBase<Bitmap>(options), IImageCache\n{\n    /// <summary>\n    /// Creates a bitmap from a stream\n    /// </summary>\n    /// <param name=\"stream\">input stream</param>\n    /// <returns>awaitable task</returns>\n    protected override async Task<Bitmap> ConvertFromAsync(Stream stream)\n    {\n        if (stream.Length == 0)\n        {\n            throw new FileNotFoundException();\n        }\n\n        return new Bitmap(stream);\n    }\n\n    /// <summary>\n    /// Creates a bitmap from a cached file\n    /// </summary>\n    /// <param name=\"baseFile\">file</param>\n    /// <returns>awaitable task</returns>\n    protected override async Task<Bitmap> ConvertFromAsync(string baseFile)\n    {\n        using (var stream = File.OpenRead(baseFile))\n        {\n            return await ConvertFromAsync(stream).ConfigureAwait(false);\n        }\n    }\n\n    /// <summary>\n    /// Checks whether file is valid or not.\n    /// </summary>\n    /// <param name=\"file\">file</param>\n    /// <param name=\"duration\">cache duration</param>\n    /// <param name=\"treatNullFileAsOutOfDate\">option to mark uninitialized file as expired</param>\n    /// <returns>bool indicate whether file has expired or not</returns>\n    protected override async Task<bool> IsFileOutOfDateAsync(\n        string? file,\n        TimeSpan duration,\n        bool treatNullFileAsOutOfDate = true\n    )\n    {\n        if (file == null)\n        {\n            return treatNullFileAsOutOfDate;\n        }\n\n        var fileInfo = new FileInfo(file);\n\n        return fileInfo.Length == 0\n            || DateTime.Now.Subtract(File.GetLastAccessTime(file)) > duration\n            || DateTime.Now.Subtract(File.GetLastWriteTime(file)) > duration;\n    }\n\n    public Task PreCacheAsync(Uri uri, CancellationToken cancellationToken = default)\n    {\n        return PreCacheAsync(uri, true, true, cancellationToken);\n    }\n\n    public async Task<IImage?> GetAsync(Uri uri, CancellationToken cancellationToken = default)\n    {\n        return await GetFromCacheAsync(uri, false, cancellationToken).ConfigureAwait(false);\n    }\n\n    public async Task<IImage?> GetWithCacheAsync(Uri uri, CancellationToken cancellationToken = default)\n    {\n        return await GetFromCacheAsync(uri, true, cancellationToken).ConfigureAwait(false);\n    }\n\n    public int ClearMemoryCache()\n    {\n        var count = InMemoryFileStorage?.Count ?? 0;\n\n        if (count > 0)\n        {\n            InMemoryFileStorage!.Clear();\n        }\n\n        return count;\n    }\n\n    public int ClearMemoryCache(DateTime olderThan)\n    {\n        var count = InMemoryFileStorage?.Count ?? 0;\n\n        if (count > 0)\n        {\n            InMemoryFileStorage!.Clear(olderThan);\n        }\n\n        return count;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/InMemoryStorage.cs",
    "content": "// Licensed to the .NET Foundation under one or more agreements.\n// The .NET Foundation licenses this file to you under the MIT license.\n// See the LICENSE file in the project root for more information.\n\nusing System;\nusing System.Collections.Concurrent;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Linq;\nusing System.Runtime.CompilerServices;\nusing KGySoft.CoreLibraries;\nusing StabilityMatrix.Core.Helper.Cache;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\n/// <summary>\n/// Generic in-memory storage of items\n/// </summary>\n/// <typeparam name=\"T\">T defines the type of item stored</typeparam>\npublic class InMemoryStorage<T>\n{\n    private readonly Dictionary<string, LinkedListNode<InMemoryStorageItem<T>>> _inMemoryStorage = new();\n    private readonly LinkedList<InMemoryStorageItem<T>> _lruList = [];\n\n    private int _maxItemCount;\n    private object _settingMaxItemCountLocker = new();\n\n    /// <summary>\n    /// Gets or sets the maximum count of Items that can be stored in this InMemoryStorage instance.\n    /// </summary>\n    public int MaxItemCount\n    {\n        get => _maxItemCount;\n        set\n        {\n            if (_maxItemCount == value)\n            {\n                return;\n            }\n\n            _maxItemCount = value;\n\n            lock (_settingMaxItemCountLocker)\n            {\n                EnsureStorageBounds(value);\n            }\n        }\n    }\n\n    public int Count => _inMemoryStorage.Count;\n\n    /// <summary>\n    /// Clears all items stored in memory\n    /// </summary>\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public void Clear()\n    {\n        _inMemoryStorage.Clear();\n        _lruList.Clear();\n    }\n\n    /// <summary>\n    /// Clears items stored in memory based on duration passed\n    /// </summary>\n    /// <param name=\"duration\">TimeSpan to identify expired items</param>\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public void Clear(TimeSpan duration)\n    {\n        Clear(DateTime.Now.Subtract(duration));\n    }\n\n    /// <summary>\n    /// Clears items stored in memory based on duration passed\n    /// </summary>\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public void Clear(DateTime expirationDate)\n    {\n        foreach (var (key, node) in _inMemoryStorage)\n        {\n            if (string.IsNullOrWhiteSpace(key))\n            {\n                continue;\n            }\n\n            var item = node.Value;\n            if (item.LastUpdated > expirationDate)\n            {\n                continue;\n            }\n\n            Remove(key);\n        }\n    }\n\n    /// <summary>\n    /// Remove items based on provided keys\n    /// </summary>\n    /// <param name=\"keys\">identified of the in-memory storage item</param>\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public void Remove(IEnumerable<string> keys)\n    {\n        foreach (var key in keys)\n        {\n            if (string.IsNullOrWhiteSpace(key))\n            {\n                continue;\n            }\n\n            Remove(key);\n        }\n    }\n\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public void Remove(string key)\n    {\n        if (!_inMemoryStorage.TryGetValue(key, out var node))\n            return;\n\n        _lruList.Remove(node);\n        _inMemoryStorage.Remove(key);\n    }\n\n    /// <summary>\n    /// Add new item to in-memory storage\n    /// </summary>\n    /// <param name=\"item\">item to be stored</param>\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public void SetItem(InMemoryStorageItem<T> item)\n    {\n        if (MaxItemCount == 0)\n        {\n            return;\n        }\n\n        if (_inMemoryStorage.TryGetValue(item.Id, out var node))\n        {\n            _lruList.Remove(node);\n        }\n        else if (_inMemoryStorage.Count >= MaxItemCount)\n        {\n            RemoveFirst();\n        }\n\n        var newNode = new LinkedListNode<InMemoryStorageItem<T>>(item);\n        _lruList.AddLast(newNode);\n        _inMemoryStorage[item.Id] = newNode;\n\n        /*// ensure max limit is maintained. trim older entries first\n        if (_inMemoryStorage.Count > MaxItemCount)\n        {\n            var itemsToRemove = _inMemoryStorage\n                .OrderBy(kvp => kvp.Value.Created)\n                .Take(_inMemoryStorage.Count - MaxItemCount)\n                .Select(kvp => kvp.Key);\n            Remove(itemsToRemove);\n        }*/\n    }\n\n    /// <summary>\n    /// Get item from in-memory storage as long as it has not ex\n    /// </summary>\n    /// <param name=\"id\">id of the in-memory storage item</param>\n    /// <param name=\"duration\">timespan denoting expiration</param>\n    /// <returns>Valid item if not out of date or return null if out of date or item does not exist</returns>\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public InMemoryStorageItem<T>? GetItem(string id, TimeSpan duration)\n    {\n        if (!_inMemoryStorage.TryGetValue(id, out var node))\n        {\n            return null;\n        }\n\n        var expirationDate = DateTime.Now.Subtract(duration);\n\n        if (node.Value.LastUpdated <= expirationDate)\n        {\n            Remove(id);\n            return null;\n        }\n\n        _lruList.Remove(node);\n        _lruList.AddLast(node);\n\n        return node.Value;\n    }\n\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public InMemoryStorageItem<T>? GetItem(string id)\n    {\n        if (!_inMemoryStorage.TryGetValue(id, out var node))\n        {\n            return null;\n        }\n\n        var value = node.Value;\n\n        _lruList.Remove(node);\n        _lruList.AddLast(node);\n\n        return value;\n    }\n\n    private void RemoveFirst()\n    {\n        // Remove from LRUPriority\n        var node = _lruList.First;\n        _lruList.RemoveFirst();\n\n        if (node == null)\n            return;\n\n        // Remove from cache\n        _inMemoryStorage.Remove(node.Value.Id);\n    }\n\n    private void EnsureStorageBounds(int maxCount)\n    {\n        if (_inMemoryStorage.Count == 0)\n        {\n            return;\n        }\n\n        if (maxCount == 0)\n        {\n            _inMemoryStorage.Clear();\n            return;\n        }\n\n        if (_inMemoryStorage.Count > maxCount)\n        {\n            Remove(_inMemoryStorage.Keys.Take(_inMemoryStorage.Count - maxCount));\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/InMemoryStorageItem.cs",
    "content": "// Licensed to the .NET Foundation under one or more agreements.\n// The .NET Foundation licenses this file to you under the MIT license.\n// See the LICENSE file in the project root for more information.\n\nusing System;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\n/// <summary>\n/// Generic InMemoryStorageItem holds items for InMemoryStorage.\n/// </summary>\n/// <typeparam name=\"T\">Type is set by consuming cache</typeparam>\npublic class InMemoryStorageItem<T>\n{\n    /// <summary>\n    /// Gets the item identifier\n    /// </summary>\n    public string Id { get; private set; }\n\n    /// <summary>\n    /// Gets the item created timestamp.\n    /// </summary>\n    public DateTime Created { get; private set; }\n\n    /// <summary>\n    /// Gets the item last updated timestamp.\n    /// </summary>\n    public DateTime LastUpdated { get; private set; }\n\n    /// <summary>\n    /// Gets the item being stored.\n    /// </summary>\n    public T Item { get; private set; }\n\n    /// <summary>\n    /// Initializes a new instance of the <see cref=\"InMemoryStorageItem{T}\"/> class.\n    /// Constructor for InMemoryStorageItem\n    /// </summary>\n    /// <param name=\"id\">uniquely identifies the item</param>\n    /// <param name=\"lastUpdated\">last updated timestamp</param>\n    /// <param name=\"item\">the item being stored</param>\n    public InMemoryStorageItem(string id, DateTime lastUpdated, T item)\n    {\n        Id = id;\n        LastUpdated = lastUpdated;\n        Item = item;\n        Created = DateTime.Now;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/Cache/MemoryImageCache.cs",
    "content": "// Parts of this file was taken from Windows Community Toolkit CacheBase implementation\n// https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp.UI/Cache/ImageCache.cs\n\n// Licensed to the .NET Foundation under one or more agreements.\n// The .NET Foundation licenses this file to you under the MIT license.\n// See the LICENSE file in the project root for more information.\n\nusing System;\nusing System.Collections.Concurrent;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Avalonia.Media;\nusing Avalonia.Media.Imaging;\nusing SkiaSharp;\nusing StabilityMatrix.Avalonia.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\ninternal class MemoryImageCache : IImageCache\n{\n    private class ConcurrentRequest\n    {\n        public Task<IImage?>? Task { get; init; }\n    }\n\n    private readonly InMemoryStorage<IImage?>? _inMemoryFileStorage = new();\n\n    private readonly ConcurrentDictionary<string, ConcurrentRequest> _concurrentTasks = new();\n\n    private HttpClient? _httpClient;\n\n    /// <summary>\n    /// Gets or sets the life duration of every cache entry.\n    /// </summary>\n    public TimeSpan CacheDuration { get; set; } = TimeSpan.FromMinutes(10);\n\n    /// <summary>\n    /// Gets or sets the number of retries trying to ensure the file is cached.\n    /// </summary>\n    public uint RetryCount { get; set; } = 1;\n\n    /// <summary>\n    /// Gets or sets max in-memory item storage count\n    /// </summary>\n    public int MaxMemoryCacheCount\n    {\n        get => _inMemoryFileStorage?.MaxItemCount ?? 0;\n        set\n        {\n            if (_inMemoryFileStorage != null)\n                _inMemoryFileStorage.MaxItemCount = value;\n        }\n    }\n\n    /// <summary>\n    /// Gets instance of <see cref=\"HttpClient\"/>\n    /// </summary>\n    protected HttpClient HttpClient\n    {\n        get\n        {\n            if (_httpClient == null)\n            {\n                var messageHandler = new HttpClientHandler();\n\n                _httpClient = new HttpClient(messageHandler);\n            }\n\n            return _httpClient;\n        }\n    }\n\n    /// <summary>\n    /// Initializes FileCache and provides root folder and cache folder name\n    /// </summary>\n    /// <param name=\"folder\">Folder that is used as root for cache</param>\n    /// <param name=\"folderName\">Cache folder name</param>\n    /// <param name=\"httpMessageHandler\">instance of <see cref=\"HttpMessageHandler\"/></param>\n    /// <returns>awaitable task</returns>\n    public virtual async Task InitializeAsync(\n        string? folder = null,\n        string? folderName = null,\n        HttpMessageHandler? httpMessageHandler = null\n    )\n    {\n        if (httpMessageHandler != null)\n        {\n            _httpClient = new HttpClient(httpMessageHandler);\n        }\n    }\n\n    /// <summary>\n    /// Clears all files in the cache\n    /// </summary>\n    public void Clear()\n    {\n        _inMemoryFileStorage?.Clear();\n    }\n\n    /// <summary>\n    /// Removes cached images that have expired\n    /// </summary>\n    /// <param name=\"duration\">Optional timespan to compute whether file has expired or not. If no value is supplied, <see cref=\"CacheDuration\"/> is used.</param>\n    public void ClearExpired(TimeSpan? duration = null)\n    {\n        var expiryDuration = duration ?? CacheDuration;\n\n        _inMemoryFileStorage?.Clear(expiryDuration);\n    }\n\n    /// <summary>\n    /// Removed items based on uri list passed\n    /// </summary>\n    /// <param name=\"uriForCachedItems\">Enumerable uri list</param>\n    /// <returns>awaitable Task</returns>\n    public void Remove(IEnumerable<Uri> uriForCachedItems)\n    {\n        _inMemoryFileStorage?.Remove(uriForCachedItems.Select(GetCacheFileName));\n    }\n\n    /// <summary>\n    /// Assures that item represented by Uri is cached.\n    /// </summary>\n    /// <param name=\"uri\">Uri of the item</param>\n    /// <param name=\"throwOnError\">Indicates whether or not exception should be thrown if item cannot be cached</param>\n    /// <param name=\"storeToMemoryCache\">Indicates if item should be loaded into the in-memory storage</param>\n    /// <param name=\"cancellationToken\">instance of <see cref=\"CancellationToken\"/></param>\n    /// <returns>Awaitable Task</returns>\n    public Task PreCacheAsync(Uri uri, CancellationToken cancellationToken = default)\n    {\n        return GetWithCacheAsync(uri, cancellationToken);\n    }\n\n    /// <summary>\n    /// Retrieves item represented by Uri from the in-memory cache if it exists and is not out of date. If item is not found or is out of date, default instance of the generic type is returned.\n    /// </summary>\n    /// <param name=\"uri\">Uri of the item.</param>\n    /// <returns>an instance of Generic type</returns>\n    public IImage? GetFromMemoryCache(Uri uri)\n    {\n        var fileName = GetCacheFileName(uri);\n\n        if (_inMemoryFileStorage?.MaxItemCount > 0)\n        {\n            var msi = _inMemoryFileStorage?.GetItem(fileName, CacheDuration);\n            if (msi != null)\n            {\n                return msi.Item;\n            }\n        }\n\n        return null;\n    }\n\n    private static string GetCacheFileName(Uri uri)\n    {\n        return CreateHash64(uri.ToString()).ToString();\n    }\n\n    private static ulong CreateHash64(string str)\n    {\n        var utf8 = System.Text.Encoding.UTF8.GetBytes(str);\n\n        var value = (ulong)utf8.Length;\n        for (var n = 0; n < utf8.Length; n++)\n        {\n            value += (ulong)utf8[n] << ((n * 5) % 56);\n        }\n\n        return value;\n    }\n\n    public async Task<IImage?> GetAsync(Uri uri, CancellationToken cancellationToken)\n    {\n        IImage? instance = null;\n\n        for (var i = 0; i < RetryCount; i++)\n        {\n            try\n            {\n                // Local\n                if (File.Exists(uri.LocalPath))\n                {\n                    instance = LoadLocalImageWithSkia(uri);\n                }\n                // Remote\n                else\n                {\n                    instance = await DownloadImageAsync(uri, cancellationToken).ConfigureAwait(false);\n                }\n\n                if (instance != null)\n                {\n                    break;\n                }\n            }\n            catch (FileNotFoundException) { }\n        }\n\n        return instance;\n    }\n\n    public async Task<IImage?> GetWithCacheAsync(Uri uri, CancellationToken cancellationToken)\n    {\n        IImage? instance = null;\n\n        var fileName = GetCacheFileName(uri);\n        _concurrentTasks.TryGetValue(fileName, out var request);\n\n        if (request != null)\n        {\n            if (request.Task != null)\n                await request.Task.ConfigureAwait(false);\n            request = null;\n        }\n\n        if (request == null)\n        {\n            request = new ConcurrentRequest\n            {\n                Task = GetItemWithCacheAsync(uri, fileName, cancellationToken),\n            };\n\n            _concurrentTasks[fileName] = request;\n        }\n\n        try\n        {\n            if (request.Task != null)\n            {\n                instance = await request.Task.ConfigureAwait(false);\n            }\n        }\n        finally\n        {\n            _concurrentTasks.TryRemove(fileName, out _);\n        }\n\n        return instance;\n    }\n\n    public int ClearMemoryCache()\n    {\n        var count = _inMemoryFileStorage?.Count ?? 0;\n\n        if (count > 0)\n        {\n            _inMemoryFileStorage!.Clear();\n        }\n\n        return count;\n    }\n\n    public int ClearMemoryCache(DateTime olderThan)\n    {\n        var count = _inMemoryFileStorage?.Count ?? 0;\n\n        if (count > 0)\n        {\n            _inMemoryFileStorage!.Clear(olderThan);\n        }\n\n        return count;\n    }\n\n    private async Task<IImage?> GetItemWithCacheAsync(\n        Uri uri,\n        string cacheKey,\n        CancellationToken cancellationToken\n    )\n    {\n        IImage? instance = null;\n\n        if (_inMemoryFileStorage?.MaxItemCount > 0)\n        {\n            var msi = _inMemoryFileStorage?.GetItem(cacheKey, CacheDuration);\n            if (msi != null)\n            {\n                instance = msi.Item;\n            }\n        }\n\n        if (instance != null)\n        {\n            return instance;\n        }\n\n        var isLocal = File.Exists(uri.LocalPath);\n\n        for (var i = 0; i < RetryCount; i++)\n        {\n            try\n            {\n                if (isLocal)\n                {\n                    instance = LoadLocalImageWithSkia(uri);\n                }\n                else\n                {\n                    instance = await DownloadImageAsync(uri, cancellationToken).ConfigureAwait(false);\n                }\n\n                if (instance != null)\n                {\n                    break;\n                }\n            }\n            catch (FileNotFoundException) { }\n        }\n\n        // Cache the item\n        if (instance != null && _inMemoryFileStorage?.MaxItemCount > 0)\n        {\n            var msi = new InMemoryStorageItem<IImage?>(cacheKey, DateTime.Now, instance);\n            _inMemoryFileStorage?.SetItem(msi);\n        }\n\n        return instance;\n    }\n\n    private static async Task<IImage?> LoadLocalImageAsync(Uri uri, CancellationToken cancellationToken)\n    {\n        using var ms = new MemoryStream();\n\n        await using (var stream = File.Open(uri.LocalPath, FileMode.Open, FileAccess.Read, FileShare.Read))\n        {\n            await stream.CopyToAsync(ms, cancellationToken);\n            await ms.FlushAsync(cancellationToken);\n        }\n\n        ms.Position = 0;\n\n        var image = new Bitmap(ms);\n        return image;\n    }\n\n    private static IImage? LoadLocalImageWithSkia(Uri uri)\n    {\n        using var skFileStream = new SKFileStream(uri.LocalPath);\n\n        return SKBitmap.Decode(skFileStream).ToAvaloniaImage();\n    }\n\n    private async Task<IImage?> DownloadImageAsync(Uri uri, CancellationToken cancellationToken)\n    {\n        using var ms = new MemoryStream();\n\n        await using (var stream = await HttpClient.GetStreamAsync(uri, cancellationToken))\n        {\n            await stream.CopyToAsync(ms, cancellationToken);\n            await ms.FlushAsync(cancellationToken);\n        }\n\n        ms.Position = 0;\n\n        var image = new Bitmap(ms);\n        return image;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 AvaloniaUI\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VendorLabs/Themes/BetterAsyncImage.axaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n  xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n  xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\">\n  <Design.PreviewWith>\n    <Border Width=\"200\"\n            Height=\"200\">\n        \n    </Border>\n  </Design.PreviewWith>\n\n  <ControlTheme x:Key=\"{x:Type vendorLabs:BetterAsyncImage}\"\n                TargetType=\"vendorLabs:BetterAsyncImage\">\n    <Setter Property=\"Background\" Value=\"Transparent\" />\n    <Setter Property=\"IsTabStop\" Value=\"False\" />\n    <Setter Property=\"Template\">\n        <ControlTemplate>\n            <Border Margin=\"0\"\n                    Padding=\"0\"\n                    ClipToBounds=\"True\"\n                    Background=\"{TemplateBinding Background}\"\n                    BorderBrush=\"{TemplateBinding BorderBrush}\"\n                    BorderThickness=\"{TemplateBinding BorderThickness}\"\n                    CornerRadius=\"{TemplateBinding CornerRadius}\">\n                <Grid HorizontalAlignment=\"Stretch\" VerticalAlignment=\"Stretch\">\n                    <Image Name=\"PART_PlaceholderImage\"\n                           HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\"\n                           VerticalAlignment=\"{TemplateBinding VerticalAlignment}\"\n                           Source=\"{TemplateBinding PlaceholderSource}\"\n                           Stretch=\"{TemplateBinding PlaceholderStretch}\"/>\n                    <Image Name=\"PART_Image\"\n                           HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\"\n                           VerticalAlignment=\"{TemplateBinding VerticalAlignment}\"\n                           Stretch=\"{TemplateBinding Stretch}\"/>\n                </Grid>                \n            </Border>\n        </ControlTemplate>\n    </Setter>\n      <Style Selector=\"^[State=Failed] /template/ Image#PART_Image\">\n          <Setter Property=\"Opacity\"\n                  Value=\"0.0\" />\n      </Style>\n      <Style Selector=\"^[State=Failed] /template/ Image#PART_PlaceholderImage\">\n          <Setter Property=\"Opacity\"\n                  Value=\"1.0\" />\n      </Style>\n  </ControlTheme>\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n        x:DataType=\"video:SvdImgToVidConditioningViewModel\"\n        xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n        xmlns:video=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference.Video\"\n        xmlns:controls1=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n        xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\">\n    <Design.PreviewWith>\n        <Grid MinWidth=\"400\">\n            <controls:VideoGenerationSettingsCard DataContext=\"{x:Static mocks:DesignData.SvdImgToVidConditioningViewModel}\" />\n        </Grid>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|VideoGenerationSettingsCard\">\n        <!-- Set Defaults -->\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"8\" HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\">\n                    <Grid Margin=\"4\" RowDefinitions=\"Auto,Auto,Auto,Auto,Auto\" ColumnDefinitions=\"Auto, *\">\n                        <TextBlock\n                            Grid.Row=\"0\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,0,8,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"{x:Static lang:Resources.Label_Frames}\" />\n                        <controls1:NumberBox\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            SelectionHighlightColor=\"Transparent\"\n                            Value=\"{Binding NumFrames}\"\n                            Margin=\"8,0,0,0\"\n                            SimpleNumberFormat=\"F0\"\n                            SmallChange=\"1\"\n                            HorizontalAlignment=\"Stretch\"\n                            SpinButtonPlacementMode=\"Inline\"/>\n                        \n                            <TextBlock\n                                Grid.Row=\"1\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,8,8,0\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"{x:Static lang:Resources.Label_Fps}\" />\n                            <controls1:NumberBox\n                                Grid.Row=\"1\"\n                                Grid.Column=\"1\"\n                                SelectionHighlightColor=\"Transparent\"\n                                Value=\"{Binding Fps}\"\n                                Margin=\"8,8,0,0\"\n                                SimpleNumberFormat=\"F0\"\n                                SmallChange=\"1\"\n                                HorizontalAlignment=\"Stretch\"\n                                SpinButtonPlacementMode=\"Inline\"/>\n                        \n                            <TextBlock\n                                Grid.Row=\"2\"\n                                Grid.Column=\"0\"\n                                Margin=\"0,8,8,0\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"{x:Static lang:Resources.Label_MinCfg}\" />\n                            <controls1:NumberBox\n                                Margin=\"8,8,0,0\"\n                                Grid.Row=\"2\"\n                                Grid.Column=\"1\"\n                                SelectionHighlightColor=\"Transparent\"\n                                Value=\"{Binding MinCfg}\"\n                                SimpleNumberFormat=\"F0\"\n                                SmallChange=\"1\"\n                                HorizontalAlignment=\"Stretch\"\n                                SpinButtonPlacementMode=\"Inline\"/>\n                        \n                            <TextBlock\n                                Margin=\"0,8,8,0\"\n                                Grid.Row=\"3\"\n                                Grid.Column=\"0\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"{x:Static lang:Resources.Label_MotionBucketId}\" />\n                            <controls1:NumberBox\n                                Margin=\"8,8,0,0\"\n                                Grid.Row=\"3\"\n                                Grid.Column=\"1\"\n                                SelectionHighlightColor=\"Transparent\"\n                                Value=\"{Binding MotionBucketId}\"\n                                SimpleNumberFormat=\"F0\"\n                                SmallChange=\"1\"\n                                HorizontalAlignment=\"Stretch\"\n                                SpinButtonPlacementMode=\"Inline\"/>\n                        \n                        <StackPanel Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"2\"\n                                    Grid.Row=\"4\" \n                                    Margin=\"0,16,0,0\">\n                            <Grid ColumnDefinitions=\"*,Auto\">\n                                <TextBlock \n                                    VerticalAlignment=\"Center\"\n                                    Text=\"{x:Static lang:Resources.Label_AugmentationLevel}\" />\n                                <controls1:NumberBox\n                                    Grid.Column=\"1\"\n                                    Margin=\"4,0,0,0\"\n                                    ValidationMode=\"InvalidInputOverwritten\"\n                                    SmallChange=\"0.01\"\n                                    SimpleNumberFormat=\"F2\"\n                                    Value=\"{Binding AugmentationLevel}\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    MinWidth=\"100\"\n                                    SpinButtonPlacementMode=\"Compact\"/>\n                            </Grid>\n                            <Slider \n                                Minimum=\"0\"\n                                Maximum=\"10\"\n                                Value=\"{Binding AugmentationLevel}\"\n                                TickFrequency=\"1\"\n                                Margin=\"0,0,0,-4\"\n                                TickPlacement=\"BottomRight\"/>\n                        </StackPanel>\n                    </Grid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VideoGenerationSettingsCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<VideoGenerationSettingsCard>]\npublic class VideoGenerationSettingsCard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VideoOutputSettingsCard.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:controls=\"using:StabilityMatrix.Avalonia.Controls\"\n        x:DataType=\"video:VideoOutputSettingsCardViewModel\"\n        xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n        xmlns:video=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference.Video\"\n        xmlns:controls1=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n        xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\">\n    <Design.PreviewWith>\n        <Grid MinWidth=\"400\">\n            <controls:VideoOutputSettingsCard\n                DataContext=\"{x:Static mocks:DesignData.SvdImgToVidConditioningViewModel}\" />\n        </Grid>\n    </Design.PreviewWith>\n\n    <Style Selector=\"controls|VideoOutputSettingsCard\">\n        <!-- Set Defaults -->\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <controls:Card Padding=\"8\" HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\">\n                    <Grid Margin=\"4\" RowDefinitions=\"Auto,Auto,Auto,Auto,Auto\" ColumnDefinitions=\"Auto, *\">\n                        <TextBlock \n                            Grid.Row=\"0\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"2\"\n                            Text=\"Video Output Settings\"\n                            FontSize=\"16\"\n                            FontWeight=\"DemiBold\"\n                            Margin=\"0,0,0,16\"\n                            />\n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,0,8,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"{x:Static lang:Resources.Label_Fps}\" />\n                        <controls1:NumberBox\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            SelectionHighlightColor=\"Transparent\"\n                            Value=\"{Binding Fps}\"\n                            Margin=\"8,0,0,0\"\n                            SimpleNumberFormat=\"F0\"\n                            SmallChange=\"1\"\n                            HorizontalAlignment=\"Stretch\"\n                            SpinButtonPlacementMode=\"Inline\" />\n\n                        <TextBlock\n                            Grid.Row=\"2\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,8,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"{x:Static lang:Resources.Label_Lossless}\" />\n                        <CheckBox\n                            Grid.Row=\"2\"\n                            Grid.Column=\"1\"\n                            IsChecked=\"{Binding Lossless}\"\n                            Margin=\"8,8,0,0\"\n                            HorizontalAlignment=\"Stretch\" />\n\n                        <TextBlock\n                            Grid.Row=\"3\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,8,8,0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"{x:Static lang:Resources.Label_VideoQuality}\" />\n                        <controls1:NumberBox\n                            Margin=\"8,8,0,0\"\n                            Grid.Row=\"3\"\n                            Grid.Column=\"1\"\n                            SelectionHighlightColor=\"Transparent\"\n                            Value=\"{Binding Quality}\"\n                            SimpleNumberFormat=\"F0\"\n                            SmallChange=\"1\"\n                            Maximum=\"100\"\n                            HorizontalAlignment=\"Stretch\"\n                            SpinButtonPlacementMode=\"Inline\" />\n\n                        <TextBlock\n                            Margin=\"0,8,8,0\"\n                            Grid.Row=\"4\"\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"{x:Static lang:Resources.Label_VideoOutputMethod}\" />\n                        <ComboBox\n                            Grid.Row=\"4\"\n                            Grid.Column=\"1\"\n                            Margin=\"8,8,0,0\"\n                            MinWidth=\"100\"\n                            ItemsSource=\"{Binding AvailableMethods}\"\n                            SelectedIndex=\"{Binding SelectedMethod}\" />\n                    </Grid>\n                </controls:Card>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Controls/VideoOutputSettingsCard.axaml.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Controls;\n\n[RegisterTransient<VideoOutputSettingsCard>]\npublic class VideoOutputSettingsCard : TemplatedControlBase { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/BooleanChoiceMultiConverter.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class BooleanChoiceMultiConverter : IMultiValueConverter\n{\n    /// <inheritdoc />\n    public object? Convert(\n        IList<object?> values,\n        Type targetType,\n        object? parameter,\n        CultureInfo culture\n    )\n    {\n        if (values.Count < 3)\n        {\n            return null;\n        }\n\n        if (values[0] is bool boolValue)\n        {\n            return boolValue ? values[1] : values[2];\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/CivitImageWidthConverter.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Diagnostics;\nusing System.Globalization;\nusing Avalonia;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\n[Localizable(false)]\npublic class CivitImageWidthConverter : IValueConverter\n{\n    private const string CivitaiHost = \"image.civitai.com\";\n    private const string WidthKey = \"width\";\n\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        // --- Input Validation ---\n        Uri? inputUri = null;\n        if (value is string urlString && Uri.TryCreate(urlString, UriKind.Absolute, out var parsedUri))\n        {\n            inputUri = parsedUri;\n        }\n        else if (value is Uri uri)\n        {\n            inputUri = uri;\n        }\n\n        // Check if it's a valid Civitai URL\n        if (\n            inputUri == null\n            || !inputUri.IsAbsoluteUri\n            || !inputUri.Host.Equals(CivitaiHost, StringComparison.OrdinalIgnoreCase)\n        )\n        {\n            // Not a valid Civitai URL, return original value or UnsetValue\n            return value is Uri ? value : AvaloniaProperty.UnsetValue;\n        }\n\n        // Check and parse the ConverterParameter for the target width\n        if (\n            parameter == null\n            || !int.TryParse(\n                parameter.ToString(),\n                NumberStyles.Integer,\n                CultureInfo.InvariantCulture,\n                out var targetWidth\n            )\n            || targetWidth <= 0\n        )\n        {\n            // Invalid or missing width parameter, return original URI\n            return inputUri;\n        }\n\n        // --- URL Modification ---\n        try\n        {\n            var builder = new UriBuilder(inputUri);\n            var pathSegments = builder.Path.Split('/', StringSplitOptions.RemoveEmptyEntries).ToList();\n\n            if (pathSegments.Count < 2) // Need at least /{guid}/{filename} or similar structure\n            {\n                // Path structure isn't recognized, return original\n                return inputUri;\n            }\n\n            // Assume the transformation segment is the second to last one\n            var transformSegmentIndex = pathSegments.Count - 2;\n            var potentialTransformSegment = pathSegments[transformSegmentIndex];\n\n            var transformSegmentFoundAndUpdated = false;\n            var parameters = ParseTransformSegment(potentialTransformSegment);\n\n            if (parameters != null) // It looks like a transformation segment\n            {\n                var widthExists = parameters.ContainsKey(WidthKey);\n                parameters[WidthKey] = targetWidth.ToString(CultureInfo.InvariantCulture); // Update or add width\n\n                // Reconstruct the segment\n                pathSegments[transformSegmentIndex] = string.Join(\n                    \",\",\n                    parameters.Select(kvp => $\"{kvp.Key}={kvp.Value}\")\n                );\n                transformSegmentFoundAndUpdated = true;\n            }\n\n            if (!transformSegmentFoundAndUpdated)\n            {\n                // Transformation segment not found or didn't parse correctly, insert a new width segment\n                pathSegments.Insert(\n                    pathSegments.Count - 1,\n                    $\"{WidthKey}={targetWidth.ToString(CultureInfo.InvariantCulture)}\"\n                );\n            }\n\n            // Reconstruct the path. UriBuilder.Path needs leading '/'\n            builder.Path = \"/\" + string.Join(\"/\", pathSegments);\n\n            // Return the modified Uri\n            // Check if the target type prefers string or Uri\n            if (targetType == typeof(string))\n            {\n                return builder.Uri.ToString();\n            }\n            return builder.Uri;\n        }\n        catch (Exception ex)\n        {\n            Debug.WriteLine($\"Error converting Civitai URL: {ex.Message}\");\n\n            // Return original URI on error\n            return inputUri;\n        }\n    }\n\n    // Simple parser for \"key1=value1,key2=value2\" format\n    private Dictionary<string, string>? ParseTransformSegment(string segment)\n    {\n        // Basic check: must contain '=' and not be just a filename (heuristic: no '.')\n        if (!segment.Contains('=') || segment.Contains('.'))\n        {\n            return null;\n        }\n\n        var parameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);\n        var pairs = segment.Split(',');\n\n        foreach (var pair in pairs)\n        {\n            var keyValue = pair.Split('=', 2); // Split only on the first '='\n            if (keyValue.Length == 2)\n            {\n                parameters[keyValue[0].Trim()] = keyValue[1].Trim();\n            }\n            else\n            {\n                // Invalid pair format, assume segment is not a transform segment\n                return null;\n            }\n        }\n        // If we successfully parsed at least one pair, return the dictionary\n        return parameters.Count > 0 ? parameters : null;\n    }\n\n    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        throw new NotSupportedException($\"{nameof(CivitImageWidthConverter)} does not support ConvertBack.\");\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/ComfyUpscalerConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class ComfyUpscalerConverter : IValueConverter\n{\n    /// <inheritdoc />\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        throw new NotImplementedException();\n    }\n\n    /// <inheritdoc />\n    public object? ConvertBack(\n        object? value,\n        Type targetType,\n        object? parameter,\n        CultureInfo culture\n    )\n    {\n        throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/CultureInfoDisplayConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class CultureInfoDisplayConverter : IValueConverter\n{\n    /// <inheritdoc />\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is not CultureInfo cultureInfo)\n            return null;\n\n        return cultureInfo.TextInfo.ToTitleCase(cultureInfo.NativeName);\n    }\n\n    /// <inheritdoc />\n    public object ConvertBack(\n        object? value,\n        Type targetType,\n        object? parameter,\n        CultureInfo culture\n    )\n    {\n        throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/CustomStringFormatConverter.cs",
    "content": "﻿using System;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class CustomStringFormatConverter<T>([StringSyntax(\"CompositeFormat\")] string format) : IValueConverter\n    where T : IFormatProvider, new()\n{\n    /// <inheritdoc />\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        return value is null ? null : string.Format(new T(), format, value);\n    }\n\n    /// <inheritdoc />\n    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        return value is null ? null : throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/EnumAttributeConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing System.Linq;\nusing System.Reflection;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\n/// <summary>\n/// Converts an enum value to an attribute\n/// </summary>\n/// <typeparam name=\"TAttribute\">Type of attribute</typeparam>\npublic class EnumAttributeConverter<TAttribute>(Func<TAttribute, object?>? accessor = null) : IValueConverter\n    where TAttribute : Attribute\n{\n    /// <inheritdoc />\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is null)\n            return null;\n\n        if (value is not Enum @enum)\n            throw new ArgumentException(\"Value must be an enum\");\n\n        var field = @enum.GetType().GetField(@enum.ToString());\n        if (field is null)\n            throw new ArgumentException(\"Value must be an enum\");\n\n        if (field.GetCustomAttributes<TAttribute>().FirstOrDefault() is not { } attribute)\n            throw new ArgumentException($\"Enum value {@enum} does not have attribute {typeof(TAttribute)}\");\n\n        return accessor is not null ? accessor(attribute) : attribute;\n    }\n\n    /// <inheritdoc />\n    public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        throw new NotSupportedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/EnumAttributeConverters.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\ninternal static class EnumAttributeConverters\n{\n    public static EnumAttributeConverter<DisplayAttribute> Display => new();\n\n    public static EnumAttributeConverter<DisplayAttribute> DisplayName => new(attribute => attribute.Name);\n\n    public static EnumAttributeConverter<DisplayAttribute> DisplayDescription =>\n        new(attribute => attribute.Description);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/EnumStringConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class EnumStringConverter : IValueConverter\n{\n    /// <inheritdoc />\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is not Enum enumValue)\n            return null;\n\n        return enumValue.GetStringValue();\n    }\n\n    /// <inheritdoc />\n    public object? ConvertBack(\n        object? value,\n        Type targetType,\n        object? parameter,\n        CultureInfo culture\n    )\n    {\n        if (value is not string stringValue)\n            return null;\n\n        return Enum.Parse(targetType, stringValue);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/EnumToBooleanConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia.Data;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class EnumToBooleanConverter : IValueConverter\n{\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        return value?.Equals(parameter);\n    }\n\n    public object? ConvertBack(\n        object? value,\n        Type targetType,\n        object? parameter,\n        CultureInfo culture\n    )\n    {\n        return value?.Equals(true) == true ? parameter : BindingOperations.DoNothing;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/EnumToIntConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing System.Runtime.CompilerServices;\nusing Avalonia.Data.Converters;\nusing KGySoft.CoreLibraries;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class EnumToIntConverter<TEnum> : IValueConverter\n    where TEnum : struct, Enum\n{\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is TEnum enumValue)\n        {\n            return System.Convert.ToInt32(enumValue);\n        }\n\n        return null;\n    }\n\n    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is int intValue)\n        {\n            return Unsafe.As<int, TEnum>(ref intValue);\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/EnumToValuesConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\n/// <summary>\n/// Converts an enum value to an array of values\n/// </summary>\npublic class EnumToValuesConverter : IValueConverter\n{\n    /// <inheritdoc />\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value == null)\n        {\n            return null;\n        }\n\n        var type = value.GetType();\n        if (!type.IsEnum)\n        {\n            return null;\n        }\n\n        return Enum.GetValues(type);\n    }\n\n    /// <inheritdoc />\n    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        throw new NotSupportedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/FileSizeConverters.cs",
    "content": "﻿using System;\nusing System.Diagnostics.CodeAnalysis;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\n[SuppressMessage(\"ReSharper\", \"LocalizableElement\")]\npublic static class FileSizeConverters\n{\n    public static FuncValueConverter<object?, string> HumanizeFileSizeConverter { get; } =\n        new(value =>\n        {\n            var size = Convert.ToDouble(value);\n            string[] sizeUnits = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n            var unitIndex = 0;\n            while (size >= 1000 && unitIndex < sizeUnits.Length - 1)\n            {\n                size /= 1000;\n                unitIndex++;\n            }\n            return $\"{size:0.##} {sizeUnits[unitIndex]}\";\n        });\n\n    public static FuncValueConverter<object?, string> HumanizeBinaryFileSizeConverter { get; } =\n        new(value =>\n        {\n            var size = Convert.ToDouble(value);\n            string[] sizeUnits = [\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\"];\n            var unitIndex = 0;\n            while (size >= 1024 && unitIndex < sizeUnits.Length - 1)\n            {\n                size /= 1024;\n                unitIndex++;\n            }\n            return $\"{size:0.##} {sizeUnits[unitIndex]}\";\n        });\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/FileUriConverter.cs",
    "content": "using System.Globalization;\nusing Avalonia.Data.Converters;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class FileUriConverter : IValueConverter\n{\n    /// <inheritdoc />\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (targetType != typeof(Uri))\n        {\n            return null;\n        }\n\n        return value switch\n        {\n            string str when str.StartsWith(\"avares://\") => new Uri(str),\n            string str when (str.StartsWith(\"https://\") || str.StartsWith(\"http://\")) => new Uri(str),\n            string str => new Uri(\"file://\" + str),\n            IFormattable formattable => new Uri(\"file://\" + formattable.ToString(null, culture)),\n            _ => null,\n        };\n    }\n\n    /// <inheritdoc />\n    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (targetType == typeof(string) && value is Uri uri)\n        {\n            return uri.ToString().StripStart(\"file://\");\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/FitSquarelyWithinAspectRatioConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class FitSquarelyWithinAspectRatioConverter : IValueConverter\n{\n    public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        var bounds = value is Rect rect ? rect : default;\n        return Math.Min(bounds.Width, bounds.Height);\n    }\n\n    public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/FuncCommandConverter.cs",
    "content": "using System;\nusing System.Globalization;\nusing System.Windows.Input;\nusing Avalonia.Data.Converters;\nusing PropertyModels.ComponentModel;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\n/// <summary>\n/// Converts an object's named <see cref=\"Func{TResult}\"/> to a <see cref=\"ICommand\"/>.\n/// </summary>\npublic class FuncCommandConverter : IValueConverter\n{\n    /// <inheritdoc />\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is null || parameter is null)\n        {\n            return null;\n        }\n\n        // Parameter is the name of the Func<T> to convert.\n        if (parameter is not string funcName)\n        {\n            // ReSharper disable once LocalizableElement\n            throw new ArgumentException(\"Parameter must be a string.\", nameof(parameter));\n        }\n\n        // Find the Func<T> on the object.\n        if (value.GetType().GetMethod(funcName) is not { } methodInfo)\n        {\n            // ReSharper disable once LocalizableElement\n            throw new ArgumentException(\n                $\"Method {funcName} not found on {value.GetType().Name}.\",\n                nameof(parameter)\n            );\n        }\n\n        // Create a delegate from the method info.\n        var func = (Action)methodInfo.CreateDelegate(typeof(Action), value);\n\n        // Create ICommand\n        var command = ReactiveCommand.Create(func);\n\n        return command;\n    }\n\n    /// <inheritdoc />\n    public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        throw new NotSupportedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/IndexPlusOneConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\n/// <summary>\n/// Converts an index to index + 1\n/// </summary>\npublic class IndexPlusOneConverter : IValueConverter\n{\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is int i)\n        {\n            return i + 1;\n        }\n\n        return value;\n    }\n\n    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is int i)\n        {\n            return i - 1;\n        }\n\n        return value;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/KiloFormatter.cs",
    "content": "﻿using System;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class KiloFormatter : ICustomFormatter, IFormatProvider\n{\n    public object? GetFormat(Type? formatType)\n    {\n        return formatType == typeof(ICustomFormatter) ? this : null;\n    }\n\n    public string Format(string? format, object? arg, IFormatProvider? formatProvider)\n    {\n        if (format == null || !format.Trim().StartsWith('K'))\n        {\n            if (arg is IFormattable formatArg)\n            {\n                return formatArg.ToString(format, formatProvider);\n            }\n\n            return arg?.ToString() ?? string.Empty;\n        }\n\n        var value = Convert.ToInt64(arg);\n\n        return FormatNumber(value);\n    }\n\n    private static string FormatNumber(long num)\n    {\n        if (num >= 100000000)\n        {\n            return (num / 1000000D).ToString(\"0.#M\");\n        }\n        if (num >= 1000000)\n        {\n            return (num / 1000000D).ToString(\"0.##M\");\n        }\n        if (num >= 100000)\n        {\n            return (num / 1000D).ToString(\"0.#K\");\n        }\n        if (num >= 10000)\n        {\n            return (num / 1000D).ToString(\"0.##K\");\n        }\n\n        return num.ToString(\"#,0\");\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/KiloFormatterStringConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class KiloFormatterStringConverter : IValueConverter\n{\n    /// <inheritdoc />\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        return value is null ? null : string.Format(new KiloFormatter(), \"{0:K}\", value);\n    }\n\n    /// <inheritdoc />\n    public object? ConvertBack(\n        object? value,\n        Type targetType,\n        object? parameter,\n        CultureInfo culture\n    )\n    {\n        return value is null ? null : throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/LaunchOptionConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class LaunchOptionConverter : IValueConverter\n{\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (targetType == typeof(string))\n        {\n            return value?.ToString() ?? \"\";\n        }\n\n        if (targetType == typeof(bool?))\n        {\n            return bool.TryParse(value?.ToString(), out var boolValue) && boolValue;\n        }\n\n        if (targetType == typeof(double?))\n        {\n            if (value == null)\n            {\n                return null;\n            }\n            return double.TryParse(value.ToString(), out var doubleValue) ? doubleValue : 0;\n        }\n        \n        if (targetType == typeof(int?))\n        {\n            if (value == null)\n            {\n                return null;\n            }\n            return int.TryParse(value.ToString(), out var intValue) ? intValue : 0;\n        }\n\n        throw new ArgumentException(\"Unsupported type\");\n    }\n\n    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        return value;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/LaunchOptionIntDoubleConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class LaunchOptionIntDoubleConverter : IValueConverter\n{\n    // Convert from int to double\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {   \n        if (targetType == typeof(double?))\n        {\n            if (value == null)\n            {\n                return null;\n            }\n            return System.Convert.ToDouble(value);\n        }\n\n        throw new ArgumentException($\"Unsupported type {targetType}\");\n    }\n\n    // Convert from double to object int (floor)\n    public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (targetType == typeof(int?) || targetType == typeof(object))\n        {\n            return System.Convert.ToInt32(value);\n        }\n\n        throw new ArgumentException($\"Unsupported type {targetType}\");\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/MemoryBytesFormatter.cs",
    "content": "﻿using System;\nusing Size = StabilityMatrix.Core.Helper.Size;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class MemoryBytesFormatter : ICustomFormatter, IFormatProvider\n{\n    /// <inheritdoc />\n    public object? GetFormat(Type? formatType)\n    {\n        return formatType == typeof(ICustomFormatter) ? this : null;\n    }\n\n    /// <inheritdoc />\n    public string Format(string? format, object? arg, IFormatProvider? formatProvider)\n    {\n        if (format == null || !format.Trim().StartsWith('M'))\n        {\n            if (arg is IFormattable formatArg)\n            {\n                return formatArg.ToString(format, formatProvider);\n            }\n\n            return arg?.ToString() ?? string.Empty;\n        }\n\n        var value = Convert.ToUInt64(arg);\n\n        var result = format.Trim().EndsWith(\"10\", StringComparison.OrdinalIgnoreCase)\n            ? Size.FormatBase10Bytes(value)\n            : Size.FormatBytes(value);\n\n        // Strip i if not Mi\n        if (!format.Trim().Contains('I', StringComparison.OrdinalIgnoreCase))\n        {\n            result = result.Replace(\"i\", string.Empty, StringComparison.OrdinalIgnoreCase);\n        }\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/MultiplyConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class MultiplyConverter : IValueConverter\n{\n    public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        var factor = System.Convert.ToDouble(value);\n        var multiplier = System.Convert.ToDouble(parameter, CultureInfo.InvariantCulture);\n        return factor * multiplier;\n    }\n\n    public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/NullableDefaultNumericConverter.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Globalization;\nusing System.Numerics;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\n/// <summary>\n/// Converts a possibly boxed nullable value type to its default value\n/// </summary>\npublic class NullableDefaultNumericConverter<TSource, TTarget> : IValueConverter\n    where TSource : unmanaged, INumber<TSource>\n    where TTarget : unmanaged, INumber<TTarget>\n{\n    public ReturnBehavior NanHandling { get; set; } = ReturnBehavior.DefaultValue;\n\n    /// <summary>\n    /// Unboxes a nullable value type\n    /// </summary>\n    private TSource Unbox(TTarget? value)\n    {\n        if (!value.HasValue)\n        {\n            return default;\n        }\n\n        if (TTarget.IsNaN(value.Value))\n        {\n            return NanHandling switch\n            {\n                ReturnBehavior.DefaultValue => default,\n                ReturnBehavior.Throw\n                    => throw new InvalidCastException(\"Cannot convert NaN to a numeric type\"),\n                _\n                    => throw new InvalidEnumArgumentException(\n                        nameof(NanHandling),\n                        (int)NanHandling,\n                        typeof(ReturnBehavior)\n                    )\n            };\n        }\n\n        return (TSource)System.Convert.ChangeType(value.Value, typeof(TSource));\n    }\n\n    /// <summary>\n    /// Convert a value type to a nullable value type\n    /// </summary>\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (targetType != typeof(TTarget?) && !targetType.IsAssignableTo(typeof(TTarget)))\n        {\n            // ReSharper disable once LocalizableElement\n            throw new ArgumentException(\n                $\"Convert Target type {targetType.Name} must be assignable to {typeof(TTarget).Name}\"\n            );\n        }\n\n        return (TTarget?)System.Convert.ChangeType(value, typeof(TTarget));\n    }\n\n    /// <summary>\n    /// Convert a nullable value type to a value type\n    /// </summary>\n    public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (!targetType.IsAssignableTo(typeof(TSource)))\n        {\n            // ReSharper disable once LocalizableElement\n            throw new ArgumentException(\n                $\"ConvertBack Target type {targetType.Name} must be assignable to {typeof(TSource).Name}\"\n            );\n        }\n\n        return Unbox((TTarget?)value);\n    }\n\n    public enum ReturnBehavior\n    {\n        DefaultValue,\n        Throw\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/NullableDefaultNumericConverters.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Converters;\n\npublic static class NullableDefaultNumericConverters\n{\n    public static readonly NullableDefaultNumericConverter<int, decimal> IntToDecimal = new();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/NumberFormatModeSampleConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\nusing StabilityMatrix.Core.Models.Settings;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\n/// <summary>\n/// Converts a <see cref=\"NumberFormatMode\"/> to a sample number string\n/// </summary>\npublic class NumberFormatModeSampleConverter : IValueConverter\n{\n    /// <inheritdoc />\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is not NumberFormatMode mode)\n            return null;\n\n        const double sample = 12345.67;\n\n        // Format the sample number based on the number format mode\n        return mode switch\n        {\n            NumberFormatMode.Default => sample.ToString(\"N2\", culture),\n            NumberFormatMode.CurrentCulture => sample.ToString(\"N2\", culture),\n            NumberFormatMode.InvariantCulture => sample.ToString(\"N2\", CultureInfo.InvariantCulture),\n            _ => throw new ArgumentOutOfRangeException()\n        };\n    }\n\n    /// <inheritdoc />\n    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        throw new NotSupportedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/StringFormatConverters.cs",
    "content": "﻿using System;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic static class StringFormatConverters\n{\n    private static StringFormatValueConverter? _decimalConverter;\n    public static StringFormatValueConverter Decimal =>\n        _decimalConverter ??= new StringFormatValueConverter(\"{0:D}\", null);\n\n    private static readonly Lazy<IValueConverter> MemoryBytesConverterLazy =\n        new(() => new CustomStringFormatConverter<MemoryBytesFormatter>(\"{0:M}\"));\n\n    public static IValueConverter MemoryBytes => MemoryBytesConverterLazy.Value;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/UriStringConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\n/// <summary>\n/// Converts an Uri to a string, excluding leading protocol and trailing slashes\n/// </summary>\npublic class UriStringConverter : IValueConverter\n{\n    /// <inheritdoc />\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is Uri uri)\n        {\n            return (\n                uri.Host\n                + (uri.IsDefaultPort ? \"\" : $\":{uri.Port}\")\n                + uri.PathAndQuery\n                + uri.Fragment\n            ).TrimEnd('/');\n        }\n\n        return null;\n    }\n\n    /// <inheritdoc />\n    public object ConvertBack(\n        object? value,\n        Type targetType,\n        object? parameter,\n        CultureInfo culture\n    )\n    {\n        throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Converters/ValueConverterGroup.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Linq;\nusing Avalonia.Data.Converters;\n\nnamespace StabilityMatrix.Avalonia.Converters;\n\npublic class ValueConverterGroup : List<IValueConverter>, IValueConverter\n{\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        return this.Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, culture));\n    }\n\n    public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/DesignData/DesignData.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.ComponentModel;\nusing System.Diagnostics.CodeAnalysis;\nusing System.IO;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Text;\nusing AvaloniaEdit.Document;\nusing AvaloniaEdit.Utils;\nusing DynamicData.Binding;\nusing Microsoft.Extensions.DependencyInjection;\nusing NSubstitute;\nusing Semver;\nusing StabilityMatrix.Avalonia.Controls.CodeCompletion;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;\nusing StabilityMatrix.Avalonia.ViewModels.CheckpointManager;\nusing StabilityMatrix.Avalonia.ViewModels.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Video;\nusing StabilityMatrix.Avalonia.ViewModels.OutputsPage;\nusing StabilityMatrix.Avalonia.ViewModels.PackageManager;\nusing StabilityMatrix.Avalonia.ViewModels.Progress;\nusing StabilityMatrix.Avalonia.ViewModels.Settings;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Api.CivitTRPC;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.Api.OpenArt;\nusing StabilityMatrix.Core.Models.Api.OpenModelsDb;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Models.Update;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Core.Updater;\nusing CivitAiBrowserViewModel = StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser.CivitAiBrowserViewModel;\nusing HuggingFacePageViewModel = StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser.HuggingFacePageViewModel;\nusing MainPackageManagerViewModel = StabilityMatrix.Avalonia.ViewModels.PackageManager.MainPackageManagerViewModel;\n\nnamespace StabilityMatrix.Avalonia.DesignData;\n\n[Localizable(false)]\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic static class DesignData\n{\n    [NotNull]\n    public static IServiceProvider? Services { get; set; }\n\n    private static bool isInitialized;\n\n    // This needs to be static method instead of static constructor\n    // or else Avalonia analyzers won't work.\n    public static void Initialize()\n    {\n        if (isInitialized)\n            throw new InvalidOperationException(\"DesignData is already initialized.\");\n\n        var services = App.ConfigureServices(disableMessagePipeInterprocess: true);\n\n        var activePackageId = Guid.NewGuid();\n        services.AddSingleton<ISettingsManager, MockSettingsManager>(_ => new MockSettingsManager\n        {\n            Settings =\n            {\n                InstalledPackages = new List<InstalledPackage>\n                {\n                    new()\n                    {\n                        Id = activePackageId,\n                        DisplayName = \"My Installed Package\",\n                        PackageName = \"framepack\",\n                        Version = new InstalledPackageVersion { InstalledReleaseVersion = \"v1.0.0\" },\n                        LibraryPath = $\"Packages{Path.DirectorySeparatorChar}example-webui\",\n                        LastUpdateCheck = DateTimeOffset.Now,\n                    },\n                    new()\n                    {\n                        Id = Guid.NewGuid(),\n                        DisplayName = \"Comfy Diffusion WebUI Dev Branch Long Name\",\n                        PackageName = \"ComfyUI\",\n                        Version = new InstalledPackageVersion\n                        {\n                            InstalledBranch = \"master\",\n                            InstalledCommitSha = \"abc12uwu345568972abaedf7g7e679a98879e879f87ga8\",\n                        },\n                        LibraryPath = $\"Packages{Path.DirectorySeparatorChar}example-webui\",\n                        LastUpdateCheck = DateTimeOffset.Now,\n                    },\n                    new()\n                    {\n                        Id = Guid.NewGuid(),\n                        DisplayName = \"Running Comfy\",\n                        PackageName = \"ComfyUI\",\n                        Version = new InstalledPackageVersion\n                        {\n                            InstalledBranch = \"master\",\n                            InstalledCommitSha = \"abc12uwu345568972abaedf7g7e679a98879e879f87ga8\",\n                        },\n                        LibraryPath = $\"Packages{Path.DirectorySeparatorChar}example-webui\",\n                        LastUpdateCheck = DateTimeOffset.Now,\n                    },\n                },\n                ActiveInstalledPackageId = activePackageId,\n            },\n        });\n\n        // General services\n        services\n            .AddLogging()\n            .AddSingleton<IPackageFactory, PackageFactory>()\n            .AddSingleton<IUpdateHelper, UpdateHelper>()\n            .AddSingleton<ModelFinder>()\n            .AddSingleton<SharedState>();\n\n        // Mock services\n        services\n            .AddSingleton(Substitute.For<INotificationService>())\n            .AddSingleton(Substitute.For<ISharedFolders>())\n            .AddSingleton(Substitute.For<IDownloadService>())\n            .AddSingleton(Substitute.For<IHttpClientFactory>())\n            .AddSingleton(Substitute.For<IApiFactory>())\n            .AddSingleton(Substitute.For<IDiscordRichPresenceService>())\n            .AddSingleton(Substitute.For<ITrackedDownloadService>())\n            .AddSingleton(Substitute.For<ILiteDbContext>())\n            .AddSingleton(Substitute.For<IAccountsService>())\n            .AddSingleton<IInferenceClientManager, MockInferenceClientManager>()\n            .AddSingleton<ICompletionProvider, MockCompletionProvider>()\n            .AddSingleton<IModelIndexService, MockModelIndexService>()\n            .AddSingleton<IImageIndexService, MockImageIndexService>()\n            .AddSingleton<IMetadataImportService, MetadataImportService>();\n\n        // Placeholder services that nobody should need during design time\n        services\n            .AddSingleton<IPyRunner>(_ => null!)\n            .AddSingleton<ILiteDbContext>(_ => null!)\n            .AddSingleton<ICivitApi>(_ => null!)\n            .AddSingleton<IGithubApiCache>(_ => null!)\n            .AddSingleton<ITokenizerProvider>(_ => null!)\n            .AddSingleton<IPrerequisiteHelper>(_ => null!)\n            .AddSingleton<IPyPiApi>(_ => null!)\n            .AddSingleton<IPyPiCache>(_ => null!);\n\n        // Override Launch page with mock\n        services.Remove(ServiceDescriptor.Singleton<LaunchPageViewModel, LaunchPageViewModel>());\n        services.AddSingleton<LaunchPageViewModel, MockLaunchPageViewModel>();\n\n        Services = services.BuildServiceProvider();\n\n        var dialogFactory = Services.GetRequiredService<IServiceManager<ViewModelBase>>();\n        var settingsManager = Services.GetRequiredService<ISettingsManager>();\n        var downloadService = Services.GetRequiredService<IDownloadService>();\n        var modelFinder = Services.GetRequiredService<ModelFinder>();\n        var packageFactory = Services.GetRequiredService<IPackageFactory>();\n        var notificationService = Services.GetRequiredService<INotificationService>();\n        var modelImportService = Services.GetRequiredService<IMetadataImportService>();\n\n        LaunchOptionsViewModel = Services.GetRequiredService<LaunchOptionsViewModel>();\n        LaunchOptionsViewModel.Cards = new[]\n        {\n            LaunchOptionCard.FromDefinition(\n                new LaunchOptionDefinition\n                {\n                    Name = \"Host\",\n                    Type = LaunchOptionType.String,\n                    Description = \"The host name for the Web UI\",\n                    DefaultValue = \"localhost\",\n                    Options = { \"--host\" },\n                }\n            ),\n            LaunchOptionCard.FromDefinition(\n                new LaunchOptionDefinition\n                {\n                    Name = \"API\",\n                    Type = LaunchOptionType.Bool,\n                    Options = { \"--api\" },\n                }\n            ),\n        };\n        LaunchOptionsViewModel.UpdateFilterCards();\n\n        NewInstallerDialogViewModel = Services.GetRequiredService<PackageInstallBrowserViewModel>();\n        // NewInstallerDialogViewModel.InferencePackages = new ObservableCollectionExtended<BasePackage>(\n        //     packageFactory.GetPackagesByType(PackageType.SdInference).OrderBy(p => p.InstallerSortOrder)\n        // );\n        // NewInstallerDialogViewModel.TrainingPackages = new ObservableCollection<BasePackage>(\n        //     packageFactory.GetPackagesByType(PackageType.SdTraining).OrderBy(p => p.InstallerSortOrder)\n        // );\n\n        PackageInstallDetailViewModel = new PackageInstallDetailViewModel(\n            packageFactory.GetAllAvailablePackages().FirstOrDefault() as BaseGitPackage,\n            settingsManager,\n            notificationService,\n            null,\n            null,\n            null,\n            packageFactory,\n            null,\n            null\n        );\n\n        PackageInstallDetailViewModel.AvailablePythonVersions = new ObservableCollection<UvPythonInfo>(\n            [\n                new UvPythonInfo(\n                    PyInstallationManager.Python_3_10_17,\n                    \"C:\\\\SMData\\\\Data\\\\Data\\\\Assets\\\\Python\\\\cpython-3.11.12-windows-x86_64-none\",\n                    true,\n                    \"cpython\",\n                    \"x86_64\",\n                    \"windows\",\n                    \"cpython-3.10.17-windows-x86_64-none\",\n                    \"default\",\n                    \"none\"\n                ),\n                new UvPythonInfo(\n                    PyInstallationManager.Python_3_12_10,\n                    null,\n                    false,\n                    \"cpython\",\n                    \"x86_64\",\n                    \"windows\",\n                    \"guh I can't be bothered\",\n                    \"freethreaded\",\n                    \"none\"\n                ),\n            ]\n        );\n\n        /*ObservableCacheEx.AddOrUpdate(\n            OldCheckpointsPageViewModel.CheckpointFoldersCache,\n            new CheckpointFolder[]\n            {\n                new(settingsManager, downloadService, modelFinder, notificationService, modelImportService)\n                {\n                    DirectoryPath = \"Models/StableDiffusion\",\n                    DisplayedCheckpointFiles = new ObservableCollectionExtended<CheckpointFile>()\n                    {\n                        new()\n                        {\n                            FilePath = \"~/Models/StableDiffusion/electricity-light.safetensors\",\n                            Title = \"Auroral Background\",\n                            PreviewImagePath =\n                                \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/\"\n                                + \"78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg\",\n                            UpdateAvailable = true,\n                            ConnectedModel = new ConnectedModelInfo\n                            {\n                                VersionName = \"Lightning Auroral\",\n                                BaseModel = \"SD 1.5\",\n                                ModelName = \"Auroral Background\",\n                                ModelType = CivitModelType.Model,\n                                FileMetadata = new CivitFileMetadata\n                                {\n                                    Format = CivitModelFormat.SafeTensor,\n                                    Fp = CivitModelFpType.fp16,\n                                    Size = CivitModelSize.pruned,\n                                },\n                                TrainedWords = [\"aurora\", \"lightning\"]\n                            }\n                        },\n                        new() { FilePath = \"~/Models/Lora/model.safetensors\", Title = \"Some model\" },\n                    },\n                },\n                new(settingsManager, downloadService, modelFinder, notificationService, modelImportService)\n                {\n                    Title = \"Lora\",\n                    DirectoryPath = \"Packages/Lora\",\n                    DisplayedCheckpointFiles = new ObservableCollectionExtended<CheckpointFile>\n                    {\n                        new() { FilePath = \"~/Models/Lora/lora_v2.pt\", Title = \"Best Lora v2\", }\n                    }\n                }\n            }\n        );*/\n\n        CivitAiBrowserViewModel.ModelCards = new ObservableCollectionExtended<CheckpointBrowserCardViewModel>\n        {\n            dialogFactory.Get<CheckpointBrowserCardViewModel>(vm =>\n            {\n                vm.CivitModel = new CivitModel\n                {\n                    Name = \"BB95 Furry Mix\",\n                    Description = \"A furry mix of BB95\",\n                    Stats = new CivitModelStats { Rating = 3.5, RatingCount = 24 },\n                    ModelVersions = [new() { Name = \"v1.2.2-Inpainting\" }],\n                    Creator = new CivitCreator\n                    {\n                        Image = \"https://gravatar.com/avatar/fe74084ae8a081dc2283f5bde4736756ad?f=y&d=retro\",\n                        Username = \"creator-1\",\n                    },\n                };\n            }),\n            dialogFactory.Get<CheckpointBrowserCardViewModel>(vm =>\n            {\n                vm.CivitModel = new CivitModel\n                {\n                    Name = \"Another Model\",\n                    Description = \"A mix of example\",\n                    Stats = new CivitModelStats { Rating = 5, RatingCount = 3500 },\n                    ModelVersions =\n                    [\n                        new()\n                        {\n                            Name = \"v1.2.2-Inpainting\",\n                            Images = new List<CivitImage>\n                            {\n                                new()\n                                {\n                                    NsfwLevel = 1,\n                                    Url =\n                                        \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/\"\n                                        + \"78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg\",\n                                },\n                            },\n                        },\n                    ],\n                    Creator = new CivitCreator\n                    {\n                        Image = \"https://gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?f=y&d=retro\",\n                        Username = \"creator-2\",\n                    },\n                };\n            }),\n        };\n\n        CheckpointsPageViewModel.Categories = new ObservableCollectionExtended<CheckpointCategory>\n        {\n            new()\n            {\n                Name = \"Category 1\",\n                Path = \"path1\",\n                SubDirectories = [new CheckpointCategory { Name = \"SubCategory 1\", Path = \"path3\" }],\n            },\n            new() { Name = \"Category 2\", Path = \"path2\" },\n        };\n\n        CheckpointsPageViewModel.Models = new ObservableCollectionExtended<CheckpointFileViewModel>()\n        {\n            new(\n                settingsManager,\n                new MockModelIndexService(),\n                notificationService,\n                downloadService,\n                dialogFactory,\n                null,\n                new LocalModelFile\n                {\n                    SharedFolderType = SharedFolderType.StableDiffusion,\n                    RelativePath = \"~/Models/StableDiffusion/electricity-light.safetensors\",\n                    PreviewImageFullPath =\n                        \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/\"\n                        + \"78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg\",\n                    HasUpdate = true,\n                    ConnectedModelInfo = new ConnectedModelInfo\n                    {\n                        VersionName = \"Lightning Auroral\",\n                        BaseModel = \"SD 1.5\",\n                        ModelName = \"Auroral Background\",\n                        ModelType = CivitModelType.Model,\n                        FileMetadata = new CivitFileMetadata\n                        {\n                            Format = CivitModelFormat.SafeTensor,\n                            Fp = \"fp16\",\n                            Size = \"pruned\",\n                        },\n                        TrainedWords = [\"aurora\", \"lightning\"],\n                    },\n                }\n            ),\n            new(\n                settingsManager,\n                new MockModelIndexService(),\n                notificationService,\n                downloadService,\n                dialogFactory,\n                null,\n                new LocalModelFile\n                {\n                    RelativePath = \"~/Models/Lora/model.safetensors\",\n                    SharedFolderType = SharedFolderType.StableDiffusion,\n                }\n            ),\n        };\n\n        var packageInstall = new PackageInstallProgressItemViewModel(\n            new PackageModificationRunner\n            {\n                CurrentProgress = new ProgressReport(0.5f, \"Installing package...\", \"Installing... 50%\"),\n                ModificationCompleteMessage = \"Package installed successfully\",\n            }\n        )\n        {\n            Progress = new ContentDialogProgressViewModelBase\n            {\n                Value = 50,\n                IsIndeterminate = false,\n                Text = \"UwU Install\",\n                Description = \"Installing...\",\n            },\n        };\n\n        ProgressManagerViewModel.ProgressItems.AddRange(\n            [\n                new ProgressItemViewModel(\n                    new ProgressItem(\n                        Guid.NewGuid(),\n                        \"Test File.exe\",\n                        new ProgressReport(0.5f, \"Downloading...\")\n                    )\n                ),\n                new MockDownloadProgressItemViewModel(\n                    \"Very Long Test File Name Need Even More Longness Thanks That's pRobably good 2.exe\"\n                ),\n                new MockDownloadProgressItemViewModel(\n                    \"Very Long Test File Name Need Even More Longness Thanks That's pRobably good 2.exe\"\n                )\n                {\n                    Progress = new ContentDialogProgressViewModelBase\n                    {\n                        Value = 50,\n                        IsIndeterminate = false,\n                        Text = \"Waiting on other downloads to finish\",\n                        Description = \"Waiting on other downloads to finish\",\n                    },\n                },\n                packageInstall,\n            ]\n        );\n\n        UpdateViewModel = Services.GetRequiredService<UpdateViewModel>();\n        UpdateViewModel.CurrentVersionText = \"v2.0.0\";\n        UpdateViewModel.NewVersionText = \"v2.0.1\";\n        UpdateViewModel.ReleaseNotes =\n            \"## v2.0.1\\n- Fixed a bug\\n- Added a feature\\n- Removed a feature\\n - Did some `--code` stuff\";\n\n        isInitialized = true;\n    }\n\n    [NotNull]\n    public static PackageInstallBrowserViewModel? NewInstallerDialogViewModel { get; private set; }\n\n    [NotNull]\n    public static PackageInstallDetailViewModel? PackageInstallDetailViewModel { get; private set; }\n\n    [NotNull]\n    public static LaunchOptionsViewModel? LaunchOptionsViewModel { get; private set; }\n\n    [NotNull]\n    public static UpdateViewModel? UpdateViewModel { get; private set; }\n\n    public static IServiceManager<ViewModelBase> DialogFactory =>\n        Services.GetRequiredService<IServiceManager<ViewModelBase>>();\n\n    public static MainWindowViewModel MainWindowViewModel =>\n        Services.GetRequiredService<MainWindowViewModel>();\n\n    public static FirstLaunchSetupViewModel FirstLaunchSetupViewModel =>\n        Services.GetRequiredService<FirstLaunchSetupViewModel>();\n\n    public static LaunchPageViewModel LaunchPageViewModel =>\n        Services.GetRequiredService<LaunchPageViewModel>();\n\n    public static HuggingFacePageViewModel HuggingFacePageViewModel =>\n        Services.GetRequiredService<HuggingFacePageViewModel>();\n\n    public static NewOneClickInstallViewModel NewOneClickInstallViewModel =>\n        Services.GetRequiredService<NewOneClickInstallViewModel>();\n\n    public static RecommendedModelsViewModel RecommendedModelsViewModel =>\n        DialogFactory.Get<RecommendedModelsViewModel>(vm =>\n        {\n            // Populate the single RecommendedModels collection for design time\n            vm.RecommendedModels.AddRange(\n                [\n                    new RecommendedModelItemViewModel\n                    {\n                        CivitModel = new CivitModel { Name = \"BB95 Furry Mix\", Id = 1 }, // Added Id for clarity\n                        ModelVersion = new CivitModelVersion\n                        {\n                            Id = 101, // Added Id for clarity\n                            Name = \"v1.0\", // Example version name\n                            BaseModel = \"SD 1.5\", // Example base model\n                            Stats = new CivitModelStats { Rating = 4.5, RatingCount = 124 },\n                            Files = [new CivitFile { Type = CivitFileType.Model }], // Example file\n                            Images =\n                            [\n                                new CivitImage\n                                {\n                                    Type = \"image\", // Ensure type is set\n                                    Url =\n                                        \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg\",\n                                },\n                            ],\n                        },\n                        Author = \"by bb95\",\n                    },\n                    new RecommendedModelItemViewModel\n                    {\n                        CivitModel = new CivitModel { Name = \"DreamShaper XL\", Id = 2 },\n                        ModelVersion = new CivitModelVersion\n                        {\n                            Id = 201,\n                            Name = \"v2.1 Turbo\",\n                            BaseModel = \"SDXL 1.0\",\n                            Stats = new CivitModelStats { Rating = 4.8, RatingCount = 589 },\n                            Files = [new CivitFile { Type = CivitFileType.Model, IsPrimary = true }],\n                            Images =\n                            [\n                                new CivitImage\n                                {\n                                    Type = \"image\",\n                                    // Placeholder - replace with an actual relevant image URL if possible\n                                    Url =\n                                        \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/0cf3e133-4dde-458b-8a70-7451a3361472/width=450/00016-3919014893.jpeg\",\n                                },\n                            ],\n                        },\n                        Author = \"by Lykon\",\n                    },\n                    new RecommendedModelItemViewModel\n                    {\n                        CivitModel = new CivitModel { Name = \"Another Model SD1.5\", Id = 3 },\n                        ModelVersion = new CivitModelVersion\n                        {\n                            Id = 301,\n                            Name = \"Final\",\n                            BaseModel = \"SD 1.5\",\n                            Stats = new CivitModelStats { Rating = 4.2, RatingCount = 99 },\n                            Files = [new CivitFile { Type = CivitFileType.Model }],\n                            Images = [new CivitImage { Type = \"image\", Url = Assets.NoImage.ToString() }], // Use placeholder\n                        },\n                        Author = \"by Creator3\",\n                    },\n                    // Add more items as needed for design-time preview\n                ]\n            );\n        });\n    public static OutputsPageViewModel OutputsPageViewModel\n    {\n        get\n        {\n            var vm = Services.GetRequiredService<OutputsPageViewModel>();\n            vm.Outputs = new ObservableCollectionExtended<OutputImageViewModel>\n            {\n                new(\n                    new LocalImageFile\n                    {\n                        AbsolutePath =\n                            \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg\",\n                        ImageType = LocalImageFileType.TextToImage,\n                    }\n                ),\n            };\n            vm.Categories = new ObservableCollectionExtended<TreeViewDirectory>\n            {\n                new()\n                {\n                    Name = \"Category 1\",\n                    Path = \"path1\",\n                    SubDirectories = [new TreeViewDirectory { Name = \"SubCategory 1\", Path = \"path3\" }],\n                },\n                new() { Name = \"Category 2\", Path = \"path2\" },\n            };\n            return vm;\n        }\n    }\n\n    public static MainPackageManagerViewModel MainPackageManagerViewModel\n    {\n        get\n        {\n            var settings = Services.GetRequiredService<ISettingsManager>();\n            var vm = Services.GetRequiredService<MainPackageManagerViewModel>();\n\n            vm.SetPackages(settings.Settings.InstalledPackages);\n            vm.SetUnknownPackages(\n                new InstalledPackage[]\n                {\n                    UnknownInstalledPackage.FromDirectoryName(\"sd-unknown-with-long-name\"),\n                }\n            );\n\n            vm.PackageCards[0].IsUpdateAvailable = true;\n\n            return vm;\n        }\n    }\n\n    public static PackageExtensionBrowserViewModel PackageExtensionBrowserViewModel =>\n        DialogFactory.Get<PackageExtensionBrowserViewModel>(vm =>\n        {\n            vm.AddExtensions(\n                [\n                    new PackageExtension\n                    {\n                        Author = \"123\",\n                        Title = \"Cool Extension\",\n                        Description = \"This is an interesting extension\",\n                        Reference = new Uri(\"https://github.com/LykosAI/StabilityMatrix\"),\n                        Files = [new Uri(\"https://github.com/LykosAI/StabilityMatrix\")],\n                    },\n                    new PackageExtension\n                    {\n                        Author = \"123\",\n                        Title = \"Cool Extension\",\n                        Description = \"This is an interesting extension\",\n                        Reference = new Uri(\"https://github.com/LykosAI/StabilityMatrix\"),\n                        Files = [new Uri(\"https://github.com/LykosAI/StabilityMatrix\")],\n                    },\n                ],\n                [\n                    new InstalledPackageExtension\n                    {\n                        GitRepositoryUrl = \"https://github.com/LykosAI/StabilityMatrix\",\n                        Paths = [new DirectoryPath(\"example-dir\")],\n                    },\n                    new InstalledPackageExtension { Paths = [new DirectoryPath(\"example-dir-2\")] },\n                ]\n            );\n            vm.AddExtensionPacks(\n                [\n                    new ExtensionPack\n                    {\n                        Name = \"Test Pack\",\n                        PackageType = \"ComfyUI\",\n                        Extensions =\n                        [\n                            new SavedPackageExtension\n                            {\n                                PackageExtension = new PackageExtension\n                                {\n                                    Author = \"TestAuthor\",\n                                    Title = \"Test\",\n                                    Reference = new Uri(\"https://github.com/LykosAI/StabilityMatrix\"),\n                                    Files = [new Uri(\"https://github.com/LykosAI/StabilityMatrix\")],\n                                },\n                                Version = new PackageExtensionVersion\n                                {\n                                    Branch = \"main\",\n                                    CommitSha = \"abcd123\",\n                                },\n                            },\n                        ],\n                    },\n                ]\n            );\n        });\n\n    public static CheckpointsPageViewModel CheckpointsPageViewModel =>\n        Services.GetRequiredService<CheckpointsPageViewModel>();\n\n    public static SettingsViewModel SettingsViewModel => Services.GetRequiredService<SettingsViewModel>();\n\n    public static PackageManagerViewModel PackageManagerViewModel =>\n        Services.GetRequiredService<PackageManagerViewModel>();\n\n    public static InferenceSettingsViewModel InferenceSettingsViewModel =>\n        Services.GetRequiredService<InferenceSettingsViewModel>();\n\n    public static MainSettingsViewModel MainSettingsViewModel\n    {\n        get\n        {\n            var vm = Services.GetRequiredService<MainSettingsViewModel>();\n            vm.AllBaseModelTypes = new List<string>()\n            {\n                CivitBaseModelType.WanVideo.GetStringValue(),\n                CivitBaseModelType.Sdxl10.GetStringValue(),\n                CivitBaseModelType.Flux1D.GetStringValue(),\n                \"Flux 1. Kontext\",\n            }\n                .Select(s => new BaseModelOptionViewModel { ModelType = s, IsSelected = true })\n                .ToList();\n            return vm;\n        }\n    }\n\n    public static AccountSettingsViewModel AccountSettingsViewModel =>\n        Services.GetRequiredService<AccountSettingsViewModel>();\n\n    public static NotificationSettingsViewModel NotificationSettingsViewModel =>\n        Services.GetRequiredService<NotificationSettingsViewModel>();\n\n    public static UpdateSettingsViewModel UpdateSettingsViewModel\n    {\n        get\n        {\n            var vm = Services.GetRequiredService<UpdateSettingsViewModel>();\n\n            var update = new UpdateInfo\n            {\n                Version = SemVersion.Parse(\"2.0.1\"),\n                ReleaseDate = DateTimeOffset.Now,\n                Url = new Uri(\"https://example.org\"),\n                Changelog = new Uri(\"https://example.org\"),\n                HashBlake3 = \"\",\n                Signature = \"\",\n            };\n\n            vm.UpdateStatus = new UpdateStatusChangedEventArgs\n            {\n                LatestUpdate = update,\n                UpdateChannels = new Dictionary<UpdateChannel, UpdateInfo>\n                {\n                    [UpdateChannel.Stable] = update,\n                    [UpdateChannel.Preview] = update,\n                    [UpdateChannel.Development] = update,\n                },\n                CheckedAt = DateTimeOffset.UtcNow,\n            };\n            return vm;\n        }\n    }\n\n    public static CivitAiBrowserViewModel CivitAiBrowserViewModel =>\n        Services.GetRequiredService<CivitAiBrowserViewModel>();\n\n    public static CheckpointBrowserViewModel CheckpointBrowserViewModel =>\n        Services.GetRequiredService<CheckpointBrowserViewModel>();\n\n    public static CivitDetailsPageViewModel CivitDetailsPageViewModel =>\n        DialogFactory.Get<CivitDetailsPageViewModel>(vm =>\n        {\n            vm.CivitModel = new CivitModel\n            {\n                Name = \"BB95 Furry Mix\",\n                Description = \"A furry mix of BB95\",\n                Stats = new CivitModelStats\n                {\n                    Rating = 3.5,\n                    RatingCount = 24,\n                    ThumbsUpCount = 1337,\n                    DownloadCount = 100_000,\n                },\n                Tags = [\"base model\", \"furry\", \"animals\", \"photorealistic\", \"highly detailed\", \"yiff\"],\n                ModelVersions =\n                [\n                    new CivitModelVersion\n                    {\n                        Name = \"v1.2.2-Inpainting\",\n                        PublishedAt = DateTimeOffset.Now,\n                        Images =\n                        [\n                            new CivitImage\n                            {\n                                Url =\n                                    \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/244bc929-9bbc-44b2-8a26-7622a1669f4a/original=true,quality=90/00123-3430906941-1girl,%20hatsune%20miku,%20white%20pupils,%20power%20elements,%20microphone,%20vibrant%20blue%20color%20palette,%20abstract,abstract%20background,%20dreamli.jpeg\",\n                            },\n                            new CivitImage\n                            {\n                                Url =\n                                    \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/aa671302-496f-4dcc-839a-b75a70a7665e/original=true,quality=90/00136-4204868169-1girl,%20solo,%20long%20hair,%20city,%20boots,%20hands%20in%20pockets,%20coat,%20blonde%20hair,%20sky,%20planet,%20night,%20knee%20boots,%20building,%20star%20_(sky_).jpeg\",\n                            },\n                        ],\n                        Files =\n                        [\n                            new CivitFile\n                            {\n                                Name = \"bb95-v100-uwu-reallylongfilename-v1234576802.safetensors\",\n                                Type = CivitFileType.Model,\n                                Metadata = new CivitFileMetadata\n                                {\n                                    Format = CivitModelFormat.SafeTensor,\n                                    Fp = \"fp16\",\n                                    Size = \"pruned\",\n                                },\n                            },\n                            new CivitFile\n                            {\n                                Name = \"bb95-v100-uwu-reallylongfilename-v1234576802-fp32.safetensors\",\n                                Type = CivitFileType.Model,\n                                Metadata = new CivitFileMetadata\n                                {\n                                    Format = CivitModelFormat.SafeTensor,\n                                    Fp = \"fp32\",\n                                    Size = \"full\",\n                                },\n                                Hashes = new CivitFileHashes\n                                {\n                                    BLAKE3 =\n                                        \"A7383E54F2E4570678B0F18545B2EB8FD95325DA76CCBA8467DBDBD481CF6B99\",\n                                    SHA256 =\n                                        \"BDB59BAC77D94AE7A55FF893170F9554C3F349E48A1B73C0C17C0B7C6F4D41A2\",\n                                },\n                            },\n                        ],\n                    },\n                    new CivitModelVersion { Name = \"v1.2.0\", PublishedAt = DateTimeOffset.Now.AddDays(-3) },\n                    new CivitModelVersion { Name = \"v1.1.0\", PublishedAt = DateTimeOffset.Now.AddDays(-3) },\n                    new CivitModelVersion { Name = \"v1.0.0\", PublishedAt = DateTimeOffset.Now.AddDays(-3) },\n                    new CivitModelVersion { Name = \"v0.9.0\", PublishedAt = DateTimeOffset.Now.AddDays(-3) },\n                    new CivitModelVersion { Name = \"v0.8.0\", PublishedAt = DateTimeOffset.Now.AddDays(-3) },\n                    new CivitModelVersion { Name = \"v0.7.0\", PublishedAt = DateTimeOffset.Now.AddDays(-3) },\n                    new CivitModelVersion { Name = \"v0.6.0\", PublishedAt = DateTimeOffset.Now.AddDays(-3) },\n                    new CivitModelVersion { Name = \"v0.5.0\", PublishedAt = DateTimeOffset.Now.AddDays(-3) },\n                    new CivitModelVersion { Name = \"v0.4.0\", PublishedAt = DateTimeOffset.Now.AddDays(-3) },\n                ],\n                Creator = new CivitCreator\n                {\n                    Image = \"https://gravatar.com/avatar/fe74084ae8a081dc2283f5bde4736756ad?f=y&d=retro\",\n                    Username = \"creator-1\",\n                },\n            };\n        });\n\n    public static SelectModelVersionViewModel SelectModelVersionViewModel =>\n        DialogFactory.Get<SelectModelVersionViewModel>(vm =>\n        {\n            // Sample data\n            var sampleCivitVersions = new List<CivitModelVersion>\n            {\n                new()\n                {\n                    Name = \"BB95 Furry Mix\",\n                    Description =\n                        @\"Introducing SnoutMix\nA Mix of non-Furry and Furry models such as Furtastic and BB95Furry to create a great variety of anthro AI generation options, but bringing out more detail, still giving a lot of freedom to customise the human aspects, and having great backgrounds, with a focus on something more realistic. Works well with realistic character loras.\nThe gallery images are often inpainted, but you will get something very similar if copying their data directly. They are inpainted using the same model, therefore all results are possible without anything custom/hidden-away. Controlnet Tiled is applied to enhance them further afterwards. Gallery images were made with same model but before it was renamed\",\n                    BaseModel = \"SD 1.5\",\n                    Files = new List<CivitFile>\n                    {\n                        new()\n                        {\n                            Name = \"bb95-v100-uwu-reallylongfilename-v1234576802.safetensors\",\n                            Type = CivitFileType.Model,\n                            Metadata = new CivitFileMetadata\n                            {\n                                Format = CivitModelFormat.SafeTensor,\n                                Fp = \"fp16\",\n                                Size = \"pruned\",\n                            },\n                        },\n                        new()\n                        {\n                            Name = \"bb95-v100-uwu-reallylongfilename-v1234576802-fp32.safetensors\",\n                            Type = CivitFileType.Model,\n                            Metadata = new CivitFileMetadata\n                            {\n                                Format = CivitModelFormat.SafeTensor,\n                                Fp = \"fp32\",\n                                Size = \"full\",\n                            },\n                            Hashes = new CivitFileHashes { BLAKE3 = \"ABCD\" },\n                        },\n                    },\n                },\n            };\n            var sampleViewModel = new ModelVersionViewModel(\n                Services.GetRequiredService<IModelIndexService>(),\n                sampleCivitVersions[0]\n            );\n\n            // Sample data for dialogs\n            vm.Versions = new List<ModelVersionViewModel> { sampleViewModel };\n            vm.Title = sampleCivitVersions[0].Name;\n            vm.Description = sampleCivitVersions[0].Description;\n            vm.SelectedVersionViewModel = sampleViewModel;\n        });\n\n    public static OneClickInstallViewModel OneClickInstallViewModel =>\n        Services.GetRequiredService<OneClickInstallViewModel>();\n\n    public static InferenceViewModel InferenceViewModel => Services.GetRequiredService<InferenceViewModel>();\n\n    public static SelectDataDirectoryViewModel SelectDataDirectoryViewModel =>\n        Services.GetRequiredService<SelectDataDirectoryViewModel>();\n\n    public static ProgressManagerViewModel ProgressManagerViewModel =>\n        Services.GetRequiredService<ProgressManagerViewModel>();\n\n    public static ExceptionViewModel ExceptionViewModel =>\n        DialogFactory.Get<ExceptionViewModel>(viewModel =>\n        {\n            // Use try-catch to generate traceback information\n            try\n            {\n                try\n                {\n                    throw new OperationCanceledException(\"Example\");\n                }\n                catch (OperationCanceledException e)\n                {\n                    throw new AggregateException(e);\n                }\n            }\n            catch (AggregateException e)\n            {\n                viewModel.Exception = e;\n            }\n        });\n\n    public static EnvVarsViewModel EnvVarsViewModel =>\n        DialogFactory.Get<EnvVarsViewModel>(viewModel =>\n        {\n            viewModel.EnvVars = new ObservableCollection<EnvVarKeyPair> { new(\"UWU\", \"TRUE\") };\n        });\n\n    public static PythonPackagesViewModel PythonPackagesViewModel =>\n        DialogFactory.Get<PythonPackagesViewModel>(vm =>\n        {\n            vm.AddPackages(new PipPackageInfo(\"pip\", \"1.0.0\"), new PipPackageInfo(\"torch\", \"2.1.0+cu121\"));\n        });\n\n    public static LykosLoginViewModel LykosLoginViewModel => DialogFactory.Get<LykosLoginViewModel>();\n\n    public static OAuthConnectViewModel OAuthConnectViewModel =>\n        DialogFactory.Get<OAuthConnectViewModel>(vm =>\n        {\n            vm.Url =\n                \"https://www.example.org/oauth2/authorize?\"\n                + \"client_id=66ad566552679cb6e650be01ed6f8d2ae9a0f803c0369850a5c9ee82a2396062&\"\n                + \"scope=identity%20identity.memberships&\"\n                + \"response_type=code&state=test%40example.org&\"\n                + \"redirect_uri=http://localhost:5022/api/oauth/patreon/callback\";\n        });\n\n    public static OAuthLoginViewModel OAuthLoginViewModel =>\n        DialogFactory.Get<OAuthLoginViewModel>(vm =>\n        {\n            vm.Url =\n                \"https://www.example.org/oauth2/authorize?\"\n                + \"client_id=66ad566552679cb6e650be01ed6f8d2ae9a0f803c0369850a5c9ee82a2396062&\"\n                + \"scope=identity%20identity.memberships&\"\n                + \"response_type=code&state=test%40example.org&\"\n                + \"redirect_uri=http://localhost:5022/api/oauth/patreon/callback\";\n        });\n\n    public static OAuthDeviceAuthViewModel OAuthDeviceAuthViewModel =>\n        DialogFactory.Get<OAuthDeviceAuthViewModel>(vm =>\n        {\n            vm.VerificationUri = new Uri(\"https://example.org/connect/verify\");\n            vm.UserCode = \"AB23-CD56\";\n        });\n\n    public static PythonPackageSpecifiersViewModel PythonPackageSpecifiersViewModel =>\n        DialogFactory.Get<PythonPackageSpecifiersViewModel>();\n\n    public static MaskEditorViewModel MaskEditorViewModel => DialogFactory.Get<MaskEditorViewModel>();\n\n    public static InferenceTextToImageViewModel InferenceTextToImageViewModel =>\n        DialogFactory.Get<InferenceTextToImageViewModel>(vm =>\n        {\n            vm.OutputProgress.Value = 10;\n            vm.OutputProgress.Maximum = 30;\n            vm.OutputProgress.Text = \"Sampler 10/30\";\n        });\n\n    public static InferenceImageToVideoViewModel InferenceImageToVideoViewModel =>\n        DialogFactory.Get<InferenceImageToVideoViewModel>(vm =>\n        {\n            vm.OutputProgress.Value = 10;\n            vm.OutputProgress.Maximum = 30;\n            vm.OutputProgress.Text = \"Sampler 10/30\";\n        });\n\n    public static InferenceImageToImageViewModel InferenceImageToImageViewModel =>\n        DialogFactory.Get<InferenceImageToImageViewModel>();\n\n    public static InferenceImageUpscaleViewModel InferenceImageUpscaleViewModel =>\n        DialogFactory.Get<InferenceImageUpscaleViewModel>();\n\n    public static InferenceWanTextToVideoViewModel InferenceWanTextToVideoViewModel =>\n        DialogFactory.Get<InferenceWanTextToVideoViewModel>();\n    public static InferenceWanImageToVideoViewModel InferenceWanImageToVideoViewModel =>\n        DialogFactory.Get<InferenceWanImageToVideoViewModel>();\n\n    public static PackageImportViewModel PackageImportViewModel =>\n        DialogFactory.Get<PackageImportViewModel>();\n\n    public static RefreshBadgeViewModel RefreshBadgeViewModel => new() { State = ProgressState.Success };\n\n    public static PropertyGridViewModel PropertyGridViewModel =>\n        DialogFactory.Get<PropertyGridViewModel>(vm =>\n        {\n            vm.SelectedObject = new INotifyPropertyChanged[]\n            {\n                new MockPropertyGridObject(),\n                new MockPropertyGridObjectAlt(),\n            };\n            vm.ExcludeCategories = [\"Excluded Category\"];\n        });\n\n    public static SeedCardViewModel SeedCardViewModel => new();\n    public static SvdImgToVidConditioningViewModel SvdImgToVidConditioningViewModel => new();\n    public static VideoOutputSettingsCardViewModel VideoOutputSettingsCardViewModel => new();\n\n    public static SamplerCardViewModel SamplerCardViewModel =>\n        DialogFactory.Get<SamplerCardViewModel>(vm =>\n        {\n            vm.Steps = 20;\n            vm.CfgScale = 7;\n            vm.IsCfgScaleEnabled = true;\n            vm.IsSamplerSelectionEnabled = true;\n            vm.IsDimensionsEnabled = true;\n            vm.IsLengthEnabled = true;\n            vm.SelectedSampler = new ComfySampler(\"euler\");\n        });\n\n    public static SamplerCardViewModel SamplerCardViewModelScaleMode =>\n        DialogFactory.Get<SamplerCardViewModel>(vm =>\n        {\n            vm.IsDenoiseStrengthEnabled = true;\n        });\n\n    public static SamplerCardViewModel SamplerCardViewModelRefinerMode =>\n        DialogFactory.Get<SamplerCardViewModel>(vm =>\n        {\n            vm.IsCfgScaleEnabled = true;\n            vm.IsSamplerSelectionEnabled = true;\n            vm.IsDimensionsEnabled = true;\n            vm.IsRefinerStepsEnabled = true;\n        });\n\n    public static ModelCardViewModel ModelCardViewModel => DialogFactory.Get<ModelCardViewModel>();\n    public static WanModelCardViewModel WanModelCardViewModel => DialogFactory.Get<WanModelCardViewModel>();\n\n    public static ImgToVidModelCardViewModel ImgToVidModelCardViewModel =>\n        DialogFactory.Get<ImgToVidModelCardViewModel>();\n\n    public static PlasmaNoiseCardViewModel PlasmaNoiseCardViewModel =>\n        DialogFactory.Get<PlasmaNoiseCardViewModel>();\n\n    public static ImageGalleryCardViewModel ImageGalleryCardViewModel =>\n        DialogFactory.Get<ImageGalleryCardViewModel>(vm =>\n        {\n            vm.ImageSources.AddRange(\n                new ImageSource[]\n                {\n                    new(\n                        new Uri(\n                            \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/4a7e00a7-6f18-42d4-87c0-10e792df2640/width=1152\"\n                        )\n                    ),\n                    new(\n                        new Uri(\n                            \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/a318ac1f-3ad0-48ac-98cc-79126febcc17/width=1024\"\n                        )\n                    ),\n                    new(\n                        new Uri(\n                            \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/16588c94-6595-4be9-8806-d7e6e22d198c/width=1152\"\n                        )\n                    ),\n                }\n            );\n        });\n\n    public static ImageFolderCardViewModel ImageFolderCardViewModel =>\n        DialogFactory.Get<ImageFolderCardViewModel>();\n\n    public static FreeUCardViewModel FreeUCardViewModel => DialogFactory.Get<FreeUCardViewModel>();\n\n    public static NrsCardViewModel NrsCardViewModel => DialogFactory.Get<NrsCardViewModel>();\n\n    public static PromptCardViewModel PromptCardViewModel =>\n        DialogFactory.Get<PromptCardViewModel>(vm =>\n        {\n            var builder = new StringBuilder();\n            builder.AppendLine(\"house, (high quality), [example], BREAK\");\n            builder.AppendLine(\"# this is a comment\");\n            builder.AppendLine(\n                \"(detailed), [purple and orange lighting], looking pleased, (cinematic, god rays:0.8), \"\n                    + \"(8k, 4k, high res:1), (intricate), (unreal engine:1.2), (shaded:1.1), \"\n                    + \"(soft focus, detailed background), (horizontal lens flare), \"\n                    + \"(clear eyes)\"\n            );\n            builder.AppendLine(\"<lora:details:0.8>, <lyco:some_model>\");\n\n            vm.PromptDocument.Text = builder.ToString();\n            vm.NegativePromptDocument.Text = \"embedding:EasyNegative, blurry, jpeg artifacts\";\n        });\n\n    public static StackCardViewModel StackCardViewModel =>\n        DialogFactory.Get<StackCardViewModel>(vm =>\n        {\n            vm.AddCards(SamplerCardViewModel, SeedCardViewModel);\n        });\n\n    public static StackEditableCardViewModel StackEditableCardViewModel =>\n        DialogFactory.Get<StackEditableCardViewModel>(vm =>\n        {\n            vm.AddCards(StackExpanderViewModel, StackExpanderViewModel2);\n        });\n\n    public static StackExpanderViewModel StackExpanderViewModel =>\n        DialogFactory.Get<StackExpanderViewModel>(vm =>\n        {\n            vm.Title = \"Hires Fix\";\n            vm.AddCards(UpscalerCardViewModel, SamplerCardViewModel);\n            vm.OnContainerIndexChanged(0);\n        });\n\n    public static StackExpanderViewModel StackExpanderViewModel2 =>\n        DialogFactory.Get<StackExpanderViewModel>(vm =>\n        {\n            vm.Title = \"Hires Fix\";\n            vm.IsSettingsEnabled = true;\n            vm.AddCards(UpscalerCardViewModel, SamplerCardViewModel);\n            vm.OnContainerIndexChanged(1);\n        });\n\n    public static UpscalerCardViewModel UpscalerCardViewModel => DialogFactory.Get<UpscalerCardViewModel>();\n\n    public static BatchSizeCardViewModel BatchSizeCardViewModel =>\n        DialogFactory.Get<BatchSizeCardViewModel>();\n\n    public static BatchSizeCardViewModel BatchSizeCardViewModelWithIndexOption =>\n        DialogFactory.Get<BatchSizeCardViewModel>(vm =>\n        {\n            vm.IsBatchIndexEnabled = true;\n        });\n\n    public static LayerDiffuseCardViewModel LayerDiffuseCardViewModel =>\n        DialogFactory.Get<LayerDiffuseCardViewModel>();\n\n    public static ExtraNetworkCardViewModel ExtraNetworkCardViewModel =>\n        DialogFactory.Get<ExtraNetworkCardViewModel>();\n\n    public static ConfirmPackageDeleteDialogViewModel ConfirmPackageDeleteDialogViewModel =>\n        DialogFactory.Get<ConfirmPackageDeleteDialogViewModel>(vm =>\n            vm.Package = new InstalledPackage\n            {\n                PackageName = \"a1111\",\n                DisplayName = \"Automatic1111 WebUI\",\n                LibraryPath = \"packages\\\\a1111\",\n            }\n        );\n\n    public static InstalledWorkflowsViewModel InstalledWorkflowsViewModel\n    {\n        get\n        {\n            var vm = Services.GetRequiredService<InstalledWorkflowsViewModel>();\n            vm.DisplayedWorkflows = new ObservableCollectionExtended<OpenArtMetadata>\n            {\n                new()\n                {\n                    Workflow = new()\n                    {\n                        Name = \"Test Workflow\",\n                        Creator = new OpenArtCreator { Name = \"Test Creator\" },\n                        Thumbnails =\n                        [\n                            new OpenArtThumbnail\n                            {\n                                Url = new Uri(\n                                    \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/dd9b038c-bd15-43ab-86ab-66e145ad7ff2/width=512\"\n                                ),\n                            },\n                        ],\n                    },\n                },\n            };\n\n            return vm;\n        }\n    }\n\n    public static OpenModelDbKeyedModel SampleOpenModelDbKeyedModel =>\n        new()\n        {\n            Id = \"test-id\",\n            Name = \"16x PSNR Prretrained Model\",\n            Author = \"author123\",\n            License = \"Apache-2.0\",\n            Tags = [\"pretrained\"],\n            Description =\n                \"Pretrained: RRDB_PSNR_x4.pth\\n\\nThe original RRDB_PSNR_x4.pth model converted to 1x, 2x, 8x and 16x scales, intended to be used as pretrained models for new models at those scales. These are compatible with victor's 4xESRGAN.pth conversions\",\n            Date = new DateOnly(2020, 4, 20),\n            Architecture = \"esrgan\",\n            Size = [\"64nf\", \"23nb\"],\n            Scale = 16,\n            InputChannels = 3,\n            OutputChannels = 3,\n            Resources =\n            [\n                new OpenModelDbResource\n                {\n                    Platform = \"pytorch\",\n                    Type = \"pth\",\n                    Size = 67254807,\n                    Sha256 = \"d7ae3b9a3572a01d1ddfc788ebca253c872d959d3765bcb3b48c65a3ab2f9aba\",\n                    Urls = [\"https://drive.google.com/open?id=0\"],\n                },\n                new OpenModelDbResource\n                {\n                    Platform = \"onnx\",\n                    Type = \"onnx\",\n                    Size = 20098601,\n                    Sha256 = \"d7ae3b9a3572a01d1ddfc788ebca253c872d959d3765bcb3b48c65a3ab2f9aba\",\n                    Urls = [\"https://drive.google.com/open?id=0\"],\n                },\n            ],\n            Images =\n            [\n                new OpenModelDbImage.Paired\n                {\n                    Lr = new Uri(\"https://imgsli.com/i/c9978a74-32ee-478b-b228-10744521fc21.jpg\"),\n                    Sr = new Uri(\"https://imgsli.com/i/c90377d5-287d-4f1c-ab4c-96267bfaa04e.jpg\"),\n                },\n                new OpenModelDbImage.Paired\n                {\n                    Lr = new Uri(\"https://imgsli.com/i/7c22ba75-01ca-407a-98b9-16e27ad6de55.jpg\"),\n                    Sr = new Uri(\"https://imgsli.com/i/12aa0959-36db-49a1-8a0c-e58f99226395.jpg\"),\n                },\n                new OpenModelDbImage.Paired\n                {\n                    Lr = new Uri(\"https://imgsli.com/i/83e9600a-a8ee-4f2b-96e7-f42dc7e5ab12.jpg\"),\n                    Sr = new Uri(\"https://imgsli.com/i/a611b9a9-422f-44ed-882a-3ada8f478625.jpg\"),\n                },\n            ],\n        };\n\n    public static OpenModelDbBrowserViewModel OpenModelDbBrowserViewModel\n    {\n        get\n        {\n            var vm = Services.GetRequiredService<OpenModelDbBrowserViewModel>();\n            return vm;\n        }\n    }\n\n    public static OpenModelDbModelDetailsViewModel OpenModelDbModelDetailsViewModel =>\n        DialogFactory.Get<OpenModelDbModelDetailsViewModel>(vm =>\n        {\n            vm.Model = SampleOpenModelDbKeyedModel;\n        });\n\n    public static IList<ICompletionData> SampleCompletionData =>\n        new List<ICompletionData>\n        {\n            new TagCompletionData(\"test1\", TagType.General),\n            new TagCompletionData(\"test2\", TagType.Artist),\n            new TagCompletionData(\"test3\", TagType.Character),\n            new TagCompletionData(\"test4\", TagType.Copyright),\n            new TagCompletionData(\"test5\", TagType.Species),\n            new TagCompletionData(\"test_unknown\", TagType.Invalid),\n        };\n\n    public static CompletionList SampleCompletionList\n    {\n        get\n        {\n            var list = new CompletionList { IsFiltering = true };\n            ExtensionMethods.AddRange(list.CompletionData, SampleCompletionData);\n            ExtensionMethods.AddRange(list.FilteredCompletionData, list.CompletionData);\n            list.SelectItem(\"te\", true);\n            return list;\n        }\n    }\n\n    public static IEnumerable<HybridModelFile> SampleHybridModels { get; } =\n        [\n            HybridModelFile.FromLocal(\n                new LocalModelFile\n                {\n                    SharedFolderType = SharedFolderType.StableDiffusion,\n                    RelativePath = \"art_shaper_v8.safetensors\",\n                    PreviewImageFullPath =\n                        \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/dd9b038c-bd15-43ab-86ab-66e145ad7ff2/width=512\",\n                    ConnectedModelInfo = new ConnectedModelInfo\n                    {\n                        ModelName = \"Art Shaper (very long name example)\",\n                        VersionName = \"Style v8 (very long name)\",\n                        ModelId = 0,\n                        VersionId = 0,\n                        ModelDescription =\n                            \"This is a very long description for the Art Shaper model, which is used to demonstrate how long descriptions can be handled in the UI. It should be able to display multiple lines and still look good.\",\n                        ModelType = CivitModelType.Checkpoint,\n                        BaseModel = \"SD 1.5\",\n                    },\n                }\n            ),\n            HybridModelFile.FromLocal(\n                new LocalModelFile\n                {\n                    SharedFolderType = SharedFolderType.StableDiffusion,\n                    RelativePath = \"background_arts.safetensors\",\n                    PreviewImageFullPath =\n                        \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/71c81ddf-d8c3-46b4-843d-9f8f20a9254a/width=512\",\n                    ConnectedModelInfo = new ConnectedModelInfo\n                    {\n                        ModelName = \"Background Arts\",\n                        VersionName = \"Anime Style v10\",\n                        ModelId = 0,\n                        VersionId = 0,\n                        ModelDescription =\n                            \"This is a very long description for the Art Shaper model, which is used to demonstrate how long descriptions can be handled in the UI. It should be able to display multiple lines and still look good.\",\n                        ModelType = CivitModelType.Checkpoint,\n                        BaseModel = \"SDXL 1.0\",\n                    },\n                }\n            ),\n            HybridModelFile.FromRemote(\"v1-5-pruned-emaonly.safetensors\"),\n            HybridModelFile.FromRemote(\"sample-model.pt\"),\n        ];\n\n    public static HybridModelFile SampleHybridModel => SampleHybridModels.First();\n\n    public static ImageViewerViewModel ImageViewerViewModel =>\n        DialogFactory.Get<ImageViewerViewModel>(vm =>\n        {\n            vm.ImageSource = new ImageSource(\n                new Uri(\n                    \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/a318ac1f-3ad0-48ac-98cc-79126febcc17/width=1500\"\n                )\n            );\n            vm.FileNameText = \"TextToImage_00041.png\";\n            vm.FileSizeText = \"2.4 MB\";\n            vm.ImageSizeText = \"1280 x 1792\";\n\n            vm.CivitImageMetadata = new CivitImageGenerationDataResponse\n            {\n                Metadata = new CivitImageMetadata\n                {\n                    Prompt =\n                        \"closeup photp of a red haired anthro wolf female,\\n holding an apple, wearing medieval drees is eating a apple, wolf ears, wolf tail with white tip\\n,anthro,furry\",\n                    NegativePrompt = \"Bad quality , watermark\",\n                    CfgScale = 2.5d,\n                    Steps = 30,\n                    Sampler = \"DPM++ SDE\",\n                    Seed = 255842256659122,\n                    Model = \"RatatoskrIllustriousV2.3\",\n                    Height = 1152,\n                    Width = 768,\n                    Scheduler = \"normal\",\n                },\n                Resources =\n                [\n                    new CivitImageResource\n                    {\n                        ModelName = \"noobAI XL (NAI-XL) really long name example with even more words\",\n                        ModelId = 1337,\n                        VersionId = 1234,\n                        VersionName = \"Epsilon-pred 1.1-Version\",\n                        ModelType = \"Checkpoint\",\n                    },\n                ],\n            };\n\n            vm.CivitImageMetadata.OtherMetadata = new Dictionary<string, string>\n            {\n                [\"CFG\"] = \"2.5\",\n                [\"Steps\"] = \"30\",\n                [\"Sampler\"] = \"DPM++ SDE\",\n                [\"Seed\"] = \"255842256659122\",\n            };\n        });\n\n    public static DownloadResourceViewModel DownloadResourceViewModel =>\n        DialogFactory.Get<DownloadResourceViewModel>(vm =>\n        {\n            vm.FileName = ComfyUpscaler.DefaultDownloadableModels[0].Name;\n            vm.FileSize = Convert.ToInt64(2 * Size.GiB);\n            vm.Resource = ComfyUpscaler.DefaultDownloadableModels[0].DownloadableResource!.Value;\n        });\n\n    public static SharpenCardViewModel SharpenCardViewModel => DialogFactory.Get<SharpenCardViewModel>();\n\n    public static InferenceConnectionHelpViewModel InferenceConnectionHelpViewModel =>\n        DialogFactory.Get<InferenceConnectionHelpViewModel>();\n\n    public static SelectImageCardViewModel SelectImageCardViewModel =>\n        DialogFactory.Get<SelectImageCardViewModel>();\n\n    public static SelectImageCardViewModel SelectImageCardViewModel_WithImage =>\n        DialogFactory.Get<SelectImageCardViewModel>(vm =>\n        {\n            vm.ImageSource = new ImageSource(\n                new Uri(\n                    \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/a318ac1f-3ad0-48ac-98cc-79126febcc17/width=1500\"\n                )\n            );\n        });\n\n    public static PaintCanvasViewModel PaintCanvasViewModel => DialogFactory.Get<PaintCanvasViewModel>();\n\n    public static ImageSource SampleImageSource =>\n        new(\n            new Uri(\n                \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/a318ac1f-3ad0-48ac-98cc-79126febcc17/width=1500\"\n            )\n        )\n        {\n            Label = \"Test Image\",\n        };\n\n    public static ControlNetCardViewModel ControlNetCardViewModel =>\n        DialogFactory.Get<ControlNetCardViewModel>();\n\n    public static ConfirmDeleteDialogViewModel ConfirmDeleteDialogViewModel =>\n        DialogFactory.Get<ConfirmDeleteDialogViewModel>(vm =>\n        {\n            vm.IsRecycleBinAvailable = true;\n            vm.PathsToDelete = Enumerable\n                .Range(1, 64)\n                .Select(i => $\"C:/Users/ExampleUser/Data/ExampleFile{i}.txt\")\n                .ToArray();\n        });\n\n    public static ConfirmBulkDownloadDialogViewModel ConfirmBulkDownloadDialogViewModel =>\n        DialogFactory.Get<ConfirmBulkDownloadDialogViewModel>(vm =>\n        {\n            vm.Model = new CivitModel\n            {\n                Name = \"Test Model\",\n                ModelVersions = Enumerable\n                    .Range(1, 64)\n                    .Select(i => new CivitModelVersion\n                    {\n                        Name = $\"Version {i}\",\n                        Files =\n                        [\n                            new CivitFile\n                            {\n                                Name = $\"test-file-{i}.safetensors\",\n                                Type = CivitFileType.Model,\n                                Metadata = new CivitFileMetadata\n                                {\n                                    Format = CivitModelFormat.SafeTensor,\n                                    Fp = \"fp16\",\n                                    Size = \"pruned\",\n                                },\n                                SizeKb = new Random().Next(1, 10) * 1024 * 1024,\n                            },\n                        ],\n                    })\n                    .ToList(),\n            };\n\n            vm.FpTypePreference = CivitModelFpType.fp16;\n            vm.IncludeVae = true;\n\n            return vm;\n        });\n\n    public static SponsorshipPromptViewModel SponsorshipPromptViewModel =>\n        DialogFactory.Get<SponsorshipPromptViewModel>(vm => { });\n\n    public static OpenArtWorkflowViewModel OpenArtWorkflowViewModel =>\n        new(Services.GetRequiredService<ISettingsManager>(), Services.GetRequiredService<IPackageFactory>())\n        {\n            Workflow = new OpenArtSearchResult\n            {\n                Name = \"Test Workflow\",\n                Creator = new OpenArtCreator\n                {\n                    Name = \"Test Creator Name\",\n                    Username = \"Test Creator Username\",\n                },\n                Thumbnails =\n                [\n                    new OpenArtThumbnail\n                    {\n                        Url = new Uri(\n                            \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/a318ac1f-3ad0-48ac-98cc-79126febcc17/width=1500\"\n                        ),\n                    },\n                ],\n                NodesIndex =\n                [\n                    \"Anything Everywhere\",\n                    \"Reroute\",\n                    \"Note\",\n                    \".\",\n                    \"ComfyUI's ControlNet Auxiliary Preprocessors\",\n                    \"DWPreprocessor\",\n                    \"PixelPerfectResolution\",\n                    \"AIO_Preprocessor\",\n                    \",\",\n                    \"ComfyUI\",\n                    \"PreviewImage\",\n                    \"CLIPTextEncode\",\n                    \"EmptyLatentImage\",\n                    \"SplitImageWithAlpha\",\n                    \"ControlNetApplyAdvanced\",\n                    \"JoinImageWithAlpha\",\n                    \"LatentUpscaleBy\",\n                    \"VAEEncode\",\n                    \"LoadImage\",\n                    \"ControlNetLoader\",\n                    \"CLIPVisionLoader\",\n                    \"SaveImage\",\n                    \",\",\n                    \"ComfyUI Impact Pack\",\n                    \"SAMLoader\",\n                    \"UltralyticsDetectorProvider\",\n                    \"FaceDetailer\",\n                    \",\",\n                ],\n            },\n        };\n\n    public static SafetensorMetadataViewModel SafetensorMetadataViewModel =>\n        DialogFactory.Get<SafetensorMetadataViewModel>(vm =>\n        {\n            vm.Metadata = new SafetensorMetadata\n            {\n                TagFrequency = Enumerable\n                    .Range(1, 100)\n                    .Select(i => new SafetensorMetadata.Tag(\"tag\" + i, i))\n                    .ToList(),\n                OtherMetadata = new List<SafetensorMetadata.Metadata>\n                {\n                    new(\"Name1\", \"Value1\"),\n                    new(\"Name2\", \"Value2\"),\n                    new(\"Name3\", \"Value3\"),\n                },\n            };\n        });\n\n    public static ModelMetadataEditorDialogViewModel MetadataEditorDialogViewModel =>\n        DialogFactory.Get<ModelMetadataEditorDialogViewModel>(vm =>\n        {\n            vm.ThumbnailFilePath = Assets.NoImage.ToString();\n            vm.Tags = \"tag1, tag2, tag3\";\n            vm.ModelDescription = \"This is a description\";\n            vm.ModelName = \"Model Name\";\n            vm.VersionName = \"1.0.0\";\n            vm.TrainedWords = \"word1, word2, word3\";\n            vm.ModelType = CivitModelType.Checkpoint;\n            vm.BaseModelType = \"Pony\";\n        });\n\n    public static PackageInstallProgressItemViewModel PackageInstallProgressItemViewModel =>\n        new(\n            new PackageModificationRunner\n            {\n                CurrentProgress = new ProgressReport(50, \"Installing Package\", \"Description\"),\n                ModificationCompleteMessage = \"Install Complete\",\n            }\n        )\n        {\n            Progress = new ContentDialogProgressViewModelBase\n            {\n                Value = 50,\n                CloseWhenFinished = true,\n                Text = \"Installing Package\",\n                Description = \"Description\",\n                Console = { Document = new TextDocument(\"Hello world\") },\n            },\n        };\n\n    public static MockGitVersionProvider MockGitVersionProvider => new();\n\n    public static string CurrentDirectory => Directory.GetCurrentDirectory();\n\n    public static Indexer Types { get; } = new();\n\n    public class Indexer\n    {\n        private List<Type> types = new();\n\n        public object? this[string typeName]\n        {\n            get\n            {\n                var type = Type.GetType(typeName);\n\n                type ??= typeof(DesignData)\n                    .Assembly.GetTypes()\n                    .FirstOrDefault(t => (t.FullName ?? t.Name).EndsWith(typeName));\n\n                if (type is null)\n                {\n                    throw new ArgumentException($\"Type {typeName} not found\");\n                }\n\n                try\n                {\n                    return Services.GetRequiredService(type);\n                }\n                catch (InvalidOperationException)\n                {\n                    return Activator.CreateInstance(type);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/DesignData/MockCompletionProvider.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Avalonia.Controls.CodeCompletion;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Avalonia.DesignData;\n\npublic class MockCompletionProvider : ICompletionProvider\n{\n    /// <inheritdoc />\n    public CompletionType AvailableCompletionTypes { get; set; }\n\n    /// <inheritdoc />\n    public Func<ICompletionData, string>? PrepareInsertionText { get; } = data => data.Text;\n\n    /// <inheritdoc />\n    public Task Setup()\n    {\n        return Task.CompletedTask;\n    }\n\n    /// <inheritdoc />\n    public Task LoadFromFile(FilePath path, bool recreate = false)\n    {\n        return Task.CompletedTask;\n    }\n\n    /// <inheritdoc />\n    public void BackgroundLoadFromFile(FilePath path, bool recreate = false) { }\n\n    /// <inheritdoc />\n    public IEnumerable<ICompletionData> GetCompletions(\n        TextCompletionRequest completionRequest,\n        int itemsCount,\n        bool suggest\n    )\n    {\n        return Array.Empty<ICompletionData>();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/DesignData/MockDownloadProgressItemViewModel.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Avalonia.DesignData;\n\npublic class MockDownloadProgressItemViewModel : PausableProgressItemViewModelBase\n{\n    private Task? dummyTask;\n    private CancellationTokenSource? cts;\n\n    public MockDownloadProgressItemViewModel(string fileName)\n    {\n        Name = fileName;\n        Progress.Value = 5;\n        Progress.IsIndeterminate = false;\n        Progress.Text = \"Downloading...\";\n    }\n    \n    /// <inheritdoc />\n    public override Task Cancel()\n    {\n        // Cancel the task that updates progress\n        cts?.Cancel();\n        cts = null;\n        dummyTask = null;\n\n        State = ProgressState.Cancelled;\n        Progress.Text = \"Cancelled\";\n        return Task.CompletedTask;\n    }\n\n    /// <inheritdoc />\n    public override Task Pause()\n    {\n        // Cancel the task that updates progress\n        cts?.Cancel();\n        cts = null;\n        dummyTask = null;\n        \n        State = ProgressState.Inactive;\n        \n        return Task.CompletedTask;\n    }\n    \n    /// <inheritdoc />\n    public override Task Resume()\n    {\n        // Start a task that updates progress every 100ms\n        cts = new CancellationTokenSource();\n        dummyTask = Task.Run(async () =>\n        {\n            while (State != ProgressState.Success)\n            {\n                await Task.Delay(100, cts.Token);\n                Progress.Value += 1;\n            }\n        }, cts.Token);\n        \n        State = ProgressState.Working;\n        \n        return Task.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/DesignData/MockGitVersionProvider.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Core.Git;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.DesignData;\n\npublic class MockGitVersionProvider : IGitVersionProvider\n{\n    public Task<IReadOnlyList<GitVersion>> FetchTagsAsync(\n        int limit = 0,\n        CancellationToken cancellationToken = default\n    )\n    {\n        return Task.FromResult<IReadOnlyList<GitVersion>>(\n            [\n                new GitVersion { Tag = \"v1.0.0\" },\n                new GitVersion { Tag = \"v1.0.1\" },\n                new GitVersion { Tag = \"v1.0.2\" },\n                new GitVersion { Tag = \"v1.0.3\" }\n            ]\n        );\n    }\n\n    public Task<IReadOnlyList<GitVersion>> FetchBranchesAsync(\n        int limit = 0,\n        CancellationToken cancellationToken = default\n    )\n    {\n        return Task.FromResult<IReadOnlyList<GitVersion>>(\n            [\n                new GitVersion { Branch = \"main\" },\n                new GitVersion { Branch = \"develop\" },\n                new GitVersion { Branch = \"feature/1\" },\n                new GitVersion { Branch = \"feature/2\" }\n            ]\n        );\n    }\n\n    public Task<IReadOnlyList<GitVersion>> FetchCommitsAsync(\n        string? branch = null,\n        int limit = 0,\n        CancellationToken cancellationToken = default\n    )\n    {\n        branch ??= \"main\";\n\n        if (limit <= 0)\n        {\n            limit = 100;\n        }\n\n        // Generate sha1 hashes using branch as rng seed\n        var rng = new Random(branch.GetHashCode());\n        var hashes = Enumerable\n            .Range(0, limit)\n            .Select(_ =>\n            {\n                var data = new byte[32];\n                rng.NextBytes(data);\n                var hash = SHA1.HashData(data);\n                return Convert.ToHexString(hash).ToLowerInvariant();\n            })\n            .ToArray();\n\n        var results = hashes.Select(hash => new GitVersion { Branch = branch, CommitSha = hash }).ToArray();\n\n        return Task.FromResult<IReadOnlyList<GitVersion>>(results);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/DesignData/MockImageIndexService.cs",
    "content": "using System.Threading.Tasks;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.DesignData;\n\npublic class MockImageIndexService : IImageIndexService\n{\n    /// <inheritdoc />\n    public IndexCollection<LocalImageFile, string> InferenceImages { get; }\n\n    public MockImageIndexService()\n    {\n        InferenceImages = new IndexCollection<LocalImageFile, string>(this, file => file.AbsolutePath)\n        {\n            RelativePath = \"Inference\"\n        };\n    }\n\n    /// <inheritdoc />\n    public Task RefreshIndexForAllCollections()\n    {\n        return RefreshIndex(InferenceImages);\n    }\n\n    private static LocalImageFile GetSampleImage(string url)\n    {\n        return new LocalImageFile\n        {\n            AbsolutePath = url,\n            GenerationParameters = GenerationParameters.GetSample(),\n            ImageSize = new System.Drawing.Size(1024, 1024)\n        };\n    }\n\n    /// <inheritdoc />\n    public Task RefreshIndex(IndexCollection<LocalImageFile, string> indexCollection)\n    {\n        var toAdd = new[]\n        {\n            GetSampleImage(\n                \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/a318ac1f-3ad0-48ac-98cc-79126febcc17/width=1024\"\n            ),\n            GetSampleImage(\n                \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/16588c94-6595-4be9-8806-d7e6e22d198c/width=1152\"\n            )\n        };\n\n        indexCollection.ItemsSource.EditDiff(toAdd);\n\n        return Task.CompletedTask;\n    }\n\n    /// <inheritdoc />\n    public void BackgroundRefreshIndex()\n    {\n        throw new System.NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/DesignData/MockInferenceClientManager.cs",
    "content": "﻿using DynamicData;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.DesignData;\n\npublic class MockInferenceClientManager : InferenceClientManager\n{\n    public MockInferenceClientManager(\n        ILogger<InferenceClientManager> logger,\n        IApiFactory apiFactory,\n        IModelIndexService modelIndexService,\n        ISettingsManager settingsManager,\n        ICompletionProvider completionProvider\n    )\n        : base(logger, apiFactory, modelIndexService, settingsManager, completionProvider)\n    {\n        // Load our initial models\n        ResetSharedProperties();\n    }\n\n    public new bool IsConnected { get; set; }\n\n    protected override Task LoadSharedPropertiesAsync()\n    {\n        if (Models.Any(m => m.IsRemote))\n        {\n            return Task.CompletedTask;\n        }\n\n        Models.Add(\n            [\n                HybridModelFile.FromRemote(\"v1-5-pruned-emaonly.safetensors\"),\n                HybridModelFile.FromRemote(\"art-shaper1.safetensors\"),\n            ]\n        );\n\n        return Task.CompletedTask;\n    }\n\n    public override async Task ConnectAsync(CancellationToken cancellationToken = default)\n    {\n        IsConnecting = true;\n        await Task.Delay(5000, cancellationToken);\n\n        IsConnecting = false;\n        IsConnected = true;\n    }\n\n    public override async Task ConnectAsync(\n        PackagePair packagePair,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await ConnectAsync(cancellationToken);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/DesignData/MockLaunchPageViewModel.cs",
    "content": "﻿using System.Threading.Tasks;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.DesignData;\n\npublic class MockLaunchPageViewModel : LaunchPageViewModel\n{\n    public MockLaunchPageViewModel(\n        ILogger<MockLaunchPageViewModel> logger,\n        ISettingsManager settingsManager,\n        IPackageFactory packageFactory,\n        IPyRunner pyRunner,\n        INotificationService notificationService,\n        ISharedFolders sharedFolders,\n        IServiceManager<ViewModelBase> dialogFactory\n    )\n        : base(\n            logger,\n            settingsManager,\n            packageFactory,\n            pyRunner,\n            notificationService,\n            sharedFolders,\n            dialogFactory\n        ) { }\n\n    public override BasePackage? SelectedBasePackage =>\n        SelectedPackage?.PackageName != \"dank-diffusion\"\n            ? base.SelectedBasePackage\n            : new DankDiffusion(null!, null!, null!, null!, null!, null!);\n\n    protected override Task LaunchImpl(string? command)\n    {\n        IsLaunchTeachingTipsOpen = false;\n\n        RunningPackage = new PackagePair(null!, new DankDiffusion(null!, null!, null!, null!, null!, null!));\n\n        Console.Document.Insert(\n            0,\n            \"\"\"\n            Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)]\n            Version: 1.5.0\n            Commit hash: <none>\n\n            Fetching updates for midas...\n            Checking out commit for midas with hash: 2e42b7f...\n            \"\"\"\n        );\n\n        return Task.CompletedTask;\n    }\n\n    public override Task Stop()\n    {\n        RunningPackage = null;\n\n        return Task.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/DesignData/MockMetadataImportService.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.DesignData;\n\npublic class MockMetadataImportService : IMetadataImportService\n{\n    public Task ScanDirectoryForMissingInfo(DirectoryPath directory, IProgress<ProgressReport>? progress = null)\n    {\n        return Task.CompletedTask;\n    }\n\n    public Task<ConnectedModelInfo?> GetMetadataForFile(\n        FilePath filePath,\n        IProgress<ProgressReport>? progress = null,\n        bool forceReimport = false\n    )\n    {\n        return null;\n    }\n\n    public Task UpdateExistingMetadata(DirectoryPath directory, IProgress<ProgressReport>? progress = null)\n    {\n        return Task.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs",
    "content": "﻿using Nito.Disposables.Internals;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.DesignData;\n\npublic class MockModelIndexService : IModelIndexService\n{\n    /// <inheritdoc />\n    public Dictionary<SharedFolderType, List<LocalModelFile>> ModelIndex { get; } =\n        new()\n        {\n            [SharedFolderType.StableDiffusion] =\n            [\n                new LocalModelFile\n                {\n                    SharedFolderType = SharedFolderType.StableDiffusion,\n                    RelativePath = \"art_shaper_v8.safetensors\",\n                    PreviewImageFullPath =\n                        \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/dd9b038c-bd15-43ab-86ab-66e145ad7ff2/width=512/img.jpeg\",\n                    ConnectedModelInfo = new ConnectedModelInfo\n                    {\n                        ModelName = \"Art Shaper (very long name example)\",\n                        VersionName = \"Style v8 (very long name)\",\n                        ModelId = 0,\n                        VersionId = 0,\n                        ModelDescription =\n                            \"This is a very long description for the Art Shaper model, which is used to demonstrate how long descriptions can be handled in the UI. It should be able to display multiple lines and still look good.\",\n                        ModelType = CivitModelType.Checkpoint,\n                        BaseModel = \"SD 1.5\",\n                    },\n                },\n                new LocalModelFile\n                {\n                    SharedFolderType = SharedFolderType.StableDiffusion,\n                    RelativePath = \"background_arts.safetensors\",\n                    PreviewImageFullPath =\n                        \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/71c81ddf-d8c3-46b4-843d-9f8f20a9254a/width=512/img.jpeg\",\n                    ConnectedModelInfo = new ConnectedModelInfo\n                    {\n                        ModelName = \"Background Arts\",\n                        VersionName = \"Anime Style v10\",\n                        ModelId = 0,\n                        VersionId = 0,\n                        ModelDescription =\n                            \"This is a very long description for the Art Shaper model, which is used to demonstrate how long descriptions can be handled in the UI. It should be able to display multiple lines and still look good.\",\n                        ModelType = CivitModelType.Checkpoint,\n                        BaseModel = \"SDXL 1.0\",\n                    },\n                },\n            ],\n            [SharedFolderType.Lora] =\n            [\n                new LocalModelFile\n                {\n                    SharedFolderType = SharedFolderType.Lora,\n                    RelativePath = \"Lora/mock_model_1.safetensors\",\n                },\n                new LocalModelFile\n                {\n                    SharedFolderType = SharedFolderType.Lora,\n                    RelativePath = \"Lora/mock_model_2.safetensors\",\n                },\n                new LocalModelFile\n                {\n                    SharedFolderType = SharedFolderType.Lora,\n                    RelativePath = \"Lora/background_arts.safetensors\",\n                    PreviewImageFullPath =\n                        \"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/71c81ddf-d8c3-46b4-843d-9f8f20a9254a/width=512/img.png\",\n                    ConnectedModelInfo = new ConnectedModelInfo\n                    {\n                        ModelName = \"Background Arts\",\n                        VersionName = \"Anime Style v10\",\n                    },\n                },\n            ],\n        };\n\n    /// <inheritdoc />\n    public IReadOnlySet<string> ModelIndexBlake3Hashes =>\n        ModelIndex.Values.SelectMany(x => x).Select(x => x.HashBlake3).WhereNotNull().ToHashSet();\n\n    /// <inheritdoc />\n    public Task RefreshIndex()\n    {\n        return Task.CompletedTask;\n    }\n\n    /// <inheritdoc />\n    public IEnumerable<LocalModelFile> FindByModelType(SharedFolderType types)\n    {\n        return ModelIndex.Where(kvp => (kvp.Key & types) != 0).SelectMany(kvp => kvp.Value);\n    }\n\n    /// <inheritdoc />\n    public Task<Dictionary<SharedFolderType, LocalModelFolder>> FindAllFolders()\n    {\n        return Task.FromResult(new Dictionary<SharedFolderType, LocalModelFolder>());\n    }\n\n    /// <inheritdoc />\n    public Task<IEnumerable<LocalModelFile>> FindByModelTypeAsync(SharedFolderType type)\n    {\n        return Task.FromResult(Enumerable.Empty<LocalModelFile>());\n    }\n\n    /// <inheritdoc />\n    public Task<IEnumerable<LocalModelFile>> FindByHashAsync(string hashBlake3)\n    {\n        return Task.FromResult(Enumerable.Empty<LocalModelFile>());\n    }\n\n    public Task<IEnumerable<LocalModelFile>> FindBySha256Async(string hashSha256)\n    {\n        return Task.FromResult(Enumerable.Empty<LocalModelFile>());\n    }\n\n    /// <inheritdoc />\n    public Task<bool> RemoveModelAsync(LocalModelFile model)\n    {\n        return Task.FromResult(false);\n    }\n\n    public Task<bool> RemoveModelsAsync(IEnumerable<LocalModelFile> models)\n    {\n        return Task.FromResult(false);\n    }\n\n    public Task CheckModelsForUpdateAsync()\n    {\n        return Task.CompletedTask;\n    }\n\n    /// <inheritdoc />\n    public void BackgroundRefreshIndex() { }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/DesignData/MockPropertyGridObject.cs",
    "content": "﻿using System.ComponentModel;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing PropertyModels.ComponentModel;\nusing StabilityMatrix.Avalonia.Languages;\n\n#pragma warning disable CS0657 // Not a valid attribute location for this declaration\n\nnamespace StabilityMatrix.Avalonia.DesignData;\n\npublic partial class MockPropertyGridObject : ObservableObject\n{\n    [ObservableProperty]\n    private string? stringProperty;\n\n    [ObservableProperty]\n    private int intProperty;\n\n    [ObservableProperty]\n    [property: Trackable(0, 50, Increment = 1, FormatString = \"{0:0}\")]\n    private int intRange = 10;\n\n    [ObservableProperty]\n    [property: Trackable(0d, 1d, Increment = 0.01, FormatString = \"{0:P0}\")]\n    private double floatPercentRange = 0.25;\n\n    [ObservableProperty]\n    [property: DisplayName(\"Int Custom Name\")]\n    private int intCustomNameProperty = 42;\n\n    [ObservableProperty]\n    [property: DisplayName(nameof(Resources.Label_Language))]\n    private int? intLocalizedNameProperty;\n\n    [ObservableProperty]\n    private bool boolProperty;\n\n    [ObservableProperty]\n    [property: Category(\"Included Category\")]\n    private string? stringIncludedCategoryProperty;\n\n    [ObservableProperty]\n    [property: Category(\"Excluded Category\")]\n    private string? stringExcludedCategoryProperty;\n}\n\npublic partial class MockPropertyGridObjectAlt : ObservableObject\n{\n    [ObservableProperty]\n    private int altIntProperty = 10;\n\n    [ObservableProperty]\n    [property: Category(\"Settings\")]\n    private string? altStringProperty;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/DesignData/MockSettingsManager.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.Logging.Abstractions;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.DesignData;\n\npublic class MockSettingsManager() : SettingsManager(NullLogger<SettingsManager>.Instance)\n{\n    protected override void LoadSettings(CancellationToken cancellationToken = default) { }\n\n    protected override Task LoadSettingsAsync(CancellationToken cancellationToken = default)\n    {\n        return Task.CompletedTask;\n    }\n\n    protected override void SaveSettings(CancellationToken cancellationToken = default) { }\n\n    protected override Task SaveSettingsAsync(CancellationToken cancellationToken = default)\n    {\n        return Task.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/DialogHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Runtime.CompilerServices;\nusing System.Text;\nusing System.Text.Json;\nusing System.Threading.Tasks;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Data;\nusing Avalonia.Layout;\nusing Avalonia.LogicalTree;\nusing Avalonia.Media;\nusing Avalonia.Threading;\nusing Avalonia.VisualTree;\nusing AvaloniaEdit;\nusing AvaloniaEdit.TextMate;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing NLog;\nusing Refit;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Services;\nusing TextMateSharp.Grammars;\nusing Process = FuzzySharp.Process;\n\nnamespace StabilityMatrix.Avalonia;\n\npublic static class DialogHelper\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    /// <summary>\n    /// Create and show a generic textbox entry content dialog. Returns the result of the dialog.\n    /// </summary>\n    public static async Task<ContentDialogValueResult<TextBoxField>> GetTextEntryDialogResultAsync(\n        TextBoxField field,\n        string title = \"\",\n        string description = \"\"\n    )\n    {\n        var dialog = CreateTextEntryDialog(title: title, description: description, textFields: [field]);\n        var result = await dialog.ShowAsync();\n\n        return new ContentDialogValueResult<TextBoxField>(result, field);\n    }\n\n    /// <summary>\n    /// Create and show a generic textbox entry content dialog. Returns the result of the dialog.\n    /// </summary>\n    public static async Task<\n        ContentDialogValueResult<IReadOnlyList<TextBoxField>>\n    > GetTextEntryDialogResultAsync(\n        IReadOnlyList<TextBoxField> fields,\n        string title = \"\",\n        string description = \"\"\n    )\n    {\n        var dialog = CreateTextEntryDialog(title: title, description: description, textFields: fields);\n        var result = await dialog.ShowAsync();\n\n        return new ContentDialogValueResult<IReadOnlyList<TextBoxField>>(result, fields);\n    }\n\n    /// <summary>\n    /// Create a generic textbox entry content dialog.\n    /// </summary>\n    public static BetterContentDialog CreateTextEntryDialog(\n        string title,\n        string description,\n        IReadOnlyList<TextBoxField> textFields\n    )\n    {\n        return CreateTextEntryDialog(\n            title,\n            new BetterMarkdownScrollViewer { Markdown = description },\n            textFields\n        );\n    }\n\n    /// <summary>\n    /// Create a generic textbox entry content dialog.\n    /// </summary>\n    public static BetterContentDialog CreateTextEntryDialog(\n        string title,\n        string description,\n        string imageSource,\n        IReadOnlyList<TextBoxField> textFields\n    )\n    {\n        var markdown = new BetterMarkdownScrollViewer { Markdown = description };\n        var image = new BetterAdvancedImage((Uri?)null)\n        {\n            Source = imageSource,\n            Stretch = Stretch.UniformToFill,\n            StretchDirection = StretchDirection.Both,\n            HorizontalAlignment = HorizontalAlignment.Center,\n            MaxWidth = 400,\n        };\n\n        Grid.SetRow(markdown, 0);\n        Grid.SetRow(image, 1);\n\n        var grid = new Grid\n        {\n            RowDefinitions = { new RowDefinition(GridLength.Star), new RowDefinition(GridLength.Auto) },\n            Children = { markdown, image }\n        };\n\n        return CreateTextEntryDialog(title, grid, textFields);\n    }\n\n    /// <summary>\n    /// Create a generic textbox entry content dialog.\n    /// </summary>\n    public static BetterContentDialog CreateTextEntryDialog(\n        string title,\n        Control content,\n        IReadOnlyList<TextBoxField> textFields\n    )\n    {\n        Dispatcher.UIThread.VerifyAccess();\n\n        var stackPanel = new StackPanel { Spacing = 4 };\n\n        Grid.SetRow(content, 0);\n        Grid.SetRow(stackPanel, 1);\n\n        var grid = new Grid\n        {\n            RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Star) },\n            Children = { content, stackPanel }\n        };\n        grid.Loaded += (_, _) =>\n        {\n            // Focus first TextBox\n            var firstTextBox = stackPanel\n                .Children.OfType<StackPanel>()\n                .FirstOrDefault()\n                .FindDescendantOfType<TextBox>();\n            firstTextBox!.Focus();\n            firstTextBox.CaretIndex = firstTextBox.Text?.LastIndexOf('.') ?? 0;\n        };\n\n        // Disable primary button if any textboxes are invalid\n        var primaryCommand = new RelayCommand(\n            delegate { },\n            () =>\n            {\n                var invalidCount = textFields.Count(field => !field.IsValid);\n                Debug.WriteLine($\"Checking can execute: {invalidCount} invalid fields\");\n                return invalidCount == 0;\n            }\n        );\n\n        // Create textboxes\n        foreach (var field in textFields)\n        {\n            var label = new TextBlock { Text = field.Label, Margin = new Thickness(0, 0, 0, 4) };\n\n            var textBox = new TextBox\n            {\n                [!TextBox.TextProperty] = new Binding(\"TextProperty\"),\n                Watermark = field.Watermark,\n                DataContext = field,\n            };\n\n            if (field.MinWidth.HasValue)\n            {\n                textBox.MinWidth = field.MinWidth.Value;\n            }\n\n            if (!string.IsNullOrEmpty(field.InnerLeftText))\n            {\n                textBox.InnerLeftContent = new TextBlock()\n                {\n                    Text = field.InnerLeftText,\n                    Foreground = Brushes.Gray,\n                    VerticalAlignment = VerticalAlignment.Center,\n                    Margin = new Thickness(8, 0, -4, 0)\n                };\n            }\n\n            stackPanel.Children.Add(new StackPanel { Spacing = 4, Children = { label, textBox } });\n\n            // When IsValid property changes, update invalid count and primary button\n            field.PropertyChanged += (_, args) =>\n            {\n                if (args.PropertyName == nameof(TextBoxField.IsValid))\n                {\n                    primaryCommand.NotifyCanExecuteChanged();\n                }\n            };\n\n            // Set initial value\n            textBox.Text = field.Text;\n\n            // See if initial value is valid\n            try\n            {\n                field.Validator?.Invoke(field.Text);\n            }\n            catch (Exception)\n            {\n                field.IsValid = false;\n            }\n        }\n\n        return new BetterContentDialog\n        {\n            Title = title,\n            Content = grid,\n            PrimaryButtonText = Resources.Action_OK,\n            CloseButtonText = Resources.Action_Cancel,\n            IsPrimaryButtonEnabled = true,\n            PrimaryButtonCommand = primaryCommand,\n            DefaultButton = ContentDialogButton.Primary\n        };\n    }\n\n    /// <summary>\n    /// Create a generic dialog for showing a markdown document\n    /// </summary>\n    public static BetterContentDialog CreateMarkdownDialog(\n        string markdown,\n        string? title = null,\n        TextEditorPreset editorPreset = default\n    )\n    {\n        Dispatcher.UIThread.VerifyAccess();\n\n        var viewer = new BetterMarkdownScrollViewer { Markdown = markdown };\n\n        // Apply syntax highlighting to code blocks if preset is provided\n        if (editorPreset != default)\n        {\n            using var _ = CodeTimer.StartDebug();\n\n            var appliedCount = 0;\n\n            if (\n                viewer.GetLogicalDescendants().FirstOrDefault()?.GetLogicalDescendants() is\n                { } stackDescendants\n            )\n            {\n                foreach (var editor in stackDescendants.OfType<TextEditor>())\n                {\n                    TextEditorConfigs.Configure(editor, editorPreset);\n\n                    editor.FontFamily = \"Cascadia Code,Consolas,Menlo,Monospace\";\n                    editor.Margin = new Thickness(0);\n                    editor.Padding = new Thickness(4);\n                    editor.IsEnabled = false;\n\n                    if (editor.GetLogicalParent() is Border border)\n                    {\n                        border.BorderThickness = new Thickness(0);\n                        border.CornerRadius = new CornerRadius(4);\n                    }\n\n                    appliedCount++;\n                }\n            }\n\n            Logger.Log(\n                appliedCount > 0 ? LogLevel.Trace : LogLevel.Warn,\n                $\"Applied syntax highlighting to {appliedCount} code blocks\"\n            );\n        }\n\n        return new BetterContentDialog\n        {\n            Title = title,\n            Content = viewer,\n            CloseButtonText = Resources.Action_Close,\n            IsPrimaryButtonEnabled = false,\n            MinDialogWidth = 800,\n            MaxDialogHeight = 1000,\n            MaxDialogWidth = 1000\n        };\n    }\n\n    /// <summary>\n    /// Create a dialog for displaying an ApiException\n    /// </summary>\n    public static BetterContentDialog CreateApiExceptionDialog(ApiException exception, string? title = null)\n    {\n        Dispatcher.UIThread.VerifyAccess();\n\n        // Setup text editor\n        var textEditor = new TextEditor\n        {\n            IsReadOnly = true,\n            WordWrap = true,\n            Options = { ShowColumnRulers = false, AllowScrollBelowDocument = false }\n        };\n        var registryOptions = new RegistryOptions(ThemeName.DarkPlus);\n        textEditor.InstallTextMate(registryOptions).SetGrammar(registryOptions.GetScopeByLanguageId(\"json\"));\n\n        var mainGrid = new StackPanel\n        {\n            Spacing = 8,\n            Margin = new Thickness(16),\n            Children =\n            {\n                new TextBlock\n                {\n                    Text = $\"{(int)exception.StatusCode} - {exception.ReasonPhrase}\",\n                    FontSize = 18,\n                    FontWeight = FontWeight.Medium,\n                    Margin = new Thickness(0, 8),\n                },\n                textEditor\n            }\n        };\n\n        var dialog = new BetterContentDialog\n        {\n            Title = title,\n            Content = mainGrid,\n            CloseButtonText = \"Close\",\n            IsPrimaryButtonEnabled = false,\n        };\n\n        // Try to deserialize to json element\n        if (exception.Content != null)\n        {\n            try\n            {\n                // Deserialize to json element then re-serialize to ensure indentation\n                var jsonElement = JsonSerializer.Deserialize<JsonElement>(\n                    exception.Content,\n                    new JsonSerializerOptions\n                    {\n                        AllowTrailingCommas = true,\n                        ReadCommentHandling = JsonCommentHandling.Skip\n                    }\n                );\n                var formatted = JsonSerializer.Serialize(\n                    jsonElement,\n                    new JsonSerializerOptions() { WriteIndented = true }\n                );\n\n                textEditor.Document.Text = formatted;\n            }\n            catch (JsonException)\n            {\n                // Otherwise just add the content as a code block\n                textEditor.Document.Text = exception.Content;\n            }\n        }\n\n        return dialog;\n    }\n\n    /// <summary>\n    /// Create a dialog for displaying json\n    /// </summary>\n    public static BetterContentDialog CreateJsonDialog(\n        string json,\n        string? title = null,\n        string? subTitle = null\n    )\n    {\n        Dispatcher.UIThread.VerifyAccess();\n\n        // Setup text editor\n        var textEditor = new TextEditor\n        {\n            IsReadOnly = true,\n            WordWrap = true,\n            Options = { ShowColumnRulers = false, AllowScrollBelowDocument = false }\n        };\n        var registryOptions = new RegistryOptions(ThemeName.DarkPlus);\n        textEditor.InstallTextMate(registryOptions).SetGrammar(registryOptions.GetScopeByLanguageId(\"json\"));\n\n        var mainGrid = new StackPanel\n        {\n            Spacing = 8,\n            Margin = new Thickness(16),\n            Children = { textEditor }\n        };\n\n        if (subTitle is not null)\n        {\n            mainGrid.Children.Insert(\n                0,\n                new TextBlock\n                {\n                    Text = subTitle,\n                    FontSize = 18,\n                    FontWeight = FontWeight.Medium,\n                    Margin = new Thickness(0, 8),\n                }\n            );\n        }\n\n        var dialog = new BetterContentDialog\n        {\n            Title = title,\n            Content = mainGrid,\n            CloseButtonText = \"Close\",\n            PrimaryButtonText = \"Copy\",\n            IsPrimaryButtonEnabled = false,\n        };\n\n        // Try to deserialize to json element\n        try\n        {\n            // Deserialize to json element then re-serialize to ensure indentation\n            var jsonElement = JsonSerializer.Deserialize<JsonElement>(\n                json,\n                new JsonSerializerOptions\n                {\n                    AllowTrailingCommas = true,\n                    ReadCommentHandling = JsonCommentHandling.Skip\n                }\n            );\n            var formatted = JsonSerializer.Serialize(\n                jsonElement,\n                new JsonSerializerOptions { WriteIndented = true }\n            );\n\n            textEditor.Document.Text = formatted;\n        }\n        catch (JsonException)\n        {\n            // Otherwise just add the content as a code block\n            textEditor.Document.Text = json;\n        }\n\n        dialog.PrimaryButtonCommand = new AsyncRelayCommand(async () =>\n        {\n            // Copy the json to clipboard\n            var clipboard = App.Clipboard;\n            await clipboard.SetTextAsync(textEditor.Document.Text);\n        });\n\n        return dialog;\n    }\n\n    /// <summary>\n    /// Create a dialog for displaying a prompt error\n    /// </summary>\n    /// <param name=\"exception\">Target exception to display</param>\n    /// <param name=\"sourceText\">Full text of the target Document</param>\n    /// <param name=\"modelIndexService\">Optional model index service to look for similar names</param>\n    public static BetterContentDialog CreatePromptErrorDialog(\n        PromptError exception,\n        string sourceText,\n        IModelIndexService? modelIndexService = null\n    )\n    {\n        Dispatcher.UIThread.VerifyAccess();\n\n        var title = exception is PromptSyntaxError ? \"Prompt Syntax Error\" : \"Prompt Validation Error\";\n\n        // Get the index of the error\n        var errorIndex = exception.TextOffset;\n\n        // Get the line of error\n        var total = 0;\n        var errorLine = string.Empty;\n        var errorLineNum = 0;\n        var errorLineOffset = -1;\n        var errorLineEndOffset = -1;\n        foreach (var (i, line) in sourceText.Split(Environment.NewLine).Enumerate())\n        {\n            var lineLength = line.Length + Environment.NewLine.Length;\n            if (total + lineLength > errorIndex)\n            {\n                // Found, set the line text and number\n                errorLine = line;\n                errorLineNum = i + 1;\n                // Calculate line offset of the error\n                errorLineOffset = exception.TextOffset - total;\n                // Calculate line offset of the end of the error\n                errorLineEndOffset = exception.TextEndOffset - total;\n                break;\n            }\n            total += lineLength;\n        }\n\n        // Format the error line\n        var errorLineFormattedBuilder = new StringBuilder();\n        // Add line number\n        var errorLinePrefix = $\"[{errorLineNum}] \";\n        errorLineFormattedBuilder.AppendLine(errorLinePrefix + errorLine);\n        // Add error indicator at line offset\n        errorLineFormattedBuilder.Append(' ', errorLinePrefix.Length + errorLineOffset);\n        errorLineFormattedBuilder.Append('^', errorLineEndOffset - errorLineOffset);\n        var errorLineFormatted = errorLineFormattedBuilder.ToString();\n\n        // Setup text editor\n        var textEditor = new TextEditor\n        {\n            IsReadOnly = true,\n            WordWrap = false,\n            IsEnabled = false,\n            ShowLineNumbers = false,\n            FontFamily = \"Cascadia Code,Consolas,Menlo,Monospace\",\n            FontSize = 15,\n            Options =\n            {\n                HighlightCurrentLine = true,\n                ShowColumnRulers = false,\n                AllowScrollBelowDocument = false\n            }\n        };\n        TextEditorConfigs.Configure(textEditor, TextEditorPreset.Prompt);\n\n        textEditor.Document.Text = errorLineFormatted;\n        textEditor.TextArea.Caret.Offset = textEditor.Document.Lines[0].EndOffset;\n\n        var mainGrid = new StackPanel\n        {\n            Spacing = 8,\n            Margin = new Thickness(16),\n            Children =\n            {\n                new TextBlock\n                {\n                    Text =\n                        $\"{exception.Message} - at line {errorLineNum} [{errorLineOffset}:{errorLineEndOffset}]\",\n                    FontSize = 18,\n                    FontWeight = FontWeight.Medium,\n                    Margin = new Thickness(0, 8),\n                },\n                textEditor\n            }\n        };\n\n        // Check model typos\n        if (modelIndexService is not null && exception is PromptUnknownModelError unknownModelError)\n        {\n            var sharedFolderType = unknownModelError.ModelType.ConvertTo<SharedFolderType>();\n            if (modelIndexService.ModelIndex.TryGetValue(sharedFolderType, out var models))\n            {\n                var result = Process.ExtractOne(\n                    unknownModelError.ModelName,\n                    models.Select(m => m.FileNameWithoutExtension)\n                );\n\n                if (result is { Score: > 40 })\n                {\n                    mainGrid.Children.Add(\n                        new InfoBar\n                        {\n                            Title = $\"Did you mean: {result.Value}?\",\n                            IsClosable = false,\n                            IsOpen = true\n                        }\n                    );\n                }\n            }\n        }\n\n        textEditor.ScrollToHorizontalOffset(errorLineEndOffset - errorLineOffset);\n\n        var dialog = new BetterContentDialog\n        {\n            Title = title,\n            Content = mainGrid,\n            CloseButtonText = \"Close\",\n            IsPrimaryButtonEnabled = false,\n        };\n\n        return dialog;\n    }\n\n    /// <summary>\n    /// Create a simple title and description task dialog.\n    /// Sets the XamlRoot to the current top level window.\n    /// </summary>\n    public static TaskDialog CreateTaskDialog(string title, string description)\n    {\n        Dispatcher.UIThread.VerifyAccess();\n\n        var content = new StackPanel\n        {\n            Children =\n            {\n                new TextBlock\n                {\n                    Margin = new Thickness(0, 2, 0, 8),\n                    FontSize = 20,\n                    FontWeight = FontWeight.DemiBold,\n                    Text = title,\n                    TextWrapping = TextWrapping.WrapWithOverflow,\n                },\n                new TextBlock { Text = description, TextWrapping = TextWrapping.WrapWithOverflow, }\n            }\n        };\n\n        return new TaskDialog\n        {\n            Title = title,\n            Content = content,\n            XamlRoot = App.VisualRoot\n        };\n    }\n}\n\n// Text fields\npublic sealed class TextBoxField : INotifyPropertyChanged\n{\n    // Label above the textbox\n    public string Label { get; init; } = string.Empty;\n\n    // Actual text value\n    public string Text { get; set; } = string.Empty;\n\n    // Watermark text\n    public string Watermark { get; init; } = string.Empty;\n\n    // Inner left value\n    public string? InnerLeftText { get; init; }\n\n    public int? MinWidth { get; init; }\n\n    // Is this a password field?\n    public bool IsPassword { get; init; } = false;\n\n    /// <summary>\n    /// Validation action on text changes. Throw exception if invalid.\n    /// </summary>\n    public Action<string>? Validator { get; init; }\n\n    public string TextProperty\n    {\n        get => Text;\n        [DebuggerStepThrough]\n        set\n        {\n            try\n            {\n                Validator?.Invoke(value);\n            }\n            catch (Exception e)\n            {\n                IsValid = false;\n                throw new DataValidationException(e.Message);\n            }\n            Text = value;\n            IsValid = true;\n            OnPropertyChanged();\n        }\n    }\n\n    // Default to true if no validator is provided\n    private bool isValid;\n    public bool IsValid\n    {\n        get => Validator == null || isValid;\n        set\n        {\n            isValid = value;\n            OnPropertyChanged();\n        }\n    }\n\n    public event PropertyChangedEventHandler? PropertyChanged;\n\n    private void OnPropertyChanged([CallerMemberName] string? propertyName = null)\n    {\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/AvaloniaEditExtensions.cs",
    "content": "﻿using AvaloniaEdit;\nusing AvaloniaEdit.Document;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\npublic static class AvaloniaEditExtensions\n{\n    public static ISegment? GetSelectionSegment(this TextEditor editor)\n    {\n        if (editor.SelectionLength == 0)\n            return null;\n\n        return new SimpleSegment(editor.SelectionStart, editor.SelectionLength);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/BitmapExtensions.cs",
    "content": "﻿using System;\nusing System.Runtime.InteropServices;\nusing Avalonia;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Platform;\nusing SkiaSharp;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\npublic static class BitmapExtensions\n{\n    /// <summary>\n    /// Converts an Avalonia <see cref=\"IBitmap\"/> to a SkiaSharp <see cref=\"SKBitmap\"/>.\n    /// </summary>\n    /// <param name=\"bitmap\">The Avalonia bitmap to convert.</param>\n    /// <returns>The SkiaSharp bitmap.</returns>\n    public static SKBitmap ToSKBitmap(this Bitmap bitmap)\n    {\n        if (bitmap.Format != PixelFormat.Rgba8888 && bitmap.Format != PixelFormat.Bgra8888)\n        {\n            throw new NotSupportedException($\"Unknown pixel format {bitmap.Format}\");\n        }\n\n        var skColorType = SKColorType.Bgra8888;\n        if (bitmap.Format == PixelFormat.Rgba8888)\n        {\n            skColorType = SKColorType.Rgba8888;\n        }\n\n        var skAlphaType = bitmap.AlphaFormat switch\n        {\n            AlphaFormat.Premul => SKAlphaType.Premul,\n            AlphaFormat.Unpremul => SKAlphaType.Unpremul,\n            AlphaFormat.Opaque => SKAlphaType.Opaque,\n            _ => SKAlphaType.Premul\n        };\n\n        var skBitmap = new SKBitmap(\n            bitmap.PixelSize.Width,\n            bitmap.PixelSize.Height,\n            skColorType,\n            skAlphaType\n        );\n\n        var stride = skBitmap.RowBytes;\n        var bufferSize = stride * skBitmap.Height;\n        var sourceRect = new PixelRect(0, 0, bitmap.PixelSize.Width, bitmap.PixelSize.Height);\n\n        bitmap.CopyPixels(sourceRect, skBitmap.GetPixels(), bufferSize, stride);\n\n        return skBitmap;\n    }\n\n    // Convert to byte array\n    public static byte[] ToByteArray(this Bitmap bitmap)\n    {\n        var pixelRect = new PixelRect(0, 0, bitmap.PixelSize.Width, bitmap.PixelSize.Height);\n        var stride = bitmap.PixelSize.Width * 4;\n\n        var bufferSize = bitmap.PixelSize.Width * bitmap.PixelSize.Height * 4;\n        var buffer = new byte[bufferSize];\n\n        var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);\n\n        try\n        {\n            bitmap.CopyPixels(pixelRect, pinnedBuffer.AddrOfPinnedObject(), bufferSize, stride);\n        }\n        finally\n        {\n            pinnedBuffer.Free();\n        }\n\n        return buffer;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/ClipboardExtensions.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.IO;\nusing System.Threading.Tasks;\nusing Avalonia.Input;\nusing Avalonia.Input.Platform;\nusing Avalonia.Platform.Storage;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\npublic static class ClipboardExtensions\n{\n    private static IStorageProvider StorageProvider => App.StorageProvider;\n\n    /// <summary>\n    /// Set file paths to the clipboard.\n    /// </summary>\n    /// <exception cref=\"IOException\">Thrown if unable to get file from path</exception>\n    public static async Task SetFileDataObjectAsync(this IClipboard clipboard, params string[] filePaths)\n    {\n        await clipboard.SetFileDataObjectAsync((IEnumerable<string>)filePaths);\n    }\n\n    /// <summary>\n    /// Set file paths to the clipboard.\n    /// </summary>\n    /// <exception cref=\"IOException\">Thrown if unable to get file from path</exception>\n    public static async Task SetFileDataObjectAsync(this IClipboard clipboard, IEnumerable<string> filePaths)\n    {\n        var files = new List<IStorageFile>();\n\n        foreach (var filePath in filePaths)\n        {\n            // Normalize path that might have come from avalonia storage provider already\n            var normalizedPath = filePath.StripStart(\"file:///\").StripStart(\"file://\");\n\n            var file = await StorageProvider.TryGetFileFromPathAsync(normalizedPath);\n            if (file is null)\n            {\n                throw new IOException($\"File {normalizedPath} ({filePath}) was not found\");\n            }\n\n            files.Add(file);\n        }\n\n        if (files.Count == 0)\n        {\n            return;\n        }\n\n        var dataObject = new DataObject();\n        dataObject.Set(DataFormats.Files, files);\n\n        await clipboard.SetDataObjectAsync(dataObject);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/ComfyNodeBuilderExtensions.cs",
    "content": "﻿using System;\nusing System.ComponentModel.DataAnnotations;\nusing System.Drawing;\nusing System.IO;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\npublic static class ComfyNodeBuilderExtensions\n{\n    public static void SetupEmptyLatentSource(\n        this ComfyNodeBuilder builder,\n        int width,\n        int height,\n        int batchSize = 1,\n        int? batchIndex = null,\n        int? length = null,\n        LatentType latentType = LatentType.Default\n    )\n    {\n        var primaryNodeConnection = latentType switch\n        {\n            LatentType.Default\n                => builder\n                    .Nodes.AddTypedNode(\n                        new ComfyNodeBuilder.EmptyLatentImage\n                        {\n                            Name = \"EmptyLatentImage\",\n                            BatchSize = batchSize,\n                            Height = height,\n                            Width = width\n                        }\n                    )\n                    .Output,\n            LatentType.Sd3\n                => builder\n                    .Nodes.AddTypedNode(\n                        new ComfyNodeBuilder.EmptySD3LatentImage\n                        {\n                            Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.EmptySD3LatentImage)),\n                            BatchSize = batchSize,\n                            Height = height,\n                            Width = width\n                        }\n                    )\n                    .Output,\n            LatentType.Hunyuan\n                => builder\n                    .Nodes.AddTypedNode(\n                        new ComfyNodeBuilder.EmptyHunyuanLatentVideo\n                        {\n                            Name = builder.Nodes.GetUniqueName(\n                                nameof(ComfyNodeBuilder.EmptyHunyuanLatentVideo)\n                            ),\n                            BatchSize = batchSize,\n                            Height = height,\n                            Width = width,\n                            Length =\n                                length\n                                ?? throw new ValidationException(\n                                    \"Length cannot be null when latentType is Hunyuan\"\n                                )\n                        }\n                    )\n                    .Output,\n            _ => throw new ArgumentOutOfRangeException(nameof(latentType), latentType, null)\n        };\n\n        builder.Connections.Primary = primaryNodeConnection;\n        builder.Connections.PrimarySize = new Size(width, height);\n\n        // If batch index is selected, add a LatentFromBatch\n        if (batchIndex is not null)\n        {\n            builder.Connections.Primary = builder\n                .Nodes.AddTypedNode(\n                    new ComfyNodeBuilder.LatentFromBatch\n                    {\n                        Name = \"LatentFromBatch\",\n                        Samples = builder.GetPrimaryAsLatent(),\n                        // remote expects a 0-based index, vm is 1-based\n                        BatchIndex = batchIndex.Value - 1,\n                        Length = 1\n                    }\n                )\n                .Output;\n        }\n    }\n\n    /// <summary>\n    /// Setup an image as the <see cref=\"ComfyNodeBuilder.NodeBuilderConnections.Primary\"/> connection\n    /// </summary>\n    public static void SetupImagePrimarySource(\n        this ComfyNodeBuilder builder,\n        ImageSource image,\n        Size imageSize,\n        int? batchIndex = null\n    )\n    {\n        // Get source image\n        var sourceImageRelativePath = Path.Combine(\"Inference\", image.GetHashGuidFileNameCached());\n\n        // Load source\n        var loadImage = builder.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.LoadImage { Name = \"LoadImage\", Image = sourceImageRelativePath }\n        );\n\n        builder.Connections.Primary = loadImage.Output1;\n        builder.Connections.PrimarySize = imageSize;\n\n        // Add batch if selected\n        if (builder.Connections.BatchSize > 1)\n        {\n            builder.Connections.Primary = builder\n                .Nodes.AddTypedNode(\n                    new ComfyNodeBuilder.RepeatLatentBatch\n                    {\n                        Name = builder.Nodes.GetUniqueName(\"RepeatLatentBatch\"),\n                        Samples = builder.GetPrimaryAsLatent(),\n                        Amount = builder.Connections.BatchSize\n                    }\n                )\n                .Output;\n        }\n\n        // If batch index is selected, add a LatentFromBatch\n        if (batchIndex is not null)\n        {\n            builder.Connections.Primary = builder\n                .Nodes.AddTypedNode(\n                    new ComfyNodeBuilder.LatentFromBatch\n                    {\n                        Name = \"LatentFromBatch\",\n                        Samples = builder.GetPrimaryAsLatent(),\n                        // remote expects a 0-based index, vm is 1-based\n                        BatchIndex = batchIndex.Value - 1,\n                        Length = 1\n                    }\n                )\n                .Output;\n        }\n    }\n\n    /// <summary>\n    /// Setup an image as the <see cref=\"ComfyNodeBuilder.NodeBuilderConnections.Primary\"/> connection\n    /// </summary>\n    public static void SetupImagePrimarySourceWithMask(\n        this ComfyNodeBuilder builder,\n        ImageSource image,\n        Size imageSize,\n        ImageSource mask,\n        Size maskSize,\n        int? batchIndex = null\n    )\n    {\n        // Get image paths\n        var sourceImageRelativePath = Path.Combine(\"Inference\", image.GetHashGuidFileNameCached());\n        var maskImageRelativePath = Path.Combine(\"Inference\", mask.GetHashGuidFileNameCached());\n\n        // Load image\n        var loadImage = builder.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.LoadImage\n            {\n                Name = builder.Nodes.GetUniqueName(\"LoadImage\"),\n                Image = sourceImageRelativePath\n            }\n        );\n\n        // Load mask for alpha channel\n        var loadMask = builder.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.LoadImageMask\n            {\n                Name = builder.Nodes.GetUniqueName(\"LoadMask\"),\n                Image = maskImageRelativePath,\n                Channel = \"red\"\n            }\n        );\n\n        builder.Connections.Primary = loadImage.Output1;\n        builder.Connections.PrimarySize = imageSize;\n\n        // new betterer inpaint\n        builder.Connections.Primary = builder\n            .Nodes.AddTypedNode(\n                new ComfyNodeBuilder.VAEEncode\n                {\n                    Name = builder.Nodes.GetUniqueName(\"VAEEncode\"),\n                    Pixels = loadImage.Output1,\n                    Vae = builder.Connections.GetDefaultVAE()\n                }\n            )\n            .Output;\n\n        // latent noise mask for betterer inpaint\n        builder.Connections.Primary = builder\n            .Nodes.AddTypedNode(\n                new ComfyNodeBuilder.SetLatentNoiseMask\n                {\n                    Name = builder.Nodes.GetUniqueName(\"SetLatentNoiseMask\"),\n                    Samples = builder.GetPrimaryAsLatent(),\n                    Mask = loadMask.Output\n                }\n            )\n            .Output;\n\n        // Add batch if selected\n        if (builder.Connections.BatchSize > 1)\n        {\n            builder.Connections.Primary = builder\n                .Nodes.AddTypedNode(\n                    new ComfyNodeBuilder.RepeatLatentBatch\n                    {\n                        Name = builder.Nodes.GetUniqueName(\"RepeatLatentBatch\"),\n                        Samples = builder.GetPrimaryAsLatent(),\n                        Amount = builder.Connections.BatchSize\n                    }\n                )\n                .Output;\n        }\n\n        // If batch index is selected, add a LatentFromBatch\n        if (batchIndex is not null)\n        {\n            builder.Connections.Primary = builder\n                .Nodes.AddTypedNode(\n                    new ComfyNodeBuilder.LatentFromBatch\n                    {\n                        Name = \"LatentFromBatch\",\n                        Samples = builder.GetPrimaryAsLatent(),\n                        // remote expects a 0-based index, vm is 1-based\n                        BatchIndex = batchIndex.Value - 1,\n                        Length = 1\n                    }\n                )\n                .Output;\n        }\n    }\n\n    public static string SetupOutputImage(this ComfyNodeBuilder builder)\n    {\n        if (builder.Connections.Primary is null)\n            throw new ArgumentException(\"No Primary\");\n\n        var image = builder.Connections.Primary.Match(\n            _ =>\n                builder.GetPrimaryAsImage(\n                    builder.Connections.PrimaryVAE\n                        ?? builder.Connections.Refiner.VAE\n                        ?? builder.Connections.Base.VAE\n                        ?? throw new ArgumentException(\"No Primary, Refiner, or Base VAE\")\n                ),\n            image => image\n        );\n\n        var previewImage = builder.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.PreviewImage\n            {\n                Name = builder.Nodes.GetUniqueName(\"SaveImage\"),\n                Images = image\n            }\n        );\n\n        builder.Connections.OutputNodes.Add(previewImage);\n\n        return previewImage.Name;\n    }\n\n    public static void SetupPlasmaLatentSource(\n        this ComfyNodeBuilder builder,\n        int width,\n        int height,\n        ulong seed,\n        NoiseType noiseType,\n        int valueMin = -1,\n        int valueMax = -1,\n        int redMin = -1,\n        int redMax = -1,\n        int greenMin = -1,\n        int greenMax = -1,\n        int blueMin = -1,\n        int blueMax = -1,\n        double turbulence = 2.75d\n    )\n    {\n        var primaryNodeConnection = noiseType switch\n        {\n            NoiseType.Plasma\n                => builder\n                    .Nodes.AddTypedNode(\n                        new ComfyNodeBuilder.PlasmaNoise\n                        {\n                            Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.PlasmaNoise)),\n                            Height = height,\n                            Width = width,\n                            Seed = seed,\n                            ValueMin = valueMin,\n                            ValueMax = valueMax,\n                            RedMin = redMin,\n                            RedMax = redMax,\n                            GreenMin = greenMin,\n                            GreenMax = greenMax,\n                            BlueMin = blueMin,\n                            BlueMax = blueMax,\n                            Turbulence = turbulence,\n                        }\n                    )\n                    .Output,\n\n            NoiseType.Random\n                => builder\n                    .Nodes.AddTypedNode(\n                        new ComfyNodeBuilder.RandNoise\n                        {\n                            Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.RandNoise)),\n                            Height = height,\n                            Width = width,\n                            Seed = seed,\n                            ValueMin = valueMin,\n                            ValueMax = valueMax,\n                            RedMin = redMin,\n                            RedMax = redMax,\n                            GreenMin = greenMin,\n                            GreenMax = greenMax,\n                            BlueMin = blueMin,\n                            BlueMax = blueMax\n                        }\n                    )\n                    .Output,\n\n            NoiseType.Greyscale\n                => builder\n                    .Nodes.AddTypedNode(\n                        new ComfyNodeBuilder.GreyNoise\n                        {\n                            Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.GreyNoise)),\n                            Height = height,\n                            Width = width,\n                            Seed = seed,\n                            ValueMin = valueMin,\n                            ValueMax = valueMax,\n                            RedMin = redMin,\n                            RedMax = redMax,\n                            GreenMin = greenMin,\n                            GreenMax = greenMax,\n                            BlueMin = blueMin,\n                            BlueMax = blueMax\n                        }\n                    )\n                    .Output,\n\n            NoiseType.Brown\n                => builder\n                    .Nodes.AddTypedNode(\n                        new ComfyNodeBuilder.BrownNoise\n                        {\n                            Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.BrownNoise)),\n                            Height = height,\n                            Width = width,\n                            Seed = seed,\n                            ValueMin = valueMin,\n                            ValueMax = valueMax,\n                            RedMin = redMin,\n                            RedMax = redMax,\n                            GreenMin = greenMin,\n                            GreenMax = greenMax,\n                            BlueMin = blueMin,\n                            BlueMax = blueMax\n                        }\n                    )\n                    .Output,\n\n            NoiseType.Pink\n                => builder\n                    .Nodes.AddTypedNode(\n                        new ComfyNodeBuilder.PinkNoise\n                        {\n                            Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.PinkNoise)),\n                            Height = height,\n                            Width = width,\n                            Seed = seed,\n                            ValueMin = valueMin,\n                            ValueMax = valueMax,\n                            RedMin = redMin,\n                            RedMax = redMax,\n                            GreenMin = greenMin,\n                            GreenMax = greenMax,\n                            BlueMin = blueMin,\n                            BlueMax = blueMax\n                        }\n                    )\n                    .Output,\n            _ => throw new ArgumentOutOfRangeException(nameof(noiseType), noiseType, null)\n        };\n\n        builder.Connections.Primary = primaryNodeConnection;\n        builder.Connections.PrimarySize = new Size(width, height);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/DataObjectExtensions.cs",
    "content": "﻿using System.Runtime.InteropServices;\nusing Avalonia.Input;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\npublic static class DataObjectExtensions\n{\n    /// <summary>\n    /// Get Context from IDataObject, set by Xaml Behaviors\n    /// </summary>\n    public static T? GetContext<T>(this IDataObject dataObject)\n    {\n        try\n        {\n            if (dataObject.Get(\"Context\") is T context)\n            {\n                return context;\n            }\n        }\n        catch (COMException)\n        {\n            return default;\n        }\n\n        return default;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/EnumExtensions.cs",
    "content": "﻿using System;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\npublic static class EnumExtensions\n{\n    public static bool TryParseEnumStringValue<T>(string? value, T defaultValue, out T result)\n        where T : Enum\n    {\n        result = defaultValue;\n\n        if (value == null)\n            return false;\n\n        foreach (T enumValue in Enum.GetValues(typeof(T)))\n        {\n            if (!enumValue.GetStringValue().Equals(value, StringComparison.OrdinalIgnoreCase))\n                continue;\n\n            result = enumValue;\n            return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/InferenceProjectTypeExtensions.cs",
    "content": "﻿using System;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Core.Models.Inference;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\npublic static class InferenceProjectTypeExtensions\n{\n    public static Type? ToViewModelType(this InferenceProjectType type)\n    {\n        return type switch\n        {\n            InferenceProjectType.TextToImage => typeof(InferenceTextToImageViewModel),\n            InferenceProjectType.ImageToImage => typeof(InferenceImageToImageViewModel),\n            InferenceProjectType.Inpainting => null,\n            InferenceProjectType.Upscale => typeof(InferenceImageUpscaleViewModel),\n            InferenceProjectType.ImageToVideo => typeof(InferenceImageToVideoViewModel),\n            InferenceProjectType.FluxTextToImage => typeof(InferenceFluxTextToImageViewModel),\n            InferenceProjectType.WanTextToVideo => typeof(InferenceWanTextToVideoViewModel),\n            InferenceProjectType.WanImageToVideo => typeof(InferenceWanImageToVideoViewModel),\n            InferenceProjectType.Unknown => null,\n            _ => throw new ArgumentOutOfRangeException(nameof(type), type, null)\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/NotificationLevelExtensions.cs",
    "content": "﻿using System;\nusing Avalonia.Controls.Notifications;\nusing StabilityMatrix.Core.Models.Settings;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\npublic static class NotificationLevelExtensions\n{\n    public static NotificationType ToNotificationType(this NotificationLevel level)\n    {\n        return level switch\n        {\n            NotificationLevel.Information => NotificationType.Information,\n            NotificationLevel.Success => NotificationType.Success,\n            NotificationLevel.Warning => NotificationType.Warning,\n            NotificationLevel.Error => NotificationType.Error,\n            _ => throw new ArgumentOutOfRangeException(nameof(level), level, null)\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/NotificationServiceExtensions.cs",
    "content": "﻿using System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing DesktopNotifications;\nusing NLog;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Settings;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\npublic static class NotificationServiceExtensions\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    public static void OnPackageInstallCompleted(\n        this INotificationService notificationService,\n        IPackageModificationRunner runner\n    )\n    {\n        OnPackageInstallCompletedAsync(notificationService, runner)\n            .SafeFireAndForget(ex => Logger.Error(ex, \"Error Showing Notification\"));\n    }\n\n    private static async Task OnPackageInstallCompletedAsync(\n        this INotificationService notificationService,\n        IPackageModificationRunner runner\n    )\n    {\n        if (runner.Failed)\n        {\n            Logger.Error(runner.Exception, \"Error Installing Package\");\n\n            await notificationService.ShowAsync(\n                NotificationKey.Package_Install_Failed,\n                new Notification\n                {\n                    Title = runner.ModificationFailedTitle,\n                    Body = runner.ModificationFailedMessage\n                }\n            );\n        }\n        else\n        {\n            await notificationService.ShowAsync(\n                NotificationKey.Package_Install_Completed,\n                new Notification\n                {\n                    Title = runner.ModificationCompleteTitle,\n                    Body = runner.ModificationCompleteMessage\n                }\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/RelayCommandExtensions.cs",
    "content": "﻿using System;\nusing System.Diagnostics.CodeAnalysis;\nusing CommunityToolkit.Mvvm.Input;\nusing NLog;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic static class RelayCommandExtensions\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private static void VerifyFlowExceptionsToTaskSchedulerEnabled(IAsyncRelayCommand command)\n    {\n        // Check that the FlowExceptionsToTaskScheduler flag is set\n        var options = command.GetPrivateField<AsyncRelayCommandOptions>(\"options\");\n\n        if (!options.HasFlag(AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler))\n        {\n            throw new ArgumentException(\n                \"The command must be created with the FlowExceptionsToTaskScheduler option enabled\"\n            );\n        }\n    }\n\n    /// <summary>\n    /// Attach an error handler to the command that will invoke the given action when an exception occurs.\n    /// </summary>\n    /// <param name=\"command\">The command to attach the error handler to.</param>\n    /// <param name=\"onError\">The action to invoke when an exception occurs.</param>\n    /// <exception cref=\"ArgumentException\">Thrown if the command was not created with the FlowExceptionsToTaskScheduler option enabled.</exception>\n    public static T WithErrorHandler<T>(this T command, Action<Exception> onError)\n        where T : IAsyncRelayCommand\n    {\n        VerifyFlowExceptionsToTaskSchedulerEnabled(command);\n\n        command.PropertyChanged += (sender, e) =>\n        {\n            if (sender is not IAsyncRelayCommand senderCommand)\n            {\n                return;\n            }\n            // On ExecutionTask updates, check if there is an exception\n            if (\n                e.PropertyName == nameof(AsyncRelayCommand.ExecutionTask)\n                && senderCommand.ExecutionTask is { Exception: { } exception }\n            )\n            {\n                onError(exception.InnerException ?? exception);\n            }\n        };\n\n        return command;\n    }\n\n    /// <summary>\n    /// Conditionally attach an error handler to the command that will invoke the given action when an exception occurs.\n    /// The error is propagated if not in DEBUG mode.\n    /// </summary>\n    /// <param name=\"command\">The command to attach the error handler to.</param>\n    /// <param name=\"onError\">The action to invoke when an exception occurs.</param>\n    /// <exception cref=\"ArgumentException\">Thrown if the command was not created with the FlowExceptionsToTaskScheduler option enabled.</exception>\n    public static T WithConditionalErrorHandler<T>(this T command, Action<Exception> onError)\n        where T : IAsyncRelayCommand\n    {\n        VerifyFlowExceptionsToTaskSchedulerEnabled(command);\n\n#if DEBUG\n        command.PropertyChanged += (sender, e) =>\n        {\n            if (sender is not IAsyncRelayCommand senderCommand)\n            {\n                return;\n            }\n            // On ExecutionTask updates, check if there is an exception\n            if (\n                e.PropertyName == nameof(AsyncRelayCommand.ExecutionTask)\n                && senderCommand.ExecutionTask is { Exception: { } exception }\n            )\n            {\n                if (exception.InnerException != null)\n                {\n                    throw exception.InnerException;\n                }\n                throw exception;\n            }\n        };\n\n        return command;\n#else\n        return WithErrorHandler(command, onError);\n#endif\n    }\n\n    /// <summary>\n    /// Attach an error handler to the command that will log the error and show a notification.\n    /// </summary>\n    /// <param name=\"command\">The command to attach the error handler to.</param>\n    /// <param name=\"notificationService\">The notification service to use to show the notification.</param>\n    /// <param name=\"logLevel\">The log level to use when logging the error. Defaults to LogLevel.Error</param>\n    /// <exception cref=\"ArgumentException\">Thrown if the command was not created with the FlowExceptionsToTaskScheduler option enabled.</exception>\n    public static T WithNotificationErrorHandler<T>(\n        this T command,\n        INotificationService notificationService,\n        LogLevel? logLevel = default\n    )\n        where T : IAsyncRelayCommand\n    {\n        logLevel ??= LogLevel.Error;\n\n        return command.WithErrorHandler(e =>\n        {\n            Logger.Log(logLevel, e, \"Error executing command\");\n            notificationService.ShowPersistent(\"Error\", $\"[{e.GetType().Name}] {e.Message}\");\n        });\n    }\n\n    /// <summary>\n    /// Attach an error handler to the command that will log the error and show a notification.\n    /// The error is propagated if not in DEBUG mode.\n    /// </summary>\n    /// <param name=\"command\">The command to attach the error handler to.</param>\n    /// <param name=\"notificationService\">The notification service to use to show the notification.</param>\n    /// <param name=\"logLevel\">The log level to use when logging the error. Defaults to LogLevel.Error</param>\n    /// <exception cref=\"ArgumentException\">Thrown if the command was not created with the FlowExceptionsToTaskScheduler option enabled.</exception>\n    public static T WithConditionalNotificationErrorHandler<T>(\n        this T command,\n        INotificationService notificationService,\n        LogLevel? logLevel = default\n    )\n        where T : IAsyncRelayCommand\n    {\n        logLevel ??= LogLevel.Error;\n\n        return command.WithConditionalErrorHandler(e =>\n        {\n            Logger.Log(logLevel, e, \"Error executing command\");\n            notificationService.ShowPersistent(\"Error\", $\"[{e.GetType().Name}] {e.Message}\");\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/ServiceManagerExtensions.cs",
    "content": "﻿using System.ComponentModel;\nusing Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\n[Localizable(false)]\npublic static class ServiceManagerExtensions\n{\n    /// <summary>\n    /// Get a view model instance, set as DataContext of its View, and return\n    /// a BetterContentDialog with that View as its Content\n    /// </summary>\n    public static BetterContentDialog GetDialog<TService>(this IServiceManager<TService> serviceManager)\n    {\n        var instance = serviceManager.Get<TService>()!;\n\n        if (\n            Attribute.GetCustomAttribute(instance.GetType(), typeof(ViewAttribute))\n            is not ViewAttribute viewAttr\n        )\n        {\n            throw new InvalidOperationException($\"View not found for {instance.GetType().FullName}\");\n        }\n\n        if (Activator.CreateInstance(viewAttr.ViewType) is not Control view)\n        {\n            throw new NullReferenceException($\"Unable to create instance for {instance.GetType().FullName}\");\n        }\n\n        return new BetterContentDialog { Content = view };\n    }\n\n    /// <summary>\n    /// Get a view model instance with initializer, set as DataContext of its View, and return\n    /// a BetterContentDialog with that View as its Content\n    /// </summary>\n    public static BetterContentDialog GetDialog<TService>(\n        this IServiceManager<TService> serviceManager,\n        Action<TService> initializer\n    )\n    {\n        var instance = serviceManager.Get(initializer)!;\n\n        if (\n            Attribute.GetCustomAttribute(instance.GetType(), typeof(ViewAttribute))\n            is not ViewAttribute viewAttr\n        )\n        {\n            throw new InvalidOperationException($\"View not found for {instance.GetType().FullName}\");\n        }\n\n        if (Activator.CreateInstance(viewAttr.ViewType) is not Control view)\n        {\n            throw new NullReferenceException($\"Unable to create instance for {instance.GetType().FullName}\");\n        }\n\n        view.DataContext = instance;\n\n        return new BetterContentDialog { Content = view };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/SkiaExtensions.cs",
    "content": "﻿using System;\nusing Avalonia;\nusing Avalonia.Media;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Platform;\nusing Avalonia.Rendering.SceneGraph;\nusing Avalonia.Skia;\nusing SkiaSharp;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\npublic static class SkiaExtensions\n{\n    private record class SKBitmapDrawOperation : ICustomDrawOperation\n    {\n        public Rect Bounds { get; set; }\n\n        public Rect SourceBounds { get; set; }\n\n        public SKBitmap? Bitmap { get; init; }\n\n        public void Dispose()\n        {\n            //nop\n        }\n\n        public bool Equals(ICustomDrawOperation? other) => false;\n\n        public bool HitTest(Point p) => Bounds.Contains(p);\n\n        public void Render(ImmediateDrawingContext context)\n        {\n            if (\n                Bitmap != null\n                && context.PlatformImpl.GetFeature<ISkiaSharpApiLeaseFeature>() is { } leaseFeature\n            )\n            {\n                using var apiLease = leaseFeature.Lease();\n\n                apiLease.SkCanvas.DrawBitmap(\n                    Bitmap,\n                    SKRect.Create(\n                        (float)SourceBounds.X,\n                        (float)SourceBounds.Y,\n                        (float)SourceBounds.Width,\n                        (float)SourceBounds.Height\n                    ),\n                    SKRect.Create((float)Bounds.X, (float)Bounds.Y, (float)Bounds.Width, (float)Bounds.Height)\n                );\n            }\n        }\n    }\n\n    private class AvaloniaImage : IImage, IDisposable\n    {\n        private readonly SKBitmap? _source;\n        SKBitmapDrawOperation? _drawImageOperation;\n\n        public AvaloniaImage(SKBitmap? source)\n        {\n            _source = source;\n            if (source?.Info.Size is { } size)\n            {\n                Size = new Size(size.Width, size.Height);\n            }\n        }\n\n        public Size Size { get; }\n\n        public void Dispose() => _source?.Dispose();\n\n        public void Draw(DrawingContext context, Rect sourceRect, Rect destRect)\n        {\n            if (_drawImageOperation is null)\n            {\n                _drawImageOperation = new SKBitmapDrawOperation { Bitmap = _source };\n            }\n\n            _drawImageOperation.SourceBounds = sourceRect;\n            _drawImageOperation.Bounds = destRect;\n            context.Custom(_drawImageOperation);\n        }\n    }\n\n    public static SKBitmap? ToSKBitmap(this System.IO.Stream? stream)\n    {\n        if (stream == null)\n            return null;\n        return SKBitmap.Decode(stream);\n    }\n\n    public static IImage? ToAvaloniaImage(this SKBitmap? bitmap)\n    {\n        if (bitmap is not null)\n        {\n            return new AvaloniaImage(bitmap);\n        }\n        return default;\n    }\n\n    public static Bitmap ToAvaloniaBitmap(this SKBitmap bitmap)\n    {\n        return ToAvaloniaBitmap(bitmap, new Vector(96, 96));\n    }\n\n    public static Bitmap ToAvaloniaBitmap(this SKBitmap bitmap, Vector dpi)\n    {\n        // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault\n        var avaloniaColorFormat = bitmap.ColorType switch\n        {\n            SKColorType.Rgba8888 => PixelFormat.Rgba8888,\n            SKColorType.Bgra8888 => PixelFormat.Bgra8888,\n            _ => throw new NotSupportedException($\"Unsupported SKColorType: {bitmap.ColorType}\")\n        };\n\n        // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault\n        var avaloniaAlphaFormat = bitmap.AlphaType switch\n        {\n            SKAlphaType.Opaque => AlphaFormat.Opaque,\n            SKAlphaType.Premul => AlphaFormat.Premul,\n            SKAlphaType.Unpremul => AlphaFormat.Unpremul,\n            _ => throw new NotSupportedException($\"Unsupported SKAlphaType: {bitmap.AlphaType}\")\n        };\n\n        var dataPointer = bitmap.GetPixels();\n\n        return new Bitmap(\n            avaloniaColorFormat,\n            avaloniaAlphaFormat,\n            dataPointer,\n            new PixelSize(bitmap.Width, bitmap.Height),\n            dpi,\n            bitmap.RowBytes\n        );\n    }\n\n    public static Bitmap ToAvaloniaBitmap(this SKImage image)\n    {\n        return ToAvaloniaBitmap(image, new Vector(96, 96));\n    }\n\n    public static Bitmap ToAvaloniaBitmap(this SKImage image, Vector dpi)\n    {\n        ArgumentNullException.ThrowIfNull(image, nameof(image));\n\n        // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault\n        var avaloniaColorFormat = image.ColorType switch\n        {\n            SKColorType.Rgba8888 => PixelFormat.Rgba8888,\n            SKColorType.Bgra8888 => PixelFormat.Bgra8888,\n            _ => throw new NotSupportedException($\"Unsupported SKColorType: {image.ColorType}\")\n        };\n\n        // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault\n        var avaloniaAlphaFormat = image.AlphaType switch\n        {\n            SKAlphaType.Opaque => AlphaFormat.Opaque,\n            SKAlphaType.Premul => AlphaFormat.Premul,\n            SKAlphaType.Unpremul => AlphaFormat.Unpremul,\n            _ => throw new NotSupportedException($\"Unsupported SKAlphaType: {image.AlphaType}\")\n        };\n\n        var pixmap = image.PeekPixels();\n        var dataPointer = pixmap.GetPixels();\n\n        return new Bitmap(\n            avaloniaColorFormat,\n            avaloniaAlphaFormat,\n            dataPointer,\n            new PixelSize(image.Width, image.Height),\n            dpi,\n            pixmap.RowBytes\n        );\n    }\n\n    public static PixelFormat ToAvaloniaPixelFormat(this SKColorType colorType)\n    {\n        // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault\n        return colorType switch\n        {\n            SKColorType.Rgba8888 => PixelFormat.Rgba8888,\n            SKColorType.Bgra8888 => PixelFormat.Bgra8888,\n            _ => throw new NotSupportedException($\"Unsupported SKColorType: {colorType}\")\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/TextMateExtensions.cs",
    "content": "using System.Collections.Generic;\nusing System.IO;\nusing System.Reflection;\nusing TextMateSharp.Grammars;\nusing TextMateSharp.Internal.Grammars;\nusing TextMateSharp.Internal.Grammars.Reader;\nusing TextMateSharp.Internal.Types;\nusing TextMateSharp.Registry;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\npublic static class TextMateExtensions\n{\n    \n    public static IGrammar LoadGrammarFromStream(\n        this Registry registry,\n        Stream stream,\n        int? initialLanguage = default,\n        Dictionary<string, int>? embeddedLanguages = default)\n    {\n        IRawGrammar rawGrammar;\n        using (var sr = new StreamReader(stream))\n        {\n            rawGrammar = GrammarReader.ReadGrammarSync(sr);\n        }\n        \n        var locatorField = typeof(Registry).GetField(\"locator\", BindingFlags.NonPublic | BindingFlags.Instance);\n        var locator = (IRegistryOptions) locatorField!.GetValue(registry)!;\n        \n        var injections = locator.GetInjections(rawGrammar.GetScopeName());\n        \n        var syncRegistryField = typeof(Registry).GetField(\"syncRegistry\", BindingFlags.NonPublic | BindingFlags.Instance);\n        var syncRegistry = (SyncRegistry) syncRegistryField!.GetValue(registry)!;\n        \n        syncRegistry.AddGrammar(rawGrammar, injections);\n        return registry.GrammarForScopeName(\n            rawGrammar.GetScopeName(), \n            initialLanguage ?? 0, \n            embeddedLanguages ?? new Dictionary<string, int>());\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Extensions/VisualExtensions.cs",
    "content": "﻿using System.Diagnostics;\nusing Avalonia.Input;\nusing Avalonia.Interactivity;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Extensions;\n\npublic static class VisualExtensions\n{\n    [Conditional(\"DEBUG\")]\n    public static void AttachDebugSaveScreenshot(this AppWindowBase visual)\n    {\n        visual.AddDisposableHandler(\n            InputElement.KeyDownEvent,\n            (s, e) =>\n            {\n                if (new KeyGesture(Key.F10).Matches(e))\n                {\n                    App.DebugSaveScreenshot();\n                }\n            },\n            RoutingStrategies.Tunnel\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ExternalAnnotations/Microsoft.Extensions.Logging.Abstractions.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<assembly name=\"Microsoft.Extensions.Logging.Abstractions\">\n    <!-- Mark all members of LoggerExtensions as non-localizable -->\n    <member name=\"T:Microsoft.Extensions.Logging.LoggerExtensions\">\n        <attribute ctor=\"M:System.ComponentModel.LocalizableAttribute.#ctor(System.Boolean)\">\n            <argument>false</argument>\n        </attribute>\n        <attribute ctor=\"M:JetBrains.Annotations.LocalizationRequiredAttribute.#ctor(System.Boolean)\">\n            <argument>false</argument>\n        </attribute>\n    </member>\n    <!-- Mark all members of ILogger as non-localizable -->\n    <member name=\"T:Microsoft.Extensions.Logging.ILogger\">\n        <attribute ctor=\"M:System.ComponentModel.LocalizableAttribute.#ctor(System.Boolean)\">\n            <argument>false</argument>\n        </attribute>\n        <attribute ctor=\"M:JetBrains.Annotations.LocalizationRequiredAttribute.#ctor(System.Boolean)\">\n            <argument>false</argument>\n        </attribute>\n    </member>\n</assembly>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ExternalAnnotations/System.Runtime.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<assembly name=\"System.Runtime\">\n    <!-- Mark all members of Path as non-localizable -->\n    <member name=\"T:System.IO.Path\">\n        <attribute ctor=\"M:System.ComponentModel.LocalizableAttribute.#ctor(System.Boolean)\">\n            <argument>false</argument>\n        </attribute>\n        <attribute ctor=\"M:JetBrains.Annotations.LocalizationRequiredAttribute.#ctor(System.Boolean)\">\n            <argument>false</argument>\n        </attribute>\n    </member>\n</assembly>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/FallbackRamCachedWebImageLoader.cs",
    "content": "﻿using System;\nusing System.Collections.Concurrent;\nusing System.Diagnostics.CodeAnalysis;\nusing System.IO;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing AsyncImageLoader.Loaders;\nusing Avalonia.Media.Imaging;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\n\nnamespace StabilityMatrix.Avalonia;\n\npublic readonly record struct ImageLoadFailedEventArgs(string Url, Exception Exception);\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class FallbackRamCachedWebImageLoader : RamCachedWebImageLoader\n{\n    private readonly WeakEventManager<ImageLoadFailedEventArgs> loadFailedEventManager = new();\n\n    public event EventHandler<ImageLoadFailedEventArgs> LoadFailed\n    {\n        add => loadFailedEventManager.AddEventHandler(value);\n        remove => loadFailedEventManager.RemoveEventHandler(value);\n    }\n\n    protected void OnLoadFailed(string url, Exception exception) =>\n        loadFailedEventManager.RaiseEvent(\n            this,\n            new ImageLoadFailedEventArgs(url, exception),\n            nameof(LoadFailed)\n        );\n\n    /// <summary>\n    /// Attempts to load bitmap\n    /// </summary>\n    /// <param name=\"url\">Target url</param>\n    /// <returns>Bitmap</returns>\n    protected override async Task<Bitmap?> LoadAsync(string url)\n    {\n        // Try to load from local file first\n        if (File.Exists(url))\n        {\n            try\n            {\n                if (!url.EndsWith(\"png\", StringComparison.OrdinalIgnoreCase))\n                    return new Bitmap(url);\n\n                using var stream = ImageMetadata.BuildImageWithoutMetadata(url);\n                return stream == null ? new Bitmap(url) : new Bitmap(stream);\n            }\n            catch (Exception e)\n            {\n                OnLoadFailed(url, e);\n                return null;\n            }\n        }\n\n        var internalOrCachedBitmap =\n            await LoadFromInternalAsync(url).ConfigureAwait(false)\n            ?? await LoadFromGlobalCache(url).ConfigureAwait(false);\n\n        if (internalOrCachedBitmap != null)\n            return internalOrCachedBitmap;\n\n        try\n        {\n            var externalBytes = await LoadDataFromExternalAsync(url).ConfigureAwait(false);\n            if (externalBytes == null)\n                return null;\n\n            using var memoryStream = new MemoryStream(externalBytes);\n            var bitmap = new Bitmap(memoryStream);\n            await SaveToGlobalCache(url, externalBytes).ConfigureAwait(false);\n            return bitmap;\n        }\n        catch (Exception)\n        {\n            return null;\n        }\n    }\n\n    public async Task<Bitmap?> LoadExternalNoCacheAsync(string url)\n    {\n        if (await LoadDataFromExternalAsync(url).ConfigureAwait(false) is not { } externalBytes)\n        {\n            return null;\n        }\n\n        using var memoryStream = new MemoryStream(externalBytes);\n        var bitmap = new Bitmap(memoryStream);\n        return bitmap;\n    }\n\n    public async Task<Bitmap?> LoadExternalAsync(string url)\n    {\n        var internalOrCachedBitmap =\n            await LoadFromInternalAsync(url).ConfigureAwait(false)\n            ?? await LoadFromGlobalCache(url).ConfigureAwait(false);\n\n        if (internalOrCachedBitmap != null)\n            return internalOrCachedBitmap;\n\n        try\n        {\n            var externalBytes = await LoadDataFromExternalAsync(url).ConfigureAwait(false);\n            if (externalBytes == null)\n                return null;\n\n            using var memoryStream = new MemoryStream(externalBytes);\n            var bitmap = new Bitmap(memoryStream);\n            await SaveToGlobalCache(url, externalBytes).ConfigureAwait(false);\n            return bitmap;\n        }\n        catch (Exception)\n        {\n            return null;\n        }\n    }\n\n    public void RemovePathFromCache(string filePath)\n    {\n        var cache =\n            this.GetPrivateField<ConcurrentDictionary<string, Task<Bitmap?>>>(\"_memoryCache\")\n            ?? throw new NullReferenceException(\"Memory cache not found\");\n\n        cache.TryRemove(filePath, out _);\n    }\n\n    public void RemoveAllNamesFromCache(string fileName)\n    {\n        var cache =\n            this.GetPrivateField<ConcurrentDictionary<string, Task<Bitmap?>>>(\"_memoryCache\")\n            ?? throw new NullReferenceException(\"Memory cache not found\");\n\n        foreach (var (key, _) in cache)\n        {\n            if (Path.GetFileName(key) == fileName)\n            {\n                cache.TryRemove(key, out _);\n            }\n        }\n    }\n\n    public void ClearCache()\n    {\n        var cache =\n            this.GetPrivateField<ConcurrentDictionary<string, Task<Bitmap?>>>(\"_memoryCache\")\n            ?? throw new NullReferenceException(\"Memory cache not found\");\n\n        cache.Clear();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.Reflection.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Reflection;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.DependencyInjection;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\ninternal static partial class AttributeServiceInjector\n{\n    /// <summary>\n    /// Registers services from the assemblies starting with \"StabilityMatrix\" using\n    /// <see cref=\"TransientAttribute\"/> and <see cref=\"SingletonAttribute\"/>.\n    /// </summary>\n    /// <param name=\"services\">\n    /// The <see cref=\"IServiceCollection\"/> to which the services will be registered.\n    /// </param>\n    /// <param name=\"assemblies\">\n    /// The assemblies to scan for services.\n    /// </param>\n    // Injectio.Attributes are conditional with the required `REGISTER_SERVICE_USAGES` compilation symbol\n    [Conditional(\"REGISTER_SERVICE_USAGES\")]\n    public static void AddServicesByAttributesReflection(\n        IServiceCollection services,\n        IEnumerable<Assembly> assemblies\n    )\n    {\n        var exportedTypes = assemblies.SelectMany(a => a.GetExportedTypes()).ToArray();\n\n        var transientTypes = exportedTypes\n            .Select(\n                t => new { t, attributes = t.GetCustomAttributes(typeof(RegisterTransientAttribute), false) }\n            )\n            .Where(t1 => t1.attributes is { Length: > 0 })\n            .Select(t1 => new { Type = t1.t, Attribute = (RegisterTransientAttribute)t1.attributes[0] })\n            .ToArray();\n\n        foreach (var typePair in transientTypes)\n        {\n            if (\n                typePair.Attribute.ServiceType is not null\n                && typePair.Attribute.ImplementationType is not null\n            )\n            {\n                services.AddTransient(typePair.Attribute.ServiceType, typePair.Attribute.ImplementationType);\n            }\n            else if (typePair.Attribute.ServiceType is not null)\n            {\n                services.AddTransient(typePair.Attribute.ServiceType, typePair.Type);\n            }\n            else\n            {\n                services.AddTransient(typePair.Type);\n            }\n        }\n\n        var singletonTypes = exportedTypes\n            .Select(\n                t => new { t, attributes = t.GetCustomAttributes(typeof(RegisterSingletonAttribute), false) }\n            )\n            .Where(\n                t1 =>\n                    t1.attributes is { Length: > 0 }\n                    && !t1.t.Name.Contains(\"Mock\", StringComparison.OrdinalIgnoreCase)\n            )\n            .Select(\n                t1 =>\n                    new\n                    {\n                        Type = t1.t,\n                        Attributes = t1.attributes.Cast<RegisterSingletonAttribute>().ToArray()\n                    }\n            )\n            .ToArray();\n\n        foreach (var typePair in singletonTypes)\n        {\n            foreach (var attribute in typePair.Attributes)\n            {\n                if (attribute.ServiceType is not null && attribute.ImplementationType is not null)\n                {\n                    services.AddSingleton(attribute.ServiceType, attribute.ImplementationType);\n                }\n                else if (attribute.ServiceType is not null)\n                {\n                    services.AddSingleton(attribute.ServiceType, typePair.Type);\n                }\n                else\n                {\n                    services.AddSingleton(typePair.Type);\n                }\n            }\n        }\n    }\n\n    /// <summary>\n    /// Registers services from the assemblies starting with \"StabilityMatrix\" using\n    /// <see cref=\"TransientAttribute\"/> and <see cref=\"SingletonAttribute\"/>.\n    /// </summary>\n    /// <param name=\"services\">\n    /// The <see cref=\"IServiceCollection\"/> to which the services will be registered.\n    /// </param>\n    /// <param name=\"assemblies\">\n    /// The assemblies to scan for services.\n    /// </param>\n    public static void AddServicesByAttributesReflectionOld(\n        this IServiceCollection services,\n        IEnumerable<Assembly> assemblies\n    )\n    {\n        var exportedTypes = assemblies.SelectMany(a => a.GetExportedTypes()).ToArray();\n\n        var transientTypes = exportedTypes\n            .Select(t => new { t, attributes = t.GetCustomAttributes(typeof(TransientAttribute), false) })\n            .Where(\n                t1 =>\n                    t1.attributes is { Length: > 0 }\n                    && !t1.t.Name.Contains(\"Mock\", StringComparison.OrdinalIgnoreCase)\n            )\n            .Select(t1 => new { Type = t1.t, Attribute = (TransientAttribute)t1.attributes[0] });\n\n        foreach (var typePair in transientTypes)\n        {\n            if (typePair.Attribute.InterfaceType is null)\n            {\n                services.AddTransient(typePair.Type);\n            }\n            else\n            {\n                services.AddTransient(typePair.Attribute.InterfaceType, typePair.Type);\n            }\n        }\n\n        var singletonTypes = exportedTypes\n            .Select(t => new { t, attributes = t.GetCustomAttributes(typeof(SingletonAttribute), false) })\n            .Where(\n                t1 =>\n                    t1.attributes is { Length: > 0 }\n                    && !t1.t.Name.Contains(\"Mock\", StringComparison.OrdinalIgnoreCase)\n            )\n            .Select(\n                t1 => new { Type = t1.t, Attributes = t1.attributes.Cast<SingletonAttribute>().ToArray() }\n            );\n\n        foreach (var typePair in singletonTypes)\n        {\n            foreach (var attribute in typePair.Attributes)\n            {\n                if (attribute.InterfaceType is null)\n                {\n                    services.AddSingleton(typePair.Type);\n                }\n                else if (attribute.ImplType is not null)\n                {\n                    services.AddSingleton(attribute.InterfaceType, attribute.ImplType);\n                }\n                else\n                {\n                    services.AddSingleton(attribute.InterfaceType, typePair.Type);\n                }\n\n                // IDisposable registering\n                var serviceType = attribute.InterfaceType ?? typePair.Type;\n\n                if (serviceType == typeof(IDisposable) || serviceType == typeof(IAsyncDisposable))\n                {\n                    continue;\n                }\n\n                if (typePair.Type.IsAssignableTo(typeof(IDisposable)))\n                {\n                    Debug.WriteLine(\"Registering IDisposable: {Name}\", typePair.Type.Name);\n                    services.AddSingleton<IDisposable>(\n                        provider => (IDisposable)provider.GetRequiredService(serviceType)\n                    );\n                }\n\n                if (typePair.Type.IsAssignableTo(typeof(IAsyncDisposable)))\n                {\n                    Debug.WriteLine(\"Registering IAsyncDisposable: {Name}\", typePair.Type.Name);\n                    services.AddSingleton<IAsyncDisposable>(\n                        provider => (IAsyncDisposable)provider.GetRequiredService(serviceType)\n                    );\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/AttributeServiceInjector.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing System.Linq;\nusing Microsoft.Extensions.DependencyInjection;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\n/// <summary>\n/// Registers services using <see cref=\"TransientAttribute\"/> and <see cref=\"SingletonAttribute\"/> attributes.\n/// </summary>\n[Localizable(false)]\ninternal static partial class AttributeServiceInjector\n{\n    /// <summary>\n    /// Registers services from the assemblies using Attributes via source generation.\n    /// - Uses Source Generation by default (<see cref=\"AddServicesByAttributesSourceGen\"/>).\n    /// If `REGISTER_SERVICE_REFLECTION` symbol is defined, (also requires `REGISTER_SERVICE_USAGES` symbol) then:\n    /// - Uses Reflection (<see cref=\"AddServicesByAttributesReflection\"/>).\n    /// </summary>\n    /// <param name=\"services\">\n    /// The <see cref=\"IServiceCollection\"/> to which the services will be registered.\n    /// </param>\n    public static IServiceCollection AddServicesByAttributes(this IServiceCollection services)\n    {\n#if REGISTER_SERVICE_REFLECTION\n        var assemblies = AppDomain\n            .CurrentDomain.GetAssemblies()\n            .Where(a => a.FullName?.StartsWith(\"StabilityMatrix\") == true);\n        AddServicesByAttributesReflection(services, assemblies);\n#else\n        AddServicesByAttributesSourceGen(services);\n#endif\n        return services;\n    }\n\n    /// <summary>\n    /// Registers services from the assemblies using Attributes via source generation.\n    /// </summary>\n    /// <param name=\"services\">\n    /// The <see cref=\"IServiceCollection\"/> to which the services will be registered.\n    /// </param>\n    public static void AddServicesByAttributesSourceGen(IServiceCollection services)\n    {\n        services.AddStabilityMatrixCore();\n        services.AddStabilityMatrixAvalonia();\n    }\n\n    private static bool IsScoped(IServiceProvider serviceProvider)\n    {\n        // Hacky check for if service provider is scoped\n        var typeName = serviceProvider.GetType().Name;\n        if (typeName == \"ServiceProviderEngineScope\" || typeName.Contains(\"Scope\"))\n        {\n            return true;\n        }\n        return false;\n    }\n\n    /// <summary>\n    /// Adds a <see cref=\"IServiceManager{T}\"/> to the <see cref=\"IServiceCollection\"/>.\n    /// </summary>\n    /// <param name=\"services\">The <see cref=\"IServiceCollection\"/> to which the <see cref=\"IServiceManager{T}\"/> will be added.</param>\n    /// <param name=\"serviceFilter\">An optional filter for the services.</param>\n    /// <typeparam name=\"TService\">The base type of the services.</typeparam>\n    /// <exception cref=\"InvalidOperationException\"></exception>\n    public static IServiceCollection AddServiceManagerWithCurrentCollectionServices<TService>(\n        this IServiceCollection services,\n        Func<ServiceDescriptor, bool>? serviceFilter = null\n    )\n    {\n        // Register main service manager as singleton\n        services.AddSingleton<ServiceManager<TService>>(provider =>\n        {\n            using var _ = CodeTimer.StartDebug(\n                callerName: $\"{nameof(AddServiceManagerWithCurrentCollectionServices)}<{typeof(TService)}>\"\n            );\n\n            var serviceManager = new ServiceManager<TService>(provider);\n\n            // Get registered services that are assignable to TService\n            var serviceDescriptors = services.Where(s => s.ServiceType.IsAssignableTo(typeof(TService)));\n\n            // Optional filter\n            if (serviceFilter is not null)\n            {\n                serviceDescriptors = serviceDescriptors.Where(serviceFilter);\n            }\n\n            foreach (var service in serviceDescriptors)\n            {\n                var type = service.ServiceType;\n                Debug.Assert(type is not null, \"type is not null\");\n                Debug.Assert(type.IsAssignableTo(typeof(TService)), \"type is assignable to TService\");\n\n                if (service.Lifetime == ServiceLifetime.Scoped)\n                {\n                    serviceManager.RegisterScoped(type, sp => (TService)sp.GetRequiredService(type));\n                }\n                else\n                {\n                    serviceManager.Register(type, () => (TService)provider.GetRequiredService(type));\n                }\n            }\n\n            return serviceManager;\n        });\n\n        // Register scoped for interface\n        services.AddScoped<IServiceManager<TService>>(provider =>\n        {\n            var rootServiceManager = provider.GetRequiredService<ServiceManager<TService>>();\n\n            // For non scoped, just return the singleton\n            if (!IsScoped(provider))\n            {\n                return rootServiceManager;\n            }\n\n            // For scoped, create a new instance using root and provider\n            var scopedServiceManager = new ScopedServiceManager<TService>(rootServiceManager, provider);\n\n            return scopedServiceManager;\n        });\n\n        return services;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/ClipboardCommands.cs",
    "content": "﻿using System;\nusing CommunityToolkit.Mvvm.Input;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\npublic static class ClipboardCommands\n{\n    private static readonly Lazy<RelayCommand<string?>> CopyTextCommandLazy =\n        new(() => new RelayCommand<string?>(text => App.Clipboard.SetTextAsync(text)));\n\n    public static RelayCommand<string?> CopyTextCommand => CopyTextCommandLazy.Value;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/ConsoleProcessRunner.cs",
    "content": "﻿using System.IO;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.PackageModification;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\npublic static class ConsoleProcessRunner\n{\n    public static async Task<PackageModificationRunner> RunProcessStepAsync(ProcessStep step)\n    {\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            CloseWhenFinished = false,\n            ModificationCompleteMessage =\n                $\"Process command executed successfully for '{Path.GetFileName(step.FileName)}'\",\n            ModificationFailedMessage = $\"Process command failed for '{Path.GetFileName(step.FileName)}'\"\n        };\n\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n\n        await runner.ExecuteSteps([step]).ConfigureAwait(false);\n\n        return runner;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/EnumHelpers.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\npublic static class EnumHelpers\n{\n    public static IEnumerable<CivitPeriod> AllCivitPeriods { get; } =\n        Enum.GetValues(typeof(CivitPeriod)).Cast<CivitPeriod>();\n\n    public static IEnumerable<CivitSortMode> AllSortModes { get; } =\n        Enum.GetValues(typeof(CivitSortMode)).Cast<CivitSortMode>();\n\n    public static IEnumerable<CivitModelType> AllCivitModelTypes { get; } =\n        Enum.GetValues(typeof(CivitModelType))\n            .Cast<CivitModelType>()\n            .Where(t => t == CivitModelType.All || t.ConvertTo<SharedFolderType>() > 0)\n            .OrderBy(t => t.ToString());\n\n    public static IEnumerable<CivitModelType> MetadataEditorCivitModelTypes { get; } =\n        Enum.GetValues(typeof(CivitModelType)).Cast<CivitModelType>().OrderBy(t => t.ToString());\n\n    public static IEnumerable<CivitBaseModelType> AllCivitBaseModelTypes { get; } =\n        Enum.GetValues(typeof(CivitBaseModelType)).Cast<CivitBaseModelType>();\n\n    public static IEnumerable<CivitBaseModelType> MetadataEditorCivitBaseModelTypes { get; } =\n        AllCivitBaseModelTypes.Where(x => x != CivitBaseModelType.All);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/IOCommands.cs",
    "content": "﻿using System;\nusing CommunityToolkit.Mvvm.Input;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\npublic static class IOCommands\n{\n    public static RelayCommand<string?> OpenUrlCommand { get; } =\n        new(\n            url =>\n            {\n                if (string.IsNullOrWhiteSpace(url))\n                    return;\n\n                ProcessRunner.OpenUrl(url);\n            },\n            url => !string.IsNullOrWhiteSpace(url)\n        );\n\n    public static RelayCommand<Uri?> OpenUriCommand { get; } =\n        new(\n            url =>\n            {\n                if (url is null)\n                    return;\n\n                ProcessRunner.OpenUrl(url);\n            },\n            url => url is not null\n        );\n\n    public static AsyncRelayCommand<string?> OpenFileBrowserCommand { get; } =\n        new(\n            async path =>\n            {\n                if (string.IsNullOrWhiteSpace(path))\n                    return;\n\n                await ProcessRunner.OpenFileBrowser(path);\n            },\n            path => !string.IsNullOrWhiteSpace(path)\n        );\n\n    public static AsyncRelayCommand<string?> OpenFolderBrowserCommand { get; } =\n        new(\n            async path =>\n            {\n                if (string.IsNullOrWhiteSpace(path))\n                    return;\n\n                await ProcessRunner.OpenFolderBrowser(path);\n            },\n            path => !string.IsNullOrWhiteSpace(path)\n        );\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/ImageProcessor.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing SkiaSharp;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\npublic static class ImageProcessor\n{\n    /// <summary>\n    /// Get the dimensions of a grid that can hold the given amount of images.\n    /// </summary>\n    public static (int rows, int columns) GetGridDimensionsFromImageCount(int count)\n    {\n        if (count <= 1)\n            return (1, 1);\n        if (count == 2)\n            return (1, 2);\n\n        // Prefer one extra row over one extra column,\n        // the row count will be the floor of the square root\n        // and the column count will be floor of count / rows\n        var rows = (int)Math.Floor(Math.Sqrt(count));\n        var columns = (int)Math.Floor((double)count / rows);\n        return (rows, columns);\n    }\n\n    public static SKImage CreateImageGrid(IReadOnlyList<SKImage> images, int spacing = 0)\n    {\n        if (images.Count == 0)\n            throw new ArgumentException(\"Must have at least one image\");\n\n        var (rows, columns) = GetGridDimensionsFromImageCount(images.Count);\n\n        var singleWidth = images[0].Width;\n        var singleHeight = images[0].Height;\n\n        // Make output image\n        using var output = new SKBitmap(\n            singleWidth * columns + spacing * (columns - 1),\n            singleHeight * rows + spacing * (rows - 1)\n        );\n\n        // Draw images\n        using var canvas = new SKCanvas(output);\n\n        foreach (\n            var (row, column) in Enumerable.Range(0, rows).Product(Enumerable.Range(0, columns))\n        )\n        {\n            // Stop if we have drawn all images\n            var index = row * columns + column;\n            if (index >= images.Count)\n                break;\n\n            // Get image\n            var image = images[index];\n\n            // Draw image\n            var destination = new SKRect(\n                singleWidth * column + spacing * column,\n                singleHeight * row + spacing * row,\n                singleWidth * column + spacing * column + image.Width,\n                singleHeight * row + spacing * row + image.Height\n            );\n            canvas.DrawImage(image, destination);\n        }\n\n        return SKImage.FromBitmap(output);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/ImageSearcher.cs",
    "content": "﻿using System;\nusing FuzzySharp;\nusing FuzzySharp.PreProcess;\nusing StabilityMatrix.Core.Models.Database;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\npublic class ImageSearcher\n{\n    public int MinimumFuzzScore { get; init; } = 80;\n\n    public ImageSearchOptions SearchOptions { get; init; } = ImageSearchOptions.All;\n\n    public Func<LocalImageFile, bool> GetPredicate(string? searchQuery)\n    {\n        if (string.IsNullOrEmpty(searchQuery))\n        {\n            return _ => true;\n        }\n\n        return file =>\n        {\n            if (file.FileName.Contains(searchQuery, StringComparison.OrdinalIgnoreCase))\n            {\n                return true;\n            }\n\n            if (\n                SearchOptions.HasFlag(ImageSearchOptions.FileName)\n                && Fuzz.WeightedRatio(searchQuery, file.FileName, PreprocessMode.Full)\n                    > MinimumFuzzScore\n            )\n            {\n                return true;\n            }\n\n            // Generation params\n            if (file.GenerationParameters is { } parameters)\n            {\n                if (\n                    SearchOptions.HasFlag(ImageSearchOptions.PositivePrompt)\n                        && (\n                            parameters.PositivePrompt?.Contains(\n                                searchQuery,\n                                StringComparison.OrdinalIgnoreCase\n                            ) ?? false\n                        )\n                    || SearchOptions.HasFlag(ImageSearchOptions.NegativePrompt)\n                        && (\n                            parameters.NegativePrompt?.Contains(\n                                searchQuery,\n                                StringComparison.OrdinalIgnoreCase\n                            ) ?? false\n                        )\n                    || SearchOptions.HasFlag(ImageSearchOptions.Seed)\n                        && parameters.Seed\n                            .ToString()\n                            .StartsWith(searchQuery, StringComparison.OrdinalIgnoreCase)\n                    || SearchOptions.HasFlag(ImageSearchOptions.Sampler)\n                        && (\n                            parameters.Sampler?.StartsWith(\n                                searchQuery,\n                                StringComparison.OrdinalIgnoreCase\n                            ) ?? false\n                        )\n                    || SearchOptions.HasFlag(ImageSearchOptions.ModelName)\n                        && (\n                            parameters.ModelName?.StartsWith(\n                                searchQuery,\n                                StringComparison.OrdinalIgnoreCase\n                            ) ?? false\n                        )\n                )\n                {\n                    return true;\n                }\n            }\n\n            return false;\n        };\n    }\n\n    [Flags]\n    public enum ImageSearchOptions\n    {\n        None = 0,\n        FileName = 1 << 0,\n        PositivePrompt = 1 << 1,\n        NegativePrompt = 1 << 2,\n        Seed = 1 << 3,\n        Sampler = 1 << 4,\n        ModelName = 1 << 5,\n        All = int.MaxValue\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/MarkdownSnippets.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Helpers;\n\npublic static class MarkdownSnippets\n{\n    public static string SMFolderMap =>\n        \"\"\"\n        # Stability Matrix Folder Reference\n        Some guides and tutorials will mention folders by the names used in other WebUIs like ComfyUI, A1111, or others.\n        Use this table to find the correct folder when following external guides:\n        \n        | If a guide says to put it in... | In Stability Matrix, use this folder |\n        |---------------------------------|--------------------------------------|\n        | Checkpoints                     | StableDiffusion                      |\n        | CLIP                            | TextEncoders                         |\n        | unet                            | DiffusionModels                      |\n        | upscale_models                  | ESRGAN                               |\n        \n        You can find this table again by clicking the 3-dots (...) -> Folder Reference button in the top right corner of the Checkpoint Manager.\n        \"\"\";\n\n    public static string SharedFolderMigration =>\n        \"\"\"\n        # Shared Folder Migration\n        Some folder names have been updated to better reflect the contents of the folder and cause less confusion.\n        Your models have been safely migrated to the new folders.\n\n        | Old Name           | New Name        |\n        |--------------------|-----------------|\n        | Unet               | DiffusionModels |\n        | InvokeClipVision   | ClipVision      |\n        | InvokeIpAdapters15 | IpAdapters15    |\n        | InvokeIpAdaptersXl | IpAdaptersXl    |\n        | TextualInversion   | Embeddings      |\n        \"\"\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/PngDataHelper.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Text.Json;\nusing Force.Crc32;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\npublic static class PngDataHelper\n{\n    private static readonly byte[] Idat = { 0x49, 0x44, 0x41, 0x54 };\n    private static readonly byte[] Text = { 0x74, 0x45, 0x58, 0x74 };\n    private static readonly byte[] Iend = { 0x49, 0x45, 0x4E, 0x44 };\n\n    public static byte[] AddMetadata(\n        Stream inputStream,\n        GenerationParameters generationParameters,\n        InferenceProjectDocument projectDocument\n    )\n    {\n        using var ms = new MemoryStream();\n        inputStream.CopyTo(ms);\n        return AddMetadata(ms.ToArray(), generationParameters, projectDocument);\n    }\n\n    public static byte[] AddMetadata(\n        byte[] inputImage,\n        GenerationParameters generationParameters,\n        InferenceProjectDocument projectDocument\n    )\n    {\n        using var memoryStream = new MemoryStream();\n        var position = 8; // Skip the PNG signature\n        memoryStream.Write(inputImage, 0, position);\n\n        var metadataInserted = false;\n\n        while (position < inputImage.Length)\n        {\n            var chunkLength = BitConverter.ToInt32(\n                inputImage[position..(position + 4)].AsEnumerable().Reverse().ToArray(),\n                0\n            );\n            var chunkType = Encoding.ASCII.GetString(inputImage[(position + 4)..(position + 8)]);\n\n            switch (chunkType)\n            {\n                case \"IHDR\":\n                {\n                    var imageWidthBytes = inputImage[(position + 8)..(position + 12)];\n                    var imageHeightBytes = inputImage[(position + 12)..(position + 16)];\n                    var imageWidth = BitConverter.ToInt32(imageWidthBytes.AsEnumerable().Reverse().ToArray());\n                    var imageHeight = BitConverter.ToInt32(\n                        imageHeightBytes.AsEnumerable().Reverse().ToArray()\n                    );\n\n                    generationParameters.Width = imageWidth;\n                    generationParameters.Height = imageHeight;\n                    break;\n                }\n                case \"IDAT\" when !metadataInserted:\n                {\n                    var smprojJson = JsonSerializer.Serialize(projectDocument);\n                    var smprojChunk = BuildTextChunk(\"smproj\", smprojJson);\n\n                    var paramsData =\n                        $\"{generationParameters.PositivePrompt}\\nNegative prompt: {generationParameters.NegativePrompt}\\n\"\n                        + $\"Steps: {generationParameters.Steps}, Sampler: {generationParameters.Sampler}, \"\n                        + $\"CFG scale: {generationParameters.CfgScale}, Seed: {generationParameters.Seed}, \"\n                        + $\"Size: {generationParameters.Width}x{generationParameters.Height}, \"\n                        + $\"Model hash: {generationParameters.ModelHash}, Model: {generationParameters.ModelName}\";\n                    var paramsChunk = BuildTextChunk(\"parameters\", paramsData);\n\n                    var paramsJson = JsonSerializer.Serialize(generationParameters);\n                    var paramsJsonChunk = BuildTextChunk(\"parameters-json\", paramsJson);\n\n                    memoryStream.Write(paramsChunk, 0, paramsChunk.Length);\n                    memoryStream.Write(paramsJsonChunk, 0, paramsJsonChunk.Length);\n                    memoryStream.Write(smprojChunk, 0, smprojChunk.Length);\n\n                    metadataInserted = true; // Ensure we only insert the metadata once\n                    break;\n                }\n            }\n\n            // Write the current chunk to the output stream\n            memoryStream.Write(inputImage, position, chunkLength + 12); // Write the length, type, data, and CRC\n            position += chunkLength + 12;\n        }\n\n        return memoryStream.ToArray();\n    }\n\n    public static byte[] RemoveMetadata(byte[] inputImage)\n    {\n        using var memoryStream = new MemoryStream();\n        var position = 8; // Skip the PNG signature\n        memoryStream.Write(inputImage, 0, position);\n\n        while (position < inputImage.Length)\n        {\n            var chunkLength = BitConverter.ToInt32(\n                inputImage[position..(position + 4)].AsEnumerable().Reverse().ToArray(),\n                0\n            );\n            var chunkType = Encoding.ASCII.GetString(inputImage[(position + 4)..(position + 8)]);\n\n            // If the chunk is not a text chunk, write it to the output\n            if (chunkType != \"tEXt\" && chunkType != \"zTXt\" && chunkType != \"iTXt\")\n            {\n                memoryStream.Write(inputImage, position, chunkLength + 12); // Write the length, type, data, and CRC\n            }\n\n            // Move to the next chunk\n            position += chunkLength + 12;\n        }\n\n        return memoryStream.ToArray();\n    }\n\n    private static byte[] BuildTextChunk(string key, string value)\n    {\n        var textData = $\"{key}\\0{value}\";\n        var dataBytes = Encoding.UTF8.GetBytes(textData);\n        var textDataLength = BitConverter.GetBytes(dataBytes.Length).AsEnumerable().Reverse().ToArray();\n        var textDataBytes = Text.Concat(dataBytes).ToArray();\n        var crc = BitConverter\n            .GetBytes(Crc32Algorithm.Compute(textDataBytes))\n            .AsEnumerable()\n            .Reverse()\n            .ToArray();\n\n        return textDataLength.Concat(textDataBytes).Concat(crc).ToArray();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/TagCsvParser.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.IO;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing Sylvan.Data.Csv;\nusing Sylvan;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\npublic class TagCsvParser\n{\n    private readonly Stream stream;\n\n    public TagCsvParser(Stream stream)\n    {\n        this.stream = stream;\n    }\n\n    public async IAsyncEnumerable<TagCsvEntry> ParseAsync()\n    {\n        var pool = new StringPool();\n        var options = new CsvDataReaderOptions\n        {\n            StringFactory = pool.GetString,\n            HasHeaders = false,\n        };\n\n        using var textReader = new StreamReader(stream);\n        await using var dataReader = await CsvDataReader.CreateAsync(textReader, options);\n\n        while (await dataReader.ReadAsync())\n        {\n            var entry = new TagCsvEntry\n            {\n                Name = dataReader.GetString(0),\n                Type = dataReader.GetInt32(1),\n                Count = dataReader.GetInt32(2),\n                Aliases = dataReader.GetString(3),\n            };\n            yield return entry;\n        }\n\n        /*var dataBinderOptions = new DataBinderOptions\n        {\n            BindingMode = DataBindingMode.Any\n        };*/\n        /*var results = dataReader.GetRecordsAsync<TagCsvEntry>(dataBinderOptions);\n        return results;*/\n    }\n\n    public async Task<Dictionary<string, TagCsvEntry>> GetDictionaryAsync()\n    {\n        var dict = new Dictionary<string, TagCsvEntry>();\n\n        await foreach (var entry in ParseAsync())\n        {\n            if (entry.Name is null || entry.Type is null)\n            {\n                continue;\n            }\n\n            dict.Add(entry.Name, entry);\n        }\n\n        return dict;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/TextEditorConfigs.cs",
    "content": "﻿using System;\nusing System.IO;\nusing Avalonia.Media;\nusing AvaloniaEdit;\nusing AvaloniaEdit.TextMate;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Styles;\nusing StabilityMatrix.Core.Extensions;\nusing TextMateSharp.Grammars;\nusing TextMateSharp.Internal.Themes.Reader;\nusing TextMateSharp.Registry;\nusing TextMateSharp.Themes;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\npublic static class TextEditorConfigs\n{\n    public static void Configure(TextEditor editor, TextEditorPreset preset)\n    {\n        switch (preset)\n        {\n            case TextEditorPreset.Prompt:\n                ConfigForPrompt(editor);\n                break;\n            case TextEditorPreset.Console:\n                ConfigForConsole(editor);\n                break;\n            case TextEditorPreset.None:\n                break;\n            default:\n                throw new ArgumentOutOfRangeException(nameof(preset), preset, null);\n        }\n    }\n\n    private static void ConfigForPrompt(TextEditor editor)\n    {\n        const ThemeName themeName = ThemeName.DimmedMonokai;\n        var registryOptions = new RegistryOptions(themeName);\n\n        var registry = new Registry(registryOptions);\n\n        using var stream = Assets.ImagePromptLanguageJson.Open();\n        var promptGrammar = registry.LoadGrammarFromStream(stream);\n\n        // Load theme\n        var theme = GetCustomTheme();\n\n        // Setup editor\n        var editorOptions = editor.Options;\n        editorOptions.ShowColumnRulers = true;\n        editorOptions.EnableTextDragDrop = true;\n        editorOptions.ExtendSelectionOnMouseUp = true;\n        // Config hyperlinks\n        editorOptions.EnableHyperlinks = true;\n        editorOptions.RequireControlModifierForHyperlinkClick = true;\n        editor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Coral;\n        editor.TextArea.SelectionBrush = ThemeColors.EditorSelectionBrush;\n\n        var installation = editor.InstallTextMate(registryOptions);\n\n        // Set the _textMateRegistry property\n        installation.SetPrivateField(\"_textMateRegistry\", registry);\n\n        installation.SetGrammar(promptGrammar.GetScopeName());\n\n        installation.SetTheme(theme);\n    }\n\n    private static void ConfigForConsole(TextEditor editor)\n    {\n        var registryOptions = new RegistryOptions(ThemeName.DarkPlus);\n\n        // Config hyperlinks\n        editor.TextArea.Options.EnableHyperlinks = true;\n        editor.TextArea.Options.RequireControlModifierForHyperlinkClick = false;\n        editor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Coral;\n\n        var textMate = editor.InstallTextMate(registryOptions);\n        var scope = registryOptions.GetScopeByLanguageId(\"log\");\n\n        if (scope is null)\n            throw new InvalidOperationException(\"Scope is null\");\n\n        textMate.SetGrammar(scope);\n        textMate.SetTheme(registryOptions.LoadTheme(ThemeName.DarkPlus));\n\n        editor.Options.ShowBoxForControlCharacters = false;\n    }\n\n    private static IRawTheme GetThemeFromStream(Stream stream)\n    {\n        using var reader = new StreamReader(stream);\n        return ThemeReader.ReadThemeSync(reader);\n    }\n\n    private static IRawTheme GetCustomTheme()\n    {\n        using var stream = Assets.ThemeMatrixDarkJson.Open();\n        return GetThemeFromStream(stream);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Runtime.Versioning;\nusing System.Threading.Tasks;\nusing Avalonia.Controls;\nusing FluentAvalonia.UI.Controls;\nusing NLog;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\n[SupportedOSPlatform(\"macos\")]\n[SupportedOSPlatform(\"linux\")]\npublic class UnixPrerequisiteHelper(\n    IDownloadService downloadService,\n    ISettingsManager settingsManager,\n    IPyRunner pyRunner,\n    IPyInstallationManager pyInstallationManager\n) : IPrerequisiteHelper\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private const string UvMacDownloadUrl =\n        \"https://github.com/astral-sh/uv/releases/download/0.9.30/uv-aarch64-apple-darwin.tar.gz\";\n    private const string UvLinuxDownloadUrl =\n        \"https://github.com/astral-sh/uv/releases/download/0.9.30/uv-x86_64-unknown-linux-gnu.tar.gz\";\n\n    private DirectoryPath HomeDir => settingsManager.LibraryDir;\n    private DirectoryPath AssetsDir => HomeDir.JoinDir(\"Assets\");\n\n    // Helper method to get Python directory for specific version\n    private DirectoryPath GetPythonDir(PyVersion version) =>\n        version == PyInstallationManager.Python_3_10_11\n            ? AssetsDir.JoinDir(\"Python310\")\n            : AssetsDir.JoinDir($\"Python{version.Major}{version.Minor}{version.Micro}\");\n\n    // Helper method to check if specific Python version is installed\n    private bool IsPythonVersionInstalled(PyVersion version) =>\n        GetPythonDir(version).JoinFile(PyRunner.RelativePythonDllPath).Exists;\n\n    // Legacy property for compatibility\n    public bool IsPythonInstalled => IsPythonVersionInstalled(PyInstallationManager.DefaultVersion);\n    private DirectoryPath PortableGitInstallDir => HomeDir + \"PortableGit\";\n    public string GitBinPath => PortableGitInstallDir + \"bin\";\n\n    private DirectoryPath NodeDir => AssetsDir.JoinDir(\"nodejs\");\n    private string NpmPath => Path.Combine(NodeDir, \"bin\", \"npm\");\n    private bool IsNodeInstalled => File.Exists(NpmPath);\n    private string DotnetPath => Path.Combine(DotnetDir, \"dotnet\");\n    private string Dotnet7SdkExistsPath => Path.Combine(DotnetDir, \"sdk\", \"7.0.405\");\n    private string Dotnet8SdkExistsPath => Path.Combine(DotnetDir, \"sdk\", \"8.0.101\");\n    private string Dotnet7DownloadUrlMacOs =>\n        \"https://download.visualstudio.microsoft.com/download/pr/5bb0e0e4-2a8d-4aba-88ad-232e1f65c281/ee6d35f762d81965b4cf336edde1b318/dotnet-sdk-7.0.405-osx-arm64.tar.gz\";\n    private string Dotnet8DownloadUrlMacOs =>\n        \"https://download.visualstudio.microsoft.com/download/pr/ef083c06-7aee-4a4f-b18b-50c9a8990753/e206864e7910e81bbd9cb7e674ff1b4c/dotnet-sdk-8.0.101-osx-arm64.tar.gz\";\n    private string Dotnet7DownloadUrlLinux =>\n        \"https://download.visualstudio.microsoft.com/download/pr/5202b091-2406-445c-b40a-68a5b97c882b/b509f2a7a0eb61aea145b990b40b6d5b/dotnet-sdk-7.0.405-linux-x64.tar.gz\";\n    private string Dotnet8DownloadUrlLinux =>\n        \"https://download.visualstudio.microsoft.com/download/pr/9454f7dc-b98e-4a64-a96d-4eb08c7b6e66/da76f9c6bc4276332b587b771243ae34/dotnet-sdk-8.0.101-linux-x64.tar.gz\";\n\n    // Cached store of whether or not git is installed\n    private bool? isGitInstalled;\n\n    private string ExpectedUvVersion => \"0.9.30\";\n\n    public bool IsVcBuildToolsInstalled => false;\n    public bool IsHipSdkInstalled => false;\n    private string UvDownloadPath => Path.Combine(AssetsDir, \"uv.tar.gz\");\n    private string UvExtractPath => Path.Combine(AssetsDir, \"uv\");\n    public string UvExePath => Path.Combine(UvExtractPath, \"uv\");\n    public bool IsUvInstalled => File.Exists(UvExePath);\n    public DirectoryPath DotnetDir => AssetsDir.JoinDir(\"dotnet\");\n\n    // Helper method to get Python download URL for a specific version\n    private RemoteResource GetPythonDownloadResource(PyVersion version)\n    {\n        if (version == PyInstallationManager.Python_3_10_11)\n        {\n            return Assets.PythonDownloadUrl;\n        }\n\n        throw new ArgumentException($\"Unsupported Python version: {version}\", nameof(version));\n    }\n\n    // Helper method to get download path for a specific Python version\n    private string GetPythonDownloadPath(PyVersion version) =>\n        Path.Combine(AssetsDir, $\"python-{version}-amd64.tar.gz\");\n\n    private async Task<bool> CheckIsGitInstalled()\n    {\n        var result = await ProcessRunner.RunBashCommand(\"git --version\");\n        isGitInstalled = result.ExitCode == 0;\n        return isGitInstalled == true;\n    }\n\n    public Task InstallPackageRequirements(\n        BasePackage package,\n        PyVersion? pyVersion = null,\n        IProgress<ProgressReport>? progress = null\n    ) => InstallPackageRequirements(package.Prerequisites.ToList(), pyVersion, progress);\n\n    public async Task InstallPackageRequirements(\n        List<PackagePrerequisite> prerequisites,\n        PyVersion? pyVersion = null,\n        IProgress<ProgressReport>? progress = null\n    )\n    {\n        await UnpackResourcesIfNecessary(progress);\n        await InstallUvIfNecessary(progress);\n\n        if (prerequisites.Contains(PackagePrerequisite.Python310))\n        {\n            await InstallPythonIfNecessary(PyInstallationManager.Python_3_10_11, progress);\n            await InstallVirtualenvIfNecessary(PyInstallationManager.Python_3_10_11, progress);\n        }\n\n        if (pyVersion is not null)\n        {\n            if (!await EnsurePythonVersion(pyVersion.Value))\n            {\n                throw new MissingPrerequisiteException(\n                    @\"Python\",\n                    @$\"Python {pyVersion} was not found and/or failed to install. Please check the logs for more details.\"\n                );\n            }\n        }\n\n        if (prerequisites.Contains(PackagePrerequisite.Git))\n        {\n            await InstallGitIfNecessary(progress);\n        }\n\n        if (prerequisites.Contains(PackagePrerequisite.Node))\n        {\n            await InstallNodeIfNecessary(progress);\n        }\n\n        if (prerequisites.Contains(PackagePrerequisite.Dotnet))\n        {\n            await InstallDotnetIfNecessary(progress);\n        }\n    }\n\n    public async Task InstallDotnetIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        var downloadUrl = Compat.IsMacOS ? Dotnet8DownloadUrlMacOs : Dotnet8DownloadUrlLinux;\n\n        var dotnet8SdkExists = Directory.Exists(Dotnet8SdkExistsPath);\n\n        if (dotnet8SdkExists && Directory.Exists(DotnetDir))\n        {\n            Logger.Info(\"Dotnet 8 SDK already installed at {DotnetDir}\", DotnetDir);\n            return;\n        }\n\n        await DownloadAndExtractPrerequisite(progress, downloadUrl, DotnetDir);\n    }\n\n    private async Task InstallVirtualenvIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        // python stuff\n        if (!PyRunner.PipInstalled || !PyRunner.VenvInstalled)\n        {\n            progress?.Report(\n                new ProgressReport(-1f, \"Installing Python prerequisites...\", isIndeterminate: true)\n            );\n\n            await pyRunner.Initialize().ConfigureAwait(false);\n\n            if (!PyRunner.PipInstalled)\n            {\n                await pyRunner.SetupPip().ConfigureAwait(false);\n            }\n            if (!PyRunner.VenvInstalled)\n            {\n                await pyRunner.InstallPackage(\"virtualenv\").ConfigureAwait(false);\n            }\n        }\n    }\n\n    public async Task InstallAllIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        await UnpackResourcesIfNecessary(progress);\n        await InstallPythonIfNecessary(PyInstallationManager.Python_3_10_11, progress);\n        await InstallUvIfNecessary(progress);\n    }\n\n    public async Task UnpackResourcesIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        // Array of (asset_uri, extract_to)\n        var assets = new[] { (Assets.SevenZipExecutable, AssetsDir), (Assets.SevenZipLicense, AssetsDir) };\n\n        progress?.Report(new ProgressReport(0, message: \"Unpacking resources\", isIndeterminate: true));\n\n        Directory.CreateDirectory(AssetsDir);\n        foreach (var (asset, extractDir) in assets)\n        {\n            await asset.ExtractToDir(extractDir);\n        }\n\n        progress?.Report(new ProgressReport(1, message: \"Unpacking resources\", isIndeterminate: false));\n    }\n\n    public async Task InstallGitIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        if (isGitInstalled == true || (isGitInstalled == null && await CheckIsGitInstalled()))\n            return;\n\n        // Show prompt to install git\n        var dialog = new ContentDialog\n        {\n            Title = \"Git not found\",\n            Content = new StackPanel\n            {\n                Children =\n                {\n                    new TextBlock\n                    {\n                        Text = \"The current operation requires Git. Please install it to continue.\",\n                    },\n                    new SelectableTextBlock { Text = \"$ sudo apt install git\" },\n                },\n            },\n            PrimaryButtonText = Resources.Action_Retry,\n            CloseButtonText = Resources.Action_Close,\n            DefaultButton = ContentDialogButton.Primary,\n        };\n\n        while (true)\n        {\n            // Return if installed\n            if (await CheckIsGitInstalled())\n                return;\n            if (await dialog.ShowAsync() == ContentDialogResult.None)\n            {\n                // Cancel\n                throw new OperationCanceledException(\"Git installation canceled\");\n            }\n            // Otherwise continue to retry indefinitely\n        }\n    }\n\n    /// <inheritdoc />\n    public Task RunGit(\n        ProcessArgs args,\n        Action<ProcessOutput>? onProcessOutput = null,\n        string? workingDirectory = null\n    )\n    {\n        // Async progress not supported on Unix\n        return RunGit(args, workingDirectory);\n    }\n\n    private async Task RunGit(ProcessArgs args, string? workingDirectory = null)\n    {\n        var command = args.Prepend(\"git\");\n\n        var result = await ProcessRunner.RunBashCommand(\n            command,\n            workingDirectory ?? string.Empty,\n            new Dictionary<string, string>\n            {\n                { \"GIT_TERMINAL_PROMPT\", \"0\" },\n                // Set UTF-8 locale to handle Unicode characters in paths\n                { \"LC_ALL\", \"C.UTF-8\" },\n                { \"LANG\", \"C.UTF-8\" },\n            }\n        );\n        if (result.ExitCode != 0)\n        {\n            Logger.Error(\n                \"Git command [{Command}] failed with exit code \" + \"{ExitCode}:\\n{StdOut}\\n{StdErr}\",\n                command,\n                result.ExitCode,\n                result.StandardOutput,\n                result.StandardError\n            );\n\n            throw new ProcessException(\n                $\"Git command [{command}] failed with exit code\"\n                    + $\" {result.ExitCode}:\\n{result.StandardOutput}\\n{result.StandardError}\"\n            );\n        }\n    }\n\n    public async Task InstallPythonIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        await InstallPythonIfNecessary(PyInstallationManager.DefaultVersion, progress);\n    }\n\n    public async Task InstallPythonIfNecessary(PyVersion version, IProgress<ProgressReport>? progress = null)\n    {\n        var pythonDir = GetPythonDir(version);\n\n        if (IsPythonVersionInstalled(version))\n            return;\n\n        Directory.CreateDirectory(AssetsDir);\n\n        // Download\n        var remote = GetPythonDownloadResource(version);\n        var url = remote.Url;\n        var hashSha256 = remote.HashSha256;\n\n        var fileName = Path.GetFileName(url.LocalPath);\n        var downloadPath = Path.Combine(AssetsDir, fileName);\n        Logger.Info($\"Downloading Python {version} from {url} to {downloadPath}\");\n        try\n        {\n            await downloadService.DownloadToFileAsync(url.ToString(), downloadPath, progress);\n\n            // Verify hash\n            var actualHash = await FileHash.GetSha256Async(downloadPath);\n            Logger.Info($\"Verifying Python {version} hash: (expected: {hashSha256}, actual: {actualHash})\");\n            if (actualHash != hashSha256)\n            {\n                throw new Exception(\n                    $\"Python {version} download hash mismatch: expected {hashSha256}, actual {actualHash}\"\n                );\n            }\n\n            // Extract\n            Logger.Info($\"Extracting Python {version} Zip: {downloadPath} to {pythonDir}\");\n            if (pythonDir.Exists)\n            {\n                await pythonDir.DeleteAsync(true);\n            }\n            progress?.Report(new ProgressReport(0, $\"Installing Python {version}\", isIndeterminate: true));\n            await ArchiveHelper.Extract7ZAuto(downloadPath, pythonDir);\n\n            // For Unix, move the inner 'python' folder up to the root PythonDir\n            if (Compat.IsUnix)\n            {\n                var innerPythonDir = pythonDir.JoinDir(\"python\");\n                if (!innerPythonDir.Exists)\n                {\n                    throw new Exception(\n                        $\"Python {version} download did not contain expected inner 'python' folder: {innerPythonDir}\"\n                    );\n                }\n\n                foreach (var folder in Directory.EnumerateDirectories(innerPythonDir))\n                {\n                    var folderName = Path.GetFileName(folder);\n                    var dest = Path.Combine(pythonDir, folderName);\n                    Directory.Move(folder, dest);\n                }\n                Directory.Delete(innerPythonDir);\n            }\n        }\n        finally\n        {\n            // Cleanup download file\n            if (File.Exists(downloadPath))\n            {\n                File.Delete(downloadPath);\n            }\n        }\n\n        // Initialize pyrunner and install virtualenv\n        await pyRunner.Initialize();\n        await pyRunner.SwitchToInstallation(version);\n        await pyRunner.InstallPackage(\"virtualenv\", version);\n\n        progress?.Report(new ProgressReport(1, $\"Installing Python {version}\", isIndeterminate: false));\n    }\n\n    public Task<ProcessResult> GetGitOutput(ProcessArgs args, string? workingDirectory = null)\n    {\n        return ProcessRunner.RunBashCommand(\n            args.Prepend(\"git\"),\n            workingDirectory ?? \"\",\n            new Dictionary<string, string>\n            {\n                // Set UTF-8 locale to handle Unicode characters in paths\n                { \"LC_ALL\", \"C.UTF-8\" },\n                { \"LANG\", \"C.UTF-8\" },\n            }\n        );\n    }\n\n    private async Task<string> RunNode(\n        ProcessArgs args,\n        string? workingDirectory = null,\n        IReadOnlyDictionary<string, string>? envVars = null\n    )\n    {\n        var nodePath = Path.Combine(NodeDir, \"bin\", \"node\");\n        var result = await ProcessRunner\n            .GetProcessResultAsync(nodePath, args, workingDirectory, envVars)\n            .ConfigureAwait(false);\n\n        result.EnsureSuccessExitCode();\n        return result.StandardOutput ?? result.StandardError ?? string.Empty;\n    }\n\n    [Localizable(false)]\n    [SupportedOSPlatform(\"Linux\")]\n    [SupportedOSPlatform(\"macOS\")]\n    public async Task RunNpm(\n        ProcessArgs args,\n        string? workingDirectory = null,\n        Action<ProcessOutput>? onProcessOutput = null,\n        IReadOnlyDictionary<string, string>? envVars = null\n    )\n    {\n        var process = ProcessRunner.StartAnsiProcess(\n            NpmPath,\n            args,\n            workingDirectory,\n            onProcessOutput,\n            envVars\n        );\n\n        await process.WaitForExitAsync();\n\n        if (process.ExitCode == 0)\n            return;\n\n        var stdOut = await process.StandardOutput.ReadToEndAsync();\n        var stdErr = await process.StandardError.ReadToEndAsync();\n\n        Logger.Error(\n            \"RunNpm with args [{Args}] failed with exit code \" + \"{ExitCode}:\\n{StdOut}\\n{StdErr}\",\n            args,\n            process.ExitCode,\n            stdOut,\n            stdErr\n        );\n\n        throw new ProcessException(\n            $\"RunNpm with args [{args}] failed with exit code\" + $\" {process.ExitCode}:\\n{stdOut}\\n{stdErr}\"\n        );\n    }\n\n    public AnsiProcess RunNpmDetached(\n        ProcessArgs args,\n        string? workingDirectory = null,\n        Action<ProcessOutput>? onProcessOutput = null,\n        IReadOnlyDictionary<string, string>? envVars = null\n    )\n    {\n        return ProcessRunner.StartAnsiProcess(\n            NpmPath,\n            args,\n            workingDirectory,\n            onProcessOutput,\n            envVars ?? new Dictionary<string, string>()\n        );\n    }\n\n    [SupportedOSPlatform(\"Linux\")]\n    [SupportedOSPlatform(\"macOS\")]\n    public async Task<Process> RunDotnet(\n        ProcessArgs args,\n        string? workingDirectory = null,\n        Action<ProcessOutput>? onProcessOutput = null,\n        IReadOnlyDictionary<string, string>? envVars = null,\n        bool waitForExit = true\n    )\n    {\n        var process = ProcessRunner.StartAnsiProcess(\n            DotnetPath,\n            args,\n            workingDirectory,\n            onProcessOutput,\n            envVars\n        );\n\n        if (!waitForExit)\n            return process;\n\n        await process.WaitForExitAsync();\n\n        if (process.ExitCode == 0)\n            return process;\n\n        Logger.Error(\n            \"dotnet8 with args [{Args}] failed with exit code \" + \"{ExitCode}:\\n{StdOut}\\n{StdErr}\",\n            args,\n            process.ExitCode,\n            process.StandardOutput,\n            process.StandardError\n        );\n\n        throw new ProcessException(\n            $\"dotnet8 with args [{args}] failed with exit code\"\n                + $\" {process.ExitCode}:\\n{process.StandardOutput}\\n{process.StandardError}\"\n        );\n    }\n\n    [SupportedOSPlatform(\"Linux\")]\n    [SupportedOSPlatform(\"macOS\")]\n    public async Task InstallNodeIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        // NOTE TO FUTURE DEVS: if this is causing merge conflicts with dev, just nuke it we don't need anymore\n        if (NodeDir.Exists)\n        {\n            try\n            {\n                var result = await RunNode(\"-v\");\n                if (result.Contains(\"20.19.3\"))\n                {\n                    Logger.Debug(\"Node.js already installed at {NodeExistsPath}\", NodeDir);\n                    return;\n                }\n            }\n            catch (Exception)\n            {\n                // ignored\n            }\n\n            Logger.Warn(\"Node.js version mismatch, reinstalling...\");\n            await NodeDir.DeleteAsync(true);\n        }\n\n        Logger.Info(\"Downloading node\");\n\n        var downloadUrl = Compat.IsMacOS\n            ? \"https://nodejs.org/dist/v20.19.3/node-v20.19.3-darwin-arm64.tar.gz\"\n            : \"https://nodejs.org/dist/v20.19.3/node-v20.19.3-linux-x64.tar.gz\";\n\n        var nodeDownloadPath = AssetsDir.JoinFile(Path.GetFileName(downloadUrl));\n\n        await downloadService.DownloadToFileAsync(downloadUrl, nodeDownloadPath, progress: progress);\n\n        Logger.Info(\"Installing node\");\n        progress?.Report(\n            new ProgressReport(\n                progress: 0.5f,\n                isIndeterminate: true,\n                type: ProgressType.Generic,\n                message: \"Installing prerequisites...\"\n            )\n        );\n\n        // unzip\n        await ArchiveHelper.Extract7ZAuto(nodeDownloadPath, AssetsDir);\n\n        var nodeDir = Compat.IsMacOS\n            ? AssetsDir.JoinDir(\"node-v20.19.3-darwin-arm64\")\n            : AssetsDir.JoinDir(\"node-v20.19.3-linux-x64\");\n        Directory.Move(nodeDir, NodeDir);\n\n        progress?.Report(\n            new ProgressReport(progress: 1f, message: \"Node install complete\", type: ProgressType.Generic)\n        );\n\n        File.Delete(nodeDownloadPath);\n    }\n\n    [SupportedOSPlatform(\"Linux\")]\n    [SupportedOSPlatform(\"macOS\")]\n    public async Task InstallVirtualenvIfNecessary(\n        PyVersion version,\n        IProgress<ProgressReport>? progress = null\n    )\n    {\n        // Check if pip and venv are installed for this version\n        var pipInstalled = File.Exists(Path.Combine(GetPythonDir(version), \"bin\", \"pip3\"));\n        var venvInstalled = Directory.Exists(\n            Path.Combine(GetPythonDir(version), \"Scripts\", \"virtualenv\" + Compat.ExeExtension)\n        );\n\n        if (!pipInstalled || !venvInstalled)\n        {\n            progress?.Report(\n                new ProgressReport(\n                    -1f,\n                    $\"Installing Python {version} prerequisites...\",\n                    isIndeterminate: true\n                )\n            );\n\n            await pyRunner.Initialize();\n            await pyRunner.SwitchToInstallation(version);\n\n            if (!pipInstalled)\n            {\n                await pyRunner.SetupPip(version).ConfigureAwait(false);\n            }\n\n            if (!venvInstalled)\n            {\n                await pyRunner.InstallPackage(\"virtualenv\", version).ConfigureAwait(false);\n            }\n        }\n    }\n\n    public async Task InstallUvIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        if (IsUvInstalled)\n        {\n            var version = await GetInstalledUvVersionAsync();\n            if (version.Contains(ExpectedUvVersion))\n            {\n                Logger.Debug(\"UV already installed at {UvExePath}\", UvExePath);\n                return;\n            }\n\n            Logger.Warn(\n                \"UV version mismatch at {UvExePath}. Expected: {ExpectedVersion}, Found: {FoundVersion}\",\n                UvExePath,\n                ExpectedUvVersion,\n                version\n            );\n        }\n\n        Logger.Info(\"UV not found at {UvExePath}, downloading...\", UvExePath);\n\n        Directory.CreateDirectory(AssetsDir);\n\n        var downloadUrl = Compat.IsMacOS ? UvMacDownloadUrl : UvLinuxDownloadUrl;\n\n        // Download UV archive\n        await downloadService.DownloadToFileAsync(downloadUrl, UvDownloadPath, progress: progress);\n\n        progress?.Report(\n            new ProgressReport(\n                progress: 0.5f,\n                isIndeterminate: true,\n                type: ProgressType.Generic,\n                message: \"Installing UV package manager...\"\n            )\n        );\n\n        // Create extraction directory\n        Directory.CreateDirectory(UvExtractPath);\n\n        // Extract UV\n        await ArchiveHelper.Extract7ZTar(UvDownloadPath, UvExtractPath);\n\n        // On Mac/Linux, the extraction might create a platform-specific folder\n        // (e.g., uv-aarch64-apple-darwin or uv-x86_64-unknown-linux-gnu)\n        // We need to move both the uv and uvx executables from that folder to the expected location\n\n        // Find platform-specific directory\n        var platformSpecificDir = Directory\n            .GetDirectories(UvExtractPath)\n            .FirstOrDefault(dir => Path.GetFileName(dir).StartsWith(\"uv-\"));\n\n        if (platformSpecificDir != null)\n        {\n            Logger.Debug(\"Found platform-specific UV directory: {PlatformDir}\", platformSpecificDir);\n\n            // List of files to move: uv and uvx\n            var filesToMove = new Dictionary<string, string>\n            {\n                { Path.Combine(platformSpecificDir, \"uv\"), Path.Combine(UvExtractPath, \"uv\") },\n                { Path.Combine(platformSpecificDir, \"uvx\"), Path.Combine(UvExtractPath, \"uvx\") },\n            };\n\n            var anyFilesMoved = false;\n\n            // Move each file if it exists\n            foreach (var entry in filesToMove)\n            {\n                var sourcePath = entry.Key;\n                var destPath = entry.Value;\n\n                if (File.Exists(sourcePath))\n                {\n                    Logger.Debug(\"Moving file from {Source} to {Destination}\", sourcePath, destPath);\n\n                    // Ensure the destination doesn't exist before moving\n                    if (File.Exists(destPath))\n                    {\n                        File.Delete(destPath);\n                    }\n\n                    File.Move(sourcePath, destPath);\n                    anyFilesMoved = true;\n\n                    // Make the executable file executable\n                    var process = ProcessRunner.StartAnsiProcess(\"chmod\", [\"+x\", destPath]);\n                    await process.WaitForExitAsync();\n                }\n            }\n\n            // Delete the now-empty platform directory after moving all files\n            if (anyFilesMoved)\n            {\n                Directory.Delete(platformSpecificDir, true);\n            }\n        }\n        else if (File.Exists(UvExePath))\n        {\n            // For Windows or if we already have the file in the right place, just make it executable\n            var process = ProcessRunner.StartAnsiProcess(\"chmod\", [\"+x\", UvExePath]);\n            await process.WaitForExitAsync();\n        }\n\n        progress?.Report(\n            new ProgressReport(progress: 1f, message: \"UV installation complete\", type: ProgressType.Generic)\n        );\n\n        // Clean up download\n        File.Delete(UvDownloadPath);\n    }\n\n    public string? GetGfxArchFromAmdGpuName(GpuInfo? gpu = null)\n    {\n        gpu ??=\n            settingsManager.Settings.PreferredGpu\n            ?? HardwareHelper.IterGpuInfo().FirstOrDefault(x => x is { Name: not null, IsAmd: true });\n\n        if (gpu?.Name is null || !gpu.IsAmd)\n            return null;\n\n        // Normalize for safer substring checks (handles RX7800 vs RX 7800, etc.)\n        var name = gpu.Name;\n        var nameNoSpaces = name.Replace(\" \", \"\", StringComparison.Ordinal);\n\n        return gpu.GetAmdGfxArch();\n    }\n\n    private async Task DownloadAndExtractPrerequisite(\n        IProgress<ProgressReport>? progress,\n        string downloadUrl,\n        string extractPath\n    )\n    {\n        Logger.Info($\"Downloading {downloadUrl}\");\n\n        var downloadPath = AssetsDir.JoinFile(Path.GetFileName(downloadUrl));\n\n        await downloadService.DownloadToFileAsync(downloadUrl, downloadPath, progress: progress);\n\n        Logger.Info(\"Installing prereq\");\n        progress?.Report(\n            new ProgressReport(\n                progress: 0.5f,\n                isIndeterminate: true,\n                type: ProgressType.Generic,\n                message: \"Installing prerequisites...\"\n            )\n        );\n\n        Directory.CreateDirectory(extractPath);\n\n        // unzip\n        await ArchiveHelper.Extract7ZAuto(downloadPath, extractPath);\n\n        progress?.Report(\n            new ProgressReport(progress: 1f, message: \"Node install complete\", type: ProgressType.Generic)\n        );\n\n        File.Delete(downloadPath);\n    }\n\n    private async Task<string> GetInstalledUvVersionAsync()\n    {\n        try\n        {\n            var processResult = await ProcessRunner.GetProcessResultAsync(UvExePath, \"--version\");\n            return processResult.StandardOutput ?? processResult.StandardError ?? string.Empty;\n        }\n        catch (Exception e)\n        {\n            Logger.Warn(e, \"Failed to get UV version from {UvExePath}\", UvExePath);\n            return string.Empty;\n        }\n    }\n\n    private async Task<bool> EnsurePythonVersion(PyVersion pyVersion)\n    {\n        var result = await pyInstallationManager.GetInstallationAsync(pyVersion);\n        return result.Exists();\n    }\n\n    [UnsupportedOSPlatform(\"Linux\")]\n    [UnsupportedOSPlatform(\"macOS\")]\n    public Task InstallTkinterIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        throw new PlatformNotSupportedException();\n    }\n\n    [UnsupportedOSPlatform(\"Linux\")]\n    [UnsupportedOSPlatform(\"macOS\")]\n    public Task InstallVcRedistIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        throw new PlatformNotSupportedException();\n    }\n\n    [UnsupportedOSPlatform(\"Linux\")]\n    [UnsupportedOSPlatform(\"macOS\")]\n    public Task<bool> FixGitLongPaths()\n    {\n        throw new PlatformNotSupportedException();\n    }\n\n    [UnsupportedOSPlatform(\"Linux\")]\n    [UnsupportedOSPlatform(\"macOS\")]\n    public Task AddMissingLibsToVenv(\n        DirectoryPath installedPackagePath,\n        PyBaseInstall baseInstall,\n        IProgress<ProgressReport>? progress = null\n    )\n    {\n        throw new PlatformNotSupportedException();\n    }\n\n    [UnsupportedOSPlatform(\"Linux\")]\n    [UnsupportedOSPlatform(\"macOS\")]\n    public Task InstallTkinterIfNecessary(PyVersion version, IProgress<ProgressReport>? progress = null)\n    {\n        throw new PlatformNotSupportedException();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/UriHandler.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Runtime.Versioning;\nusing System.Text.Json;\nusing System.Threading.Tasks;\nusing Avalonia.Controls.ApplicationLifetimes;\nusing Avalonia.Platform;\nusing MessagePipe;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Win32;\nusing StabilityMatrix.Core.Helper;\nusing URIScheme;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\n/// <summary>\n/// Custom URI scheme handler for the stabilitymatrix:// protocol\n/// </summary>\n/// <remarks>Need to call <see cref=\"RegisterUriScheme\"/> on App startup</remarks>\npublic class UriHandler\n{\n    public const string IpcKeySend = \"uri_handler_send\";\n\n    public string Scheme { get; }\n    public string Description { get; }\n\n    public UriHandler(string scheme, string description)\n    {\n        Scheme = scheme;\n        Description = description;\n    }\n\n    /// <summary>\n    /// Send a received Uri over MessagePipe and exits\n    /// </summary>\n    /// <param name=\"uri\"></param>\n    [DoesNotReturn]\n    public void SendAndExit(Uri uri)\n    {\n        var services = new ServiceCollection();\n        services.AddMessagePipe().AddNamedPipeInterprocess(\"StabilityMatrix\");\n\n        var provider = services.BuildServiceProvider();\n        var publisher = provider.GetRequiredService<IDistributedPublisher<string, Uri>>();\n\n        var sendTask = Task.Run(async () => await publisher.PublishAsync(IpcKeySend, uri));\n        sendTask.Wait();\n\n        var info = JsonSerializer.Serialize(new Dictionary<string, Uri> { [IpcKeySend] = uri });\n\n        Debug.WriteLine(info);\n        Console.WriteLine(info);\n\n        Environment.Exit(0);\n    }\n\n    public void RegisterUriScheme()\n    {\n        if (Compat.IsWindows)\n        {\n            RegisterUriSchemeWin();\n        }\n        else if (Compat.IsLinux)\n        {\n            // Try to register on linux but ignore errors\n            // Library does not support some distros\n            try\n            {\n                RegisterUriSchemeLinux();\n            }\n            catch (Exception e)\n            {\n                Debug.WriteLine(e);\n                Console.WriteLine(e);\n            }\n        }\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    private void RegisterUriSchemeWin()\n    {\n        using var key = Registry.CurrentUser.CreateSubKey(@$\"SOFTWARE\\Classes\\{Scheme}\", true);\n\n        key.SetValue(\"\", \"URL:\" + Description);\n        key.SetValue(null, Description);\n        key.SetValue(\"URL Protocol\", \"\");\n\n        using (var defaultIcon = key.CreateSubKey(\"DefaultIcon\"))\n        {\n            defaultIcon.SetValue(\"\", Compat.AppCurrentPath.FullPath + \",1\");\n        }\n\n        using (var commandKey = key.CreateSubKey(@\"shell\\open\\command\"))\n        {\n            commandKey.SetValue(\"\", \"\\\"\" + Compat.AppCurrentPath.FullPath + \"\\\" --uri \\\"%1\\\"\");\n        }\n    }\n\n    [SupportedOSPlatform(\"linux\")]\n    private void RegisterUriSchemeLinux()\n    {\n        var service = URISchemeServiceFactory.GetURISchemeSerivce(\n            Scheme,\n            Description,\n            Compat.AppCurrentPath.FullPath\n        );\n        service.Set();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/ViewModelSerializer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\npublic static class ViewModelSerializer\n{\n    public static Dictionary<string, Type> GetDerivedTypes(Type baseType)\n    {\n        return GetJsonDerivedTypeAttributes(baseType).ToDictionary(x => x.typeDiscriminator, x => x.subType);\n    }\n\n    public static IEnumerable<(Type subType, string typeDiscriminator)> GetJsonDerivedTypeAttributes(Type type)\n    {\n        return type.GetCustomAttributes<JsonDerivedTypeAttribute>()\n            .Select(x => (x.DerivedType, x.TypeDiscriminator as string ?? x.DerivedType.Name));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/Win32ClipboardFormat.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n[SuppressMessage(\"ReSharper\", \"IdentifierTypo\")]\ninternal enum Win32ClipboardFormat : uint\n{\n    CF_TEXT = 1,\n    CF_BITMAP = 2,\n    CF_SYLK = 4,\n    CF_DIF = 5,\n    CF_TIFF = 6,\n    CF_OEMTEXT = 7,\n    CF_DIB = 8,\n    CF_PALETTE = 9,\n    CF_PENDATA = 10,\n    CF_RIFF = 11,\n    CF_WAVE = 12,\n    CF_UNICODETEXT = 13\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/WindowsClipboard.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.IO;\nusing System.Runtime.InteropServices;\nusing System.Runtime.Versioning;\nusing System.Threading.Tasks;\nusing Avalonia.Media.Imaging;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\n[SupportedOSPlatform(\"windows\")]\npublic static class WindowsClipboard\n{\n    public static async Task SetBitmapAsync(Bitmap bitmap)\n    {\n        await Task.Run(() => SetBitmap(bitmap));\n    }\n\n    public static void SetBitmap(Bitmap bitmap)\n    {\n        if (bitmap == null)\n            throw new ArgumentNullException(nameof(bitmap));\n\n        // Convert from Avalonia Bitmap to System Bitmap\n        var memoryStream = new MemoryStream();\n        bitmap.Save(memoryStream); // this returns a png from Skia (we could save/load it from the system bitmap to convert it to a bmp first, but this seems to work well already)\n\n        var systemBitmap = new System.Drawing.Bitmap(memoryStream);\n\n        var hBitmap = systemBitmap.GetHbitmap();\n\n        var screenDC = GetDC(IntPtr.Zero);\n\n        var sourceDC = CreateCompatibleDC(screenDC);\n        var sourceBitmapSelection = SelectObject(sourceDC, hBitmap);\n\n        var destDC = CreateCompatibleDC(screenDC);\n        var compatibleBitmap = CreateCompatibleBitmap(screenDC, systemBitmap.Width, systemBitmap.Height);\n\n        var destinationBitmapSelection = SelectObject(destDC, compatibleBitmap);\n\n        BitBlt(destDC, 0, 0, systemBitmap.Width, systemBitmap.Height, sourceDC, 0, 0, 0x00CC0020); // SRCCOPY\n\n        try\n        {\n            OpenClipboard(IntPtr.Zero);\n\n            EmptyClipboard();\n\n            var result = SetClipboardData((uint)Win32ClipboardFormat.CF_BITMAP, compatibleBitmap);\n\n            if (result == IntPtr.Zero)\n            {\n                var errno = Marshal.GetLastWin32Error();\n                throw new Win32Exception(errno, $\"SetClipboardData failed\");\n            }\n        }\n        finally\n        {\n            CloseClipboard();\n        }\n    }\n\n    [DllImport(\"user32.dll\", SetLastError = true)]\n    static extern bool OpenClipboard(IntPtr hWndNewOwner);\n\n    [DllImport(\"user32.dll\", SetLastError = true)]\n    private static extern bool CloseClipboard();\n\n    [DllImport(\"user32.dll\")]\n    static extern IntPtr SetClipboardData(uint uFormat, IntPtr hMem);\n\n    [DllImport(\"user32.dll\", SetLastError = true)]\n    private static extern bool EmptyClipboard();\n\n    [DllImport(\"user32.dll\", ExactSpelling = true)]\n    public static extern IntPtr GetDC(IntPtr hWnd);\n\n    [DllImport(\"gdi32.dll\", ExactSpelling = true)]\n    public static extern IntPtr CreateCompatibleDC(IntPtr hDC);\n\n    [DllImport(\"gdi32.dll\", ExactSpelling = true)]\n    public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int cx, int cy);\n\n    [DllImport(\"gdi32.dll\", SetLastError = true, ExactSpelling = true)]\n    public static extern IntPtr SelectObject(IntPtr hdc, IntPtr h);\n\n    [DllImport(\"gdi32.dll\", SetLastError = true, ExactSpelling = true)]\n    public static extern bool BitBlt(\n        IntPtr hdc,\n        int x,\n        int y,\n        int cx,\n        int cy,\n        IntPtr hdcSrc,\n        int x1,\n        int y1,\n        uint rop\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/WindowsElevated.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Runtime.Versioning;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\n[SupportedOSPlatform(\"windows\")]\npublic static class WindowsElevated\n{\n    /// <summary>\n    /// Move a file from source to target using elevated privileges.\n    /// </summary>\n    public static async Task<int> MoveFiles(params (string sourcePath, string targetPath)[] moves)\n    {\n        // Combine into single command\n        var args = string.Join(\" & \", moves.Select(x => $\"move \\\"{x.sourcePath}\\\" \\\"{x.targetPath}\\\"\"));\n\n        using var process = new Process();\n        process.StartInfo.FileName = \"cmd.exe\";\n        process.StartInfo.Arguments = $\"/c {args}\";\n        process.StartInfo.UseShellExecute = true;\n        process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;\n        process.StartInfo.Verb = \"runas\";\n\n        process.Start();\n        await process.WaitForExitAsync().ConfigureAwait(false);\n\n        return process.ExitCode;\n    }\n\n    /// <summary>\n    /// Move a file or folder from source to target using elevated privileges.\n    /// </summary>\n    public static async Task<int> Robocopy(\n        DirectoryPath sourcePath,\n        DirectoryPath targetPath,\n        FilePath? targetFile = null\n    )\n    {\n        var targetStr = targetFile is null ? string.Empty : $\" \\\"{targetFile.Name}\\\"\";\n        var args = $\"\\\"{sourcePath.FullPath}\\\" \\\"{targetPath.FullPath}\\\"{targetStr} /E /MOVE /IS /IT\";\n\n        using var process = new Process();\n        process.StartInfo.FileName = \"Robocopy.exe\";\n        process.StartInfo.Arguments = args;\n        process.StartInfo.UseShellExecute = true;\n        process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;\n        process.StartInfo.Verb = \"runas\";\n\n        process.Start();\n        await process.WaitForExitAsync().ConfigureAwait(false);\n\n        return process.ExitCode;\n    }\n\n    /// <summary>\n    /// Set a registry key integer using elevated privileges.\n    /// </summary>\n    public static async Task<int> SetRegistryValue(string key, string valueName, int value)\n    {\n        using var process = new Process();\n        process.StartInfo.FileName = \"reg.exe\";\n        process.StartInfo.Arguments = $\"add \\\"{key}\\\" /v \\\"{valueName}\\\" /t REG_DWORD /d {value} /f\";\n        process.StartInfo.UseShellExecute = true;\n        process.StartInfo.CreateNoWindow = true;\n        process.StartInfo.Verb = \"runas\";\n\n        process.Start();\n        await process.WaitForExitAsync().ConfigureAwait(false);\n\n        return process.ExitCode;\n    }\n\n    /// <summary>\n    /// Set a registry key string using elevated privileges.\n    /// </summary>\n    public static async Task<int> SetRegistryValue(string key, string valueName, string value)\n    {\n        using var process = new Process();\n        process.StartInfo.FileName = \"reg.exe\";\n        process.StartInfo.Arguments = $\"add \\\"{key}\\\" /v \\\"{valueName}\\\" /t REG_SZ /d \\\"{value}\\\" /f\";\n        process.StartInfo.UseShellExecute = true;\n        process.StartInfo.CreateNoWindow = true;\n        process.StartInfo.Verb = \"runas\";\n\n        process.Start();\n        await process.WaitForExitAsync().ConfigureAwait(false);\n\n        return process.ExitCode;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Runtime.Versioning;\nusing Microsoft.Win32;\nusing NLog;\nusing Python.Runtime;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\n[SupportedOSPlatform(\"windows\")]\npublic class WindowsPrerequisiteHelper(\n    IDownloadService downloadService,\n    ISettingsManager settingsManager,\n    IPyRunner pyRunner,\n    IPyInstallationManager pyInstallationManager\n) : IPrerequisiteHelper\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private const string PortableGitDownloadUrl =\n        \"https://github.com/git-for-windows/git/releases/download/v2.52.0.windows.1/PortableGit-2.52.0-64-bit.7z.exe\";\n    private const string ExpectedGitVersion = \"2.52.0\";\n\n    private const string VcRedistDownloadUrl = \"https://aka.ms/vs/16/release/vc_redist.x64.exe\";\n\n    private const string TkinterDownloadUrl =\n        \"https://cdn.lykos.ai/tkinter-cpython-embedded-3.10.11-win-x64.zip\";\n\n    private const string NodeDownloadUrl = \"https://nodejs.org/dist/v20.19.3/node-v20.19.3-win-x64.zip\";\n\n    private const string Dotnet7DownloadUrl =\n        \"https://download.visualstudio.microsoft.com/download/pr/2133b143-9c4f-4daa-99b0-34fa6035d67b/193ede446d922eb833f1bfe0239be3fc/dotnet-sdk-7.0.405-win-x64.zip\";\n\n    private const string Dotnet8DownloadUrl =\n        \"https://download.visualstudio.microsoft.com/download/pr/6902745c-34bd-4d66-8e84-d5b61a17dfb7/e61732b00f7e144e162d7e6914291f16/dotnet-sdk-8.0.101-win-x64.zip\";\n\n    private const string CppBuildToolsUrl = \"https://aka.ms/vs/17/release/vs_BuildTools.exe\";\n\n    private const string HipSdkDownloadUrl =\n        \"https://download.amd.com/developer/eula/rocm-hub/AMD-Software-PRO-Edition-25.Q3-Win10-Win11-For-HIP.exe\";\n    private const string PythonLibsDownloadUrl = \"https://cdn.lykos.ai/python_libs_for_sage.zip\";\n\n    private const string UvWindowsDownloadUrl =\n        \"https://github.com/astral-sh/uv/releases/download/0.9.30/uv-x86_64-pc-windows-msvc.zip\";\n\n    private string HomeDir => settingsManager.LibraryDir;\n\n    private string VcRedistDownloadPath => Path.Combine(HomeDir, \"vcredist.x64.exe\");\n\n    private string AssetsDir => Path.Combine(HomeDir, \"Assets\");\n    private string SevenZipPath => Path.Combine(AssetsDir, \"7za.exe\");\n\n    private string GetPythonDownloadPath(PyVersion version) =>\n        Path.Combine(\n            AssetsDir,\n            version == PyInstallationManager.Python_3_10_11\n                ? \"python-3.10.11-embed-amd64.zip\"\n                : $\"python-{version}-amd64.tar.gz\"\n        );\n\n    private string GetPythonDir(PyVersion version) =>\n        version == PyInstallationManager.Python_3_10_11\n            ? Path.Combine(AssetsDir, \"Python310\")\n            : Path.Combine(AssetsDir, $\"Python{version.Major}{version.Minor}{version.Micro}\");\n\n    private string GetPythonDllPath(PyVersion version) =>\n        Path.Combine(GetPythonDir(version), $\"python{version.Major}{version.Minor}.dll\");\n\n    private string GetPythonLibraryZipPath(PyVersion version) =>\n        Path.Combine(GetPythonDir(version), $\"python{version.Major}{version.Minor}.zip\");\n\n    private string GetPipPath(PyVersion version) => Path.Combine(GetPythonDir(version), \"get-pip.pyc\");\n\n    // Temporary directory to extract venv to during python install\n    private string GetVenvTempDir(PyVersion version) => Path.Combine(GetPythonDir(version), \"venv\");\n\n    private string PortableGitInstallDir => Path.Combine(HomeDir, \"PortableGit\");\n    private string PortableGitDownloadPath => Path.Combine(HomeDir, \"PortableGit.7z.exe\");\n    private string GitExePath => Path.Combine(PortableGitInstallDir, \"bin\", \"git.exe\");\n    private string TkinterZipPath => Path.Combine(AssetsDir, \"tkinter.zip\");\n    private string TkinterExtractPath => Path.Combine(AssetsDir, \"Python310\");\n    private string TkinterExistsPath => Path.Combine(TkinterExtractPath, \"tkinter\");\n    private string NodeExistsPath => Path.Combine(AssetsDir, \"nodejs\", \"npm.cmd\");\n    private string NodeExePath => Path.Combine(AssetsDir, \"nodejs\", \"node.exe\");\n    private string NodeDownloadPath => Path.Combine(AssetsDir, \"nodejs.zip\");\n    private string Dotnet7DownloadPath => Path.Combine(AssetsDir, \"dotnet-sdk-7.0.405-win-x64.zip\");\n    private string Dotnet8DownloadPath => Path.Combine(AssetsDir, \"dotnet-sdk-8.0.101-win-x64.zip\");\n    private string DotnetExistsPath => Path.Combine(DotnetDir, \"dotnet.exe\");\n    private string Dotnet7SdkExistsPath => Path.Combine(DotnetDir, \"sdk\", \"7.0.405\");\n    private string Dotnet8SdkExistsPath => Path.Combine(DotnetDir, \"sdk\", \"8.0.101\");\n    private string VcBuildToolsDownloadPath => Path.Combine(AssetsDir, \"vs_BuildTools.exe\");\n\n    private string VcBuildToolsExistsPath =>\n        Path.Combine(\n            Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),\n            \"Microsoft Visual Studio\",\n            \"2022\",\n            \"BuildTools\"\n        );\n\n    private string HipSdkDownloadPath => Path.Combine(AssetsDir, \"AMD-HIP-SDK.exe\");\n\n    private string HipInstalledPath =>\n        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), \"AMD\", \"ROCm\", \"6.4\");\n\n    private string UvDownloadPath => Path.Combine(AssetsDir, \"uv.zip\");\n    private string UvExtractPath => Path.Combine(AssetsDir, \"uv\");\n    public string UvExePath => Path.Combine(UvExtractPath, \"uv.exe\");\n    public bool IsUvInstalled => File.Exists(UvExePath);\n    private string ExpectedUvVersion => \"0.9.30\";\n\n    public string GitBinPath => Path.Combine(PortableGitInstallDir, \"bin\");\n    public bool IsVcBuildToolsInstalled => Directory.Exists(VcBuildToolsExistsPath);\n    public bool IsHipSdkInstalled => Directory.Exists(HipInstalledPath);\n    public DirectoryPath DotnetDir => Path.Combine(AssetsDir, \"dotnet\");\n\n    // Check if a specific Python version is installed\n    public bool IsPythonVersionInstalled(PyVersion version) => File.Exists(GetPythonDllPath(version));\n\n    // Legacy property for compatibility\n    public bool IsPythonInstalled => IsPythonVersionInstalled(PyInstallationManager.DefaultVersion);\n\n    // Implement interface method - uses default Python version\n    public Task InstallPythonIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        return InstallPythonIfNecessary(PyInstallationManager.DefaultVersion, progress);\n    }\n\n    // Implement interface method - uses default Python version\n    public Task InstallTkinterIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        return InstallTkinterIfNecessary(PyInstallationManager.DefaultVersion, progress);\n    }\n\n    public async Task RunGit(\n        ProcessArgs args,\n        Action<ProcessOutput>? onProcessOutput = null,\n        string? workingDirectory = null\n    )\n    {\n        var process = ProcessRunner.StartAnsiProcess(\n            GitExePath,\n            args,\n            workingDirectory,\n            onProcessOutput,\n            environmentVariables: new Dictionary<string, string>\n            {\n                { \"PATH\", Compat.GetEnvPathWithExtensions(GitBinPath) },\n                { \"GIT_TERMINAL_PROMPT\", \"0\" },\n                // Set UTF-8 locale to handle Unicode characters in paths\n                // This helps Git load libcurl-4.dll when paths contain non-ASCII characters\n                { \"LC_ALL\", \"C.UTF-8\" },\n                { \"LANG\", \"C.UTF-8\" },\n            }\n        );\n        await process.WaitForExitAsync().ConfigureAwait(false);\n        if (process.ExitCode != 0)\n        {\n            throw new ProcessException($\"Git exited with code {process.ExitCode}\");\n        }\n    }\n\n    public Task<ProcessResult> GetGitOutput(ProcessArgs args, string? workingDirectory = null)\n    {\n        return ProcessRunner.GetProcessResultAsync(\n            GitExePath,\n            args,\n            workingDirectory: workingDirectory,\n            environmentVariables: new Dictionary<string, string>\n            {\n                { \"PATH\", Compat.GetEnvPathWithExtensions(GitBinPath) },\n                // Set UTF-8 locale to handle Unicode characters in paths\n                // This helps Git load libcurl-4.dll when paths contain non-ASCII characters\n                { \"LC_ALL\", \"C.UTF-8\" },\n                { \"LANG\", \"C.UTF-8\" },\n            }\n        );\n    }\n\n    private async Task<string> RunNode(\n        ProcessArgs args,\n        string? workingDirectory = null,\n        IReadOnlyDictionary<string, string>? envVars = null\n    )\n    {\n        var result = await ProcessRunner\n            .GetProcessResultAsync(NodeExePath, args, workingDirectory, envVars)\n            .ConfigureAwait(false);\n\n        result.EnsureSuccessExitCode();\n        return result.StandardOutput ?? result.StandardError ?? string.Empty;\n    }\n\n    public async Task RunNpm(\n        ProcessArgs args,\n        string? workingDirectory = null,\n        Action<ProcessOutput>? onProcessOutput = null,\n        IReadOnlyDictionary<string, string>? envVars = null\n    )\n    {\n        var result = await ProcessRunner\n            .GetProcessResultAsync(NodeExistsPath, args, workingDirectory, envVars)\n            .ConfigureAwait(false);\n\n        result.EnsureSuccessExitCode();\n        onProcessOutput?.Invoke(ProcessOutput.FromStdOutLine(result.StandardOutput));\n        onProcessOutput?.Invoke(ProcessOutput.FromStdErrLine(result.StandardError));\n    }\n\n    public AnsiProcess RunNpmDetached(\n        ProcessArgs args,\n        string? workingDirectory = null,\n        Action<ProcessOutput>? onProcessOutput = null,\n        IReadOnlyDictionary<string, string>? envVars = null\n    )\n    {\n        return ProcessRunner.StartAnsiProcess(\n            NodeExistsPath,\n            args,\n            workingDirectory,\n            onProcessOutput,\n            envVars ?? new Dictionary<string, string>()\n        );\n    }\n\n    public Task InstallPackageRequirements(\n        BasePackage package,\n        PyVersion? pyVersion = null,\n        IProgress<ProgressReport>? progress = null\n    ) => InstallPackageRequirements(package.Prerequisites.ToList(), pyVersion, progress);\n\n    public async Task InstallUvIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        if (IsUvInstalled)\n        {\n            var version = await GetInstalledUvVersionAsync();\n            if (version.Contains(ExpectedUvVersion))\n            {\n                Logger.Debug(\"UV already installed at {UvExePath}\", UvExePath);\n                return;\n            }\n\n            Logger.Warn(\n                \"UV version mismatch at {UvExePath}. Expected: {ExpectedVersion}, Found: {FoundVersion}\",\n                UvExePath,\n                ExpectedUvVersion,\n                version\n            );\n        }\n\n        Logger.Info(\"UV not found at {UvExePath}, downloading...\", UvExePath);\n\n        Directory.CreateDirectory(AssetsDir);\n\n        // Download UV zip\n        await downloadService.DownloadToFileAsync(UvWindowsDownloadUrl, UvDownloadPath, progress: progress);\n\n        progress?.Report(\n            new ProgressReport(\n                progress: 0.5f,\n                isIndeterminate: true,\n                type: ProgressType.Generic,\n                message: \"Installing UV package manager...\"\n            )\n        );\n\n        // Create extraction directory\n        Directory.CreateDirectory(UvExtractPath);\n\n        // Extract UV\n        await ArchiveHelper.Extract(UvDownloadPath, UvExtractPath, progress);\n\n        progress?.Report(\n            new ProgressReport(progress: 1f, message: \"UV installation complete\", type: ProgressType.Generic)\n        );\n\n        // Clean up download\n        File.Delete(UvDownloadPath);\n    }\n\n    public async Task InstallPackageRequirements(\n        List<PackagePrerequisite> prerequisites,\n        PyVersion? pyVersion = null,\n        IProgress<ProgressReport>? progress = null\n    )\n    {\n        await UnpackResourcesIfNecessary(progress);\n        await InstallUvIfNecessary(progress);\n\n        if (prerequisites.Contains(PackagePrerequisite.HipSdk))\n        {\n            await InstallHipSdkIfNecessary(progress);\n        }\n\n        if (prerequisites.Contains(PackagePrerequisite.Python310))\n        {\n            await InstallPythonIfNecessary(PyInstallationManager.Python_3_10_11, progress);\n            await InstallVirtualenvIfNecessary(PyInstallationManager.Python_3_10_11, progress);\n        }\n\n        if (pyVersion is not null)\n        {\n            if (!await EnsurePythonVersion(pyVersion.Value))\n            {\n                throw new MissingPrerequisiteException(\n                    @\"Python\",\n                    @$\"Python {pyVersion} was not found and/or failed to install. Please check the logs for more details.\"\n                );\n            }\n        }\n\n        if (prerequisites.Contains(PackagePrerequisite.Git))\n        {\n            await InstallGitIfNecessary(progress);\n        }\n\n        if (prerequisites.Contains(PackagePrerequisite.VcRedist))\n        {\n            await InstallVcRedistIfNecessary(progress);\n        }\n\n        if (prerequisites.Contains(PackagePrerequisite.Node))\n        {\n            await InstallNodeIfNecessary(progress);\n        }\n\n        if (prerequisites.Contains(PackagePrerequisite.Dotnet))\n        {\n            await InstallDotnetIfNecessary(progress);\n        }\n\n        if (prerequisites.Contains(PackagePrerequisite.Tkinter))\n        {\n            await InstallTkinterIfNecessary(PyInstallationManager.Python_3_10_11, progress);\n        }\n\n        if (prerequisites.Contains(PackagePrerequisite.VcBuildTools))\n        {\n            await InstallVcBuildToolsIfNecessary(progress);\n        }\n    }\n\n    public async Task InstallAllIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        await InstallVcRedistIfNecessary(progress);\n        await UnpackResourcesIfNecessary(progress);\n        await InstallPythonIfNecessary(PyInstallationManager.Python_3_10_11, progress);\n        await InstallGitIfNecessary(progress);\n        await InstallNodeIfNecessary(progress);\n        await InstallVcBuildToolsIfNecessary(progress);\n        await InstallHipSdkIfNecessary(progress);\n        await InstallUvIfNecessary(progress);\n    }\n\n    public async Task UnpackResourcesIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        // Array of (asset_uri, extract_to)\n        var assets = new[] { (Assets.SevenZipExecutable, AssetsDir), (Assets.SevenZipLicense, AssetsDir) };\n\n        progress?.Report(new ProgressReport(0, message: \"Unpacking resources\", isIndeterminate: true));\n\n        Directory.CreateDirectory(AssetsDir);\n        foreach (var (asset, extractDir) in assets)\n        {\n            await asset.ExtractToDir(extractDir);\n        }\n\n        progress?.Report(new ProgressReport(1, message: \"Unpacking resources\", isIndeterminate: false));\n    }\n\n    public async Task InstallPythonIfNecessary(PyVersion version, IProgress<ProgressReport>? progress = null)\n    {\n        var pythonDllPath = GetPythonDllPath(version);\n\n        if (File.Exists(pythonDllPath))\n        {\n            Logger.Debug(\"Python {Version} already installed at {PythonDllPath}\", version, pythonDllPath);\n            return;\n        }\n\n        Logger.Info(\"Python {Version} not found at {PythonDllPath}, downloading...\", version, pythonDllPath);\n\n        Directory.CreateDirectory(AssetsDir);\n\n        var pythonLibraryZipPath = GetPythonLibraryZipPath(version);\n\n        // Delete existing python zip if it exists\n        if (File.Exists(pythonLibraryZipPath))\n        {\n            File.Delete(pythonLibraryZipPath);\n        }\n\n        // Get the correct download URL for this Python version\n        RemoteResource? remote = null;\n        if (version == PyInstallationManager.Python_3_10_11)\n        {\n            remote = Assets.PythonDownloadUrl;\n        }\n        else\n        {\n            throw new ArgumentException($\"No download URL configured for Python {version}\");\n        }\n\n        var url = remote.Value.Url.ToString();\n        var pythonDownloadPath = GetPythonDownloadPath(version);\n\n        Logger.Info($\"Downloading Python {version} from {url} to {pythonDownloadPath}\");\n\n        // Cleanup to remove zip if download fails\n        try\n        {\n            // Download python zip\n            await downloadService.DownloadToFileAsync(url, pythonDownloadPath, progress: progress);\n\n            // Verify python hash\n            var downloadHash = await FileHash.GetSha256Async(pythonDownloadPath, progress);\n            if (downloadHash != remote.Value.HashSha256)\n            {\n                var fileExists = File.Exists(pythonDownloadPath);\n                var fileSize = new FileInfo(pythonDownloadPath).Length;\n                var msg =\n                    $\"Python download hash mismatch: {downloadHash} != {remote.Value.HashSha256} \"\n                    + $\"(file exists: {fileExists}, size: {fileSize})\";\n                throw new Exception(msg);\n            }\n\n            progress?.Report(\n                new ProgressReport(progress: 1f, message: $\"Python {version} download complete\")\n            );\n\n            progress?.Report(\n                new ProgressReport(-1, $\"Installing Python {version}...\", isIndeterminate: true)\n            );\n\n            // We also need 7z if it's not already unpacked\n            if (!File.Exists(SevenZipPath))\n            {\n                await Assets.SevenZipExecutable.ExtractToDir(AssetsDir);\n                await Assets.SevenZipLicense.ExtractToDir(AssetsDir);\n            }\n\n            var pythonDir = GetPythonDir(version);\n\n            // Delete existing python dir\n            if (Directory.Exists(pythonDir))\n            {\n                Directory.Delete(pythonDir, true);\n            }\n\n            // For Python 3.10.11, we need to handle embedded venv folder\n            if (version == PyInstallationManager.Python_3_10_11)\n            {\n                try\n                {\n                    // Extract embedded venv folder\n                    var venvTempDir = GetVenvTempDir(version);\n                    Directory.CreateDirectory(venvTempDir);\n                    foreach (var (resource, relativePath) in Assets.PyModuleVenv)\n                    {\n                        var path = Path.Combine(venvTempDir, relativePath);\n                        // Create missing directories\n                        var dir = Path.GetDirectoryName(path);\n                        if (dir != null)\n                        {\n                            Directory.CreateDirectory(dir);\n                        }\n\n                        await resource.ExtractTo(path);\n                    }\n                    // Add venv to python's library zip\n                    await ArchiveHelper.AddToArchive7Z(pythonLibraryZipPath, venvTempDir);\n                }\n                finally\n                {\n                    // Remove venv\n                    var venvTempDir = GetVenvTempDir(version);\n                    if (Directory.Exists(venvTempDir))\n                    {\n                        Directory.Delete(venvTempDir, true);\n                    }\n                }\n            }\n\n            // Unzip python\n            if (version == PyInstallationManager.Python_3_10_11)\n            {\n                await ArchiveHelper.Extract7Z(pythonDownloadPath, pythonDir);\n                // We need to uncomment the #import site line in python._pth for pip to work\n                var pythonPthPath = Path.Combine(pythonDir, $\"python{version.Major}{version.Minor}._pth\");\n                var pythonPthContent = await File.ReadAllTextAsync(pythonPthPath);\n                pythonPthContent = pythonPthContent.Replace(\"#import site\", \"import site\");\n                await File.WriteAllTextAsync(pythonPthPath, pythonPthContent);\n\n                // Install TKinter\n                await InstallTkinterIfNecessary(version, progress);\n            }\n            else\n            {\n                await ArchiveHelper.Extract7ZTar(pythonDownloadPath, pythonDir);\n                // it gets extracted into a folder named `python`, we need to move the contents to this pythonDir\n                var extractedDir = Path.Combine(pythonDir, \"python\");\n                if (Directory.Exists(extractedDir))\n                {\n                    foreach (var file in Directory.GetFiles(extractedDir))\n                    {\n                        var destFile = Path.Combine(pythonDir, Path.GetFileName(file));\n                        File.Move(file, destFile);\n                    }\n\n                    foreach (var dir in Directory.GetDirectories(extractedDir))\n                    {\n                        var destDir = Path.Combine(pythonDir, Path.GetFileName(dir));\n                        Directory.Move(dir, destDir);\n                    }\n\n                    Directory.Delete(extractedDir, true);\n                }\n            }\n\n            // Extract get-pip.pyc\n            await Assets.PyScriptGetPip.ExtractToDir(pythonDir);\n            progress?.Report(new ProgressReport(1f, $\"Python {version} install complete\"));\n        }\n        finally\n        {\n            // Always delete zip after download\n            if (File.Exists(pythonDownloadPath))\n            {\n                File.Delete(pythonDownloadPath);\n            }\n        }\n    }\n\n    public async Task InstallVirtualenvIfNecessary(\n        PyVersion version,\n        IProgress<ProgressReport>? progress = null\n    )\n    {\n        var installation = await pyInstallationManager.GetInstallationAsync(version);\n\n        // Check if pip and venv are installed for this version\n        if (!installation.PipInstalled || !installation.VenvInstalled)\n        {\n            progress?.Report(\n                new ProgressReport(\n                    -1f,\n                    $\"Installing Python {version} prerequisites...\",\n                    isIndeterminate: true\n                )\n            );\n\n            // Switch to this version if needed\n            if (PythonEngine.IsInitialized)\n            {\n                await pyRunner.SwitchToInstallation(version).ConfigureAwait(false);\n            }\n            else\n            {\n                // Initialize with this version\n                await pyRunner.Initialize().ConfigureAwait(false);\n                await pyRunner.SwitchToInstallation(version).ConfigureAwait(false);\n            }\n\n            if (!installation.PipInstalled)\n            {\n                await pyRunner.SetupPip(version).ConfigureAwait(false);\n            }\n\n            if (!installation.VenvInstalled)\n            {\n                await pyRunner.InstallPackage(\"virtualenv\", version).ConfigureAwait(false);\n            }\n        }\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    public async Task InstallTkinterIfNecessary(PyVersion version, IProgress<ProgressReport>? progress = null)\n    {\n        var pythonDir = GetPythonDir(version);\n        var tkinterPath = Path.Combine(pythonDir, \"tkinter\");\n\n        if (!Directory.Exists(tkinterPath))\n        {\n            Logger.Info($\"Downloading Tkinter for Python {version}\");\n            await downloadService.DownloadToFileAsync(TkinterDownloadUrl, TkinterZipPath, progress: progress);\n            progress?.Report(\n                new ProgressReport(\n                    progress: 1f,\n                    message: \"Tkinter download complete\",\n                    type: ProgressType.Download\n                )\n            );\n\n            await ArchiveHelper.Extract(TkinterZipPath, pythonDir, progress);\n\n            File.Delete(TkinterZipPath);\n        }\n\n        progress?.Report(\n            new ProgressReport(progress: 1f, message: \"Tkinter install complete\", type: ProgressType.Generic)\n        );\n    }\n\n    public async Task InstallGitIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        if (File.Exists(GitExePath))\n        {\n            var installedVersion = await GetInstalledGitVersionAsync();\n            if (installedVersion.Contains(ExpectedGitVersion))\n            {\n                Logger.Debug(\n                    \"Git {Version} already installed at {GitExePath}\",\n                    ExpectedGitVersion,\n                    GitExePath\n                );\n                return;\n            }\n\n            Logger.Info(\n                \"Git version mismatch. Installed: {Installed}, Expected: {Expected}. Upgrading...\",\n                installedVersion.Trim(),\n                ExpectedGitVersion\n            );\n\n            // Delete existing installation to upgrade\n            if (Directory.Exists(PortableGitInstallDir))\n            {\n                await new DirectoryPath(PortableGitInstallDir).DeleteAsync(true);\n            }\n        }\n\n        Logger.Info(\"Git not found or outdated at {GitExePath}, downloading...\", GitExePath);\n\n        // Download\n        if (!File.Exists(PortableGitDownloadPath))\n        {\n            await downloadService.DownloadToFileAsync(\n                PortableGitDownloadUrl,\n                PortableGitDownloadPath,\n                progress: progress\n            );\n            progress?.Report(new ProgressReport(progress: 1f, message: \"Git download complete\"));\n        }\n\n        await UnzipGit(progress);\n\n        await FixGitLongPaths();\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    public async Task<bool> FixGitLongPaths()\n    {\n        if (!Compat.IsWindows)\n            return false;\n\n        try\n        {\n            await RunGit([\"config\", \"--system\", \"core.longpaths\", \"true\"]);\n            return true;\n        }\n        catch (Exception e)\n        {\n            Logger.Error(e, \"Failed to set git longpaths\");\n        }\n\n        return false;\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    public async Task InstallVcRedistIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        var registry = Registry.LocalMachine;\n        var key = registry.OpenSubKey(@\"SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\X64\", false);\n        if (key != null)\n        {\n            var buildId = Convert.ToUInt32(key.GetValue(\"Bld\"));\n            if (buildId >= 30139)\n            {\n                return;\n            }\n        }\n\n        Logger.Info(\"Downloading VC Redist\");\n\n        await downloadService.DownloadToFileAsync(\n            VcRedistDownloadUrl,\n            VcRedistDownloadPath,\n            progress: progress\n        );\n        progress?.Report(\n            new ProgressReport(\n                progress: 1f,\n                message: \"Visual C++ download complete\",\n                type: ProgressType.Download\n            )\n        );\n\n        Logger.Info(\"Installing VC Redist\");\n        progress?.Report(\n            new ProgressReport(\n                progress: 0.5f,\n                isIndeterminate: true,\n                type: ProgressType.Generic,\n                message: \"Installing prerequisites...\"\n            )\n        );\n        var process = ProcessRunner.StartAnsiProcess(VcRedistDownloadPath, \"/install /quiet /norestart\");\n        await process.WaitForExitAsync();\n        progress?.Report(\n            new ProgressReport(\n                progress: 1f,\n                message: \"Visual C++ install complete\",\n                type: ProgressType.Generic\n            )\n        );\n\n        File.Delete(VcRedistDownloadPath);\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    public async Task InstallNodeIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        // NOTE TO FUTURE DEVS: if this is causing merge conflicts with dev, just nuke it we don't need anymore\n        var nodeFolder = new DirectoryPath(AssetsDir, \"nodejs\");\n        if (nodeFolder.Exists)\n        {\n            try\n            {\n                var result = await RunNode(\"-v\");\n                if (result.Contains(\"20.19.3\"))\n                {\n                    Logger.Debug(\"Node.js already installed at {NodeExistsPath}\", NodeExistsPath);\n                    return;\n                }\n            }\n            catch (Exception)\n            {\n                // ignored\n            }\n\n            Logger.Warn(\"Node.js version mismatch, reinstalling...\");\n            await nodeFolder.DeleteAsync(true);\n        }\n\n        await DownloadAndExtractPrerequisite(progress, NodeDownloadUrl, NodeDownloadPath, AssetsDir);\n\n        var extractedNodeDir = Path.Combine(AssetsDir, \"node-v20.19.3-win-x64\");\n        if (Directory.Exists(extractedNodeDir))\n        {\n            Directory.Move(extractedNodeDir, Path.Combine(AssetsDir, \"nodejs\"));\n        }\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    public async Task InstallDotnetIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        if (!Directory.Exists(Dotnet7SdkExistsPath))\n        {\n            await DownloadAndExtractPrerequisite(\n                progress,\n                Dotnet7DownloadUrl,\n                Dotnet7DownloadPath,\n                DotnetDir\n            );\n        }\n\n        if (!Directory.Exists(Dotnet8SdkExistsPath))\n        {\n            await DownloadAndExtractPrerequisite(\n                progress,\n                Dotnet8DownloadUrl,\n                Dotnet8DownloadPath,\n                DotnetDir\n            );\n        }\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    public async Task InstallVcBuildToolsIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        if (\n            Directory.Exists(VcBuildToolsExistsPath)\n            && Directory.EnumerateDirectories(VcBuildToolsExistsPath).Any()\n        )\n            return;\n\n        await downloadService.DownloadToFileAsync(\n            CppBuildToolsUrl,\n            VcBuildToolsDownloadPath,\n            progress: progress\n        );\n\n        Logger.Info(\"Installing VC Build Tools\");\n        progress?.Report(\n            new ProgressReport(\n                progress: 0.5f,\n                isIndeterminate: true,\n                type: ProgressType.Generic,\n                message: \"Installing prerequisites...\"\n            )\n        );\n\n        var process = ProcessRunner.StartAnsiProcess(\n            VcBuildToolsDownloadPath,\n            \"--quiet --wait \"\n                + \"--add Microsoft.VisualStudio.Workload.VCTools \"\n                + \"--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 \"\n                + \"--add Microsoft.VisualStudio.Component.VC.CMake.Project \"\n                + \"--add Microsoft.VisualStudio.Component.VC.Llvm.Clang \"\n                + \"--add Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Llvm.Clang \"\n                + \"--add Microsoft.VisualStudio.Component.Windows10SDK.18362 \"\n                + \"--add Microsoft.VisualStudio.Component.Windows10SDK.19041 \"\n                + \"--add Microsoft.VisualStudio.Component.Windows10SDK.20348 \"\n                + \"--add Microsoft.VisualStudio.Component.Windows11SDK.22000 \"\n                + \"--add Microsoft.VisualStudio.Component.Windows11SDK.22621 \"\n                + \"--add Microsoft.VisualStudio.Component.Windows11SDK.26100\",\n            outputDataReceived: output =>\n                progress?.Report(\n                    new ProgressReport(\n                        progress: 0.5f,\n                        isIndeterminate: true,\n                        type: ProgressType.Generic,\n                        message: output.ApcMessage?.Data ?? output.Text\n                    )\n                )\n        );\n        await process.WaitForExitAsync();\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    public async Task InstallHipSdkIfNecessary(IProgress<ProgressReport>? progress = null)\n    {\n        if (IsHipSdkInstalled)\n        {\n            await PatchHipSdkIfNecessary(progress);\n            return;\n        }\n\n        await downloadService.DownloadToFileAsync(HipSdkDownloadUrl, HipSdkDownloadPath, progress: progress);\n        Logger.Info(\"Downloaded & installing HIP SDK\");\n\n        progress?.Report(\n            new ProgressReport(\n                progress: 0.5f,\n                isIndeterminate: true,\n                type: ProgressType.Generic,\n                message: \"Installing HIP SDK, this may take a few minutes...\"\n            )\n        );\n\n        var info = new ProcessStartInfo\n        {\n            FileName = HipSdkDownloadPath,\n            Arguments = \"-install -log hip_install.log\",\n            UseShellExecute = true,\n            CreateNoWindow = true,\n            Verb = \"runas\",\n        };\n\n        if (Process.Start(info) is { } process)\n        {\n            await process.WaitForExitAsync();\n        }\n\n        await PatchHipSdkIfNecessary(progress);\n    }\n\n    public async Task<Process> RunDotnet(\n        ProcessArgs args,\n        string? workingDirectory = null,\n        Action<ProcessOutput>? onProcessOutput = null,\n        IReadOnlyDictionary<string, string>? envVars = null,\n        bool waitForExit = true\n    )\n    {\n        var process = ProcessRunner.StartAnsiProcess(\n            DotnetExistsPath,\n            args,\n            workingDirectory,\n            onProcessOutput,\n            envVars\n        );\n\n        if (!waitForExit)\n            return process;\n\n        await process.WaitForExitAsync();\n\n        if (process.ExitCode == 0)\n            return process;\n\n        Logger.Error(\n            \"dotnet8 with args [{Args}] failed with exit code \" + \"{ExitCode}:\\n{StdOut}\\n{StdErr}\",\n            args,\n            process.ExitCode,\n            process.StandardOutput,\n            process.StandardError\n        );\n\n        throw new ProcessException(\n            $\"dotnet8 with args [{args}] failed with exit code\"\n                + $\" {process.ExitCode}:\\n{process.StandardOutput}\\n{process.StandardError}\"\n        );\n    }\n\n    [SupportedOSPlatform(\"Windows\")]\n    public async Task AddMissingLibsToVenv(\n        DirectoryPath installedPackagePath,\n        PyBaseInstall baseInstall,\n        IProgress<ProgressReport>? progress = null\n    )\n    {\n        var venvLibsDir = installedPackagePath.JoinDir(\"venv\", \"libs\");\n        var venvIncludeDir = installedPackagePath.JoinDir(\"venv\", \"include\");\n        if (venvLibsDir.Exists && venvIncludeDir.Exists && venvLibsDir.JoinFile(\"python3.lib\").Exists)\n        {\n            Logger.Debug(\"Python libs already installed at {VenvLibsDir}\", venvLibsDir);\n            return;\n        }\n\n        var sourceLibsDir = new DirectoryPath(baseInstall.RootPath, \"libs\");\n        var sourceIncludeDir = new DirectoryPath(baseInstall.RootPath, \"include\");\n\n        var destLibsDir = installedPackagePath.JoinDir(\"venv\", \"libs\");\n        var destIncludeDir = installedPackagePath.JoinDir(\"venv\", \"include\");\n        var destIncludeScriptsDir = installedPackagePath.JoinDir(\"venv\", \"Scripts\", \"include\");\n\n        destLibsDir.Create();\n        destIncludeDir.Create();\n        destIncludeScriptsDir.Create();\n\n        // Copy libs\n        await sourceLibsDir.CopyToAsync(destLibsDir);\n        await sourceIncludeDir.CopyToAsync(destIncludeDir);\n        await sourceIncludeDir.CopyToAsync(destIncludeScriptsDir);\n\n        // var downloadPath = installedPackagePath.JoinFile(\"python_libs_for_sage.zip\");\n        // var venvDir = installedPackagePath.JoinDir(\"venv\");\n        // await downloadService\n        //     .DownloadToFileAsync(PythonLibsDownloadUrl, downloadPath, progress)\n        //     .ConfigureAwait(false);\n        //\n        // progress?.Report(\n        //     new ProgressReport(-1f, message: \"Extracting Python libraries\", isIndeterminate: true)\n        // );\n        // await ArchiveHelper.Extract7Z(downloadPath, venvDir, progress);\n        //\n        // var includeFolder = venvDir.JoinDir(\"include\");\n        // var scriptsIncludeFolder = venvDir.JoinDir(\"Scripts\").JoinDir(\"include\");\n        // await includeFolder.CopyToAsync(scriptsIncludeFolder);\n        //\n        // await downloadPath.DeleteAsync();\n    }\n\n    public string? GetGfxArchFromAmdGpuName(GpuInfo? gpu = null)\n    {\n        gpu ??=\n            settingsManager.Settings.PreferredGpu\n            ?? HardwareHelper.IterGpuInfo().FirstOrDefault(x => x is { Name: not null, IsAmd: true });\n\n        if (gpu?.Name is null || !gpu.IsAmd)\n            return null;\n\n        return gpu.GetAmdGfxArch();\n    }\n\n    private async Task DownloadAndExtractPrerequisite(\n        IProgress<ProgressReport>? progress,\n        string downloadUrl,\n        string downloadPath,\n        string extractPath\n    )\n    {\n        Logger.Info($\"Downloading {downloadUrl} to {downloadPath}\");\n        await downloadService.DownloadToFileAsync(downloadUrl, downloadPath, progress: progress);\n\n        Logger.Info(\"Extracting prerequisite\");\n        progress?.Report(\n            new ProgressReport(\n                progress: 0.5f,\n                isIndeterminate: true,\n                type: ProgressType.Generic,\n                message: \"Installing prerequisites...\"\n            )\n        );\n\n        Directory.CreateDirectory(extractPath);\n\n        // unzip\n        await ArchiveHelper.Extract(downloadPath, extractPath, progress);\n\n        progress?.Report(\n            new ProgressReport(\n                progress: 1f,\n                message: \"Prerequisite install complete\",\n                type: ProgressType.Generic\n            )\n        );\n\n        File.Delete(downloadPath);\n    }\n\n    private async Task UnzipGit(IProgress<ProgressReport>? progress = null)\n    {\n        if (progress == null)\n        {\n            await ArchiveHelper.Extract7Z(PortableGitDownloadPath, PortableGitInstallDir);\n        }\n        else\n        {\n            await ArchiveHelper.Extract7Z(PortableGitDownloadPath, PortableGitInstallDir, progress);\n        }\n\n        Logger.Info(\"Extracted Git\");\n\n        File.Delete(PortableGitDownloadPath);\n    }\n\n    private async Task PatchHipSdkIfNecessary(IProgress<ProgressReport>? progress)\n    {\n        var theGpu =\n            settingsManager.Settings.PreferredGpu\n            ?? HardwareHelper.IterGpuInfo().FirstOrDefault(x => x.Name != null && x.Name.Contains(\"AMD\"));\n\n        if (theGpu?.Name is null)\n            return;\n        var downloadUrl = GetDownloadUrlFromGpuName(theGpu.Name);\n        if (downloadUrl is null)\n            return;\n\n        progress?.Report(new ProgressReport(-1, \"Patching ROCm for your GPU\", isIndeterminate: true));\n\n        var rocmLibsDownloadPath = new FilePath(AssetsDir, \"rocmLibs.7z\");\n        await downloadService.DownloadToFileAsync(downloadUrl, rocmLibsDownloadPath, progress: progress);\n\n        var rocmLibsExtractPath = new DirectoryPath(AssetsDir, \"rocmLibs\");\n        await ArchiveHelper.Extract7Z(rocmLibsDownloadPath, rocmLibsExtractPath, progress);\n\n        var hipInstalledPath = new DirectoryPath(HipInstalledPath);\n\n        var zipFolderName = downloadUrl switch\n        {\n            _ when downloadUrl.Contains(\"gfx1103.for.hip\") => \"rocm gfx1103 for hip 6.4.2\",\n            _ when downloadUrl.Contains(\"gfx1034\") => \"rocm gfx1034.gfx1035.gfx1036 for hip 6.4.2\",\n            _ when downloadUrl.Contains(\"gfx1032\") => \"rocm gfx1032 for hip 6.4.2\",\n            _ when downloadUrl.Contains(\"gfx1031\") => \"rocm gfx1031 for hip 6.4.2\",\n            _ when downloadUrl.Contains(\"gfx1010\") => \"rocm gfx1010-xnack-gfx1012-xnack-.for.hip6.4.2\",\n            _ => null,\n        };\n\n        var librarySourceDir = rocmLibsExtractPath;\n        if (!string.IsNullOrWhiteSpace(zipFolderName))\n        {\n            librarySourceDir = librarySourceDir.JoinDir(zipFolderName);\n        }\n\n        librarySourceDir = librarySourceDir.JoinDir(\"library\");\n        var libraryDestDir = hipInstalledPath.JoinDir(\"bin\", \"rocblas\", \"library\");\n        await WindowsElevated.Robocopy(librarySourceDir, libraryDestDir);\n\n        var rocblasSource = rocmLibsExtractPath;\n        if (!string.IsNullOrWhiteSpace(zipFolderName))\n        {\n            rocblasSource = rocblasSource.JoinDir(zipFolderName);\n        }\n\n        var rocblasSourceFile = rocblasSource.JoinFile(\"rocblas.dll\");\n        var rocblasDest = hipInstalledPath.JoinDir(\"bin\").JoinFile(\"rocblas.dll\");\n        if (rocblasSourceFile.Exists)\n        {\n            await WindowsElevated.MoveFiles((rocblasSourceFile.FullPath, rocblasDest.FullPath));\n        }\n    }\n\n    private string? GetDownloadUrlFromGpuName(string name)\n    {\n        // not used anymore but good to know for gfx -> name reference\n        // gfx1201\n        // if (name.Contains(\"9060\") || name.Contains(\"9070\"))\n        // {\n        //     return \"https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.2.4/rocm.gfx1201.for.hip.skd.6.2.4-no-optimized.7z\";\n        // }\n\n        // not used anymore but good to know for gfx -> name reference\n        // gfx1150\n        // if (\n        //     name.Contains(\"8050S\")\n        //     || name.Contains(\"8060S\")\n        //     || name.Contains(\"880M\")\n        //     || name.Contains(\"890M\")\n        // )\n        // {\n        //     return \"https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.2.4/rocm.gfx1150.for.hip.skd.6.2.4.7z\";\n        // }\n\n        // gfx1103\n        if (name.Contains(\"740M\") || name.Contains(\"760M\") || name.Contains(\"780M\") || name.Contains(\"Z1\"))\n        {\n            return \"https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.4.2/rocm.gfx1103.for.hip.6.4.2.7z\";\n        }\n\n        // gfx1034, gfx1035, gfx1036\n        if (\n            name.Contains(\"6300\")\n            || name.Contains(\"6400\")\n            || name.Contains(\"6450\")\n            || name.Contains(\"6500\")\n            || name.Contains(\"6550\")\n            || name.Contains(\"660M\")\n            || name.Contains(\"680M\")\n            || name.Contains(\"Graphics 128SP\")\n        )\n        {\n            return \"https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.4.2/rocm.gfx1034.gfx1035.gfx1036.for.hip.6.4.2.7z\";\n        }\n\n        // gfx1032\n        if (\n            name.Contains(\"6700S\")\n            || name.Contains(\"6800S\")\n            || name.Contains(\"6600\")\n            || name.Contains(\"6650\")\n        )\n        {\n            return \"https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.4.2/rocm.gfx1032.for.hip.6.4.2.7z\";\n        }\n\n        // gfx1031\n        if (\n            name.Contains(\"6700\")\n            || name.Contains(\"6750\")\n            || name.Contains(\"6800M\")\n            || name.Contains(\"6850M\")\n        )\n        {\n            return \"https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.4.2/rocm.gfx1031.for.hip.6.4.2.7z\";\n        }\n\n        // gfx1010/1012\n        if (name.Contains(\"5700\") || name.Contains(\"5600\") || name.Contains(\"5300\") || name.Contains(\"5500\"))\n        {\n            return \"https://github.com/likelovewant/ROCmLibs-for-gfx1103-AMD780M-APU/releases/download/v0.6.4.2/rocm.gfx1010-xnack-gfx1012-xnack-.for.hip6.4.2.7z\";\n        }\n\n        return null;\n    }\n\n    private async Task<string> GetInstalledUvVersionAsync()\n    {\n        try\n        {\n            var processResult = await ProcessRunner.GetProcessResultAsync(UvExePath, \"--version\");\n            return processResult.StandardOutput ?? processResult.StandardError ?? string.Empty;\n        }\n        catch (Exception e)\n        {\n            Logger.Warn(e, \"Failed to get UV version from {UvExePath}\", UvExePath);\n            return string.Empty;\n        }\n    }\n\n    private async Task<string> GetInstalledGitVersionAsync()\n    {\n        try\n        {\n            var result = await GetGitOutput([\"--version\"]);\n            return result.StandardOutput ?? string.Empty;\n        }\n        catch (Exception e)\n        {\n            Logger.Warn(e, \"Failed to get Git version\");\n            return string.Empty;\n        }\n    }\n\n    private async Task<bool> EnsurePythonVersion(PyVersion pyVersion)\n    {\n        var result = await pyInstallationManager.GetInstallationAsync(pyVersion);\n        return result.Exists();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Helpers/WindowsShortcuts.cs",
    "content": "﻿using System;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Runtime.InteropServices;\nusing System.Runtime.InteropServices.ComTypes;\nusing System.Runtime.Versioning;\nusing System.Text;\n\nnamespace StabilityMatrix.Avalonia.Helpers;\n\n[SupportedOSPlatform(\"windows\")]\n[SuppressMessage(\"ReSharper\", \"IdentifierTypo\")]\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\npublic static class WindowsShortcuts\n{\n    public static void CreateShortcut(\n        string shortcutPath, \n        string targetPath,\n        string iconPath,\n        string description)\n    {\n        // ReSharper disable once SuspiciousTypeConversion.Global\n        var link = (IShellLink) new ShellLink();\n\n        // setup shortcut information\n        link.SetDescription(description);\n        link.SetPath(targetPath);\n        link.SetIconLocation(iconPath, 0);\n\n        // ReSharper disable once SuspiciousTypeConversion.Global\n        var file = (IPersistFile) link;\n        file.Save(shortcutPath, false);\n    }\n    \n    [ComImport]\n    [Guid(\"00021401-0000-0000-C000-000000000046\")]\n    private class ShellLink\n    {\n    }\n\n    [ComImport]\n    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]\n    [Guid(\"000214F9-0000-0000-C000-000000000046\")]\n    private interface IShellLink\n    {\n        void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out IntPtr pfd, int fFlags);\n        void GetIDList(out IntPtr ppidl);\n        void SetIDList(IntPtr pidl);\n        void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);\n        void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);\n        void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);\n        void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);\n        void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);\n        void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);\n        void GetHotkey(out short pwHotkey);\n        void SetHotkey(short wHotkey);\n        void GetShowCmd(out int piShowCmd);\n        void SetShowCmd(int iShowCmd);\n        void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);\n        void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);\n        void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);\n        void Resolve(IntPtr hwnd, int fFlags);\n        void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Cultures.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Globalization;\nusing System.Threading;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Settings;\n\nnamespace StabilityMatrix.Avalonia.Languages;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic static class Cultures\n{\n    public static CultureInfo Default { get; } = new(\"en-US\");\n\n    public static CultureInfo? Current => Resources.Culture;\n\n    public static NumberFormatInfo CurrentNumberFormat => Thread.CurrentThread.CurrentCulture.NumberFormat;\n\n    public static readonly Dictionary<string, CultureInfo> SupportedCulturesByCode = new()\n    {\n        [\"en-US\"] = Default,\n        [\"ja-JP\"] = new CultureInfo(\"ja-JP\"),\n        [\"zh-Hans\"] = new CultureInfo(\"zh-Hans\"),\n        [\"zh-Hant\"] = new CultureInfo(\"zh-Hant\"),\n        [\"it-IT\"] = new CultureInfo(\"it-IT\"),\n        [\"fr-FR\"] = new CultureInfo(\"fr-FR\"),\n        [\"es\"] = new CultureInfo(\"es\"),\n        [\"ru-RU\"] = new CultureInfo(\"ru-RU\"),\n        [\"tr-TR\"] = new CultureInfo(\"tr-TR\"),\n        [\"de\"] = new CultureInfo(\"de\"),\n        [\"pt-PT\"] = new CultureInfo(\"pt-PT\"),\n        [\"pt-BR\"] = new CultureInfo(\"pt-BR\"),\n        [\"ko-KR\"] = new CultureInfo(\"ko-KR\"),\n        [\"uk-UA\"] = new CultureInfo(\"uk-UA\"),\n        [\"cs-CZ\"] = new CultureInfo(\"cs-CZ\"),\n    };\n\n    public static IReadOnlyList<CultureInfo> SupportedCultures =>\n        SupportedCulturesByCode.Values.ToImmutableList();\n\n    public static CultureInfo GetSupportedCultureOrDefault(string? cultureCode)\n    {\n        if (cultureCode is null || !SupportedCulturesByCode.TryGetValue(cultureCode, out var culture))\n        {\n            return Default;\n        }\n\n        return culture;\n    }\n\n    public static void SetSupportedCultureOrDefault(string? cultureCode, NumberFormatInfo numberFormat)\n    {\n        if (!TrySetSupportedCulture(cultureCode, numberFormat))\n        {\n            TrySetSupportedCulture(Default, numberFormat);\n        }\n    }\n\n    public static void SetSupportedCultureOrDefault(string? cultureCode, NumberFormatMode numberFormatMode)\n    {\n        if (!TrySetSupportedCulture(cultureCode, numberFormatMode))\n        {\n            TrySetSupportedCulture(Default, numberFormatMode);\n        }\n    }\n\n    public static bool TrySetSupportedCulture(string? cultureCode)\n    {\n        if (cultureCode is null || !SupportedCulturesByCode.TryGetValue(cultureCode, out var culture))\n        {\n            return false;\n        }\n\n        if (Current?.Name != culture.Name)\n        {\n            Resources.Culture = culture;\n\n            Thread.CurrentThread.CurrentCulture = culture;\n            Thread.CurrentThread.CurrentUICulture = culture;\n\n            EventManager.Instance.OnCultureChanged(culture);\n        }\n\n        return true;\n    }\n\n    public static bool TrySetSupportedCulture(string? cultureCode, NumberFormatInfo numberFormat)\n    {\n        if (cultureCode is null || !SupportedCulturesByCode.TryGetValue(cultureCode, out var culture))\n        {\n            return false;\n        }\n\n        if (Current?.Name != culture.Name || CurrentNumberFormat != numberFormat)\n        {\n            Resources.Culture = culture;\n\n            var cultureInfo = GetCultureInfoWithNumberFormat(culture, numberFormat);\n            Thread.CurrentThread.CurrentCulture = cultureInfo;\n            Thread.CurrentThread.CurrentUICulture = cultureInfo;\n\n            EventManager.Instance.OnCultureChanged(culture);\n        }\n\n        return true;\n    }\n\n    public static bool TrySetSupportedCulture(string? cultureCode, NumberFormatMode numberFormatMode)\n    {\n        if (cultureCode is null || !SupportedCulturesByCode.TryGetValue(cultureCode, out var culture))\n        {\n            return false;\n        }\n\n        var numberFormat = numberFormatMode switch\n        {\n            NumberFormatMode.CurrentCulture => culture.NumberFormat,\n            NumberFormatMode.InvariantCulture => CultureInfo.InvariantCulture.NumberFormat,\n            _ => culture.NumberFormat,\n        };\n\n        if (Current?.Name != culture.Name || CurrentNumberFormat != numberFormat)\n        {\n            Resources.Culture = culture;\n\n            var cultureInfo = GetCultureInfoWithNumberFormat(culture, numberFormat);\n            Thread.CurrentThread.CurrentCulture = cultureInfo;\n            Thread.CurrentThread.CurrentUICulture = cultureInfo;\n\n            EventManager.Instance.OnCultureChanged(culture);\n        }\n\n        return true;\n    }\n\n    public static bool TrySetSupportedCulture(CultureInfo? cultureInfo)\n    {\n        return cultureInfo is not null && TrySetSupportedCulture(cultureInfo.Name);\n    }\n\n    public static bool TrySetSupportedCulture(CultureInfo? cultureInfo, NumberFormatInfo numberFormat)\n    {\n        return cultureInfo is not null && TrySetSupportedCulture(cultureInfo.Name, numberFormat);\n    }\n\n    public static bool TrySetSupportedCulture(CultureInfo? cultureInfo, NumberFormatMode numberFormatMode)\n    {\n        return cultureInfo is not null && TrySetSupportedCulture(cultureInfo.Name, numberFormatMode);\n    }\n\n    // ReSharper disable once SuggestBaseTypeForParameter\n    private static CultureInfo GetCultureInfoWithNumberFormat(\n        CultureInfo culture,\n        NumberFormatInfo numberFormat\n    )\n    {\n        ArgumentNullException.ThrowIfNull(culture);\n\n        var cultureInfo = (CultureInfo)culture.Clone();\n        cultureInfo.NumberFormat = numberFormat;\n        return cultureInfo;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace StabilityMatrix.Avalonia.Languages {\n    using System;\n    \n    \n    /// <summary>\n    ///   A strongly-typed resource class, for looking up localized strings, etc.\n    /// </summary>\n    // This class was auto-generated by the StronglyTypedResourceBuilder\n    // class via a tool like ResGen or Visual Studio.\n    // To add or remove a member, edit your .ResX file then rerun ResGen\n    // with the /str option, or rebuild your VS project.\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"4.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    public class Resources {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal Resources() {\n        }\n        \n        /// <summary>\n        ///   Returns the cached ResourceManager instance used by this class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        public static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"StabilityMatrix.Avalonia.Languages.Resources\", typeof(Resources).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   Overrides the current thread's CurrentUICulture property for all\n        ///   resource lookups using this strongly typed resource class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        public static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Add for All Users.\n        /// </summary>\n        public static string Action_AddForAllUsers {\n            get {\n                return ResourceManager.GetString(\"Action_AddForAllUsers\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Add for Current User.\n        /// </summary>\n        public static string Action_AddForCurrentUser {\n            get {\n                return ResourceManager.GetString(\"Action_AddForCurrentUser\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Add Package.\n        /// </summary>\n        public static string Action_AddPackage {\n            get {\n                return ResourceManager.GetString(\"Action_AddPackage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Cancel.\n        /// </summary>\n        public static string Action_Cancel {\n            get {\n                return ResourceManager.GetString(\"Action_Cancel\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Check for Updates.\n        /// </summary>\n        public static string Action_CheckForUpdates {\n            get {\n                return ResourceManager.GetString(\"Action_CheckForUpdates\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Check Version.\n        /// </summary>\n        public static string Action_CheckVersion {\n            get {\n                return ResourceManager.GetString(\"Action_CheckVersion\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Clear Selection.\n        /// </summary>\n        public static string Action_ClearSelection {\n            get {\n                return ResourceManager.GetString(\"Action_ClearSelection\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Close.\n        /// </summary>\n        public static string Action_Close {\n            get {\n                return ResourceManager.GetString(\"Action_Close\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Connect.\n        /// </summary>\n        public static string Action_Connect {\n            get {\n                return ResourceManager.GetString(\"Action_Connect\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Consolidate.\n        /// </summary>\n        public static string Action_Consolidate {\n            get {\n                return ResourceManager.GetString(\"Action_Consolidate\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Continue.\n        /// </summary>\n        public static string Action_Continue {\n            get {\n                return ResourceManager.GetString(\"Action_Continue\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Copy.\n        /// </summary>\n        public static string Action_Copy {\n            get {\n                return ResourceManager.GetString(\"Action_Copy\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Copy and Open.\n        /// </summary>\n        public static string Action_CopyAndOpen {\n            get {\n                return ResourceManager.GetString(\"Action_CopyAndOpen\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Copy as Bitmap.\n        /// </summary>\n        public static string Action_CopyAsBitmap {\n            get {\n                return ResourceManager.GetString(\"Action_CopyAsBitmap\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Copy Details.\n        /// </summary>\n        public static string Action_CopyDetails {\n            get {\n                return ResourceManager.GetString(\"Action_CopyDetails\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Copy Trigger Words.\n        /// </summary>\n        public static string Action_CopyTriggerWords {\n            get {\n                return ResourceManager.GetString(\"Action_CopyTriggerWords\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Delete.\n        /// </summary>\n        public static string Action_Delete {\n            get {\n                return ResourceManager.GetString(\"Action_Delete\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Disable.\n        /// </summary>\n        public static string Action_Disable {\n            get {\n                return ResourceManager.GetString(\"Action_Disable\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Disconnect.\n        /// </summary>\n        public static string Action_Disconnect {\n            get {\n                return ResourceManager.GetString(\"Action_Disconnect\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Downgrade.\n        /// </summary>\n        public static string Action_Downgrade {\n            get {\n                return ResourceManager.GetString(\"Action_Downgrade\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Download.\n        /// </summary>\n        public static string Action_Download {\n            get {\n                return ResourceManager.GetString(\"Action_Download\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Edit.\n        /// </summary>\n        public static string Action_Edit {\n            get {\n                return ResourceManager.GetString(\"Action_Edit\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Enable.\n        /// </summary>\n        public static string Action_Enable {\n            get {\n                return ResourceManager.GetString(\"Action_Enable\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Exit Application.\n        /// </summary>\n        public static string Action_ExitApplication {\n            get {\n                return ResourceManager.GetString(\"Action_ExitApplication\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Go to Settings.\n        /// </summary>\n        public static string Action_GoToSettings {\n            get {\n                return ResourceManager.GetString(\"Action_GoToSettings\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Hide.\n        /// </summary>\n        public static string Action_Hide {\n            get {\n                return ResourceManager.GetString(\"Action_Hide\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Import.\n        /// </summary>\n        public static string Action_Import {\n            get {\n                return ResourceManager.GetString(\"Action_Import\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Install.\n        /// </summary>\n        public static string Action_Install {\n            get {\n                return ResourceManager.GetString(\"Action_Install\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Install Now.\n        /// </summary>\n        public static string Action_InstallNow {\n            get {\n                return ResourceManager.GetString(\"Action_InstallNow\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Launch.\n        /// </summary>\n        public static string Action_Launch {\n            get {\n                return ResourceManager.GetString(\"Action_Launch\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Login.\n        /// </summary>\n        public static string Action_Login {\n            get {\n                return ResourceManager.GetString(\"Action_Login\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Maybe Later.\n        /// </summary>\n        public static string Action_MaybeLater {\n            get {\n                return ResourceManager.GetString(\"Action_MaybeLater\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Move to Trash.\n        /// </summary>\n        public static string Action_MoveToTrash {\n            get {\n                return ResourceManager.GetString(\"Action_MoveToTrash\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to New.\n        /// </summary>\n        public static string Action_New {\n            get {\n                return ResourceManager.GetString(\"Action_New\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to OK.\n        /// </summary>\n        public static string Action_OK {\n            get {\n                return ResourceManager.GetString(\"Action_OK\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open on GitHub.\n        /// </summary>\n        public static string Action_OpenGithub {\n            get {\n                return ResourceManager.GetString(\"Action_OpenGithub\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open in Browser.\n        /// </summary>\n        public static string Action_OpenInBrowser {\n            get {\n                return ResourceManager.GetString(\"Action_OpenInBrowser\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open in Explorer.\n        /// </summary>\n        public static string Action_OpenInExplorer {\n            get {\n                return ResourceManager.GetString(\"Action_OpenInExplorer\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open in Finder.\n        /// </summary>\n        public static string Action_OpenInFinder {\n            get {\n                return ResourceManager.GetString(\"Action_OpenInFinder\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open in Image Viewer.\n        /// </summary>\n        public static string Action_OpenInViewer {\n            get {\n                return ResourceManager.GetString(\"Action_OpenInViewer\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open on CivitAI.\n        /// </summary>\n        public static string Action_OpenOnCivitAi {\n            get {\n                return ResourceManager.GetString(\"Action_OpenOnCivitAi\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open on Hugging Face.\n        /// </summary>\n        public static string Action_OpenOnHuggingFace {\n            get {\n                return ResourceManager.GetString(\"Action_OpenOnHuggingFace\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open on OpenArt.\n        /// </summary>\n        public static string Action_OpenOnOpenArt {\n            get {\n                return ResourceManager.GetString(\"Action_OpenOnOpenArt\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open on OpenModelDB.\n        /// </summary>\n        public static string Action_OpenOnOpenModelDb {\n            get {\n                return ResourceManager.GetString(\"Action_OpenOnOpenModelDb\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open Project....\n        /// </summary>\n        public static string Action_OpenProjectEllipsis {\n            get {\n                return ResourceManager.GetString(\"Action_OpenProjectEllipsis\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open Web UI.\n        /// </summary>\n        public static string Action_OpenWebUI {\n            get {\n                return ResourceManager.GetString(\"Action_OpenWebUI\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Preview Preprocessor.\n        /// </summary>\n        public static string Action_PreviewPreprocessor {\n            get {\n                return ResourceManager.GetString(\"Action_PreviewPreprocessor\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Quit.\n        /// </summary>\n        public static string Action_Quit {\n            get {\n                return ResourceManager.GetString(\"Action_Quit\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Refresh.\n        /// </summary>\n        public static string Action_Refresh {\n            get {\n                return ResourceManager.GetString(\"Action_Refresh\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Relaunch.\n        /// </summary>\n        public static string Action_Relaunch {\n            get {\n                return ResourceManager.GetString(\"Action_Relaunch\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Relaunch Later.\n        /// </summary>\n        public static string Action_RelaunchLater {\n            get {\n                return ResourceManager.GetString(\"Action_RelaunchLater\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Remind Me Later.\n        /// </summary>\n        public static string Action_RemindMeLater {\n            get {\n                return ResourceManager.GetString(\"Action_RemindMeLater\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Remove.\n        /// </summary>\n        public static string Action_Remove {\n            get {\n                return ResourceManager.GetString(\"Action_Remove\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Rename.\n        /// </summary>\n        public static string Action_Rename {\n            get {\n                return ResourceManager.GetString(\"Action_Rename\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Replace Contents.\n        /// </summary>\n        public static string Action_ReplaceContents {\n            get {\n                return ResourceManager.GetString(\"Action_ReplaceContents\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Restart.\n        /// </summary>\n        public static string Action_Restart {\n            get {\n                return ResourceManager.GetString(\"Action_Restart\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Restore Default Layout.\n        /// </summary>\n        public static string Action_RestoreDefaultLayout {\n            get {\n                return ResourceManager.GetString(\"Action_RestoreDefaultLayout\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Retry.\n        /// </summary>\n        public static string Action_Retry {\n            get {\n                return ResourceManager.GetString(\"Action_Retry\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Save.\n        /// </summary>\n        public static string Action_Save {\n            get {\n                return ResourceManager.GetString(\"Action_Save\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Save As....\n        /// </summary>\n        public static string Action_SaveAsEllipsis {\n            get {\n                return ResourceManager.GetString(\"Action_SaveAsEllipsis\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Search.\n        /// </summary>\n        public static string Action_Search {\n            get {\n                return ResourceManager.GetString(\"Action_Search\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Select All.\n        /// </summary>\n        public static string Action_SelectAll {\n            get {\n                return ResourceManager.GetString(\"Action_SelectAll\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Select Directory.\n        /// </summary>\n        public static string Action_SelectDirectory {\n            get {\n                return ResourceManager.GetString(\"Action_SelectDirectory\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Select File.\n        /// </summary>\n        public static string Action_SelectFile {\n            get {\n                return ResourceManager.GetString(\"Action_SelectFile\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Send.\n        /// </summary>\n        public static string Action_Send {\n            get {\n                return ResourceManager.GetString(\"Action_Send\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Send Input.\n        /// </summary>\n        public static string Action_SendInput {\n            get {\n                return ResourceManager.GetString(\"Action_SendInput\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Send to Inference.\n        /// </summary>\n        public static string Action_SendToInference {\n            get {\n                return ResourceManager.GetString(\"Action_SendToInference\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Show in Explorer.\n        /// </summary>\n        public static string Action_ShowInExplorer {\n            get {\n                return ResourceManager.GetString(\"Action_ShowInExplorer\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Show Log in Explorer.\n        /// </summary>\n        public static string Action_ShowLogInExplorer {\n            get {\n                return ResourceManager.GetString(\"Action_ShowLogInExplorer\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Show Log in Finder.\n        /// </summary>\n        public static string Action_ShowLogInFinder {\n            get {\n                return ResourceManager.GetString(\"Action_ShowLogInFinder\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Signup.\n        /// </summary>\n        public static string Action_Signup {\n            get {\n                return ResourceManager.GetString(\"Action_Signup\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Stop.\n        /// </summary>\n        public static string Action_Stop {\n            get {\n                return ResourceManager.GetString(\"Action_Stop\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Toggle Visibility.\n        /// </summary>\n        public static string Action_ToggleVisibility {\n            get {\n                return ResourceManager.GetString(\"Action_ToggleVisibility\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Uninstall.\n        /// </summary>\n        public static string Action_Uninstall {\n            get {\n                return ResourceManager.GetString(\"Action_Uninstall\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Update.\n        /// </summary>\n        public static string Action_Update {\n            get {\n                return ResourceManager.GetString(\"Action_Update\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Update Existing Metadata.\n        /// </summary>\n        public static string Action_UpdateExistingMetadata {\n            get {\n                return ResourceManager.GetString(\"Action_UpdateExistingMetadata\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Upgrade.\n        /// </summary>\n        public static string Action_Upgrade {\n            get {\n                return ResourceManager.GetString(\"Action_Upgrade\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to View Support Options.\n        /// </summary>\n        public static string Action_ViewSupportOptions {\n            get {\n                return ResourceManager.GetString(\"Action_ViewSupportOptions\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Yes.\n        /// </summary>\n        public static string Action_Yes {\n            get {\n                return ResourceManager.GetString(\"Action_Yes\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to {\n        ///  &quot;packageName&quot;: &quot;stable-diffusion-webui&quot;,\n        ///  &quot;packageVersion&quot;: &quot;v1.10.0&quot;,\n        ///  &quot;isSuccess&quot;: true,\n        ///  &quot;type&quot;: &quot;install&quot;,\n        ///  &quot;timestamp&quot;: &quot;2024-09-04T02:14:04.1967404+00:00&quot;\n        ///}.\n        /// </summary>\n        public static string AnalyticsExample_InstallData {\n            get {\n                return ResourceManager.GetString(\"AnalyticsExample_InstallData\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Enter a new name for &apos;{0}&apos;.\n        /// </summary>\n        public static string Description_RenamePackage {\n            get {\n                return ResourceManager.GetString(\"Description_RenamePackage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Please select a download location..\n        /// </summary>\n        public static string Error_PleaseSelectDownloadLocation {\n            get {\n                return ResourceManager.GetString(\"Error_PleaseSelectDownloadLocation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to About.\n        /// </summary>\n        public static string Label_About {\n            get {\n                return ResourceManager.GetString(\"Label_About\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Accounts.\n        /// </summary>\n        public static string Label_Accounts {\n            get {\n                return ResourceManager.GetString(\"Label_Accounts\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Account Settings.\n        /// </summary>\n        public static string Label_AccountSettings {\n            get {\n                return ResourceManager.GetString(\"Label_AccountSettings\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to This action cannot be undone..\n        /// </summary>\n        public static string Label_ActionCannotBeUndone {\n            get {\n                return ResourceManager.GetString(\"Label_ActionCannotBeUndone\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Addons.\n        /// </summary>\n        public static string Label_Addons {\n            get {\n                return ResourceManager.GetString(\"Label_Addons\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Add to Existing Pack.\n        /// </summary>\n        public static string Label_AddToExistingPack {\n            get {\n                return ResourceManager.GetString(\"Label_AddToExistingPack\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Add Stability Matrix to the Start Menu.\n        /// </summary>\n        public static string Label_AddToStartMenu {\n            get {\n                return ResourceManager.GetString(\"Label_AddToStartMenu\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Uses the current app location, you can run this again if you move the app.\n        /// </summary>\n        public static string Label_AddToStartMenu_Details {\n            get {\n                return ResourceManager.GetString(\"Label_AddToStartMenu_Details\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Advanced Options.\n        /// </summary>\n        public static string Label_AdvancedOptions {\n            get {\n                return ResourceManager.GetString(\"Label_AdvancedOptions\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to All Versions.\n        /// </summary>\n        public static string Label_AllVersions {\n            get {\n                return ResourceManager.GetString(\"Label_AllVersions\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Analytics.\n        /// </summary>\n        public static string Label_Analytics {\n            get {\n                return ResourceManager.GetString(\"Label_Analytics\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Another instance of Stability Matrix is already running. Please close it before starting a new one..\n        /// </summary>\n        public static string Label_AnotherInstanceAlreadyRunning {\n            get {\n                return ResourceManager.GetString(\"Label_AnotherInstanceAlreadyRunning\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to API Key.\n        /// </summary>\n        public static string Label_ApiKey {\n            get {\n                return ResourceManager.GetString(\"Label_ApiKey\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to App Data.\n        /// </summary>\n        public static string Label_AppData {\n            get {\n                return ResourceManager.GetString(\"Label_AppData\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Appearance.\n        /// </summary>\n        public static string Label_Appearance {\n            get {\n                return ResourceManager.GetString(\"Label_Appearance\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to App Folders.\n        /// </summary>\n        public static string Label_AppFolders {\n            get {\n                return ResourceManager.GetString(\"Label_AppFolders\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Are you sure?.\n        /// </summary>\n        public static string Label_AreYouSure {\n            get {\n                return ResourceManager.GetString(\"Label_AreYouSure\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Are you sure you want to delete {0} images?.\n        /// </summary>\n        public static string Label_AreYouSureDeleteImages {\n            get {\n                return ResourceManager.GetString(\"Label_AreYouSureDeleteImages\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Are you sure you want to delete {0} models?.\n        /// </summary>\n        public static string Label_AreYouSureDeleteModels {\n            get {\n                return ResourceManager.GetString(\"Label_AreYouSureDeleteModels\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Augmentation Level.\n        /// </summary>\n        public static string Label_AugmentationLevel {\n            get {\n                return ResourceManager.GetString(\"Label_AugmentationLevel\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Author.\n        /// </summary>\n        public static string Label_Author {\n            get {\n                return ResourceManager.GetString(\"Label_Author\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Auto Completion.\n        /// </summary>\n        public static string Label_AutoCompletion {\n            get {\n                return ResourceManager.GetString(\"Label_AutoCompletion\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Automatically scroll to end of console output.\n        /// </summary>\n        public static string Label_AutoScrollToEnd {\n            get {\n                return ResourceManager.GetString(\"Label_AutoScrollToEnd\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Auto-Search on Load.\n        /// </summary>\n        public static string Label_AutoSearchOnLoad {\n            get {\n                return ResourceManager.GetString(\"Label_AutoSearchOnLoad\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Automatically initiate a search when the model browser page is loaded.\n        /// </summary>\n        public static string Label_AutoSearchOnLoad_Description {\n            get {\n                return ResourceManager.GetString(\"Label_AutoSearchOnLoad_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Auto Updates.\n        /// </summary>\n        public static string Label_AutoUpdates {\n            get {\n                return ResourceManager.GetString(\"Label_AutoUpdates\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Base Model.\n        /// </summary>\n        public static string Label_BaseModel {\n            get {\n                return ResourceManager.GetString(\"Label_BaseModel\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Batches.\n        /// </summary>\n        public static string Label_Batches {\n            get {\n                return ResourceManager.GetString(\"Label_Batches\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Batch Index.\n        /// </summary>\n        public static string Label_BatchIndex {\n            get {\n                return ResourceManager.GetString(\"Label_BatchIndex\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Batch Size.\n        /// </summary>\n        public static string Label_BatchSize {\n            get {\n                return ResourceManager.GetString(\"Label_BatchSize\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Become a Patron.\n        /// </summary>\n        public static string Label_BecomeAPatron {\n            get {\n                return ResourceManager.GetString(\"Label_BecomeAPatron\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Beta.\n        /// </summary>\n        public static string Label_Beta {\n            get {\n                return ResourceManager.GetString(\"Label_Beta\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Branch.\n        /// </summary>\n        public static string Label_Branch {\n            get {\n                return ResourceManager.GetString(\"Label_Branch\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Branches.\n        /// </summary>\n        public static string Label_Branches {\n            get {\n                return ResourceManager.GetString(\"Label_Branches\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Bulk Download Started.\n        /// </summary>\n        public static string Label_BulkDownloadStarted {\n            get {\n                return ResourceManager.GetString(\"Label_BulkDownloadStarted\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to {0} files have started downloading. Check the Downloads tab for progress..\n        /// </summary>\n        public static string Label_BulkDownloadStartedMessage {\n            get {\n                return ResourceManager.GetString(\"Label_BulkDownloadStartedMessage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Callstack.\n        /// </summary>\n        public static string Label_Callstack {\n            get {\n                return ResourceManager.GetString(\"Label_Callstack\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Categories.\n        /// </summary>\n        public static string Label_Categories {\n            get {\n                return ResourceManager.GetString(\"Label_Categories\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to CFG Scale.\n        /// </summary>\n        public static string Label_CFGScale {\n            get {\n                return ResourceManager.GetString(\"Label_CFGScale\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Changes Applied.\n        /// </summary>\n        public static string Label_ChangesApplied {\n            get {\n                return ResourceManager.GetString(\"Label_ChangesApplied\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to We&apos;re checking some hardware specifications to determine compatibility..\n        /// </summary>\n        public static string Label_CheckingHardware {\n            get {\n                return ResourceManager.GetString(\"Label_CheckingHardware\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Checkpoint Manager.\n        /// </summary>\n        public static string Label_CheckpointManager {\n            get {\n                return ResourceManager.GetString(\"Label_CheckpointManager\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Checkpoints.\n        /// </summary>\n        public static string Label_Checkpoints {\n            get {\n                return ResourceManager.GetString(\"Label_Checkpoints\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to CivitAI.\n        /// </summary>\n        public static string Label_CivitAi {\n            get {\n                return ResourceManager.GetString(\"Label_CivitAi\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to You must be logged in to download this checkpoint. Please enter a CivitAI API Key in the settings..\n        /// </summary>\n        public static string Label_CivitAiLoginRequired {\n            get {\n                return ResourceManager.GetString(\"Label_CivitAiLoginRequired\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Clipping Mask.\n        /// </summary>\n        public static string Label_ClippingMask {\n            get {\n                return ResourceManager.GetString(\"Label_ClippingMask\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to CLIP Skip.\n        /// </summary>\n        public static string Label_CLIPSkip {\n            get {\n                return ResourceManager.GetString(\"Label_CLIPSkip\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to CLIP Strength.\n        /// </summary>\n        public static string Label_CLIPStrength {\n            get {\n                return ResourceManager.GetString(\"Label_CLIPStrength\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Close dialog when finished.\n        /// </summary>\n        public static string Label_CloseDialogWhenFinished {\n            get {\n                return ResourceManager.GetString(\"Label_CloseDialogWhenFinished\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to ComfyUI is required to install this package. Would you like to install it now?.\n        /// </summary>\n        public static string Label_ComfyRequiredDetail {\n            get {\n                return ResourceManager.GetString(\"Label_ComfyRequiredDetail\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to ComfyUI Required.\n        /// </summary>\n        public static string Label_ComfyRequiredTitle {\n            get {\n                return ResourceManager.GetString(\"Label_ComfyRequiredTitle\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Comments.\n        /// </summary>\n        public static string Label_Comments {\n            get {\n                return ResourceManager.GetString(\"Label_Comments\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Commit.\n        /// </summary>\n        public static string Label_Commit {\n            get {\n                return ResourceManager.GetString(\"Label_Commit\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Replace underscores with spaces when inserting completions.\n        /// </summary>\n        public static string Label_CompletionReplaceUnderscoresWithSpaces {\n            get {\n                return ResourceManager.GetString(\"Label_CompletionReplaceUnderscoresWithSpaces\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Config.\n        /// </summary>\n        public static string Label_Config {\n            get {\n                return ResourceManager.GetString(\"Label_Config\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Confirm Delete.\n        /// </summary>\n        public static string Label_ConfirmDelete {\n            get {\n                return ResourceManager.GetString(\"Label_ConfirmDelete\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Confirm Exit.\n        /// </summary>\n        public static string Label_ConfirmExit {\n            get {\n                return ResourceManager.GetString(\"Label_ConfirmExit\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Are you sure you want to exit? This will also close any currently running packages..\n        /// </summary>\n        public static string Label_ConfirmExitDetail {\n            get {\n                return ResourceManager.GetString(\"Label_ConfirmExitDetail\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Confirm Password.\n        /// </summary>\n        public static string Label_ConfirmPassword {\n            get {\n                return ResourceManager.GetString(\"Label_ConfirmPassword\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Confirm?.\n        /// </summary>\n        public static string Label_ConfirmQuestion {\n            get {\n                return ResourceManager.GetString(\"Label_ConfirmQuestion\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to We had some trouble connecting your account.\n        /// </summary>\n        public static string Label_ConnectAccountFailed {\n            get {\n                return ResourceManager.GetString(\"Label_ConnectAccountFailed\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Connected.\n        /// </summary>\n        public static string Label_Connected {\n            get {\n                return ResourceManager.GetString(\"Label_Connected\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Connected Model.\n        /// </summary>\n        public static string Label_ConnectedModel {\n            get {\n                return ResourceManager.GetString(\"Label_ConnectedModel\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Connecting....\n        /// </summary>\n        public static string Label_ConnectingEllipsis {\n            get {\n                return ResourceManager.GetString(\"Label_ConnectingEllipsis\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Console.\n        /// </summary>\n        public static string Label_Console {\n            get {\n                return ResourceManager.GetString(\"Label_Console\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to This will move all generated images from the selected packages to the Consolidated directory of the shared outputs folder. This action cannot be undone..\n        /// </summary>\n        public static string Label_ConsolidateExplanation {\n            get {\n                return ResourceManager.GetString(\"Label_ConsolidateExplanation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Control Steps.\n        /// </summary>\n        public static string Label_ControlSteps {\n            get {\n                return ResourceManager.GetString(\"Label_ControlSteps\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Control Weight.\n        /// </summary>\n        public static string Label_ControlWeight {\n            get {\n                return ResourceManager.GetString(\"Label_ControlWeight\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Copy Link to Clipboard.\n        /// </summary>\n        public static string Label_CopyLinkToClipboard {\n            get {\n                return ResourceManager.GetString(\"Label_CopyLinkToClipboard\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to To create one, simply select the extensions you want from the &apos;Available Extensions&apos; or &apos;Installed Extensions&apos; tab and click &apos;Save&apos;.\n        /// </summary>\n        public static string Label_CreateExtensionPackExplanation {\n            get {\n                return ResourceManager.GetString(\"Label_CreateExtensionPackExplanation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Current directory:.\n        /// </summary>\n        public static string Label_CurrentDirectory {\n            get {\n                return ResourceManager.GetString(\"Label_CurrentDirectory\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Data Directory.\n        /// </summary>\n        public static string Label_DataDirectory {\n            get {\n                return ResourceManager.GetString(\"Label_DataDirectory\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to This is where application data (model checkpoints, web UIs, etc.) will be installed..\n        /// </summary>\n        public static string Label_DataDirectoryExplanation {\n            get {\n                return ResourceManager.GetString(\"Label_DataDirectoryExplanation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Data Folder Name.\n        /// </summary>\n        public static string Label_DataFolderName {\n            get {\n                return ResourceManager.GetString(\"Label_DataFolderName\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Data provided by CivitAI.\n        /// </summary>\n        public static string Label_DataProvidedByCivitAi {\n            get {\n                return ResourceManager.GetString(\"Label_DataProvidedByCivitAi\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Deemphasis.\n        /// </summary>\n        public static string Label_Deemphasis {\n            get {\n                return ResourceManager.GetString(\"Label_Deemphasis\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Delete Permanently.\n        /// </summary>\n        public static string Label_DeletePermanently {\n            get {\n                return ResourceManager.GetString(\"Label_DeletePermanently\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Denoising Strength.\n        /// </summary>\n        public static string Label_DenoisingStrength {\n            get {\n                return ResourceManager.GetString(\"Label_DenoisingStrength\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Dependency Specifiers.\n        /// </summary>\n        public static string Label_DependencySpecifiers {\n            get {\n                return ResourceManager.GetString(\"Label_DependencySpecifiers\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Details.\n        /// </summary>\n        public static string Label_Details {\n            get {\n                return ResourceManager.GetString(\"Label_Details\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Disable Update Check.\n        /// </summary>\n        public static string Label_DisableUpdateCheck {\n            get {\n                return ResourceManager.GetString(\"Label_DisableUpdateCheck\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Discord Rich Presence.\n        /// </summary>\n        public static string Label_DiscordRichPresence {\n            get {\n                return ResourceManager.GetString(\"Label_DiscordRichPresence\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Display Name.\n        /// </summary>\n        public static string Label_DisplayName {\n            get {\n                return ResourceManager.GetString(\"Label_DisplayName\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Download All Files (All Versions).\n        /// </summary>\n        public static string Label_DownloadAllFilesAllVersions {\n            get {\n                return ResourceManager.GetString(\"Label_DownloadAllFilesAllVersions\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Download Failed.\n        /// </summary>\n        public static string Label_DownloadFailed {\n            get {\n                return ResourceManager.GetString(\"Label_DownloadFailed\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Downloads.\n        /// </summary>\n        public static string Label_Downloads {\n            get {\n                return ResourceManager.GetString(\"Label_Downloads\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Download Started.\n        /// </summary>\n        public static string Label_DownloadStarted {\n            get {\n                return ResourceManager.GetString(\"Label_DownloadStarted\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to {0} will be saved to {1}.\n        /// </summary>\n        public static string Label_DownloadWillBeSavedToLocation {\n            get {\n                return ResourceManager.GetString(\"Label_DownloadWillBeSavedToLocation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Drag &amp; Drop checkpoints here to import.\n        /// </summary>\n        public static string Label_DragAndDropCheckpointsHereToImport {\n            get {\n                return ResourceManager.GetString(\"Label_DragAndDropCheckpointsHereToImport\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Drop file here to import.\n        /// </summary>\n        public static string Label_DropFileToImport {\n            get {\n                return ResourceManager.GetString(\"Label_DropFileToImport\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Early Access Models.\n        /// </summary>\n        public static string Label_EarlyAccessModels {\n            get {\n                return ResourceManager.GetString(\"Label_EarlyAccessModels\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Edit Model Metadata.\n        /// </summary>\n        public static string Label_EditModelMetadata {\n            get {\n                return ResourceManager.GetString(\"Label_EditModelMetadata\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Email.\n        /// </summary>\n        public static string Label_Email {\n            get {\n                return ResourceManager.GetString(\"Label_Email\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Embedded Python.\n        /// </summary>\n        public static string Label_EmbeddedPython {\n            get {\n                return ResourceManager.GetString(\"Label_EmbeddedPython\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Embeddings / Textual Inversion.\n        /// </summary>\n        public static string Label_EmbeddingsOrTextualInversion {\n            get {\n                return ResourceManager.GetString(\"Label_EmbeddingsOrTextualInversion\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Emphasis.\n        /// </summary>\n        public static string Label_Emphasis {\n            get {\n                return ResourceManager.GetString(\"Label_Emphasis\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Enable Long Paths.\n        /// </summary>\n        public static string Label_EnableLongPaths {\n            get {\n                return ResourceManager.GetString(\"Label_EnableLongPaths\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Remove MAX_PATH limitations from common Win32 file and directory functions.\n        /// </summary>\n        public static string Label_EnableLongPathsDescription {\n            get {\n                return ResourceManager.GetString(\"Label_EnableLongPathsDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Environment Variables.\n        /// </summary>\n        public static string Label_EnvironmentVariables {\n            get {\n                return ResourceManager.GetString(\"Label_EnvironmentVariables\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Name.\n        /// </summary>\n        public static string Label_EnvVarsTable_Name {\n            get {\n                return ResourceManager.GetString(\"Label_EnvVarsTable_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Value.\n        /// </summary>\n        public static string Label_EnvVarsTable_Value {\n            get {\n                return ResourceManager.GetString(\"Label_EnvVarsTable_Value\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Error installing package.\n        /// </summary>\n        public static string Label_ErrorInstallingPackage {\n            get {\n                return ResourceManager.GetString(\"Label_ErrorInstallingPackage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Error retrieving workflows.\n        /// </summary>\n        public static string Label_ErrorRetrievingWorkflows {\n            get {\n                return ResourceManager.GetString(\"Label_ErrorRetrievingWorkflows\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Everything looks good!.\n        /// </summary>\n        public static string Label_EverythingLooksGood {\n            get {\n                return ResourceManager.GetString(\"Label_EverythingLooksGood\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Extension Packs.\n        /// </summary>\n        public static string Label_ExtensionPacks {\n            get {\n                return ResourceManager.GetString(\"Label_ExtensionPacks\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Extra Networks (Lora / LyCORIS).\n        /// </summary>\n        public static string Label_ExtraNetworks {\n            get {\n                return ResourceManager.GetString(\"Label_ExtraNetworks\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to You may encounter errors when using a FAT32 or exFAT drive. Select a different drive for a smoother experience..\n        /// </summary>\n        public static string Label_FatWarning {\n            get {\n                return ResourceManager.GetString(\"Label_FatWarning\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to File Name Pattern.\n        /// </summary>\n        public static string Label_FileNamePattern {\n            get {\n                return ResourceManager.GetString(\"Label_FileNamePattern\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Files.\n        /// </summary>\n        public static string Label_Files {\n            get {\n                return ResourceManager.GetString(\"Label_Files\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Filter.\n        /// </summary>\n        public static string Label_Filter {\n            get {\n                return ResourceManager.GetString(\"Label_Filter\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Find Connected Metadata.\n        /// </summary>\n        public static string Label_FindConnectedMetadata {\n            get {\n                return ResourceManager.GetString(\"Label_FindConnectedMetadata\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Find in Model Browser.\n        /// </summary>\n        public static string Label_FindInModelBrowser {\n            get {\n                return ResourceManager.GetString(\"Label_FindInModelBrowser\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Finished importing workflow and custom nodes.\n        /// </summary>\n        public static string Label_FinishedImportingWorkflow {\n            get {\n                return ResourceManager.GetString(\"Label_FinishedImportingWorkflow\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to First Page.\n        /// </summary>\n        public static string Label_FirstPage {\n            get {\n                return ResourceManager.GetString(\"Label_FirstPage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Folder.\n        /// </summary>\n        public static string Label_Folder {\n            get {\n                return ResourceManager.GetString(\"Label_Folder\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Frames Per Second.\n        /// </summary>\n        public static string Label_Fps {\n            get {\n                return ResourceManager.GetString(\"Label_Fps\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Frames.\n        /// </summary>\n        public static string Label_Frames {\n            get {\n                return ResourceManager.GetString(\"Label_Frames\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to General.\n        /// </summary>\n        public static string Label_General {\n            get {\n                return ResourceManager.GetString(\"Label_General\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Hash.\n        /// </summary>\n        public static string Label_Hash {\n            get {\n                return ResourceManager.GetString(\"Label_Hash\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Height.\n        /// </summary>\n        public static string Label_Height {\n            get {\n                return ResourceManager.GetString(\"Label_Height\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Hide Empty Categories.\n        /// </summary>\n        public static string Label_HideEmptyCategories {\n            get {\n                return ResourceManager.GetString(\"Label_HideEmptyCategories\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to History Size.\n        /// </summary>\n        public static string Label_HistorySize {\n            get {\n                return ResourceManager.GetString(\"Label_HistorySize\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to The number of lines above the ones displayed in the console you can scroll back to.\n        /// </summary>\n        public static string Label_HistorySize_Description {\n            get {\n                return ResourceManager.GetString(\"Label_HistorySize_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Holiday Mode.\n        /// </summary>\n        public static string Label_HolidayMode {\n            get {\n                return ResourceManager.GetString(\"Label_HolidayMode\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Hugging Face.\n        /// </summary>\n        public static string Label_HuggingFace {\n            get {\n                return ResourceManager.GetString(\"Label_HuggingFace\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to You must be logged in to download this checkpoint. Please enter a Hugging Face Access Token in the settings..\n        /// </summary>\n        public static string Label_HuggingFaceLoginRequired {\n            get {\n                return ResourceManager.GetString(\"Label_HuggingFaceLoginRequired\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Image Hidden.\n        /// </summary>\n        public static string Label_ImageHidden {\n            get {\n                return ResourceManager.GetString(\"Label_ImageHidden\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Image to Image.\n        /// </summary>\n        public static string Label_ImageToImage {\n            get {\n                return ResourceManager.GetString(\"Label_ImageToImage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Image to Video.\n        /// </summary>\n        public static string Label_ImageToVideo {\n            get {\n                return ResourceManager.GetString(\"Label_ImageToVideo\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Image Viewer.\n        /// </summary>\n        public static string Label_ImageViewer {\n            get {\n                return ResourceManager.GetString(\"Label_ImageViewer\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Import with Metadata.\n        /// </summary>\n        public static string Label_ImportAsConnected {\n            get {\n                return ResourceManager.GetString(\"Label_ImportAsConnected\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Search for connected metadata on new local imports.\n        /// </summary>\n        public static string Label_ImportAsConnectedExplanation {\n            get {\n                return ResourceManager.GetString(\"Label_ImportAsConnectedExplanation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Add a .json extension pack file to the ExtensionPacks folder within your Data directory..\n        /// </summary>\n        public static string Label_ImportExtensionPacksExplanation {\n            get {\n                return ResourceManager.GetString(\"Label_ImportExtensionPacksExplanation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Import Latest -.\n        /// </summary>\n        public static string Label_ImportLatest {\n            get {\n                return ResourceManager.GetString(\"Label_ImportLatest\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Indexing....\n        /// </summary>\n        public static string Label_Indexing {\n            get {\n                return ResourceManager.GetString(\"Label_Indexing\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Inference.\n        /// </summary>\n        public static string Label_Inference {\n            get {\n                return ResourceManager.GetString(\"Label_Inference\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Inference Defaults.\n        /// </summary>\n        public static string Label_InferenceDefaultsHeader {\n            get {\n                return ResourceManager.GetString(\"Label_InferenceDefaultsHeader\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Infinite Scrolling.\n        /// </summary>\n        public static string Label_InfiniteScrolling {\n            get {\n                return ResourceManager.GetString(\"Label_InfiniteScrolling\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Inner exception.\n        /// </summary>\n        public static string Label_InnerException {\n            get {\n                return ResourceManager.GetString(\"Label_InnerException\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Inpainting.\n        /// </summary>\n        public static string Label_Inpainting {\n            get {\n                return ResourceManager.GetString(\"Label_Inpainting\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Input.\n        /// </summary>\n        public static string Label_Input {\n            get {\n                return ResourceManager.GetString(\"Label_Input\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Input required.\n        /// </summary>\n        public static string Label_InputRequired {\n            get {\n                return ResourceManager.GetString(\"Label_InputRequired\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to An installation with this name already exists..\n        /// </summary>\n        public static string Label_InstallationWithThisNameExists {\n            get {\n                return ResourceManager.GetString(\"Label_InstallationWithThisNameExists\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Installed.\n        /// </summary>\n        public static string Label_Installed {\n            get {\n                return ResourceManager.GetString(\"Label_Installed\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Installed Models.\n        /// </summary>\n        public static string Label_InstalledModels {\n            get {\n                return ResourceManager.GetString(\"Label_InstalledModels\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Install Extension Pack.\n        /// </summary>\n        public static string Label_InstallExtensionPack {\n            get {\n                return ResourceManager.GetString(\"Label_InstallExtensionPack\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Installing.\n        /// </summary>\n        public static string Label_Installing {\n            get {\n                return ResourceManager.GetString(\"Label_Installing\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Integrations.\n        /// </summary>\n        public static string Label_Integrations {\n            get {\n                return ResourceManager.GetString(\"Label_Integrations\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Invalid Package type.\n        /// </summary>\n        public static string Label_InvalidPackageType {\n            get {\n                return ResourceManager.GetString(\"Label_InvalidPackageType\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Join Discord Server.\n        /// </summary>\n        public static string Label_JoinDiscord {\n            get {\n                return ResourceManager.GetString(\"Label_JoinDiscord\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Language.\n        /// </summary>\n        public static string Label_Language {\n            get {\n                return ResourceManager.GetString(\"Label_Language\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Last Page.\n        /// </summary>\n        public static string Label_LastPage {\n            get {\n                return ResourceManager.GetString(\"Label_LastPage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Last Updated.\n        /// </summary>\n        public static string Label_LastUpdatedAt {\n            get {\n                return ResourceManager.GetString(\"Label_LastUpdatedAt\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Let&apos;s get started.\n        /// </summary>\n        public static string Label_LetsGetStarted {\n            get {\n                return ResourceManager.GetString(\"Label_LetsGetStarted\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to License.\n        /// </summary>\n        public static string Label_License {\n            get {\n                return ResourceManager.GetString(\"Label_License\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to License Agreement..\n        /// </summary>\n        public static string Label_LicenseAgreement {\n            get {\n                return ResourceManager.GetString(\"Label_LicenseAgreement\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to License and Open Source Notices.\n        /// </summary>\n        public static string Label_LicenseAndOpenSourceNotices {\n            get {\n                return ResourceManager.GetString(\"Label_LicenseAndOpenSourceNotices\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Local Model.\n        /// </summary>\n        public static string Label_LocalModel {\n            get {\n                return ResourceManager.GetString(\"Label_LocalModel\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Login required to download this model.\n        /// </summary>\n        public static string Label_LoginRequired {\n            get {\n                return ResourceManager.GetString(\"Label_LoginRequired\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Logs.\n        /// </summary>\n        public static string Label_Logs {\n            get {\n                return ResourceManager.GetString(\"Label_Logs\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Lossless.\n        /// </summary>\n        public static string Label_Lossless {\n            get {\n                return ResourceManager.GetString(\"Label_Lossless\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Max Size.\n        /// </summary>\n        public static string Label_MaxSize {\n            get {\n                return ResourceManager.GetString(\"Label_MaxSize\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Min CFG.\n        /// </summary>\n        public static string Label_MinCfg {\n            get {\n                return ResourceManager.GetString(\"Label_MinCfg\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Missing Image File.\n        /// </summary>\n        public static string Label_MissingImageFile {\n            get {\n                return ResourceManager.GetString(\"Label_MissingImageFile\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Model.\n        /// </summary>\n        public static string Label_Model {\n            get {\n                return ResourceManager.GetString(\"Label_Model\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Model Browser.\n        /// </summary>\n        public static string Label_ModelBrowser {\n            get {\n                return ResourceManager.GetString(\"Label_ModelBrowser\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Model Description.\n        /// </summary>\n        public static string Label_ModelDescription {\n            get {\n                return ResourceManager.GetString(\"Label_ModelDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Search models, #tags, or @users.\n        /// </summary>\n        public static string Label_ModelSearchWatermark {\n            get {\n                return ResourceManager.GetString(\"Label_ModelSearchWatermark\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Models Folder.\n        /// </summary>\n        public static string Label_ModelsFolder {\n            get {\n                return ResourceManager.GetString(\"Label_ModelsFolder\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Model Type.\n        /// </summary>\n        public static string Label_ModelType {\n            get {\n                return ResourceManager.GetString(\"Label_ModelType\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Motion Bucket ID.\n        /// </summary>\n        public static string Label_MotionBucketId {\n            get {\n                return ResourceManager.GetString(\"Label_MotionBucketId\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Negative Prompt.\n        /// </summary>\n        public static string Label_NegativePrompt {\n            get {\n                return ResourceManager.GetString(\"Label_NegativePrompt\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Networks (Lora / LyCORIS).\n        /// </summary>\n        public static string Label_NetworksLoraOrLycoris {\n            get {\n                return ResourceManager.GetString(\"Label_NetworksLoraOrLycoris\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to New Extension Pack.\n        /// </summary>\n        public static string Label_NewExtensionPack {\n            get {\n                return ResourceManager.GetString(\"Label_NewExtensionPack\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to New Folder.\n        /// </summary>\n        public static string Label_NewFolder {\n            get {\n                return ResourceManager.GetString(\"Label_NewFolder\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to A new version of Stability Matrix is available!.\n        /// </summary>\n        public static string Label_NewVersionAvailable {\n            get {\n                return ResourceManager.GetString(\"Label_NewVersionAvailable\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Next Image.\n        /// </summary>\n        public static string Label_NextImage {\n            get {\n                return ResourceManager.GetString(\"Label_NextImage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Next Page.\n        /// </summary>\n        public static string Label_NextPage {\n            get {\n                return ResourceManager.GetString(\"Label_NextPage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to No.\n        /// </summary>\n        public static string Label_No {\n            get {\n                return ResourceManager.GetString(\"Label_No\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Node Details.\n        /// </summary>\n        public static string Label_NodeDetails {\n            get {\n                return ResourceManager.GetString(\"Label_NodeDetails\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to No Extension Packs Found.\n        /// </summary>\n        public static string Label_NoExtensionPacksFound {\n            get {\n                return ResourceManager.GetString(\"Label_NoExtensionPacksFound\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to No extensions found..\n        /// </summary>\n        public static string Label_NoExtensionsFound {\n            get {\n                return ResourceManager.GetString(\"Label_NoExtensionsFound\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to No Image Found.\n        /// </summary>\n        public static string Label_NoImageFound {\n            get {\n                return ResourceManager.GetString(\"Label_NoImageFound\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Non-Model Files.\n        /// </summary>\n        public static string Label_NonModelFiles {\n            get {\n                return ResourceManager.GetString(\"Label_NonModelFiles\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to None.\n        /// </summary>\n        public static string Label_NotificationOption_None {\n            get {\n                return ResourceManager.GetString(\"Label_NotificationOption_None\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Notifications.\n        /// </summary>\n        public static string Label_Notifications {\n            get {\n                return ResourceManager.GetString(\"Label_Notifications\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to NSFW.\n        /// </summary>\n        public static string Label_NSFW {\n            get {\n                return ResourceManager.GetString(\"Label_NSFW\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to NSFW Content.\n        /// </summary>\n        public static string Label_NsfwContent {\n            get {\n                return ResourceManager.GetString(\"Label_NsfwContent\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Number Format.\n        /// </summary>\n        public static string Label_NumberFormat {\n            get {\n                return ResourceManager.GetString(\"Label_NumberFormat\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to {0} images selected.\n        /// </summary>\n        public static string Label_NumImagesSelected {\n            get {\n                return ResourceManager.GetString(\"Label_NumImagesSelected\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to We recommend a GPU with CUDA support for the best experience. You can continue without one, but some packages may not work, and inference may be slower..\n        /// </summary>\n        public static string Label_NvidiaGpuRecommended {\n            get {\n                return ResourceManager.GetString(\"Label_NvidiaGpuRecommended\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to 1 image selected.\n        /// </summary>\n        public static string Label_OneImageSelected {\n            get {\n                return ResourceManager.GetString(\"Label_OneImageSelected\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Only available on Windows.\n        /// </summary>\n        public static string Label_OnlyAvailableOnWindows {\n            get {\n                return ResourceManager.GetString(\"Label_OnlyAvailableOnWindows\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to OpenArt Browser.\n        /// </summary>\n        public static string Label_OpenArtBrowser {\n            get {\n                return ResourceManager.GetString(\"Label_OpenArtBrowser\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open Extension Packs Folder.\n        /// </summary>\n        public static string Label_OpenExtensionPacksFolder {\n            get {\n                return ResourceManager.GetString(\"Label_OpenExtensionPacksFolder\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to - or -.\n        /// </summary>\n        public static string Label_OrWithHyphensAround {\n            get {\n                return ResourceManager.GetString(\"Label_OrWithHyphensAround\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Output Folder.\n        /// </summary>\n        public static string Label_OutputFolder {\n            get {\n                return ResourceManager.GetString(\"Label_OutputFolder\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Output Image Files.\n        /// </summary>\n        public static string Label_OutputImageFiles {\n            get {\n                return ResourceManager.GetString(\"Label_OutputImageFiles\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Output Browser.\n        /// </summary>\n        public static string Label_OutputsPageTitle {\n            get {\n                return ResourceManager.GetString(\"Label_OutputsPageTitle\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Output Type.\n        /// </summary>\n        public static string Label_OutputType {\n            get {\n                return ResourceManager.GetString(\"Label_OutputType\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Package Environment.\n        /// </summary>\n        public static string Label_PackageEnvironment {\n            get {\n                return ResourceManager.GetString(\"Label_PackageEnvironment\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Packages.\n        /// </summary>\n        public static string Label_Packages {\n            get {\n                return ResourceManager.GetString(\"Label_Packages\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Package Type.\n        /// </summary>\n        public static string Label_PackageType {\n            get {\n                return ResourceManager.GetString(\"Label_PackageType\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Package Uninstalled.\n        /// </summary>\n        public static string Label_PackageUninstalled {\n            get {\n                return ResourceManager.GetString(\"Label_PackageUninstalled\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Page.\n        /// </summary>\n        public static string Label_Page {\n            get {\n                return ResourceManager.GetString(\"Label_Page\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Password.\n        /// </summary>\n        public static string Label_Password {\n            get {\n                return ResourceManager.GetString(\"Label_Password\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Please choose a different name or select a different install location..\n        /// </summary>\n        public static string Label_PleaseChooseDifferentName {\n            get {\n                return ResourceManager.GetString(\"Label_PleaseChooseDifferentName\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Please Select a Data Directory.\n        /// </summary>\n        public static string Label_PleaseSelectDataDirectory {\n            get {\n                return ResourceManager.GetString(\"Label_PleaseSelectDataDirectory\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Portable Mode.\n        /// </summary>\n        public static string Label_PortableMode {\n            get {\n                return ResourceManager.GetString(\"Label_PortableMode\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to In Portable Mode, all data and settings will be stored in the same directory as the application. You will be able to move the application with its &apos;Data&apos; folder to a different location or computer..\n        /// </summary>\n        public static string Label_PortableModeExplanation {\n            get {\n                return ResourceManager.GetString(\"Label_PortableModeExplanation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Preprocessor.\n        /// </summary>\n        public static string Label_Preprocessor {\n            get {\n                return ResourceManager.GetString(\"Label_Preprocessor\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Preview Image.\n        /// </summary>\n        public static string Label_PreviewImage {\n            get {\n                return ResourceManager.GetString(\"Label_PreviewImage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Previous Image.\n        /// </summary>\n        public static string Label_PreviousImage {\n            get {\n                return ResourceManager.GetString(\"Label_PreviousImage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Previous Page.\n        /// </summary>\n        public static string Label_PreviousPage {\n            get {\n                return ResourceManager.GetString(\"Label_PreviousPage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Privacy Policy.\n        /// </summary>\n        public static string Label_PrivacyPolicy {\n            get {\n                return ResourceManager.GetString(\"Label_PrivacyPolicy\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Prompt.\n        /// </summary>\n        public static string Label_Prompt {\n            get {\n                return ResourceManager.GetString(\"Label_Prompt\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Prompt Tags.\n        /// </summary>\n        public static string Label_PromptTags {\n            get {\n                return ResourceManager.GetString(\"Label_PromptTags\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Tags file to use for suggesting completions (Supports the a1111-sd-webui-tagcomplete .csv format).\n        /// </summary>\n        public static string Label_PromptTagsDescription {\n            get {\n                return ResourceManager.GetString(\"Label_PromptTagsDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Import Prompt tags.\n        /// </summary>\n        public static string Label_PromptTagsImport {\n            get {\n                return ResourceManager.GetString(\"Label_PromptTagsImport\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Add, replace, or remove dependencies for install and update.\n        /// </summary>\n        public static string Label_PythonDependenciesOverride_Description {\n            get {\n                return ResourceManager.GetString(\"Label_PythonDependenciesOverride_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Python Dependencies Override.\n        /// </summary>\n        public static string Label_PythonDependenciesOverride_Title {\n            get {\n                return ResourceManager.GetString(\"Label_PythonDependenciesOverride_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Python Packages.\n        /// </summary>\n        public static string Label_PythonPackages {\n            get {\n                return ResourceManager.GetString(\"Label_PythonPackages\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Python Version Info.\n        /// </summary>\n        public static string Label_PythonVersionInfo {\n            get {\n                return ResourceManager.GetString(\"Label_PythonVersionInfo\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Unsupported Python Versions.\n        /// </summary>\n        public static string Label_PythonVersionWarningTitle {\n            get {\n                return ResourceManager.GetString(\"Label_PythonVersionWarningTitle\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to PyTorch Version.\n        /// </summary>\n        public static string Label_PyTorchVersion {\n            get {\n                return ResourceManager.GetString(\"Label_PyTorchVersion\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to I have read and agree to the.\n        /// </summary>\n        public static string Label_ReadAndAgree {\n            get {\n                return ResourceManager.GetString(\"Label_ReadAndAgree\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Recommended Models.\n        /// </summary>\n        public static string Label_RecommendedModels {\n            get {\n                return ResourceManager.GetString(\"Label_RecommendedModels\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to While your package is installing, here are some models we recommend to help you get started..\n        /// </summary>\n        public static string Label_RecommendedModelsSubText {\n            get {\n                return ResourceManager.GetString(\"Label_RecommendedModelsSubText\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Refiner.\n        /// </summary>\n        public static string Label_Refiner {\n            get {\n                return ResourceManager.GetString(\"Label_Refiner\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Relaunch Required.\n        /// </summary>\n        public static string Label_RelaunchRequired {\n            get {\n                return ResourceManager.GetString(\"Label_RelaunchRequired\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Release Notes.\n        /// </summary>\n        public static string Label_ReleaseNotes {\n            get {\n                return ResourceManager.GetString(\"Label_ReleaseNotes\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Releases.\n        /// </summary>\n        public static string Label_Releases {\n            get {\n                return ResourceManager.GetString(\"Label_Releases\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Releases are unavailable for this package..\n        /// </summary>\n        public static string Label_ReleasesUnavailableForThisPackage {\n            get {\n                return ResourceManager.GetString(\"Label_ReleasesUnavailableForThisPackage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Remove shared folder symbolic links on shutdown.\n        /// </summary>\n        public static string Label_RemoveSymlinksOnShutdown {\n            get {\n                return ResourceManager.GetString(\"Label_RemoveSymlinksOnShutdown\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Removes both model and output folder symbolic links when closing Stability Matrix. Select this option if you&apos;re having problems moving Stability Matrix to another drive.\n        /// </summary>\n        public static string Label_RemoveSymlinksOnShutdown_Details {\n            get {\n                return ResourceManager.GetString(\"Label_RemoveSymlinksOnShutdown_Details\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Reset Checkpoints Cache.\n        /// </summary>\n        public static string Label_ResetCheckpointsCache {\n            get {\n                return ResourceManager.GetString(\"Label_ResetCheckpointsCache\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Rebuilds the installed checkpoints cache. Use if checkpoints are incorrectly labeled in the Model Browser.\n        /// </summary>\n        public static string Label_ResetCheckpointsCache_Details {\n            get {\n                return ResourceManager.GetString(\"Label_ResetCheckpointsCache_Details\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Sampler.\n        /// </summary>\n        public static string Label_Sampler {\n            get {\n                return ResourceManager.GetString(\"Label_Sampler\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Save Intermediate Image.\n        /// </summary>\n        public static string Label_SaveIntermediateImage {\n            get {\n                return ResourceManager.GetString(\"Label_SaveIntermediateImage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Scheduler.\n        /// </summary>\n        public static string Label_Scheduler {\n            get {\n                return ResourceManager.GetString(\"Label_Scheduler\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Search....\n        /// </summary>\n        public static string Label_SearchEllipsis {\n            get {\n                return ResourceManager.GetString(\"Label_SearchEllipsis\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Seed.\n        /// </summary>\n        public static string Label_Seed {\n            get {\n                return ResourceManager.GetString(\"Label_Seed\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Select Download Location:.\n        /// </summary>\n        public static string Label_SelectDownloadLocation {\n            get {\n                return ResourceManager.GetString(\"Label_SelectDownloadLocation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Select new Data Directory.\n        /// </summary>\n        public static string Label_SelectNewDataDirectory {\n            get {\n                return ResourceManager.GetString(\"Label_SelectNewDataDirectory\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Does not move existing data. Requires application restart..\n        /// </summary>\n        public static string Label_SelectNewDataDirectory_Details {\n            get {\n                return ResourceManager.GetString(\"Label_SelectNewDataDirectory_Details\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Settings.\n        /// </summary>\n        public static string Label_Settings {\n            get {\n                return ResourceManager.GetString(\"Label_Settings\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Shared Model Folder Strategy.\n        /// </summary>\n        public static string Label_SharedModelFolderStrategy {\n            get {\n                return ResourceManager.GetString(\"Label_SharedModelFolderStrategy\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Model Sharing.\n        /// </summary>\n        public static string Label_SharedModelStrategyShort {\n            get {\n                return ResourceManager.GetString(\"Label_SharedModelStrategyShort\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Show.\n        /// </summary>\n        public static string Label_Show {\n            get {\n                return ResourceManager.GetString(\"Label_Show\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Show Model Images.\n        /// </summary>\n        public static string Label_ShowModelImages {\n            get {\n                return ResourceManager.GetString(\"Label_ShowModelImages\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Show NSFW Content.\n        /// </summary>\n        public static string Label_ShowNsfwContent {\n            get {\n                return ResourceManager.GetString(\"Label_ShowNsfwContent\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Show NSFW Images.\n        /// </summary>\n        public static string Label_ShowNsfwImages {\n            get {\n                return ResourceManager.GetString(\"Label_ShowNsfwImages\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Show pixel grid at high zoom levels.\n        /// </summary>\n        public static string Label_ShowPixelGridAtHighZoomLevels {\n            get {\n                return ResourceManager.GetString(\"Label_ShowPixelGridAtHighZoomLevels\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Show Unsupported Python Versions.\n        /// </summary>\n        public static string Label_ShowUnsupportedPythonVersions {\n            get {\n                return ResourceManager.GetString(\"Label_ShowUnsupportedPythonVersions\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Skip first-time setup.\n        /// </summary>\n        public static string Label_SkipSetup {\n            get {\n                return ResourceManager.GetString(\"Label_SkipSetup\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Sort.\n        /// </summary>\n        public static string Label_Sort {\n            get {\n                return ResourceManager.GetString(\"Label_Sort\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Stability Matrix.\n        /// </summary>\n        public static string Label_StabilityMatrix {\n            get {\n                return ResourceManager.GetString(\"Label_StabilityMatrix\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Stability Matrix is already running.\n        /// </summary>\n        public static string Label_StabilityMatrixAlreadyRunning {\n            get {\n                return ResourceManager.GetString(\"Label_StabilityMatrixAlreadyRunning\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Status.\n        /// </summary>\n        public static string Label_Status {\n            get {\n                return ResourceManager.GetString(\"Label_Status\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Steps.\n        /// </summary>\n        public static string Label_Steps {\n            get {\n                return ResourceManager.GetString(\"Label_Steps\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Steps - Base.\n        /// </summary>\n        public static string Label_StepsBase {\n            get {\n                return ResourceManager.GetString(\"Label_StepsBase\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Steps - Refiner.\n        /// </summary>\n        public static string Label_StepsRefiner {\n            get {\n                return ResourceManager.GetString(\"Label_StepsRefiner\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Strength.\n        /// </summary>\n        public static string Label_Strength {\n            get {\n                return ResourceManager.GetString(\"Label_Strength\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to System.\n        /// </summary>\n        public static string Label_System {\n            get {\n                return ResourceManager.GetString(\"Label_System\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to System Information.\n        /// </summary>\n        public static string Label_SystemInformation {\n            get {\n                return ResourceManager.GetString(\"Label_SystemInformation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to System Settings.\n        /// </summary>\n        public static string Label_SystemSettings {\n            get {\n                return ResourceManager.GetString(\"Label_SystemSettings\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Tags.\n        /// </summary>\n        public static string Label_Tags {\n            get {\n                return ResourceManager.GetString(\"Label_Tags\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Text to Image.\n        /// </summary>\n        public static string Label_TextToImage {\n            get {\n                return ResourceManager.GetString(\"Label_TextToImage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Theme.\n        /// </summary>\n        public static string Label_Theme {\n            get {\n                return ResourceManager.GetString(\"Label_Theme\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Period.\n        /// </summary>\n        public static string Label_TimePeriod {\n            get {\n                return ResourceManager.GetString(\"Label_TimePeriod\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Auto-Scroll to End.\n        /// </summary>\n        public static string Label_ToggleAutoScrolling {\n            get {\n                return ResourceManager.GetString(\"Label_ToggleAutoScrolling\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Trained Words.\n        /// </summary>\n        public static string Label_TrainedWords {\n            get {\n                return ResourceManager.GetString(\"Label_TrainedWords\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Trigger words:.\n        /// </summary>\n        public static string Label_TriggerWords {\n            get {\n                return ResourceManager.GetString(\"Label_TriggerWords\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to An unexpected error occurred.\n        /// </summary>\n        public static string Label_UnexpectedErrorOccurred {\n            get {\n                return ResourceManager.GetString(\"Label_UnexpectedErrorOccurred\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Unknown Package.\n        /// </summary>\n        public static string Label_UnknownPackage {\n            get {\n                return ResourceManager.GetString(\"Label_UnknownPackage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to You may encounter problems with some packages when using unsupported Python versions.\n        /// </summary>\n        public static string Label_UnsupportedPythonVersionDetails {\n            get {\n                return ResourceManager.GetString(\"Label_UnsupportedPythonVersionDetails\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to This will show all available Python versions, including those that are not supported by Stability Matrix. Are you sure?.\n        /// </summary>\n        public static string Label_UnsupportedPythonVersionWarningDescription {\n            get {\n                return ResourceManager.GetString(\"Label_UnsupportedPythonVersionWarningDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Update Available.\n        /// </summary>\n        public static string Label_UpdateAvailable {\n            get {\n                return ResourceManager.GetString(\"Label_UpdateAvailable\", resourceCulture);\n            }\n        }\n\n        /// <summary>\n        ///   Looks up a localized string similar to Early Access Update Available.\n        /// </summary>\n        public static string Label_EarlyAccessUpdateAvailable {\n            get {\n                return ResourceManager.GetString(\"Label_EarlyAccessUpdateAvailable\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Updates.\n        /// </summary>\n        public static string Label_Updates {\n            get {\n                return ResourceManager.GetString(\"Label_Updates\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to For technical users. Be the first to access our Development builds from feature branches as soon as they are available. There may be some rough edges and bugs as we experiment with new features..\n        /// </summary>\n        public static string Label_UpdatesDevChannelDescription {\n            get {\n                return ResourceManager.GetString(\"Label_UpdatesDevChannelDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to For early adopters. Preview builds will be more reliable than those from the Dev channel, and will be available closer to stable releases. Your feedback will help us greatly in discovering issues and polishing design elements..\n        /// </summary>\n        public static string Label_UpdatesPreviewChannelDescription {\n            get {\n                return ResourceManager.GetString(\"Label_UpdatesPreviewChannelDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Upscale.\n        /// </summary>\n        public static string Label_Upscale {\n            get {\n                return ResourceManager.GetString(\"Label_Upscale\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Usage data.\n        /// </summary>\n        public static string Label_UsageData {\n            get {\n                return ResourceManager.GetString(\"Label_UsageData\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Username.\n        /// </summary>\n        public static string Label_Username {\n            get {\n                return ResourceManager.GetString(\"Label_Username\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Use Separate Prompt.\n        /// </summary>\n        public static string Label_UseSeparatePrompt {\n            get {\n                return ResourceManager.GetString(\"Label_UseSeparatePrompt\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Output Sharing.\n        /// </summary>\n        public static string Label_UseSharedOutputFolder {\n            get {\n                return ResourceManager.GetString(\"Label_UseSharedOutputFolder\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to VAE.\n        /// </summary>\n        public static string Label_VAE {\n            get {\n                return ResourceManager.GetString(\"Label_VAE\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Version.\n        /// </summary>\n        public static string Label_Version {\n            get {\n                return ResourceManager.GetString(\"Label_Version\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Version Name.\n        /// </summary>\n        public static string Label_VersionName {\n            get {\n                return ResourceManager.GetString(\"Label_VersionName\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Version Type.\n        /// </summary>\n        public static string Label_VersionType {\n            get {\n                return ResourceManager.GetString(\"Label_VersionType\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Method.\n        /// </summary>\n        public static string Label_VideoOutputMethod {\n            get {\n                return ResourceManager.GetString(\"Label_VideoOutputMethod\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Quality.\n        /// </summary>\n        public static string Label_VideoQuality {\n            get {\n                return ResourceManager.GetString(\"Label_VideoQuality\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to View.\n        /// </summary>\n        public static string Label_View {\n            get {\n                return ResourceManager.GetString(\"Label_View\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Waiting to connect....\n        /// </summary>\n        public static string Label_WaitingToConnectEllipsis {\n            get {\n                return ResourceManager.GetString(\"Label_WaitingToConnectEllipsis\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Web UI.\n        /// </summary>\n        public static string Label_WebUi {\n            get {\n                return ResourceManager.GetString(\"Label_WebUi\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Width.\n        /// </summary>\n        public static string Label_Width {\n            get {\n                return ResourceManager.GetString(\"Label_Width\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Wildcards.\n        /// </summary>\n        public static string Label_Wildcards {\n            get {\n                return ResourceManager.GetString(\"Label_Wildcards\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Not yet available.\n        /// </summary>\n        public static string Label_WipFeature {\n            get {\n                return ResourceManager.GetString(\"Label_WipFeature\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Feature will be available in a future update.\n        /// </summary>\n        public static string Label_WipFeatureDescription {\n            get {\n                return ResourceManager.GetString(\"Label_WipFeatureDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Workflow Browser.\n        /// </summary>\n        public static string Label_WorkflowBrowser {\n            get {\n                return ResourceManager.GetString(\"Label_WorkflowBrowser\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Workflow Deleted.\n        /// </summary>\n        public static string Label_WorkflowDeleted {\n            get {\n                return ResourceManager.GetString(\"Label_WorkflowDeleted\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to {0} deleted successfully.\n        /// </summary>\n        public static string Label_WorkflowDeletedSuccessfully {\n            get {\n                return ResourceManager.GetString(\"Label_WorkflowDeletedSuccessfully\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Workflow Description.\n        /// </summary>\n        public static string Label_WorkflowDescription {\n            get {\n                return ResourceManager.GetString(\"Label_WorkflowDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to The workflow and custom nodes have been imported..\n        /// </summary>\n        public static string Label_WorkflowImportComplete {\n            get {\n                return ResourceManager.GetString(\"Label_WorkflowImportComplete\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Workflow Imported.\n        /// </summary>\n        public static string Label_WorkflowImported {\n            get {\n                return ResourceManager.GetString(\"Label_WorkflowImported\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Workflows.\n        /// </summary>\n        public static string Label_Workflows {\n            get {\n                return ResourceManager.GetString(\"Label_Workflows\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to You&apos;re up to date.\n        /// </summary>\n        public static string Label_YouAreUpToDate {\n            get {\n                return ResourceManager.GetString(\"Label_YouAreUpToDate\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Active.\n        /// </summary>\n        public static string ModelDiscovery_Active {\n            get {\n                return ResourceManager.GetString(\"ModelDiscovery_Active\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Experience faster search results when browsing models from online repositories, such as Civitai..\n        /// </summary>\n        public static string ModelDiscovery_Description {\n            get {\n                return ResourceManager.GetString(\"ModelDiscovery_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Experimental optimization for third-party repositories. Not officially affiliated; availability may vary..\n        /// </summary>\n        public static string ModelDiscovery_Disclaimer {\n            get {\n                return ResourceManager.GetString(\"ModelDiscovery_Disclaimer\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Inactive (Using standard connection).\n        /// </summary>\n        public static string ModelDiscovery_Inactive {\n            get {\n                return ResourceManager.GetString(\"ModelDiscovery_Inactive\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Accelerated Model Discovery.\n        /// </summary>\n        public static string ModelDiscovery_Title {\n            get {\n                return ResourceManager.GetString(\"ModelDiscovery_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Download complete.\n        /// </summary>\n        public static string Progress_DownloadComplete {\n            get {\n                return ResourceManager.GetString(\"Progress_DownloadComplete\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Downloading package....\n        /// </summary>\n        public static string Progress_DownloadingPackage {\n            get {\n                return ResourceManager.GetString(\"Progress_DownloadingPackage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Installation complete.\n        /// </summary>\n        public static string Progress_InstallationComplete {\n            get {\n                return ResourceManager.GetString(\"Progress_InstallationComplete\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Installing package requirements....\n        /// </summary>\n        public static string Progress_InstallingPackageRequirements {\n            get {\n                return ResourceManager.GetString(\"Progress_InstallingPackageRequirements\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Installing prerequisites....\n        /// </summary>\n        public static string Progress_InstallingPrerequisites {\n            get {\n                return ResourceManager.GetString(\"Progress_InstallingPrerequisites\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Uninstalling package....\n        /// </summary>\n        public static string Progress_UninstallingPackage {\n            get {\n                return ResourceManager.GetString(\"Progress_UninstallingPackage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Update complete.\n        /// </summary>\n        public static string Progress_UpdateComplete {\n            get {\n                return ResourceManager.GetString(\"Progress_UpdateComplete\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Update failed.\n        /// </summary>\n        public static string Progress_UpdateFailed {\n            get {\n                return ResourceManager.GetString(\"Progress_UpdateFailed\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to ### 🪄 Introducing: Prompt Amplifier  \n        ///Our AI assistant, powered by our experimental Spark model, will generate creative expansions of your prompt.\n        ///\n        ///The Prompt Amplifier runs in our secure, enterprise-grade cloud environment—it does **not** run locally on your machine.\n        ///\n        ///### ☁️️ Why Cloud?  \n        ///The Spark model operates at a scale comparable to trillion-parameter foundation models, requiring substantial computational power. While we’re committed to maximizing locally-runnable features, Spark’s advanced cap [rest of string was truncated]&quot;;.\n        /// </summary>\n        public static string PromptAmplifier_Disclaimer {\n            get {\n                return ResourceManager.GetString(\"PromptAmplifier_Disclaimer\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to If you&apos;re already supporting us on Patreon, please link your account to continue..\n        /// </summary>\n        public static string Sponsorship_ConnectPatreonPrompt {\n            get {\n                return ResourceManager.GetString(\"Sponsorship_ConnectPatreonPrompt\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Thank you for being a Stability Matrix supporter!.\n        /// </summary>\n        public static string Sponsorship_ExistingSupporterPreamble {\n            get {\n                return ResourceManager.GetString(\"Sponsorship_ExistingSupporterPreamble\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Features like **{0}** are one of many benefits available to our supporters. Your contribution helps us cover server costs and supports Stability Matrix development..\n        /// </summary>\n        public static string Sponsorship_ReqAnyTier {\n            get {\n                return ResourceManager.GetString(\"Sponsorship_ReqAnyTier\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Features like **{0}** are available at the **{1}** supporter level (or higher). Your contribution helps us cover server costs for more advanced connected features and allows us to keep improving Stability Matrix for everyone..\n        /// </summary>\n        public static string Sponsorship_ReqSpecificTier {\n            get {\n                return ResourceManager.GetString(\"Sponsorship_ReqSpecificTier\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Support Stability Matrix.\n        /// </summary>\n        public static string Sponsorship_Title {\n            get {\n                return ResourceManager.GetString(\"Sponsorship_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Installed Workflows.\n        /// </summary>\n        public static string TabLabel_InstalledWorkflows {\n            get {\n                return ResourceManager.GetString(\"TabLabel_InstalledWorkflows\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Add a package to get started!.\n        /// </summary>\n        public static string TeachingTip_AddPackageToGetStarted {\n            get {\n                return ResourceManager.GetString(\"TeachingTip_AddPackageToGetStarted\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Click Launch to get started!.\n        /// </summary>\n        public static string TeachingTip_ClickLaunchToGetStarted {\n            get {\n                return ResourceManager.GetString(\"TeachingTip_ClickLaunchToGetStarted\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Check the progress of your package installations and model downloads here..\n        /// </summary>\n        public static string TeachingTip_DownloadsExplanation {\n            get {\n                return ResourceManager.GetString(\"TeachingTip_DownloadsExplanation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Click here to review prompt syntax and how to include Lora / Embeddings..\n        /// </summary>\n        public static string TeachingTip_InferencePromptHelpButton {\n            get {\n                return ResourceManager.GetString(\"TeachingTip_InferencePromptHelpButton\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Additional folders such as IPAdapters and TextualInversions (embeddings) can be enabled here.\n        /// </summary>\n        public static string TeachingTip_MoreCheckpointCategories {\n            get {\n                return ResourceManager.GetString(\"TeachingTip_MoreCheckpointCategories\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Try out the new Prompt Amplifier! Enhance your prompts for better results!.\n        /// </summary>\n        public static string TeachingTip_PromptAmplifier {\n            get {\n                return ResourceManager.GetString(\"TeachingTip_PromptAmplifier\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to The &apos;Open Web UI&apos; button has moved to the command bar.\n        /// </summary>\n        public static string TeachingTip_WebUiButtonMoved {\n            get {\n                return ResourceManager.GetString(\"TeachingTip_WebUiButtonMoved\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Please allow your browser to open this app when prompted to continue..\n        /// </summary>\n        public static string Text_AllowBrowserOpenAppLink {\n            get {\n                return ResourceManager.GetString(\"Text_AllowBrowserOpenAppLink\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to The data sent is never associated with you or your account and will not include personal data or any sensitive information..\n        /// </summary>\n        public static string Text_AnalyticsDataPrivacyInfo {\n            get {\n                return ResourceManager.GetString(\"Text_AnalyticsDataPrivacyInfo\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Help us improve Stability Matrix by sending anonymous data about features used, operating system versions, types of packages installed, etc. The data sent is never associated with you or your account and will not include personal data or any sensitive information..\n        /// </summary>\n        public static string Text_AnalyticsDescription {\n            get {\n                return ResourceManager.GetString(\"Text_AnalyticsDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Help us improve Stability Matrix by sending anonymous data about features used, operating system versions, types of packages installed, etc..\n        /// </summary>\n        public static string Text_AnalyticsDescriptionShort {\n            get {\n                return ResourceManager.GetString(\"Text_AnalyticsDescriptionShort\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to The app will relaunch after updating.\n        /// </summary>\n        public static string Text_AppWillRelaunchAfterUpdate {\n            get {\n                return ResourceManager.GetString(\"Text_AppWillRelaunchAfterUpdate\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to You are about to delete the following items:.\n        /// </summary>\n        public static string Text_DeleteFollowingItems {\n            get {\n                return ResourceManager.GetString(\"Text_DeleteFollowingItems\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Sign in with your Lykos Account to use connected features..\n        /// </summary>\n        public static string Text_Login_ConnectDescription {\n            get {\n                return ResourceManager.GetString(\"Text_Login_ConnectDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Connect your Lykos Account.\n        /// </summary>\n        public static string Text_Login_ConnectTitle {\n            get {\n                return ResourceManager.GetString(\"Text_Login_ConnectTitle\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Your login has expired. Please sign in again to continue..\n        /// </summary>\n        public static string Text_Login_ExpiredDescription {\n            get {\n                return ResourceManager.GetString(\"Text_Login_ExpiredDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Please Sign In Again.\n        /// </summary>\n        public static string Text_Login_ExpiredTitle {\n            get {\n                return ResourceManager.GetString(\"Text_Login_ExpiredTitle\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to ## Heads Up: Lykos Account Sign-Out &amp; Security Upgrade\n        ///\n        ///We&apos;ve made some important behind-the-scenes improvements to how Stability Matrix handles Lykos accounts, upgrading to a more secure and convenient login system **(OAuth 2.0 with OpenID Connect)**. Because of this, you&apos;ve been signed out of your Lykos account.\n        ///\n        ///### Why the change?\n        ///\n        ///Your security and privacy are important to us. This upgrade brings:\n        ///\n        ///*   **Streamlined Experience:** Sign in once to [account.lykos.ai](https://account.lykos.ai) to c [rest of string was truncated]&quot;;.\n        /// </summary>\n        public static string Text_LykosAccountUpgradeNotice {\n            get {\n                return ResourceManager.GetString(\"Text_LykosAccountUpgradeNotice\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open the link in your browser and enter the following code to authorize your account with Stability Matrix..\n        /// </summary>\n        public static string Text_OAuthDeviceAuthDescription {\n            get {\n                return ResourceManager.GetString(\"Text_OAuthDeviceAuthDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open the link in your browser and follow the instructions to connect your account..\n        /// </summary>\n        public static string Text_OAuthLoginDescription {\n            get {\n                return ResourceManager.GetString(\"Text_OAuthLoginDescription\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Choose your preferred interface to get started.\n        /// </summary>\n        public static string Text_OneClickInstaller_SubHeader {\n            get {\n                return ResourceManager.GetString(\"Text_OneClickInstaller_SubHeader\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to This will delete the package folder and all its contents, including any generated images and files you may have added..\n        /// </summary>\n        public static string Text_PackageUninstall_Details {\n            get {\n                return ResourceManager.GetString(\"Text_PackageUninstall_Details\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Proceeding to Launch page.\n        /// </summary>\n        public static string Text_ProceedingToLaunchPage {\n            get {\n                return ResourceManager.GetString(\"Text_ProceedingToLaunchPage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Relaunch is required for new language option to take effect.\n        /// </summary>\n        public static string Text_RelaunchRequiredToApplyLanguage {\n            get {\n                return ResourceManager.GetString(\"Text_RelaunchRequiredToApplyLanguage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to A restart may be required for system changes to take effect..\n        /// </summary>\n        public static string Text_RestartMayBeRequiredForSystemChanges {\n            get {\n                return ResourceManager.GetString(\"Text_RestartMayBeRequiredForSystemChanges\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Some files could not be deleted. Please close any open files in the package directory and try again..\n        /// </summary>\n        public static string Text_SomeFilesCouldNotBeDeleted {\n            get {\n                return ResourceManager.GetString(\"Text_SomeFilesCouldNotBeDeleted\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Please let us know about this issue with the details below and attach the zipped log files..\n        /// </summary>\n        public static string Text_UnexpectedError_Description {\n            get {\n                return ResourceManager.GetString(\"Text_UnexpectedError_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to You can continue, but full functionality will be available after restart. Please let us know about this issue with the details below and attach the zipped log files..\n        /// </summary>\n        public static string Text_UnexpectedErrorRecoverable_Description {\n            get {\n                return ResourceManager.GetString(\"Text_UnexpectedErrorRecoverable_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Welcome to Stability Matrix!.\n        /// </summary>\n        public static string Text_WelcomeToStabilityMatrix {\n            get {\n                return ResourceManager.GetString(\"Text_WelcomeToStabilityMatrix\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to You are about to delete the following {0} items:.\n        /// </summary>\n        public static string TextTemplate_DeleteFollowingCountItems {\n            get {\n                return ResourceManager.GetString(\"TextTemplate_DeleteFollowingCountItems\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Error updating {0}.\n        /// </summary>\n        public static string TextTemplate_ErrorUpdatingPackage {\n            get {\n                return ResourceManager.GetString(\"TextTemplate_ErrorUpdatingPackage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Last checked: {0}.\n        /// </summary>\n        public static string TextTemplate_LastChecked {\n            get {\n                return ResourceManager.GetString(\"TextTemplate_LastChecked\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Sign in with {0}.\n        /// </summary>\n        public static string TextTemplate_OAuthLoginTitle {\n            get {\n                return ResourceManager.GetString(\"TextTemplate_OAuthLoginTitle\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to {0} has been updated to the latest version.\n        /// </summary>\n        public static string TextTemplate_PackageUpdatedToLatest {\n            get {\n                return ResourceManager.GetString(\"TextTemplate_PackageUpdatedToLatest\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to {0} has been updated to the selected version.\n        /// </summary>\n        public static string TextTemplate_PackageUpdatedToSelected {\n            get {\n                return ResourceManager.GetString(\"TextTemplate_PackageUpdatedToSelected\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Updating {0}.\n        /// </summary>\n        public static string TextTemplate_UpdatingPackage {\n            get {\n                return ResourceManager.GetString(\"TextTemplate_UpdatingPackage\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to You can always change this behavior in {0}..\n        /// </summary>\n        public static string TextTemplate_YouCanChangeThisBehavior {\n            get {\n                return ResourceManager.GetString(\"TextTemplate_YouCanChangeThisBehavior\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to When enabled, these settings will be applied automatically when this model is selected in the Inference tab.\n        /// </summary>\n        public static string Tooltip_InferenceDefaults {\n            get {\n                return ResourceManager.GetString(\"Tooltip_InferenceDefaults\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Package name cannot be empty.\n        /// </summary>\n        public static string Validation_PackageNameCannotBeEmpty {\n            get {\n                return ResourceManager.GetString(\"Validation_PackageNameCannotBeEmpty\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Package named &apos;{0}&apos; already exists.\n        /// </summary>\n        public static string ValidationError_PackageExists {\n            get {\n                return ResourceManager.GetString(\"ValidationError_PackageExists\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to PLEASE EXTRACT THE APP FROM THE ZIP FILE BEFORE RUNNING STABILITY MATRIX.\n        /// </summary>\n        public static string Warning_PleaseExtractFirst {\n            get {\n                return ResourceManager.GetString(\"Warning_PleaseExtractFirst\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Enter package name.\n        /// </summary>\n        public static string Watermark_EnterPackageName {\n            get {\n                return ResourceManager.GetString(\"Watermark_EnterPackageName\", resourceCulture);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.cs-CZ.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>Přidat pro všechny uživatele</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>Přidat pro aktuálního uživatele</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>Přidat balíček</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>Zrušit</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>Zkontrolovat aktualizace</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>Zkontrolovat verzi</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>Zrušit výběr</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>Zavřít</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>Připojit</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>Konsolidovat</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>Pokračovat</value>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>Kopírovat</value>\n  </data>\n  <data name=\"Action_CopyAndOpen\" xml:space=\"preserve\">\n    <value>Kopírovat a otevřít</value>\n  </data>\n  <data name=\"Action_CopyAsBitmap\" xml:space=\"preserve\">\n    <value>Kopírovat jako bitmapu</value>\n  </data>\n  <data name=\"Action_CopyDetails\" xml:space=\"preserve\">\n    <value>Kopírovat detaily</value>\n  </data>\n  <data name=\"Action_CopyTriggerWords\" xml:space=\"preserve\">\n    <value>Kopírovat spouštěcí slova</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>Smazat</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>Zakázat</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>Odpojit</value>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>Downgradovat</value>\n  </data>\n  <data name=\"Action_Download\" xml:space=\"preserve\">\n    <value>Stáhnout</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>Upravit</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>Povolit</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>Ukončit aplikaci</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>Přejít do Nastavení</value>\n  </data>\n  <data name=\"Action_Hide\" xml:space=\"preserve\">\n    <value>Skrýt</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>Importovat</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>Instalovat</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>Instalovat nyní</value>\n  </data>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>Spustit</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>Přihlásit se</value>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>Možná později</value>\n  </data>\n  <data name=\"Action_MoveToTrash\" xml:space=\"preserve\">\n    <value>Přesunout do koše</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>Nový</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>OK</value>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>Otevřít na GitHubu</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>Otevřít v prohlížeči</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>Otevřít v Průzkumníkovi</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>Otevřít ve Finderu</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>Otevřít v prohlížeči obrázků</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>Otevřít na CivitAI</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>Otevřít na Hugging Face</value>\n  </data>\n  <data name=\"Action_OpenOnOpenArt\" xml:space=\"preserve\">\n    <value>Otevřít na OpenArt</value>\n  </data>\n  <data name=\"Action_OpenOnOpenModelDb\" xml:space=\"preserve\">\n    <value>Otevřít na OpenModelDB</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>Otevřít projekt...</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Otevřít webové UI</value>\n  </data>\n  <data name=\"Action_PreviewPreprocessor\" xml:space=\"preserve\">\n    <value>Náhled preprocesoru</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>Ukončit</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>Obnovit</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>Restartovat</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>Restartovat později</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>Připomenout později</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>Odebrat</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>Přejmenovat</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>Nahradit obsah</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>Restartovat</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>Obnovit výchozí rozložení</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>Zkusit znovu</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>Uložit</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>Uložit jako...</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>Hledat</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>Vybrat vše</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>Vybrat adresář</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>Vybrat soubor</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>Odeslat</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>Odeslat vstup</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>Odeslat do Inference</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>Zobrazit v Průzkumníkovi</value>\n  </data>\n  <data name=\"Action_ShowLogInExplorer\" xml:space=\"preserve\">\n    <value>Zobrazit záznam v Průzkumníkovi</value>\n  </data>\n  <data name=\"Action_ShowLogInFinder\" xml:space=\"preserve\">\n    <value>Zobrazit záznam ve Finderu</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>Zaregistrovat se</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>Zastavit</value>\n  </data>\n  <data name=\"Action_ToggleVisibility\" xml:space=\"preserve\">\n    <value>Přepnout viditelnost</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>Odinstalovat</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>Aktualizovat</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>Aktualizovat stávající metadata</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>Upgradovat</value>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>Zobrazit možnosti podpory</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>Ano</value>\n  </data>\n  <data name=\"AnalyticsExample_InstallData\" xml:space=\"preserve\">\n    <value>{\n  &quot;packageName&quot;: &quot;stable-diffusion-webui&quot;,\n  &quot;packageVersion&quot;: &quot;v1.10.0&quot;,\n  &quot;isSuccess&quot;: true,\n  &quot;type&quot;: &quot;install&quot;,\n  &quot;timestamp&quot;: &quot;2024-09-04T02:14:04.1967404+00:00&quot;\n}</value>\n  </data>\n  <data name=\"Description_RenamePackage\" xml:space=\"preserve\">\n    <value>Zadejte nový název pro &apos;{0}&apos;</value>\n  </data>\n  <data name=\"Error_PleaseSelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Vyberte prosím umístění pro stažení.</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>O aplikaci</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>Účty</value>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>Nastavení účtu</value>\n  </data>\n  <data name=\"Label_ActionCannotBeUndone\" xml:space=\"preserve\">\n    <value>Tuto akci nelze vrátit zpět.</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>Doplňky</value>\n    <comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_AddToExistingPack\" xml:space=\"preserve\">\n    <value>Přidat do existující sady</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>Přidat Stability Matrix do nabídky Start</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>Použije aktuální umístění aplikace, můžete spustit znovu, pokud aplikaci přesunete</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>Pokročilé možnosti</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>Všechny verze</value>\n  </data>\n  <data name=\"Label_Analytics\" xml:space=\"preserve\">\n    <value>Analytika</value>\n  </data>\n  <data name=\"Label_AnotherInstanceAlreadyRunning\" xml:space=\"preserve\">\n    <value>Jiná instance Stability Matrix je již spuštěna. Před spuštěním nové ji prosím zavřete.</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>API klíč</value>\n  </data>\n  <data name=\"Label_AppData\" xml:space=\"preserve\">\n    <value>Data aplikace</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>Vzhled</value>\n  </data>\n  <data name=\"Label_AppFolders\" xml:space=\"preserve\">\n    <value>Adresáře aplikace</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>Jste si jisti?</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteImages\" xml:space=\"preserve\">\n    <value>Jste si jisti, že chcete smazat {0} obrázků?</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteModels\" xml:space=\"preserve\">\n    <value>Jste si jisti, že chcete smazat {0} modelů?</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>Úroveň augmentace</value>\n  </data>\n  <data name=\"Label_Author\" xml:space=\"preserve\">\n    <value>Autor</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>Automatické doplňování</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>Automaticky rolovat na konec výstupu konzole</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad\" xml:space=\"preserve\">\n    <value>Automatické hledání při načtení</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad_Description\" xml:space=\"preserve\">\n    <value>Automaticky zahájit hledání při načtení stránky prohlížeče modelů</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>Automatické aktualizace</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>Základní model</value>\n  </data>\n  <data name=\"Label_Batches\" xml:space=\"preserve\">\n    <value>Dávky</value>\n  </data>\n  <data name=\"Label_BatchIndex\" xml:space=\"preserve\">\n    <value>Index dávky</value>\n  </data>\n  <data name=\"Label_BatchSize\" xml:space=\"preserve\">\n    <value>Velikost dávky</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>Staňte se patronem</value>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>Beta</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>Větev</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>Větve</value>\n  </data>\n  <data name=\"Label_BulkDownloadStarted\" xml:space=\"preserve\">\n    <value>Hromadné stahování zahájeno</value>\n  </data>\n  <data name=\"Label_BulkDownloadStartedMessage\" xml:space=\"preserve\">\n    <value>{0} souborů se začalo stahovat. Průběh sledujte v záložce Stahování.</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>Zásobník volání</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>Kategorie</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>CFG Scale</value>\n  </data>\n  <data name=\"Label_ChangesApplied\" xml:space=\"preserve\">\n    <value>Změny byly aplikovány</value>\n  </data>\n  <data name=\"Label_CheckingHardware\" xml:space=\"preserve\">\n    <value>Kontrolujeme některé hardwarové specifikace pro určení kompatibility.</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>Správce Checkpointů</value>\n  </data>\n  <data name=\"Label_Checkpoints\" xml:space=\"preserve\">\n    <value>Checkpointy</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>Pro stažení tohoto Checkpointu musíte být přihlášeni. Zadejte prosím API klíč pro CivitAI v nastavení.</value>\n  </data>\n  <data name=\"Label_ClippingMask\" xml:space=\"preserve\">\n    <value>Ořezová maska</value>\n  </data>\n  <data name=\"Label_CLIPSkip\" xml:space=\"preserve\">\n    <value>CLIP Skip</value>\n  </data>\n  <data name=\"Label_CLIPStrength\" xml:space=\"preserve\">\n    <value>Síla CLIP</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>Po dokončení zavřít dialogové okno</value>\n  </data>\n  <data name=\"Label_ComfyRequiredDetail\" xml:space=\"preserve\">\n    <value>Pro instalaci tohoto balíčku je vyžadováno ComfyUI. Chcete jej nyní nainstalovat?</value>\n  </data>\n  <data name=\"Label_ComfyRequiredTitle\" xml:space=\"preserve\">\n    <value>Vyžadováno ComfyUI</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>Komentáře</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>Revize</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>Při vkládání doplňků nahradit podtržítka mezerami</value>\n  </data>\n  <data name=\"Label_Config\" xml:space=\"preserve\">\n    <value>Konfigurace</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>Potvrdit smazání</value>\n  </data>\n  <data name=\"Label_ConfirmExit\" xml:space=\"preserve\">\n    <value>Potvrdit ukončení</value>\n  </data>\n  <data name=\"Label_ConfirmExitDetail\" xml:space=\"preserve\">\n    <value>Opravdu si přejete aplikaci ukončit? Tím se také uzavřou všechny aktuálně spuštěné balíčky.</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>Potvrdit heslo</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>Potvrdit?</value>\n  </data>\n  <data name=\"Label_ConnectAccountFailed\" xml:space=\"preserve\">\n    <value>Nepodařilo se nám připojit váš účet</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>Připojeno</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>Připojený model</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>Připojování...</value>\n  </data>\n  <data name=\"Label_Console\" xml:space=\"preserve\">\n    <value>Konzole</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>Tímto přesunete všechny vygenerované obrázky z vybraných balíčků do konsolidovaného adresáře ve sdíleném výstupním adresáři. Tuto akci nelze vrátit zpět.</value>\n  </data>\n  <data name=\"Label_ControlSteps\" xml:space=\"preserve\">\n    <value>Kroky ControlNetu</value>\n  </data>\n  <data name=\"Label_ControlWeight\" xml:space=\"preserve\">\n    <value>Váha ControlNetu</value>\n  </data>\n  <data name=\"Label_CopyLinkToClipboard\" xml:space=\"preserve\">\n    <value>Kopírovat odkaz do schránky</value>\n  </data>\n  <data name=\"Label_CreateExtensionPackExplanation\" xml:space=\"preserve\">\n    <value>Pro vytvoření sady jednoduše vyberte požadovaná rozšíření z karty &apos;Dostupná rozšíření&apos; nebo &apos;Nainstalovaná rozšíření&apos; a klikněte na &apos;Uložit&apos;</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>Aktuální adresář:</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>Adresář s daty</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>Zde budou instalována data aplikace (modelové Checkpointy, webová UI atd.).</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>Název adresáře s daty</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>Data poskytuje CivitAI</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>Potlačení</value>\n  </data>\n  <data name=\"Label_DeletePermanently\" xml:space=\"preserve\">\n    <value>Smazat trvale</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Síla odšumění</value>\n  </data>\n  <data name=\"Label_DependencySpecifiers\" xml:space=\"preserve\">\n    <value>Specifikátory závislostí</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>Detaily</value>\n  </data>\n  <data name=\"Label_DisableUpdateCheck\" xml:space=\"preserve\">\n    <value>Vypnout kontrolu aktualizace</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Discord Rich Presence</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>Zobrazovaný název</value>\n  </data>\n  <data name=\"Label_DownloadAllFilesAllVersions\" xml:space=\"preserve\">\n    <value>Stáhnout všechny soubory (všechny verze)</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>Stažení se nezdařilo</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>Stahování</value>\n  </data>\n  <data name=\"Label_DownloadStarted\" xml:space=\"preserve\">\n    <value>Stahování zahájeno</value>\n  </data>\n  <data name=\"Label_DownloadWillBeSavedToLocation\" xml:space=\"preserve\">\n    <value>{0} bude uložen do {1}</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>Přetáhněte Checkpointy sem pro import</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>Přetáhněte soubor sem pro import</value>\n  </data>\n  <data name=\"Label_EarlyAccessModels\" xml:space=\"preserve\">\n    <value>Modely s předběžným přístupem</value>\n  </data>\n  <data name=\"Label_EditModelMetadata\" xml:space=\"preserve\">\n    <value>Upravit metadata modelu</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>E-mail</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>Integrovaný Python</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>Embeddings / Textual Inversion</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>Zvýraznění</value>\n  </data>\n  <data name=\"Label_EnableLongPaths\" xml:space=\"preserve\">\n    <value>Povolit dlouhé cesty</value>\n    <comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_EnableLongPathsDescription\" xml:space=\"preserve\">\n    <value>Odstranit omezení MAX_PATH z běžných funkcí Win32 pro soubory a adresáře</value>\n    <comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>Proměnné prostředí</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>Název</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>Hodnota</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>Chyba při instalaci balíčku</value>\n  </data>\n  <data name=\"Label_ErrorRetrievingWorkflows\" xml:space=\"preserve\">\n    <value>Chyba při načítání Workflows</value>\n  </data>\n  <data name=\"Label_EverythingLooksGood\" xml:space=\"preserve\">\n    <value>Všechno vypadá v pořádku!</value>\n  </data>\n  <data name=\"Label_ExtensionPacks\" xml:space=\"preserve\">\n    <value>Sady rozšíření</value>\n  </data>\n  <data name=\"Label_ExtraNetworks\" xml:space=\"preserve\">\n    <value>Extra sítě (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>Při použití disku s formátem FAT32 nebo exFAT může docházet k chybám. Pro bezproblémový chod zvolte jiný disk.</value>\n  </data>\n  <data name=\"Label_FileNamePattern\" xml:space=\"preserve\">\n    <value>Vzor názvu souboru</value>\n  </data>\n  <data name=\"Label_Files\" xml:space=\"preserve\">\n    <value>Soubory</value>\n  </data>\n  <data name=\"Label_Filter\" xml:space=\"preserve\">\n    <value>Filtr</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>Najít připojená metadata</value>\n  </data>\n  <data name=\"Label_FindInModelBrowser\" xml:space=\"preserve\">\n    <value>Najít v Prohlížeči modelů</value>\n  </data>\n  <data name=\"Label_FinishedImportingWorkflow\" xml:space=\"preserve\">\n    <value>Dokončen import Workflow a vlastních uzlů</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>První stránka</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>Adresář</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>Snímků za sekundu</value>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>Snímky</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>Obecné</value>\n    <comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Hash\" xml:space=\"preserve\">\n    <value>Hash</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>Výška</value>\n  </data>\n  <data name=\"Label_HideEmptyCategories\" xml:space=\"preserve\">\n    <value>Skrýt prázdné kategorie</value>\n  </data>\n  <data name=\"Label_HistorySize\" xml:space=\"preserve\">\n    <value>Velikost historie</value>\n  </data>\n  <data name=\"Label_HistorySize_Description\" xml:space=\"preserve\">\n    <value>Počet řádků nad těmi zobrazenými v konzoli, ke kterým se můžete vrátit</value>\n  </data>\n  <data name=\"Label_HolidayMode\" xml:space=\"preserve\">\n    <value>Prázdninový režim</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_HuggingFaceLoginRequired\" xml:space=\"preserve\">\n    <value>Pro stažení tohoto Checkpointu musíte být přihlášeni. Zadejte prosím přístupový token pro Hugging Face v nastavení.</value>\n  </data>\n  <data name=\"Label_ImageHidden\" xml:space=\"preserve\">\n    <value>Obrázek skryt</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>Obrázek na obrázek</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>Obrázek na video</value>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>Prohlížeč obrázků</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>Importovat s metadaty</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>Hledat připojená metadata u nových lokálních importů</value>\n  </data>\n  <data name=\"Label_ImportExtensionPacksExplanation\" xml:space=\"preserve\">\n    <value>Přidejte soubor sady rozšíření .json do adresáře ExtensionPacks ve vašem adresáři s daty.</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>Importovat nejnovější -</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>Indexování...</value>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>Inference</value>\n    <comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_InferenceDefaultsHeader\" xml:space=\"preserve\">\n    <value>Výchozí nastavení Inference</value>\n  </data>\n  <data name=\"Label_InfiniteScrolling\" xml:space=\"preserve\">\n    <value>Nekonečné posouvání</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>Vnitřní výjimka</value>\n  </data>\n  <data name=\"Label_Inpainting\" xml:space=\"preserve\">\n    <value>Inpainting</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>Vstup</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>Vyžadován vstup</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>Instalace s tímto názvem již existuje.</value>\n  </data>\n  <data name=\"Label_Installed\" xml:space=\"preserve\">\n    <value>Nainstalováno</value>\n  </data>\n  <data name=\"Label_InstalledModels\" xml:space=\"preserve\">\n    <value>Nainstalované modely</value>\n  </data>\n  <data name=\"Label_InstallExtensionPack\" xml:space=\"preserve\">\n    <value>Instalovat sadu rozšíření</value>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>Instaluje se</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>Integrace</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>Neplatný typ balíčku</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Připojit se na Discord server</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>Jazyk</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>Poslední stránka</value>\n  </data>\n  <data name=\"Label_LastUpdatedAt\" xml:space=\"preserve\">\n    <value>Poslední aktualizace</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>Začněme</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>Licence</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>licenčním ujednáním.</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>Licence a upozornění na open source</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>Lokální model</value>\n  </data>\n  <data name=\"Label_LoginRequired\" xml:space=\"preserve\">\n    <value>Pro stažení tohoto modelu je vyžadováno přihlášení</value>\n  </data>\n  <data name=\"Label_Logs\" xml:space=\"preserve\">\n    <value>Záznamy (logy)</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>Bezztrátové</value>\n  </data>\n  <data name=\"Label_MaxSize\" xml:space=\"preserve\">\n    <value>Maximální velikost</value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>Min CFG</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>Chybějící soubor obrázku</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>Model</value>\n  </data>\n  <data name=\"Label_ModelBrowser\" xml:space=\"preserve\">\n    <value>Prohlížeč modelů</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>Popis modelu</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>Hledat modely,</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>Adresář modelů</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>Typ modelu</value>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>Motion Bucket ID</value>\n  </data>\n  <data name=\"Label_NegativePrompt\" xml:space=\"preserve\">\n    <value>Negativní prompt</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>Sítě (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_NewExtensionPack\" xml:space=\"preserve\">\n    <value>Nová sada rozšíření</value>\n  </data>\n  <data name=\"Label_NewFolder\" xml:space=\"preserve\">\n    <value>Nový adresář</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>Je dostupná nová verze Stability Matrix!</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>Další obrázek</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>Další stránka</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>Ne</value>\n  </data>\n  <data name=\"Label_NodeDetails\" xml:space=\"preserve\">\n    <value>Detaily uzlu</value>\n  </data>\n  <data name=\"Label_NoExtensionPacksFound\" xml:space=\"preserve\">\n    <value>Nebyly nalezeny žádné sady rozšíření</value>\n  </data>\n  <data name=\"Label_NoExtensionsFound\" xml:space=\"preserve\">\n    <value>Nebyla nalezena žádná rozšíření.</value>\n  </data>\n  <data name=\"Label_NoImageFound\" xml:space=\"preserve\">\n    <value>Nebyl nalezen žádný obrázek</value>\n  </data>\n  <data name=\"Label_NonModelFiles\" xml:space=\"preserve\">\n    <value>Soubory, které nejsou modely</value>\n  </data>\n  <data name=\"Label_NotificationOption_None\" xml:space=\"preserve\">\n    <value>Žádné</value>\n  </data>\n  <data name=\"Label_Notifications\" xml:space=\"preserve\">\n    <value>Upozornění</value>\n  </data>\n  <data name=\"Label_NSFW\" xml:space=\"preserve\">\n    <value>NSFW</value>\n  </data>\n  <data name=\"Label_NsfwContent\" xml:space=\"preserve\">\n    <value>NSFW obsah</value>\n  </data>\n  <data name=\"Label_NumberFormat\" xml:space=\"preserve\">\n    <value>Formát čísla</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>{0} obrázků vybráno</value>\n  </data>\n  <data name=\"Label_NvidiaGpuRecommended\" xml:space=\"preserve\">\n    <value>Pro nejlepší zážitek doporučujeme GPU s podporou CUDA. Můžete pokračovat i bez ní, ale některé balíčky nemusí fungovat a inference může být pomalejší.</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>1 obrázek vybrán</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Dostupné pouze ve Windows</value>\n  </data>\n  <data name=\"Label_OpenArtBrowser\" xml:space=\"preserve\">\n    <value>Prohlížeč OpenArt</value>\n  </data>\n  <data name=\"Label_OpenExtensionPacksFolder\" xml:space=\"preserve\">\n    <value>Otevřít adresář sad rozšíření</value>\n  </data>\n  <data name=\"Label_OrWithHyphensAround\" xml:space=\"preserve\">\n    <value>- nebo -</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>Výstupní adresář</value>\n  </data>\n  <data name=\"Label_OutputImageFiles\" xml:space=\"preserve\">\n    <value>Výstupní soubory obrázků</value>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>Prohlížeč výstupů</value>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>Typ výstupu</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>Prostředí balíčku</value>\n  </data>\n  <data name=\"Label_Packages\" xml:space=\"preserve\">\n    <value>Balíčky</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>Typ balíčku</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>Balíček odinstalován</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>Stránka</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>Heslo</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>Zvolte prosím jiný název nebo vyberte jiné umístění instalace.</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>Vyberte prosím adresář s daty</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>Přenosný režim</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>V přenosném režimu budou všechna data a nastavení uložena ve stejném adresáři jako aplikace. Aplikaci budete moci přesunout i s jejím adresářem &apos;Data&apos; na jiné místo nebo počítač.</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>Preprocesor</value>\n  </data>\n  <data name=\"Label_PreviewImage\" xml:space=\"preserve\">\n    <value>Náhledový obrázek</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>Předchozí obrázek</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>Předchozí stránka</value>\n  </data>\n  <data name=\"Label_PrivacyPolicy\" xml:space=\"preserve\">\n    <value>Zásady ochrany osobních údajů</value>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>Prompt</value>\n    <comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTags\" xml:space=\"preserve\">\n    <value>Tagy pro prompt</value>\n    <comment>Tags for image generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTagsDescription\" xml:space=\"preserve\">\n    <value>Soubor s tagy pro navrhování doplňků (podporuje formát .csv z a1111-sd-webui-tagcomplete)</value>\n  </data>\n  <data name=\"Label_PromptTagsImport\" xml:space=\"preserve\">\n    <value>Importovat tagy pro prompt</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Description\" xml:space=\"preserve\">\n    <value>Přidat, nahradit nebo odebrat závislosti pro instalaci a aktualizaci</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Title\" xml:space=\"preserve\">\n    <value>Přepsání závislostí Pythonu</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Balíčky Pythonu</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Informace o verzi Pythonu</value>\n  </data>\n  <data name=\"Label_PythonVersionWarningTitle\" xml:space=\"preserve\">\n    <value>Nepodporované verze Pythonu</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>Verze PyTorch</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>Přečetl/a jsem si a souhlasím s</value>\n  </data>\n  <data name=\"Label_RecommendedModels\" xml:space=\"preserve\">\n    <value>Doporučené modely</value>\n  </data>\n  <data name=\"Label_RecommendedModelsSubText\" xml:space=\"preserve\">\n    <value>Zatímco se váš balíček instaluje, zde je několik modelů, které doporučujeme pro začátek.</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Refiner</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>Vyžadován restart</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>Poznámky k vydání</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>Vydání</value>\n  </data>\n  <data name=\"Label_ReleasesUnavailableForThisPackage\" xml:space=\"preserve\">\n    <value>Pro tento balíček nejsou dostupná žádná vydání.</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>Při vypnutí odstranit symbolické odkazy sdíleného adresáře Checkpointů</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Tuto možnost vyberte, pokud máte problémy s přesunem Stability Matrix na jiný disk</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>Resetovat mezipaměť Checkpointů</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>Znovu sestaví mezipaměť nainstalovaných Checkpointů. Použijte, pokud jsou Checkpointy nesprávně označeny v Prohlížeči modelů</value>\n  </data>\n  <data name=\"Label_Sampler\" xml:space=\"preserve\">\n    <value>Sampler</value>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>Uložit mezilehlý obrázek</value>\n    <comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Scheduler\" xml:space=\"preserve\">\n    <value>Scheduler</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>Hledat...</value>\n  </data>\n  <data name=\"Label_Seed\" xml:space=\"preserve\">\n    <value>Seed</value>\n  </data>\n  <data name=\"Label_SelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Vyberte umístění pro stažení:</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>Vybrat nový adresář s daty</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>Nepřesouvá existující data. Vyžaduje restart aplikace.</value>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>Nastavení</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>Strategie sdíleného adresáře modelů</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>Sdílení modelů</value>\n  </data>\n  <data name=\"Label_Show\" xml:space=\"preserve\">\n    <value>Zobrazit</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>Zobrazit obrázky modelů</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>Zobrazit NSFW obsah</value>\n  </data>\n  <data name=\"Label_ShowNsfwImages\" xml:space=\"preserve\">\n    <value>Zobrazit NSFW obrázky</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>Zobrazit mřížku pixelů při vysokém přiblížení</value>\n  </data>\n  <data name=\"Label_ShowUnsupportedPythonVersions\" xml:space=\"preserve\">\n    <value>Zobrazit nepodporované verze Pythonu</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>Přeskočit úvodní nastavení</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>Seřadit</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_StabilityMatrixAlreadyRunning\" xml:space=\"preserve\">\n    <value>Stability Matrix je již spuštěna</value>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>Stav</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>Kroky</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>Kroky - Základ</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>Kroky - Refiner</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>Síla</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>Systém</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>Systémové informace</value>\n  </data>\n  <data name=\"Label_SystemSettings\" xml:space=\"preserve\">\n    <value>Nastavení systému</value>\n  </data>\n  <data name=\"Label_Tags\" xml:space=\"preserve\">\n    <value>Tagy</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>Text na obrázek</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>Motiv</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>Časové období</value>\n  </data>\n  <data name=\"Label_ToggleAutoScrolling\" xml:space=\"preserve\">\n    <value>Automatické rolování na konec</value>\n  </data>\n  <data name=\"Label_TrainedWords\" xml:space=\"preserve\">\n    <value>Trénovaná slova</value>\n  </data>\n  <data name=\"Label_TriggerWords\" xml:space=\"preserve\">\n    <value>Spouštěcí slova:</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>Došlo k neočekávané chybě</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>Neznámý balíček</value>\n  </data>\n  <data name=\"Label_UnsupportedPythonVersionDetails\" xml:space=\"preserve\">\n    <value>Při použití nepodporovaných verzí Pythonu se můžete setkat s problémy u některých balíčků</value>\n  </data>\n  <data name=\"Label_UnsupportedPythonVersionWarningDescription\" xml:space=\"preserve\">\n    <value>Tímto se zobrazí všechny dostupné verze Pythonu, včetně těch, které nejsou podporovány Stability Matrix. Jste si jisti?</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>Dostupná aktualizace</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>Aktualizace</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>Pro technické uživatele. Získejte jako první přístup k našim vývojovým verzím z funkčních větví, jakmile budou dostupné. Mohou se vyskytnout nedokonalosti a chyby, protože experimentujeme s novými funkcemi.</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>Pro první uživatele. Preview verze budou spolehlivější než ty z Dev kanálu a budou dostupné blíže ke stabilním vydáním. Vaše zpětná vazba nám výrazně pomůže při odhalování problémů a ladění designových prvků.</value>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>Upscale</value>\n  </data>\n  <data name=\"Label_UsageData\" xml:space=\"preserve\">\n    <value>Data o používání</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>Uživatelské jméno</value>\n  </data>\n  <data name=\"Label_UseSeparatePrompt\" xml:space=\"preserve\">\n    <value>Použít oddělený prompt</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>Sdílení výstupního adresáře</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>Verze</value>\n  </data>\n  <data name=\"Label_VersionName\" xml:space=\"preserve\">\n    <value>Název verze</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>Typ verze</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>Metoda</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>Kvalita</value>\n  </data>\n  <data name=\"Label_View\" xml:space=\"preserve\">\n    <value>Zobrazení</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>Čekání na připojení...</value>\n  </data>\n  <data name=\"Label_WebUi\" xml:space=\"preserve\">\n    <value>Webové UI</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>Šířka</value>\n  </data>\n  <data name=\"Label_Wildcards\" xml:space=\"preserve\">\n    <value>Žolíky (Wildcards)</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>Zatím nedostupné</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>Funkce bude dostupná v budoucí aktualizaci</value>\n  </data>\n  <data name=\"Label_WorkflowBrowser\" xml:space=\"preserve\">\n    <value>Prohlížeč Workflow</value>\n  </data>\n  <data name=\"Label_WorkflowDeleted\" xml:space=\"preserve\">\n    <value>Workflow smazán</value>\n  </data>\n  <data name=\"Label_WorkflowDeletedSuccessfully\" xml:space=\"preserve\">\n    <value>{0} úspěšně smazán</value>\n  </data>\n  <data name=\"Label_WorkflowDescription\" xml:space=\"preserve\">\n    <value>Popis Workflow</value>\n  </data>\n  <data name=\"Label_WorkflowImportComplete\" xml:space=\"preserve\">\n    <value>Workflow a vlastní uzly byly importovány.</value>\n  </data>\n  <data name=\"Label_WorkflowImported\" xml:space=\"preserve\">\n    <value>Workflow importován</value>\n  </data>\n  <data name=\"Label_Workflows\" xml:space=\"preserve\">\n    <value>Workflows</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>Máte aktuální verzi</value>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>Aktivní</value>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>Zažijte rychlejší výsledky vyhledávání při procházení modelů z online repozitářů, jako je Civitai.</value>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>Experimentální optimalizace pro repozitáře třetích stran. Není oficiálně přidruženo; dostupnost se může lišit.</value>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>Neaktivní (používá se standardní připojení)</value>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>Zrychlené objevování modelů</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>Stahování dokončeno</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>Stahování balíčku...</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>Instalace dokončena</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>Instalace požadavků balíčku...</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>Instalace předpokladů...</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>Odinstalovávání balíčku...</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>Aktualizace dokončena</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>Aktualizace se nezdařila</value>\n  </data>\n  <data name=\"PromptAmplifier_Disclaimer\" xml:space=\"preserve\">\n    <value>### 🪄 Představujeme: Zesilovač promptů\nNáš AI asistent, poháněný naším experimentálním modelem Spark, bude generovat kreativní rozšíření vašeho promptu.\n\nZesilovač promptů běží v našem zabezpečeném, podnikovém cloudovém prostředí – **neběží** lokálně na vašem počítači.\n\n### ☁️️ Proč cloud?\nModel Spark pracuje v měřítku srovnatelném se základními modely s biliony parametrů, což vyžaduje značný výpočetní výkon. I když jsme odhodláni maximalizovat lokálně spustitelné funkce, pokročilé schopnosti Sparku jsou dostupné **nyní** prostřednictvím naší cloudové infrastruktury.\n\n### 🔒 Soukromí na prvním místě\nUpřednostňujeme vaše soukromí ([Podmínky Gen AI](&lt;https://lykos.ai/gen-ai-terms&gt;)). **Vaše prompty/výstupy NEJSOU NIKDY používány pro trénování AI společností Lykos AI ani našimi nezbytnými partnery pro cloudovou infrastrukturu.** Bezpečné zpracování probíhá pouze za účelem generování vašeho zesílení, **poté si ponecháváme pouze metadata (jako jsou časová razítka a počty tokenů), nikoli samotný obsah promptu.** Vaše data se nikdy neprodávají ani nesdílejí.</value>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>Pokud nás již podporujete na Patreonu, propojte prosím svůj účet pro pokračování.</value>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>Děkujeme, že podporujete Stability Matrix!</value>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>Funkce jako **{0}** jsou jednou z mnoha výhod dostupných našim podporovatelům. Váš příspěvek nám pomáhá pokrýt náklady na servery a podporuje vývoj Stability Matrix.</value>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>Funkce jako **{0}** jsou dostupné na úrovni podpory **{1}** (nebo vyšší). Váš příspěvek nám pomáhá pokrýt náklady na servery pro pokročilejší propojené funkce a umožňuje nám neustále vylepšovat Stability Matrix pro všechny.</value>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Podpořte Stability Matrix</value>\n  </data>\n  <data name=\"TabLabel_InstalledWorkflows\" xml:space=\"preserve\">\n    <value>Nainstalované Workflows</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>Pro začátek přidejte balíček!</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>Pro zahájení klikněte na Spustit!</value>\n  </data>\n  <data name=\"TeachingTip_DownloadsExplanation\" xml:space=\"preserve\">\n    <value>Zde sledujte postup instalací balíčků a stahování modelů.</value>\n  </data>\n  <data name=\"TeachingTip_InferencePromptHelpButton\" xml:space=\"preserve\">\n    <value>Klikněte zde pro přehled syntaxe promptu a jak zahrnout Lora / Embeddings.</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>Zde lze povolit další adresáře, jako jsou IPAdapters a TextualInversions (embeddings)</value>\n  </data>\n  <data name=\"TeachingTip_PromptAmplifier\" xml:space=\"preserve\">\n    <value>Vyzkoušejte nový Zesilovač promptů! Vylepšete své prompty pro lepší výsledky!</value>\n  </data>\n  <data name=\"TeachingTip_WebUiButtonMoved\" xml:space=\"preserve\">\n    <value>Tlačítko &apos;Otevřít webové UI&apos; bylo přesunuto do příkazové lišty</value>\n  </data>\n  <data name=\"Text_AllowBrowserOpenAppLink\" xml:space=\"preserve\">\n    <value>Povolte prosím svému prohlížeči otevřít tuto aplikaci, když budete vyzváni k pokračování.</value>\n  </data>\n  <data name=\"Text_AnalyticsDataPrivacyInfo\" xml:space=\"preserve\">\n    <value>Odeslaná data nejsou nikdy spojena s vámi nebo vaším účtem a nebudou obsahovat osobní údaje ani žádné citlivé informace.</value>\n  </data>\n  <data name=\"Text_AnalyticsDescription\" xml:space=\"preserve\">\n    <value>Pomozte nám vylepšit Stability Matrix zasíláním anonymních dat o používaných funkcích, verzích operačního systému, typech instalovaných balíčků atd. Odeslaná data nejsou nikdy spojena s vámi nebo vaším účtem a nebudou obsahovat osobní údaje ani žádné citlivé informace.</value>\n  </data>\n  <data name=\"Text_AnalyticsDescriptionShort\" xml:space=\"preserve\">\n    <value>Pomozte nám vylepšit Stability Matrix zasíláním anonymních dat o používaných funkcích, verzích operačního systému, typech instalovaných balíčků atd.</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>Aplikace se po aktualizaci restartuje</value>\n  </data>\n  <data name=\"Text_DeleteFollowingItems\" xml:space=\"preserve\">\n    <value>Chystáte se smazat následující položky:</value>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>Přihlaste se svým účtem Lykos pro použití propojených funkcí.</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Připojte svůj účet Lykos</value>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>Vaše přihlášení vypršelo. Pro pokračování se prosím znovu přihlaste.</value>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>Přihlaste se prosím znovu</value>\n  </data>\n  <data name=\"Text_LykosAccountUpgradeNotice\" xml:space=\"preserve\">\n    <value>## Upozornění: Odhlášení z účtu Lykos a vylepšení zabezpečení\n\nProvedli jsme několik důležitých vylepšení v zákulisí, jak Stability Matrix nakládá s účty Lykos, a přešli jsme na bezpečnější a pohodlnější systém přihlašování **(OAuth 2.0 s OpenID Connect)**. Z tohoto důvodu jste byli odhlášeni ze svého účtu Lykos.\n\n### Proč tato změna?\n\nVaše bezpečnost a soukromí jsou pro nás důležité. Toto vylepšení přináší:\n\n* **Zjednodušené prostředí:** Přihlaste se jednou na [account.lykos.ai](https://account.lykos.ai) pro spojení se Stability Matrix a dalšími službami Lykos AI.\n* **Více způsobů přihlášení:** Použijte svůj stávající účet [lykos.ai](https://lykos.ai) nebo se přihlaste pomocí **Apple**, **GitHub** nebo **Google**.\n* **Zvýšené soukromí:** Stability Matrix požaduje pouze oprávnění, která potřebuje.\n* **Bezpečnostní standard:** Používáme OAuth 2.0, zlatý standard pro bezpečné přihlašování.\n* **Připraveno na budoucnost:** To umožňuje bezpečné spojení s dalšími aplikacemi a službami.\n\n### Co mám dělat?\n\nKlikněte na tlačítko **&quot;Přejít do Nastavení&quot;** a poté klikněte na **&quot;Připojit&quot;** vedle **&quot;Účet Lykos&quot;**.\n\n### Je účet Lykos vyžadován?\n\nNe! Stability Matrix je stále plně funkční i bez něj. Váš účet Lykos ale umožňuje některé extra propojené funkce, jako jsou automatické aktualizace vývojových verzí pro naše podporovatele na Patreonu (a další přijdou!).\n</value>\n  </data>\n  <data name=\"Text_OAuthDeviceAuthDescription\" xml:space=\"preserve\">\n    <value>Otevřete odkaz ve svém prohlížeči a zadejte následující kód pro autorizaci vašeho účtu se Stability Matrix.</value>\n  </data>\n  <data name=\"Text_OAuthLoginDescription\" xml:space=\"preserve\">\n    <value>Otevřete odkaz ve svém prohlížeči a postupujte podle pokynů pro připojení vašeho účtu.</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>Vyberte si preferované rozhraní a začněte</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>Tímto smažete adresář balíčku a veškerý jeho obsah, včetně všech vygenerovaných obrázků a souborů, které jste mohli přidat.</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>Přechod na stránku Spuštění</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>Pro aplikování nového jazyka je vyžadován restart aplikace.</value>\n  </data>\n  <data name=\"Text_RestartMayBeRequiredForSystemChanges\" xml:space=\"preserve\">\n    <value>Pro projevení systémových změn může být vyžadován restart.</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>Některé soubory se nepodařilo smazat. Zavřete prosím všechny otevřené soubory v adresáři balíčku a zkuste to znovu.</value>\n  </data>\n  <data name=\"Text_UnexpectedError_Description\" xml:space=\"preserve\">\n    <value>Informujte nás prosím o tomto problému s níže uvedenými detaily a přiložte zazipované soubory se záznamy (logy).</value>\n  </data>\n  <data name=\"Text_UnexpectedErrorRecoverable_Description\" xml:space=\"preserve\">\n    <value>Můžete pokračovat, ale plná funkčnost bude dostupná až po restartu. Informujte nás prosím o tomto problému s níže uvedenými detaily a přiložte zazipované soubory se záznamy (logy).</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>Vítejte ve Stability Matrix!</value>\n  </data>\n  <data name=\"TextTemplate_DeleteFollowingCountItems\" xml:space=\"preserve\">\n    <value>Chystáte se smazat následujících {0} položek:</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>Chyba při aktualizaci {0}</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>Poslední kontrola: {0}</value>\n  </data>\n  <data name=\"TextTemplate_OAuthLoginTitle\" xml:space=\"preserve\">\n    <value>Přihlásit se pomocí {0}</value>\n    <comment>e.g. &apos;Sign in with Google&apos;</comment>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} byl aktualizován na nejnovější verzi</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToSelected\" xml:space=\"preserve\">\n    <value>{0} byl aktualizován na vybranou verzi</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>Aktualizace {0}</value>\n  </data>\n  <data name=\"TextTemplate_YouCanChangeThisBehavior\" xml:space=\"preserve\">\n    <value>Toto chování můžete kdykoli změnit v {0}.</value>\n    <comment>e.g. &apos;You can always change this behavior in Settings &gt; Category &gt; Item.&apos;</comment>\n  </data>\n  <data name=\"Tooltip_InferenceDefaults\" xml:space=\"preserve\">\n    <value>Pokud je povoleno, tato nastavení se automaticky použijí, když je tento model vybrán v záložce Inference</value>\n  </data>\n  <data name=\"Validation_PackageNameCannotBeEmpty\" xml:space=\"preserve\">\n    <value>Název balíčku nemůže být prázdný</value>\n  </data>\n  <data name=\"ValidationError_PackageExists\" xml:space=\"preserve\">\n    <value>Balíček s názvem &apos;{0}&apos; již existuje</value>\n  </data>\n  <data name=\"Warning_PleaseExtractFirst\" xml:space=\"preserve\">\n    <value>PŘED SPUŠTĚNÍM STABILITY MATRIX PROSÍM ROZBALTE APLIKACI ZE ZIP SOUBORU</value>\n  </data>\n  <data name=\"Watermark_EnterPackageName\" xml:space=\"preserve\">\n    <value>Zadejte název balíčku</value>\n  </data>\n</root>"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.de.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>Starten</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>Beenden</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>Speichern</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>Abbrechen</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>Sprache</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>Ein Neustart ist nötig, um Sprachänderungen vorzunehmen</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>Neustarten</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>Später Neustarten</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>Neustart benötigt</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>Unbekanntes Paket</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>Importieren</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>Pakettyp</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>Version</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>Versionstyp</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>Veröffentlichungen</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>Zweige</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>Checkpoints hierherziehen zum importieren</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>Betonung</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>Negativ Betonung</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>Embeddings</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>Netzwerk (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>Kommentare</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>Pixel-Gitter bei hohen Zoomstufen anzeigen</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>Schritte</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>Schritte - Base</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>Schritte - Refiner</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>CFG Wert</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Entrauschungsstärke</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>Breite</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>Höhe</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Refiner</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>Modell</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>Verbinden</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>Verbindet...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>Schließen</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>Warten auf die Verbindung...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>Updates verfügbar</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>Werde ein Patreon</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Trete dem Discord Server bei</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>Downloads</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>Installieren</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>Erstmalige Einstellung überspringen</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>Ein unerwarteter Fehler ist aufgetreten</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>Beende die Applikation</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>Anzeigename</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>Eine Installation mit diesem namen existiert bereits.</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>Bitte wähle einen anderen namen oder ändere den Installationsordner.</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>Erweiterte Optionen</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>Commit</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>Geteilte Modell Ordnerstrategie</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>PyTorch Version</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>Dialog nach Beendigung schließen</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>Daten Ordner</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>Hier werden die Applikationsdaten (Modell Checkpoints, Web UIs, etc.) installiert.</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>Bei der Verwendung eines FAT32- oder exFAT-Laufwerks können Fehler auftreten. Wählen Sie ein anderes Laufwerk, um ein reibungsloseres Arbeiten zu ermöglichen.</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>Tragbarer Modus</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>Im portablen Modus werden alle Daten und Einstellungen in demselben Verzeichnis wie die Anwendung gespeichert. Sie können die Anwendung mit ihrem &apos;Daten&apos;-Ordner an einen anderen Ort oder auf einen anderen Computer verschieben.</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>Weiter</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>Vorheriges Bild</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>Nächstes Bild</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>Modellbeschreibung</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>Eine neue Version von Stability Matrix ist verfügbar!</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>Neueste Importieren -</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>Alle Versionen</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>Suche modelle, #tags, or @nutzer</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>Suchen</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>Sortieren</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>Periode</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>Modelltyp</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>Basis Modell</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>Zeige NSFW Inhalte</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>Daten bereitgestellt von CivitAI</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>Seite</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>Erste Seite</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>Vorherige Seite</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>Nächste Seite</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>Letzte Seite</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>Umbenennen</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>Löschen</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>Öffnen in CivitAI</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>Verbundenes Modell</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>Lokales Modell</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>Im Explorer anzeigen</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>Neu</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>Ordner</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>Datei zum Importieren hier ablegen</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>Importieren mit Metadaten</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>Suche nach verbundenen Metadaten bei neuen lokalen Importen</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>Indizierung...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>Modellordner</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>Kategorien</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>Lass uns starten</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>Ich habe gelesen und akzeptiere die</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>Lizensbestimmungen.</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>Finde verbundene Metadaten</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>Zeige Modell Bilder</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>Aussehen</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>Thema</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>Checkpoint Manager</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>Symbolische Links auf gemeinsame Checkpoints beim Herunterfahren entfernen</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Wählen Sie diese Option, wenn Sie Probleme beim Verschieben von Stability Matrix auf ein anderes Laufwerk haben</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>Checkpoint-Cache zurücksetzen</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>Stellt den installierten Checkpoint-Cache wieder her. Wird verwendet, wenn Prüfpunkte im Modell-Browser falsch beschriftet sind</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>Paket Umgebung</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>Bearbeiten</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>Umgebungsvariablen</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>Eingebettetes Python</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>Version überprüfen</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>Integrationen</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Discord Rich Presence</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>System</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>Füge Stability Matrix zum Startmenü hinzu</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>Verwendet den aktuellen Standort der Anwendung, Sie können dies erneut ausführen, wenn Sie die Anwendung verschieben</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Nur auf Windows verfügbar</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>Hinzufügen für aktuellen nutzer</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>Hinzufügen für alle Nutzer</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>Wähle einen neuen Daten Ordner aus</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>Verschiebt keine existierenden Daten</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>Ordner auswählen</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>Über</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>Lizenz und Open Source Notizen</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>Klicken Sie auf Start, um loszulegen!</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>Stopp</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>Eingaben senden</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>Eingaben</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>Senden</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>Eingaben benötigt</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>Bestätigen?</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>Ja</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>Nein</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Web UI öffnen</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>Willkommen zu Stability Matrix!</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>Wählen Sie Ihre bevorzugte Schnittstelle und klicken Sie auf Installieren, um loszulegen.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>Installiert</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>Weiter zur Seite Start</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>Herunterladen des Pakets...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>Herunterladen abgeschlossen</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>Installation abgeschlossen</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>Voraussetzungen installieren...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>Paket-Voraussetzungen installieren...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>Öffnen im Explorer</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>Öffnen in Finder</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>Deinstallieren</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>Auf Updates überprüfen</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>Update</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>Paket hinzufügen</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>Füge ein Paket hinzu, um loszulegen!</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>Name</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>Wert</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>Entfernen</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>Details</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>Callstack</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>Inner Exception</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>Suchen...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>OK</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>Erneut versuchen</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Python Versionsinfo</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>Neustarten</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>Löschen bestätigen</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>Dadurch werden der Paketordner und sein gesamter Inhalt gelöscht, einschließlich aller generierten Bilder und Dateien, die Sie hinzugefügt haben.</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>Paket deinstallieren...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>Paket deinstalliert</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>Einige Dateien konnten nicht gelöscht werden. Bitte schließen Sie alle offenen Dateien im Paketverzeichnis und versuchen Sie es erneut.</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>Ungültiger Pakettyp</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>Aktualisiert {0}</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>Aktualisierung abgeschlossen</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} wurde auf die neueste Version aktualisiert</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>Fehler beim Aktualisieren {0}</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>Aktualisierung fehlgeschlagen</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>Öffnen im Browser</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>Fehler bei der Installation des Pakets</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>Zweig</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>Automatisch zum Ende der Konsolenausgabe blättern</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>Lizenz</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>Modell-Sharing</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>Bitte wählen Sie ein Datenverzeichnis</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>Name des Datenordners</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>Aktuelles Verzeichnis:</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>Die App wird nach der Aktualisierung neu gestartet</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>Erinnern Sie mich später</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>Jetzt installieren</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>Anmerkungen zur Veröffentlichung</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>Projekt öffnen...</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>Speichern als...</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>Standard-Layout wiederherstellen</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>Gemeinsame Nutzung des Outputs</value>\n  </data>\n  <data name=\"Label_BatchIndex\" xml:space=\"preserve\">\n    <value>Batch Index</value>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>Kopieren</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>Öffnen im Bildbetrachter</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>{0} Bilder ausgewählt</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>Ausgabe-Ordner</value>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>Ausgabetyp</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>Auswahl löschen</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>Alle auswählen</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>An Inference senden</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>Text zu Bild</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>Bild zu Bild</value>\n  </data>\n  <data name=\"Label_Inpainting\" xml:space=\"preserve\">\n    <value>Inpainting</value>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>Hochskalieren</value>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>Ausgabe-Browser</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>1 Bild ausgewählt</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Python Pakete</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>Konsolidieren</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>Sind Sie sicher?</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>Dadurch werden alle generierten Bilder aus den ausgewählten Paketen in das konsolidierte Verzeichnis des gemeinsamen Ausgabeordners verschoben. Diese Aktion kann nicht rückgängig gemacht werden.</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>Aktualisieren</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>Upgrade</value>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>Downgrade</value>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>Öffnen in GitHub</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>Verbunden</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>Nicht verbunden</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>Email</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>Nutzername</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>Password</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>Einloggen</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>Anmelden</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>Passwort bestätigen</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>API Schlüssel</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>Accounts</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>Preprocessor</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>Stärke</value>\n  </data>\n  <data name=\"Label_ControlWeight\" xml:space=\"preserve\">\n    <value>Kontrollgewicht</value>\n  </data>\n  <data name=\"Label_ControlSteps\" xml:space=\"preserve\">\n    <value>Kontrollschritte</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>Sie müssen eingeloggt sein, um diesen Checkpoint herunterladen zu können. Bitte geben Sie einen CivitAI API Schlüssel in den Einstellungen ein.</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>Download fehlgeschlagen</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>Automatische Updates</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>Für Early Adopters. Preview-Builds sind zuverlässiger als die aus dem Dev-Channel und werden näher an den stabilen Versionen verfügbar sein. Ihr Feedback hilft uns sehr dabei, Probleme zu entdecken und Designelemente zu verbessern.</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>Für technische Benutzer. Seien Sie der Erste, der auf unsere Entwicklungs-Builds aus den Funktionszweigen zugreift, sobald sie verfügbar sind. Da wir mit neuen Funktionen experimentieren, kann es noch einige Ecken und Kanten und Bugs geben.</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>Updates</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>Sie sind auf dem aktuellsten Stand</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>Zuletzt überprüft: {0}</value>\n  </data>\n  <data name=\"Action_CopyTriggerWords\" xml:space=\"preserve\">\n    <value>Trigger Wörter kopieren</value>\n  </data>\n  <data name=\"Label_TriggerWords\" xml:space=\"preserve\">\n    <value>Trigger Wörter:</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>Zusätzliche Ordner wie IPAdapter und TextualInversions (Einbettungen) können hier aktiviert werden</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>Öffnen in Hugging Face</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>Vorhandene Metadaten aktualisieren</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>Generell</value>\n    <comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>Inference</value>\n    <comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>Prompt</value>\n    <comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_OutputImageFiles\" xml:space=\"preserve\">\n    <value>Ausgabe von Bilddateien</value>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>Bildbetrachter</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>Automatische Vervollständigung</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>Ersetzen von Unterstrichen durch Leerzeichen beim Einfügen von Vervollständigungen</value>\n  </data>\n  <data name=\"Label_PromptTags\" xml:space=\"preserve\">\n    <value>Prompt Tags</value>\n    <comment>Tags for image generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTagsImport\" xml:space=\"preserve\">\n    <value>Importiere Prompt Tags</value>\n  </data>\n  <data name=\"Label_PromptTagsDescription\" xml:space=\"preserve\">\n    <value>Tags-Datei, die zum Vorschlagen von Vervollständigungen verwendet wird (unterstützt das Format a1111-sd-webui-tagcomplete .csv)</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>Systeminformationen</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>Zusätze</value>\n    <comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>Zwischenbild speichern</value>\n    <comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>Einstellungen</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>Datei auswählen</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>Inhalt ersetzen</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>Noch nicht verfügbar</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>Die Funktion wird in einem zukünftigen Update verfügbar sein</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>Fehlende Bilddatei</value>\n  </data>\n  <data name=\"Label_HolidayMode\" xml:space=\"preserve\">\n    <value>Urlaubsmodus</value>\n  </data>\n  <data name=\"Label_CLIPSkip\" xml:space=\"preserve\">\n    <value>CLIP überspringen</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>Bild zu Video</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>Bilder pro Sekunde </value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>Min CFG</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>Fehlerfrei</value>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>Bilder</value>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>Motion Bucket ID</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>Augmentierungslevel</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>Methode</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>Qualität</value>\n  </data>\n  <data name=\"Label_CLIPStrength\" xml:space=\"preserve\">\n    <value>CLIP Stärke</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>Zu den Einstellungen</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>Aktivieren</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>Deaktivieren</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Lykos-Konto verbinden</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>Melde dich mit deinem Lykos-Konto an, um verbundene Funktionen zu nutzen.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>Bitte erneut anmelden</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>Deine Anmeldung ist abgelaufen. Bitte melde dich erneut an, um fortzufahren.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Stability Matrix unterstützen</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>Vielen Dank, dass Sie Stability Matrix unterstützen!</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>Funktionen wie **{0}** sind einer von vielen Vorteilen, die unseren Unterstützern zur Verfügung stehen. Ihr Beitrag hilft uns, Serverkosten zu decken und die Entwicklung von Stability Matrix zu unterstützen.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>Funktionen wie **{0}** sind ab der Unterstützerstufe **{1}** (oder höher) verfügbar. Ihr Beitrag hilft uns, die Serverkosten für erweiterte verbundene Funktionen zu decken und ermöglicht es uns, Stability Matrix für alle kontinuierlich zu verbessern.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>Wenn Sie uns bereits auf Patreon unterstützen, verknüpfen Sie bitte Ihr Konto, um fortzufahren.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>Kontoeinstellungen</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>Unterstützungsoptionen anzeigen</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>Vielleicht später</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>Status</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>Aktiv</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>Inaktiv (Standardverbindung wird verwendet)</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>Erleben Sie schnellere Suchergebnisse beim Durchsuchen von Modellen aus Online-Repositories wie Civitai.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>Experimentelle Optimierung für Repositories von Drittanbietern. Nicht offiziell angegliedert; Verfügbarkeit kann variieren.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>Beta</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>Beschleunigte Modellsuche</value>\n    <comment>Fuzzy</comment>\n  </data>\n</root>"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.es.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>Iniciar</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>Salir</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>Guardar</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>Cancelar</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>Idioma</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>Es necesario reiniciar para que la nueva opción de idioma surta efecto</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>Reiniciar</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>Reiniciar Luego</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>Reinicio Necesario</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>Paquete Desconocido</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>Importar</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>Tipo de Paquete</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>Versión</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>Tipo de Versión</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>Lanzamientos</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>Ramas</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>Arrastra y Suelta los checkpoints aquí para importarlos</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>Intensificar</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>Suavizar</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>Incrustaciones / Inversión Textual</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>Redes (LoRa / LyCORIS)</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>Comentarios</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>Mostrar cuadrícula de píxeles en niveles altos de zoom</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>Pasos</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>Pasos - Base</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>Pasos - Refinador</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>Escala CFG</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Fuerza de Atenuación de Ruido</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>Ancho</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>Alto</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Refinador</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>Modelo</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>Conectar</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>Conectando...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>Cerrar</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>Esperando conexión...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>Actualización disponible</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>Conviértete en Patrocinador</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Únete al Servidor de Discord</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>Descargas</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>Instalar</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>Saltar configuración inicial</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>Ha ocurrido un error inesperado</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>Salir de la Aplicación</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>Mostrar Nombre</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>Ya existe una instalación con este nombre.</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>Por favor, elige un nombre diferente o selecciona otra ubicación para la instalación.</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>Opciones Avanzadas</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>Commit</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>Estrategia de Carpeta de Modelo Compartida</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>Versión de PyTorch</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>Cerrar cuadro de diálogo al finalizar</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>Directorio de datos</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>Aquí es donde se instalarán los checkpoints, LORAs, interfaces web de usuario, configuraciones, etc.</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>Puedes encontrar errores al usar una unidad FAT32 o exFAT. Selecciona una unidad diferente para una experiencia más fluida.</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>Modo Portátil</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>En el Modo Portátil, todos los datos y configuraciones se almacenarán en el mismo directorio que la aplicación. Podrás mover la aplicación con su carpeta &apos;Data&apos; a una ubicación o computadora diferente.</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>Continuar</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>Imagen Anterior</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>Imagen Siguiente</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>Descripción del Modelo</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>¡Hay disponible una nueva versión de Stability Matrix!</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>Importar el más Reciente -</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>Todas las Versiones</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>Buscar modelos, #etiquetas o @usuarios</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>Buscar</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>Tipos</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>Período</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>Tipo de Modelo</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>Modelo Base</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>Mostrar Contenido NSFW (Adulto)</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>Datos proporcionados por CivitAI</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>Página</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>Primera Página</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>Página Anterior</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>Página Siguiente</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>Última Página</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>Renombrar</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>Eliminar</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>Abrir en CivitAI</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>Modelo Conectado</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>Modelo Local</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>Mostrar en el Explorador</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>Nuevo</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>Carpeta</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>Suelta el archivo aquí para importar</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>Importar como Conectado</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>Buscar metadatos conectados en las nuevas importaciones locales</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>Indexando...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>Carpeta de Modelos</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>Categorías</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>Comencemos</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>He leído y acepto el</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>Acuerdo de Licencia.</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>Buscar Metadatos Conectados</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>Mostrar Imágenes del Modelo</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>Apariencia</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>Tema</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>Gestor de Checkpoint</value>\n    <comment>The word &apos;Checkpoint&apos; should not be translated into Spanish, because the community uses this word in English</comment>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>Eliminar enlaces simbólicos del directorio de checkpoints compartidos al apagar</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Selecciona esta opción si tienes problemas al mover Stability Matrix a otro disco</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>Restablecer Caché de Checkpoints</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>Reconstruye la caché de Checkpoints instalados. Úsalo si los Checkpoints tienen etiquetas incorrectas en el Explorador de Modelos</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>Entorno del Paquete</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>Editar</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>Variables del Entorno</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>Python Incorporado</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>Comprobar Versión</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>Integraciones</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Discord Rich Presence</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>Sistema</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>Agregar Stability Matrix al Menú de Inicio</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>Usa la ubicación actual de la aplicación, puedes ejecutarla nuevamente si mueves la aplicación</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Disponible solo en Windows</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>Agregar para el Usuario Actual</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>Agregar para Todos los Usuarios</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>Seleccionar Nuevo Directorio de Datos</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>No mueve los datos existentes</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>Seleccionar Directorio</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>Acerca de</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>Licencia y Avisos de Código Abierto</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>¡Pulsa Iniciar para comenzar!</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>Detener</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>Enviar Entrada</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>Entrada</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>Enviar</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>Entrada requerida</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>¿Confirmar?</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>Sí</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>No</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Abrir Interfaz de Usuario Web</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>¡Bienvenido a Stability Matrix!</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>Elige tu interfaz preferida y pulsa Instalar para comenzar</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>Instalando</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>Entrando a página de Inicio</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>Descargando paquete...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>Descarga completada</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>Instalación completada</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>Instalando prerrequisitos...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>Instalando requisitos del paquete...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>Abrir en el Explorador</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>Abrir en Finder</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>Desinstalar</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>Buscar Actualizaciones</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>Actualizar</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>Añadir Paquete</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>¡Añade un paquete para empezar!</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>Nombre</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>Valor</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>Eliminar</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>Detalles</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>Pila de llamadas</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>Excepción interna</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>Buscar...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>OK</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>Reintentar</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Información de la Versión de Python</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>Reiniciar</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>Confirmar Eliminación</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>Esto eliminará la carpeta del paquete y todo su contenido, incluyendo cualquiera de las imágenes y archivos generados que hayas añadido.</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>Desinstalando paquete...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>Paquete Desinstalado</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>Algunos archivos no pudieron eliminarse. Por favor, cierra cualquier archivo abierto en el directorio del paquete y vuelve a intentarlo.</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>Tipo de Paquete Incorrecto</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>Actualizando {0}</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>Actualización completa</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} se ha actualizado a la última versión</value>\n    <comment>El mensaje se muestra cuando se ha actualizado un paquete por ej. ComfyUI, por lo tanto, es correcto decir que se &quot;ha actualizado&quot;</comment>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>Error al actualizar {0}</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>Actualización fallida</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>Abrir en el navegador</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>Error al instalar el paquete</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>Rama</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>Desplazarse automáticamente hasta el final de la salida de consola</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>Licencia</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>Modelo Compartido</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>Por favor, Selecciona un Directorio de Datos</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>Nombre del Directorio de Datos</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>Directorio actual:</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>La aplicación se reiniciará después de actualizar</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>Recuérdamelo Más Tarde</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>Instalar Ahora</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>Notas de la Versión</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>Abrir Proyecto...</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>Guardar como...</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>Restablecer Diseño Predeterminado</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>Compartir Resultado</value>\n  </data>\n  <data name=\"Label_BatchIndex\" xml:space=\"preserve\">\n    <value>Índice de lotes</value>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>Copiar</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>Abrir en el Visor de imágenes</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>{0} imágenes seleccionadas</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>Carpeta de Resultados</value>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>Tipo de Resultados</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>Borrar Selección</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>Seleccionar Todo</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>Enviar a Inference</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>Texto a Imagen</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>Imagen a Imagen</value>\n  </data>\n  <data name=\"Label_Inpainting\" xml:space=\"preserve\">\n    <value>Pintar Región</value>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>Escalar</value>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>Navegador de Resultados</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>1 imagen seleccionada</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Paquetes Python</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>Agrupar</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>¿Estás seguro?</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>Esto moverá todas las imágenes generadas desde los paquetes seleccionados, al directorio &apos;Grupo&apos; de la carpeta de resultados compartidos. Esta acción no se puede deshacer.</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>Refrescar</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>Mejorar</value>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>Degradar</value>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>Abrir en GitHub</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>Conectado</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>Desconectar</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>Email</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>Usuario</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>Contraseña</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>Entrar</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>Registrarme</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>Confirmar Contraseña</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>Clave API</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>Cuentas</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>Preprocesador</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>Fortaleza</value>\n  </data>\n  <data name=\"Label_ControlWeight\" xml:space=\"preserve\">\n    <value>Control de Peso</value>\n  </data>\n  <data name=\"Label_ControlSteps\" xml:space=\"preserve\">\n    <value>Control de Pasos</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>Debes iniciar sesión para descargar este checkpoint. Ingresa una clave API de CivitAI en la configuración.</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>Descarga Fallida</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>Actualizaciones automáticas</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>Para los usuarios primerizos. Las versiones preliminares serán más fiables que las del canal de desarrollo y estarán disponibles antes de las versiones estables. Tus comentarios nos ayudarán enormemente a descubrir problemas y pulir elementos de diseño.</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>Para usuarios técnicos. Sé el primero en acceder a nuestras compilaciones de desarrollo desde las ramas de funciones tan pronto como estén disponibles. Es posible que haya algunas asperezas y errores a medida que experimentamos con nuevas funciones.</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>Actualizaciones</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>Estás al día</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>Última comprobación: {0}</value>\n  </data>\n  <data name=\"Action_CopyTriggerWords\" xml:space=\"preserve\">\n    <value>Copiar palabras desencadenantes</value>\n  </data>\n  <data name=\"Label_TriggerWords\" xml:space=\"preserve\">\n    <value>Palabras desencadenantes:</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>Aquí se pueden habilitar carpetas adicionales como IPAdapters y TextualInversions (embeddings)</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>Abrir en Hugging Face</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>Actualizar metadatos existentes</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>General</value>\n    <comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>Inferencia</value>\n    <comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>Prompt</value>\n    <comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_OutputImageFiles\" xml:space=\"preserve\">\n    <value>Archivos de imágenes resultantes</value>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>Visor de Imágenes</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>Auto Completado</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>Reemplazar guiones bajos con espacios al insertar terminaciones</value>\n  </data>\n  <data name=\"Label_PromptTags\" xml:space=\"preserve\">\n    <value>Etiquetas de Prompt</value>\n    <comment>Tags for image generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTagsImport\" xml:space=\"preserve\">\n    <value>Importar Etiquetas de Prompt</value>\n  </data>\n  <data name=\"Label_PromptTagsDescription\" xml:space=\"preserve\">\n    <value>Archivo de etiquetas que se utilizarán para sugerir terminaciones (admite el formato .csv a1111-sd-webui-tagcomplete)</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>Información del Sistema</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>Complementos</value>\n    <comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>Guardar Imagen Intermedia</value>\n    <comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>Ajustes</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>Seleccionar Archivo</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>Reemplazar contenido</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>No disponible aún</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>La función estará disponible en una actualización futura</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>Archivo de Imagen Faltante</value>\n  </data>\n  <data name=\"Label_HolidayMode\" xml:space=\"preserve\">\n    <value>Modo Vacaciones</value>\n  </data>\n  <data name=\"Label_CLIPSkip\" xml:space=\"preserve\">\n    <value>Saltar CLIP</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>Imagen a Vídeo</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>Frames por Segundo</value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>CFG Mínimo</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>Sin pérdidas</value>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>Frames</value>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>Motion Bucket ID</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>Nivel de aumento</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>Método</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>Calidad</value>\n  </data>\n  <data name=\"Label_FindInModelBrowser\" xml:space=\"preserve\">\n    <value>Buscar en el navegador de modelos</value>\n  </data>\n  <data name=\"Label_Installed\" xml:space=\"preserve\">\n    <value>Instalado</value>\n  </data>\n  <data name=\"Label_NoExtensionsFound\" xml:space=\"preserve\">\n    <value>No se encuentran extensiones.</value>\n  </data>\n  <data name=\"Action_Hide\" xml:space=\"preserve\">\n    <value>Ocultar</value>\n  </data>\n  <data name=\"Action_CopyDetails\" xml:space=\"preserve\">\n    <value>Copiar detalles</value>\n  </data>\n  <data name=\"Action_Download\" xml:space=\"preserve\">\n    <value>Descargar</value>\n  </data>\n  <data name=\"TeachingTip_DownloadsExplanation\" xml:space=\"preserve\">\n    <value>Verifica el progreso de instalación de tus paquetes y descargas de modelos aquí.</value>\n  </data>\n  <data name=\"Label_RecommendedModels\" xml:space=\"preserve\">\n    <value>Modelos recomendados</value>\n  </data>\n  <data name=\"Label_RecommendedModelsSubText\" xml:space=\"preserve\">\n    <value>Mientras se instala tu paquete, aquí hay algunos modelos que recomendamos para ayudarte a comenzar.</value>\n  </data>\n  <data name=\"Label_Notifications\" xml:space=\"preserve\">\n    <value>Notificaciones</value>\n  </data>\n  <data name=\"Label_NotificationOption_None\" xml:space=\"preserve\">\n    <value>Ninguno</value>\n  </data>\n  <data name=\"Label_ComfyRequiredTitle\" xml:space=\"preserve\">\n    <value>Se Requiere ComfyUI</value>\n  </data>\n  <data name=\"Label_ComfyRequiredDetail\" xml:space=\"preserve\">\n    <value>Se requiere ComfyUI para instalar este paquete. ¿Quieres instalarlo ahora?</value>\n  </data>\n  <data name=\"Error_PleaseSelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Por favor, seleccione una ubicación de descarga.</value>\n  </data>\n  <data name=\"Label_SelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Seleccione la Ubicación de Descarga:</value>\n  </data>\n  <data name=\"Label_Config\" xml:space=\"preserve\">\n    <value>Configuración</value>\n    <comment>This is used in inference when models have a yaml config file</comment>\n  </data>\n  <data name=\"Label_ToggleAutoScrolling\" xml:space=\"preserve\">\n    <value>Desplazamiento Automático hasta el Final</value>\n  </data>\n  <data name=\"Label_ConfirmExit\" xml:space=\"preserve\">\n    <value>Confirmar Salida</value>\n  </data>\n  <data name=\"Label_ConfirmExitDetail\" xml:space=\"preserve\">\n    <value>¿Seguro que quieres salir? Esto también cerrará cualquier paquete que se esté ejecutando actualmente.</value>\n  </data>\n  <data name=\"Label_Console\" xml:space=\"preserve\">\n    <value>Consola</value>\n  </data>\n  <data name=\"Label_WebUi\" xml:space=\"preserve\">\n    <value>Interfaz Web</value>\n    <comment>This will be used on a button to launch the web ui</comment>\n  </data>\n  <data name=\"Label_Packages\" xml:space=\"preserve\">\n    <value>Paquetes</value>\n  </data>\n  <data name=\"Label_ActionCannotBeUndone\" xml:space=\"preserve\">\n    <value>Esta acción no se puede deshacer.</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteImages\" xml:space=\"preserve\">\n    <value>¿Estás seguro de que deseas eliminar {0} imágenes?</value>\n  </data>\n  <data name=\"Label_CheckingHardware\" xml:space=\"preserve\">\n    <value>Estamos verificando algunas especificaciones de hardware para determinar la compatibilidad.</value>\n  </data>\n  <data name=\"Label_EverythingLooksGood\" xml:space=\"preserve\">\n    <value>¡Todo parece estar bien!</value>\n  </data>\n  <data name=\"Label_NvidiaGpuRecommended\" xml:space=\"preserve\">\n    <value>Recomendamos una GPU con soporte CUDA para obtener la mejor experiencia. Puedes continuar sin una, pero es posible que algunos paquetes no funcionen y la inferencia pueda ser más lenta.</value>\n  </data>\n  <data name=\"Label_Checkpoints\" xml:space=\"preserve\">\n    <value>Checkpoints</value>\n  </data>\n  <data name=\"Label_ModelBrowser\" xml:space=\"preserve\">\n    <value>Navegador de Modelos</value>\n  </data>\n  <data name=\"Label_Workflows\" xml:space=\"preserve\">\n    <value>Workflows</value>\n  </data>\n  <data name=\"Label_InfiniteScrolling\" xml:space=\"preserve\">\n    <value>Scroll Infinito</value>\n  </data>\n  <data name=\"Label_WorkflowBrowser\" xml:space=\"preserve\">\n    <value>Navegador de Workflows</value>\n  </data>\n  <data name=\"Action_OpenOnOpenArt\" xml:space=\"preserve\">\n    <value>Abrir en OpenArt</value>\n  </data>\n  <data name=\"Label_NodeDetails\" xml:space=\"preserve\">\n    <value>Detalles del Nodo</value>\n  </data>\n  <data name=\"Label_WorkflowDescription\" xml:space=\"preserve\">\n    <value>Descripción del Workflow</value>\n  </data>\n  <data name=\"Label_OpenArtBrowser\" xml:space=\"preserve\">\n    <value>Navegador OpenArt</value>\n  </data>\n  <data name=\"Action_PreviewPreprocessor\" xml:space=\"preserve\">\n    <value>Vista previa del Preprocesador</value>\n  </data>\n  <data name=\"TeachingTip_WebUiButtonMoved\" xml:space=\"preserve\">\n    <value>El botón &apos;Abrir interfaz de usuario web&apos; se ha movido a la barra de comandos</value>\n  </data>\n  <data name=\"Label_AnotherInstanceAlreadyRunning\" xml:space=\"preserve\">\n    <value>Ya se está ejecutando otra instancia de Stability Matrix. Ciérrelo antes de comenzar uno nuevo.</value>\n  </data>\n  <data name=\"Label_StabilityMatrixAlreadyRunning\" xml:space=\"preserve\">\n    <value>Stability Matrix ya se está ejecutando</value>\n  </data>\n  <data name=\"Label_WorkflowDeletedSuccessfully\" xml:space=\"preserve\">\n    <value>{0} eliminados correctamente</value>\n  </data>\n  <data name=\"Label_WorkflowDeleted\" xml:space=\"preserve\">\n    <value>Workflow Eliminado</value>\n  </data>\n  <data name=\"Label_ErrorRetrievingWorkflows\" xml:space=\"preserve\">\n    <value>Error al recuperar workflows</value>\n  </data>\n  <data name=\"TabLabel_InstalledWorkflows\" xml:space=\"preserve\">\n    <value>Workflows Instalados</value>\n  </data>\n  <data name=\"Label_WorkflowImported\" xml:space=\"preserve\">\n    <value>Workflow Importado</value>\n  </data>\n  <data name=\"Label_FinishedImportingWorkflow\" xml:space=\"preserve\">\n    <value>Terminada la importación del workflow y los nodos personalizados</value>\n  </data>\n  <data name=\"Label_WorkflowImportComplete\" xml:space=\"preserve\">\n    <value>Se han importado el workflow y los nodos personalizados.</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>Ir a la configuración</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>Activar</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>Desactivar</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Conectar cuenta de Lykos</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>Inicia sesión con tu cuenta de Lykos para usar las funciones conectadas.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>Por favor, inicia sesión de nuevo</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>Tu inicio de sesión ha caducado. Por favor, inicia sesión de nuevo para continuar.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Apoyar a Stability Matrix</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>¡Gracias por apoyar a Stability Matrix!</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>Funciones como **{0}** son uno de los muchos beneficios disponibles para nuestros colaboradores. Su contribución nos ayuda a cubrir los costos del servidor y apoya el desarrollo de Stability Matrix.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>Funciones como **{0}** están disponibles en el nivel de colaborador **{1}** (o superior). Su contribución nos ayuda a cubrir los costos del servidor para funciones conectadas más avanzadas y nos permite seguir mejorando Stability Matrix para todos.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>Si ya nos apoya en Patreon, por favor, vincule su cuenta para continuar.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>Configuración de la cuenta</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>Ver opciones de apoyo</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>Más tarde</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>Estado</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>Activo</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>Inactivo (Usando conexión estándar)</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>Experimente resultados de búsqueda más rápidos al explorar modelos de repositorios en línea, como Civitai.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>Optimización experimental para repositorios de terceros. Sin afiliación oficial; la disponibilidad puede variar.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>Beta</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>Búsqueda Acelerada de Modelos</value>\n    <comment>Fuzzy</comment>\n  </data>\n</root>"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.fr-FR.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>Lancer</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>Quitter</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>Enregistrer</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>Annuler</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>Langue</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>Un redémarrage est nécessaire pour que la nouvelle option linguistique prenne effet.</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>Relancer</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>Relancer plus tard</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>Relancement nécessaire</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>Paquet inconnu</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>Importer</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>Type de package</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>Version</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>Version Type</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>Version</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>Branches</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>Glisser-déposer les checkpoints ici pour les importer</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>Emphase</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>Emphase inverse</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>Emebeddings / Inversion textuelle</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>Réseaux (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>Commentaires</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>Afficher la grille de pixels à des niveaux de zoom élevés</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>Étapes</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>Étapes - Base</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>Étapes - Affineur</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>Échelle CFG</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Force de denoising</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>Largeur</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>Hauteur</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Raffineur</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>Modèle</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>Connecter</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>Connexion...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>Fermer</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>En attente de connexion...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>Mise à jour disponible</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>Devenir un Patreon</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Rejoindre le serveur Discord</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>Téléchargements</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>Installer</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>Sauter la première installation</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>Une erreur inattendue s&apos;est produite</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>Quitter l&apos;application</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>Nom d&apos;affichage</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>Une installation portant ce nom existe déjà.</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>Veuillez choisir un autre nom ou un autre lieu d&apos;installation.</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>Options avancées</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>Commit</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>Stratégie du dossier de modéles partagé</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>Version PyTorch</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>Fermer la boîte de dialogue lorsque l&apos;opération est terminée</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>Répertoire de données</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>C&apos;est là que seront installés les points de contrôle du modèle, les LORA, les interfaces web, les paramètres, etc.</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>Vous pouvez rencontrer des erreurs lors de l&apos;utilisation d&apos;un disque FAT32 ou exFAT. Sélectionnez un autre disque pour une meilleure expérience.</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>Mode portable</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>En mode portable, toutes les données et tous les paramètres sont stockés dans le même répertoire que l&apos;application. Vous pourrez déplacer l&apos;application et son dossier &quot;Data&quot; vers un autre endroit ou un autre ordinateur.</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>Continuer</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>Image précédente</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>Image suivante</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>Description du modèle</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>Une nouvelle version de Stability Matrix est disponible !</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>Importer les dernières -</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>Toutes les versions</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>Rechercher des modèles, des #tags ou des @utilisateurs</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>Recherche</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>Tri</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>Période</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>Type de modèle</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>Modèle de base</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>Afficher le contenu NSFW</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>Données fournies par CivitAI</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>Page</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>Première page</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>Page précédente</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>Page suivante</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>Dernière page</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>Renommer</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>Supprimer</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>Ouvrir sur CivitAI</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>Modèle connecté</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>Modèle local</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>Afficher dans l&apos;explorateur</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>Nouveau</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>Dossier</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>Déposer le fichier ici pour l&apos;importer</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>Importer avec les métadonnées</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>Recherche de métadonnées en ligne sur les nouvelles importations locales</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>Indexation...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>Dossier des modèles</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>Catégories</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>Commençons</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>J&apos;ai lu et j&apos;accepte l&apos;</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>Accord de licence.</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>Trouver des métadonnées en ligne</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>Afficher les vignettes</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>Apparence</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>Thème</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>Gestionnaire de checkpoints</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>Suppression des liens symboliques des répertoires de checkpoints partagés lors de l&apos;arrêt de l&apos;application</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Sélectionnez cette option si vous rencontrez des problèmes pour déplacer Stability Matrix sur un autre lecteur.</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>Réinitialiser le cache des checkpoints</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>Reconstruit le cache des checkpoints installés. À utiliser si les checkpoints sont mal étiquetés dans le navigateur de modèles.</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>Environnement du paquet</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>Editer</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>Variables d&apos;environnement</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>Python embarqué</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>Vérifier la version</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>Intégrations</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Activer Statut Discord</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>Système</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>Ajouter Stability Matrix au menu de démarrage</value>\n    <comment>updated because &quot;Stability Matrix&quot; was translated in french here haha</comment>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>Utilise l&apos;emplacement actuel de l&apos;application, vous pouvez relancer cette opération si vous déplacez l&apos;application.</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Uniquement disponible sous Windows</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>Ajouter pour l&apos;utilisateur actuel</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>Ajouter pour tous les utilisateurs</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>Sélectionner un nouveau répertoire de données</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>Ne pas déplacer les données existantes</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>Sélectionner un répertoire</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>A propos de</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>Licence et avis sur les logiciels libres</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>Cliquez sur Lancer pour commencer !</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>Arrêter</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>Envoyer input</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>Input</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>Envoyer</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>Données requises</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>Confirmer ?</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>Oui</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>Non</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Open Web UI</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>Bienvenue à Stability Matrix !</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>Choisissez votre interface préférée et cliquez sur Installer pour commencer.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>Installation</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>Aller à la page de lancement</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>Téléchargement du paquet...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>Téléchargement complet</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>L&apos;installation est terminée</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>Installation des prérequis...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>Installation des exigences du paquet...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>Ouvrir dans l&apos;explorateur</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>Ouvrir dans le Finder</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>Désinstaller</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>Vérifier les mises à jour</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>Mettre à jour</value>\n    <comment>switched to verb as it&apos;s action label</comment>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>Ajouter un paquet</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>Ajoutez un paquet pour commencer !</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>Nom</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>Valeur</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>Retirer</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>Détails</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>Callstack</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>Exception interne</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>Recherche...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>OK</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>Réessayer</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Informations sur la version de Python</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>Redémarrer</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>Confirmer la suppression</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>Cette opération va supprimer le dossier du paquet et tout son contenu, y compris les images générées et les fichiers que vous avez éventuellement ajoutés.</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>Désinstallation du paquet...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>Paquet désinstallé</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>Certains fichiers n&apos;ont pas pu être supprimés. Veuillez fermer tous les fichiers ouverts dans le répertoire du paquet et réessayer.</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>Type de paquet non valide</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>Mise à jour de {0}</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>Mise à jour complète</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} a été mis à jour vers la dernière version</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>Erreur de mise à jour de {0}</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>Échec de la mise à jour</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>Ouvrir dans le navigateur</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>Erreur lors de l&apos;installation du paquet</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>Branche</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>Défilement automatique jusqu&apos;à la dernière sortie de la console</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>Licence</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>Partage de modèle</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>Veuillez sélectionner un répertoire de données</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>Nom du dossier de données</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>Répertoire actuel :</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>L&apos;application sera relancée après la mise à jour</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>Rappelez-moi plus tard</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>Installer maintenant</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>Notes de version</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>Ouvrir projet...</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>Enregistrer sous...</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>Rétablir la présentation par défaut</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>Partager des générations</value>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>Copier</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>Ouvrir dans la visionneuse d&apos;images</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>{0} images sélectionnées</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>Dossier</value>\n    <comment>Hey hey :) is this used somewhere else ? if it&apos;s only inside the &quot;output browser&quot;, I suggest to remove &quot;output&quot; from translation, but you&apos;re the boss, lmk :)</comment>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>Type</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>Annuler la sélection</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>Sélectionner tout</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>Envoyer à l&apos;inférence</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>Texte vers image</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>Image vers image</value>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>Amélioration de qualité</value>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>Explorateur de génération</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>1 image sélectionnée</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Paquets Python</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>Consolider</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>Êtes-vous sure ?</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>Cela déplacera toutes les images générées des packages sélectionnés vers le répertoire consolidé du dossier de sorties partagées. Cette action ne peut pas être annulée.</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>Actualiser</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>Passer à la version supérieure</value>\n    <comment>What is this related to ?</comment>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>Passer à la version inférieure</value>\n    <comment>What is this related to ?</comment>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>Ouvrir sur GitHub</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>Connecté</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>Se déconnecter</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>Email</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>Nom d&apos;utilisateur</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>Mot de passe</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>Ouvrir une session</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>S&apos;enregistrer</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>Confirmer le mot de passe</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>Clé d&apos;API</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>Comptes</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>Préprocesseur</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>Force</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>Vous devez être loggué pour télécharger ce checkpoint. Veuillez entrer une clé d&apos;API CivitAI dans les paramètres.</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>Echec du téléchargement</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>Mises à jour automatiques</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>Pour les early adopters. Version de preview, plus stable que les versions de développement et plus proche des versions stables. Vos retours vont grandement nous aider à identifier des problèmes et ajuster les éléments de design.</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>Pour les utilisateurs expérimentés. Soyez parmis les premiers à accéder aux builds développement incluants les branches de fonctionnalités dès qu&apos;elles sont disponibles. Il peut y avoir quelques disfonctionnements et bugs au fur et à mesure que nous expérimentons de nouvelles fonctionnalités.</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>Mises à jour</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>Vous êtes à jour</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>Dernière vérification: {0}</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>Les dossiers additionnels comme IPAdapters ou TextualInversions (embeddings) peuvent être activés ici</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>Ouvrir sur Hugging Face</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>Mettre à jour les métadonnées existantes</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>Générale</value>\n    <comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>Inférence</value>\n    <comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>Instruction</value>\n    <comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>Visionneuse d&apos;images</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>Auto-complétion</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>Remplacer les underscores par des espaces lors de l&apos;auto-complétion.</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>Informations système</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>Modules complémentaires</value>\n    <comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>Sauvegarder les images intermédiaires</value>\n    <comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>Paramètres</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>Sélectionner un fichier</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>Remplacer des contenus</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>Pas encore disponible</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>Fonctionnalité disponible dans une mise à jour future</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>Fichier image manquant</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>Image vers vidéo</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>Images par seconde</value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>CFG Min</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>Sans perte</value>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>Images</value>\n    <comment>What is the context on this one ?</comment>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>Motion Bucket ID</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>Niveau d&apos;augmentation</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>Méthode</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>Qualité</value>\n  </data>\n  <data name=\"Label_FindInModelBrowser\" xml:space=\"preserve\">\n    <value>Trouver dans l&apos;explorateur de modèle</value>\n  </data>\n  <data name=\"Label_Installed\" xml:space=\"preserve\">\n    <value>Installé</value>\n  </data>\n  <data name=\"Label_NoExtensionsFound\" xml:space=\"preserve\">\n    <value>Pas d&apos;extensions trouvés.</value>\n  </data>\n  <data name=\"Action_Hide\" xml:space=\"preserve\">\n    <value>Masquer</value>\n  </data>\n  <data name=\"Action_CopyDetails\" xml:space=\"preserve\">\n    <value>Copier les détails</value>\n  </data>\n  <data name=\"Action_Download\" xml:space=\"preserve\">\n    <value>Télécharger</value>\n  </data>\n  <data name=\"TeachingTip_DownloadsExplanation\" xml:space=\"preserve\">\n    <value>Vérifiez la progression de l&apos;installation de votre paquet ainsi que le téléchargement de votre modèle ici.</value>\n  </data>\n  <data name=\"Label_RecommendedModels\" xml:space=\"preserve\">\n    <value>Modèles recommandés</value>\n  </data>\n  <data name=\"Label_RecommendedModelsSubText\" xml:space=\"preserve\">\n    <value>Pendant que votre paquet s&apos;installe, voici quelques modèles que nous recommandons pour vous aidez à démarrer.</value>\n  </data>\n  <data name=\"Label_Notifications\" xml:space=\"preserve\">\n    <value>Notifications</value>\n  </data>\n  <data name=\"Label_NotificationOption_None\" xml:space=\"preserve\">\n    <value>Aucun</value>\n  </data>\n  <data name=\"Label_ComfyRequiredTitle\" xml:space=\"preserve\">\n    <value>ComfyUI est requis</value>\n  </data>\n  <data name=\"Label_ComfyRequiredDetail\" xml:space=\"preserve\">\n    <value>ComfyUI est requis pour installer ce paquet. Voulez vous l&apos;installer maintenant ?</value>\n  </data>\n  <data name=\"Error_PleaseSelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Merci de sélectionner un dossier de téléchargement.</value>\n  </data>\n  <data name=\"Label_SelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Sélectionner un dossier de téléchargement:</value>\n  </data>\n  <data name=\"Label_ToggleAutoScrolling\" xml:space=\"preserve\">\n    <value>Défilement automatique</value>\n  </data>\n  <data name=\"Label_ConfirmExit\" xml:space=\"preserve\">\n    <value>Confirmer la sortie</value>\n  </data>\n  <data name=\"Label_ConfirmExitDetail\" xml:space=\"preserve\">\n    <value>Êtes vous certain de vouloir quitter ? Cela va fermer toutes les instances actuellement lancées.</value>\n  </data>\n  <data name=\"Label_Console\" xml:space=\"preserve\">\n    <value>Console</value>\n  </data>\n  <data name=\"Label_Packages\" xml:space=\"preserve\">\n    <value>Paquets</value>\n  </data>\n  <data name=\"Label_ActionCannotBeUndone\" xml:space=\"preserve\">\n    <value>Cette action ne peut être annulée.</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteImages\" xml:space=\"preserve\">\n    <value>Êtes vous certain de vouloir supprimer les {0} images?</value>\n  </data>\n  <data name=\"Label_CheckingHardware\" xml:space=\"preserve\">\n    <value>Nous vérifions quelques spécifications matérielles pour vérifier la compatibilité.</value>\n  </data>\n  <data name=\"Label_EverythingLooksGood\" xml:space=\"preserve\">\n    <value>Tout à l&apos;air bon!</value>\n  </data>\n  <data name=\"Label_NvidiaGpuRecommended\" xml:space=\"preserve\">\n    <value>Nous recommandons un GPU avec prise en charge CUDA pour une meilleure expérience. Vous pouvez continuer sans en avoir un, mais certains packages peuvent ne pas fonctionner et l&apos;inférence peut être plus lente.</value>\n  </data>\n  <data name=\"Label_ModelBrowser\" xml:space=\"preserve\">\n    <value>Explorateur de modèle</value>\n  </data>\n  <data name=\"Label_Workflows\" xml:space=\"preserve\">\n    <value>Workflows</value>\n  </data>\n  <data name=\"Label_InfiniteScrolling\" xml:space=\"preserve\">\n    <value>Scroll infini</value>\n  </data>\n  <data name=\"Label_WorkflowBrowser\" xml:space=\"preserve\">\n    <value>Explorateur de workflow</value>\n  </data>\n  <data name=\"Action_OpenOnOpenArt\" xml:space=\"preserve\">\n    <value>Ouvrir sur OpenArt</value>\n  </data>\n  <data name=\"Label_NodeDetails\" xml:space=\"preserve\">\n    <value>Plus de détails</value>\n  </data>\n  <data name=\"Label_WorkflowDescription\" xml:space=\"preserve\">\n    <value>Description du workflow</value>\n  </data>\n  <data name=\"Label_OpenArtBrowser\" xml:space=\"preserve\">\n    <value>Explorateur OpenArt</value>\n  </data>\n  <data name=\"Label_AnotherInstanceAlreadyRunning\" xml:space=\"preserve\">\n    <value>Une autre instance de Stability Matrix est déjà lancée. Merci de la quitter avant d&apos;en démarrer une nouvelle.</value>\n  </data>\n  <data name=\"Label_StabilityMatrixAlreadyRunning\" xml:space=\"preserve\">\n    <value>Stability Matrix est déjà lancé</value>\n  </data>\n  <data name=\"Label_WorkflowDeletedSuccessfully\" xml:space=\"preserve\">\n    <value>{0} suppressions réalisées</value>\n  </data>\n  <data name=\"Label_WorkflowDeleted\" xml:space=\"preserve\">\n    <value>Workflow supprimé</value>\n  </data>\n  <data name=\"Label_ErrorRetrievingWorkflows\" xml:space=\"preserve\">\n    <value>Erreur lors de l&apos;obtention des workflows</value>\n  </data>\n  <data name=\"TabLabel_InstalledWorkflows\" xml:space=\"preserve\">\n    <value>Workflows installés</value>\n  </data>\n  <data name=\"Label_WorkflowImported\" xml:space=\"preserve\">\n    <value>Workflows importé</value>\n  </data>\n  <data name=\"Label_FinishedImportingWorkflow\" xml:space=\"preserve\">\n    <value>L&apos;import du workflow et des custom nodes est terminé</value>\n  </data>\n  <data name=\"Label_WorkflowImportComplete\" xml:space=\"preserve\">\n    <value>Le workflow et les custom nodes ont été importés.</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>Aller aux paramètres</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>Activer</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>Désactiver</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Connecter le compte Lykos</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>Connectez-vous avec votre compte Lykos pour utiliser les fonctionnalités connectées.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>Veuillez vous reconnecter</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>Votre connexion a expiré. Veuillez vous reconnecter pour continuer.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Soutenir Stability Matrix</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>Merci de soutenir Stability Matrix !</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>Les fonctionnalités comme **{0}** font partie des nombreux avantages offerts à nos contributeurs. Votre contribution nous aide à couvrir les frais de serveur et soutient le développement de Stability Matrix.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>Les fonctionnalités comme **{0}** sont disponibles à partir du niveau de contributeur **{1}** (ou supérieur). Votre contribution nous aide à couvrir les frais de serveur pour les fonctionnalités connectées plus avancées et nous permet d&apos;améliorer continuellement Stability Matrix pour tous.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>Si vous nous soutenez déjà sur Patreon, veuillez lier votre compte pour continuer.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>Paramètres du compte</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>Voir les options de soutien</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>Plus tard</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>Statut</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>Actif</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>Inactif (Connexion standard utilisée)</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>Bénéficiez de résultats de recherche plus rapides lors de la navigation dans les modèles des dépôts en ligne, tels que Civitai.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>Optimisation expérimentale pour les dépôts tiers. Non officiellement affilié ; la disponibilité peut varier.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>Bêta</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>Recherche de Modèles Accélérée</value>\n    <comment>Fuzzy</comment>\n  </data>\n</root>"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.it-it.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>Avvia</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>Esci</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>Salva</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>Cancella</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>Lingua</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>E&apos; richiesto il riavvio per rendere effettivi i cambiamenti</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>Riavvia</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>Riavvia Dopo</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>Riavvio Richiesto</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>Pacchetto Sconosciuto</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>Importa</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>Tipologia Pacchetto</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>Versione</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>Tipologia Versione</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>Releases</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>Branches</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>Trascina qui i checkpoint per importarli</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>Accentuazione</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>Attenuazione</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>Embeddings / Inversione Testuale</value>\n    <comment>I&apos;m not sure I&apos;ll translate this term because it is specifically contextual to Stable Diffusion, and it may confuse several users who are already using it.</comment>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>Reti (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>Commenti</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>Mostra la griglia di pixel a livelli di zoom elevati</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>Steps</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>Steps - Base</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>Steps - Refiner</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>Scala CFG</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Valore di Denoising</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>Larghezza</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>Altezza</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Refiner</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>Modello</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>Connetti</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>In connessione...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>Chiudi</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>In attesa della connessione...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>Aggiornamento Disponibile</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>Diventa un Patreon</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Unisciti al Server Discord</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>Scaricamenti</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>Installa</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>Salta la configurazione iniziale</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>Si è verificato un errore imprevisto</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>Esci dall&apos;Applicazione</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>Nome da Visualizzare</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>Esiste già un&apos;installazione con questo nome.</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>Scegli un nome diverso o seleziona un percorso di installazione diverso.</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>Opzioni Avanzate</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>Commit</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>Strategia Cartella Modello Condiviso</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>Versione PyTorch</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>Chiudi la finestra di dialogo al termine</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>Cartella dei Dati</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>Qui vengono installati i modelli checkpoint, le LORA, l&apos;interfaccia utente Web, le impostazioni ecc.</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>Potresti riscontrare errori quando utilizzi un&apos;unità FAT32 o exFAT. Seleziona un&apos;unità diversa per un&apos;esperienza più fluida.</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>Modalità Portable</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>In Modalità Portable, tutti i dati e le impostazioni verranno archiviati nella stessa directory dell&apos;applicazione. Potrai spostare l&apos;applicazione con la sua cartella &apos;Dati&apos; in una posizione o computer diversi.</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>Continua</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>Immagine Precedente</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>Immagine Successiva</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>Descrizione Modello</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>E&apos; disponibile una nuova versione di Stability Matrix!</value>\n    <comment>Maybe a variable with the {appname} would be a better solution?</comment>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>Importa più Recente -</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>Tutte le Versioni</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>Cerca modelli, #tags, o @utenti</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>Cerca</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>Ordina</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>Periodo</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>Tipo di Modello</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>Modello Base</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>Mostra Contenuti NSFW</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>Dati forniti da CivitAI</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>Pagina</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>Prima Pagina</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>Pagina Precedente</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>Pagina Successiva</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>Ultima Pagina</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>Rinomina</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>Cancella</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>Apri su CivitAI</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>Modello Collegato</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>Modello Locale</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>Mostra in Explorer</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>Nuovo</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>Cartella</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>Rilascia il file qui per importarlo</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>Recupera i metadati durante l&apos;importazione</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>Cerca metadati collegati sulle nuove importazioni locali</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>Indicizzazione...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>Cartella Modelli</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>Categorie</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>Iniziamo</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>Ho letto e acconsento al</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>Contratto di Licenza.</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>Trova i Metadati Collegati</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>Mostra Immagini del Modello</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>Aspetto</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>Tema</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>Gestione Checkpoint</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>Rimuovi allo spegnimento i collegamenti simbolici alla directory dei checkpoint condivisi</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Seleziona questa opzione se riscontri problemi nello spostamento di Stability Matrix su un&apos;altra unità</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>Reimposta la Cache dei Checkpoint</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>Ricostruisci la cache dei checkpoint installati. Da utilizzare se i checkpoint sono etichettati in modo errato nel Browser Modello</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>Ambiente del Pacchetto</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>Modifica</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>Variabili Ambiente</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>Python Incorporato</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>Controlla Versione</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>Integrazioni</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Discord Rich Presence</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>Sistema</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>Aggiungi Stability Matrix al Menù Start</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>Utilizza la posizione corrente, puoi eseguirla di nuovo se sposti l&apos;app</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Disponibile solo su Windows</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>Aggiungi per l&apos;Utente Corrente</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>Aggiungi per Tutti gli Utenti</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>Seleziona una nuova Cartella Dati</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>Non sposta i dati esistenti</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>Seleziona Cartella</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>Informazioni</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>Informazioni sulla Licenza e Open Source</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>Fai clic su Avvia per iniziare!</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>Ferma</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>Manda Input</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>Input</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>Invia</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>Input richiesto</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>Confermi?</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>Si</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>No</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Apri la Web UI</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>Benvenuto su Stability Matrix!</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>Scegli la tua interfaccia preferita e clicca su Installa per inziare</value>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>Installazione in corso</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>Procediamo alla pagina di Avvio</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>Download del pacchetto in corso...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>Download completato</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>Installazione completata</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>Installazione dei prerequisiti...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>Installazione requisiti del pacchetto...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>Apri in Explorer</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>Apri sul Finder</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>Disinstalla</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>Controlla gli Aggiornamenti</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>Aggiorna</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>Aggiungi un Pacchetto</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>Aggiungi un pacchetto per iniziare!</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>Nome</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>Valore</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>Rimuovi</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>Dettagli</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>Stack di chiamate</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>Eccezione interna</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>Cerca...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>OK</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>Riprova</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Informazioni Versione Python</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>Riavvia</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>Conferma Cancellazione</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>Ciò eliminerà la cartella del pacchetto e tutto il suo contenuto, comprese eventuali immagini e file generati che potresti aver aggiunto.</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>Disinstallazione del pacchetto in corso...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>Pacchetto Disinstallato</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>Impossibile eliminare alcuni file. Chiudi tutti i file aperti nella directory del pacchetto e riprova.</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>Tipo di pacchetto non valido</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>Aggiornamento {0}</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>Aggiornamento Completato</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} è stato aggiornato all&apos;ultima versione</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>Errore aggiornamento {0}</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>Aggiornamento fallito</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>Apri nel Browser</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>Errore installazione pacchetto</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>Branch</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>Vai alle impostazioni</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>Abilita</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>Disabilita</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Collega il tuo account Lykos</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>Accedi con il tuo account Lykos per utilizzare le funzionalità connesse.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>Effettua nuovamente l&apos;accesso</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>Il tuo accesso è scaduto. Effettua nuovamente l&apos;accesso per continuare.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Sostegno a Stability Matrix</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>Grazie per il Suo sostegno a Stability Matrix!</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>Funzionalità come **{0}** sono uno dei tanti vantaggi disponibili per i nostri sostenitori. Il Suo contributo ci aiuta a coprire i costi del server e supporta lo sviluppo di Stability Matrix.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>Funzionalità come **{0}** sono disponibili al livello sostenitore **{1}** (o superiore). Il Suo contributo ci aiuta a coprire i costi del server per le funzionalità connesse più avanzate e ci permette di migliorare costantemente Stability Matrix per tutti.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>Se ci sta già sostenendo su Patreon, La preghiamo di collegare il Suo account per continuare.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>Impostazioni account</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>Visualizza opzioni di sostegno</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>Più tardi</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>Stato</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>Attivo</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>Non attivo (Utilizzando connessione standard)</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>Ottenga risultati di ricerca più veloci durante l&apos;esplorazione dei modelli dai repository online, come Civitai.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>Ottimizzazione sperimentale per repository di terze parti. Non affiliato ufficialmente; la disponibilità può variare.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>Beta</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>Ricerca Accelerata dei Modelli</value>\n    <comment>Fuzzy</comment>\n  </data>\n</root>"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>Launch</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>終了</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>保存</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>キャンセル</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>言語</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>言語を変更するにはアプリの再起動が必要です</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>再起動</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>後で再起動</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>再起動が必要です</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>不明なパッケージ</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>インポート</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>パッケージタイプ</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>Version</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>Version Type</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>Releases</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>ブランチ</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>インポートするCheckpointをここにドラッグ＆ドロップ</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>プロンプトの強調</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>プロンプトを弱める</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>Embeddings / Textual Inversion</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>Networks (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>コメント</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>高倍率でピクセルグリッドを表示</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>Steps</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>Steps - Base</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>Steps - Refiner</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>CFG Scale</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Denoising Strength</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>幅</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>高</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Refiner</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>モデル</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>接続</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>接続中...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>閉じる</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>接続待ち...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>更新あり</value>\n  </data>\n  <data name=\"Label_EarlyAccessUpdateAvailable\" xml:space=\"preserve\">\n    <value>Early Accessの更新あり</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>Patreonになる</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Discordに参加</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>ダウンロード</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>インストール</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>セットアップをスキップ</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>予期せぬエラーが発生しました</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>アプリケーションを終了</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>表示名</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>同じ名前が既に存在します。</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>別の名前を選択するか、別のインストール場所を選択してください。</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>高度なオプション</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>コミット</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>Shared Model Folder Strategy</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>PyTorchバージョン</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>終了時にダイアログを閉じる</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>データフォルダ</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>ここにCheckpoint、LORA、Web UI、設定などがインストールされます。</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>フォーマット形式がFAT32またはexFATのドライブを使用するとエラーが発生する場合があります。他のドライブを選択することで、よりスムーズにご利用いただけます。</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>Portableモード</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>Portableモードではすべてのデータと設定はアプリケーションと同じフォルダに保存されます。アプリケーションと「Data」フォルダを一緒に移動させることで、別のフォルダや別のコンピュータに移すことができます。</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>続ける</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>前の画像</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>次の画像</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>モデルの説明</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>Stability Matrixの最新版がリリース！</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>最新版DL</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>すべてのバージョン</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>検索 ( models, #tags, @users )</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>検索</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>並び替え</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>期間</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>モデルタイプ</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>ベースモデル</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>NSFWコンテンツを表示</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>CivitAIによる情報</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>ページ</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>最初のページ</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>前のページ</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>次のページ</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>最終ページ</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>名前変更</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>削除</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>CivitAIで開く</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>メタデータ取得済みモデル</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>ローカルモデル</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>エクスプローラで表示</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>New</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>フォルダ</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>ここにファイルをドロップしてインポート</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>インポート時にメタデータを自動検索</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>ローカルからのインポート時にオンラインでメタデータを検索して適用します</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>インデックス作成中...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>モデルフォルダ</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>カテゴリ</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>さあ、始めよう</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>次の約款を読み、同意します：</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>使用許諾契約書</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>メタデータを探す</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>モデルの見出し画像を表示</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>外観</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>テーマ</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>Checkpointマネージャー</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>Checkpointフォルダ内のシンボリックリンクをシャットダウンか再起動時に削除</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Stability Matrix を別のドライブに移動する際に問題が起きた場合、ここにチェック</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>Checkpointキャッシュのリセット</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>Checkpointsキャッシュを再構築します。モデルブラウザでCheckpointsのラベルが正しくない場合に使用してください</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>パッケージ環境</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>編集</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>環境変数</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>組み込みPython</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>バージョンを確認</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>統合</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Stability Matrix利用中にDiscordステータス欄に表示</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>システム</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>スタートメニューにStability Matrixを追加</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>現在のアプリの場所を使用します。アプリを移動すると、これを再度実行できます</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Windowsのみ</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>現在のユーザーに追加</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>全ユーザーに追加</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>新しいデータフォルダを選択</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>既にあるデータは移動しません</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>フォルダを選択</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>About</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>ライセンスとオープンソースに関する注意事項</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>&quot;Launch&quot;をクリックして始めよう！</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>ストップ</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>インプットを送信</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>インプット</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>送信</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>入力が必要</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>これでいい？</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>はい</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>いいえ</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Web UIを開く</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrixへようこそ！</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>好きなUIをインストールして始めよう</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>インストール中</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>Launchページに進む</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>パッケージをダウンロード中...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>ダウンロード完了</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>インストール完了</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>前提ライブラリのインストール中...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>パッケージに必要なライブラリのインストール中...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>エクスプローラで開く</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>ファインダーで開く</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>アンインストール</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>アップデートを確認</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>更新</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>パッケージの追加</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>パッケージを追加して始めよう！</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>変数名</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>値</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>削除</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>詳細</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>コールスタック</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>内部例外</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>検索...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>OK</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>リトライ</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Pythonバージョン情報</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>リスタート</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>削除する</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>これにより、生成された画像や追加したファイルを含め、パッケージフォルダとそのすべてのコンテンツが削除されます。</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>パッケージのアンインストール...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>パッケージのアンインストール</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>一部のファイルを削除できませんでした。該当のフォルダの開いているファイルを全て閉じて、もう一度試してください。</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>無効なパッケージタイプ</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>{0}の更新</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>更新完了</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0}が最新版に更新されました</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>{0}の更新エラー</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>更新失敗</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>ブラウザで開く</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>パッケージのインストールエラー</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>Branch</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>コンソール画面の最後まで自動スクロールする</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>ライセンス</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>モデルの共有</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>データフォルダを選択してください</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>データフォルダの名前</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>現在のフォルダ:</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>アップデート完了後に再起動します</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>また後で</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>インストールする</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>リリースノート</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>プロジェクトファイルを開く</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>名前をつけて保存</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>レイアウトを初期状態に戻す</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>共有画像フォルダ</value>\n  </data>\n  <data name=\"Label_BatchIndex\" xml:space=\"preserve\">\n    <value>Batch Index</value>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>コピー</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>Image Viewerで開く</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>{0}枚の画像を選択</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>Outputフォルダ</value>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>アウトプットタイプ</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>Clear Selection</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>全て選択</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>Inferenceに送る</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>Text to Image</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>Image to Image</value>\n  </data>\n  <data name=\"Label_Inpainting\" xml:space=\"preserve\">\n    <value>Inpainting</value>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>アップスケール</value>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>アウトプットブラウザ</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>1枚の画像を選択</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Pythonパッケージ</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>Consolidate</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>よろしいですか？</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>これにより、選択したパッケージから生成されたすべてのイメージが、共有出力フォルダの Consolidated フォルダに移動します。この操作は元に戻せません。</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>更新</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>アップグレード</value>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>ダウングレード</value>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>GitHubで開く</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>接続しました</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>切断しました</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>Email</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>ユーザ名</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>パスワード</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>ログイン</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>サインアップ</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>パスワード再確認</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>API Key</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>アカウント</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>プリプロセッサ</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>強度</value>\n  </data>\n  <data name=\"Label_ControlWeight\" xml:space=\"preserve\">\n    <value>Control Weight</value>\n  </data>\n  <data name=\"Label_ControlSteps\" xml:space=\"preserve\">\n    <value>Control Steps</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>ダウンロードにはCivitAIのログインが必要です。SettingからAPIキーを入力してください。</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>ダウンロード失敗</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>自動アップデート</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>Previewビルドはアーリーアダプター向けです。Devビルドよりも信頼性が高く、安定版に近い状態で利用できます。ぜひ意見や感想を送ってください。問題とデザインの改善に大いに役立ちます。</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>Devビルドはテクニカルなユーザ向けです。新機能をいち早く利用できます。荒削りな部分やバグがあるかもしれません。</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>アップデート</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>最新版です</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>Last checked: {0}</value>\n  </data>\n  <data name=\"Action_CopyTriggerWords\" xml:space=\"preserve\">\n    <value>トリガーワードをコピー</value>\n  </data>\n  <data name=\"Label_TriggerWords\" xml:space=\"preserve\">\n    <value>トリガーワード:</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>IPAdaptersやTextualInversions(embeddings)などのフォルダは、ここで有効にできます。</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Faceで開く</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>Metadataをアップデートする</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>General</value>\n    <comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>Inference</value>\n    <comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>プロンプト</value>\n    <comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_OutputImageFiles\" xml:space=\"preserve\">\n    <value>アウトプット画像</value>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>イメージビューア</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>自動補完</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>入力補完でアンダースコアをスペースに置き換える</value>\n  </data>\n  <data name=\"Label_PromptTags\" xml:space=\"preserve\">\n    <value>Prompt Tags</value>\n    <comment>Tags for image generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTagsImport\" xml:space=\"preserve\">\n    <value>Import Prompt tags</value>\n  </data>\n  <data name=\"Label_PromptTagsDescription\" xml:space=\"preserve\">\n    <value>入力補完に使用するタグリスト(拡張機能a1111-sd-webui-tagcomplete内にあるcsvファイルが使えます)</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>システム情報</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>アドオン</value>\n    <comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>中間画像を保存</value>\n    <comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>設定</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>ファイルを選択</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>Replace Contents</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>まだです</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>今後のアップデートにご期待ください</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>画像ファイルを見失ってます</value>\n  </data>\n  <data name=\"Label_HolidayMode\" xml:space=\"preserve\">\n    <value>ホリデーモード</value>\n  </data>\n  <data name=\"Label_CLIPSkip\" xml:space=\"preserve\">\n    <value>CLIP Skip</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>Image to Video</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>フレームレート(FPS)</value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>Min CFG</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>非圧縮</value>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>Frames</value>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>Motion Bucket ID</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>拡張レベル</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>Method</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>Quality</value>\n  </data>\n  <data name=\"Label_FindInModelBrowser\" xml:space=\"preserve\">\n    <value>モデルブラウザで探す</value>\n  </data>\n  <data name=\"Label_Installed\" xml:space=\"preserve\">\n    <value>Installed</value>\n  </data>\n  <data name=\"Label_NoExtensionsFound\" xml:space=\"preserve\">\n    <value>拡張機能はありません</value>\n  </data>\n  <data name=\"Action_Hide\" xml:space=\"preserve\">\n    <value>隠す</value>\n  </data>\n  <data name=\"Action_CopyDetails\" xml:space=\"preserve\">\n    <value>Copy Details</value>\n  </data>\n  <data name=\"Action_Download\" xml:space=\"preserve\">\n    <value>ダウンロード</value>\n  </data>\n  <data name=\"TeachingTip_DownloadsExplanation\" xml:space=\"preserve\">\n    <value>Packageのインストール・Modelのダウンロードの進捗状況はこちらで確認できます。</value>\n  </data>\n  <data name=\"Label_RecommendedModels\" xml:space=\"preserve\">\n    <value>推奨Model</value>\n  </data>\n  <data name=\"Label_RecommendedModelsSubText\" xml:space=\"preserve\">\n    <value>Packageのインストール中に、推奨Modelをいくつかご紹介しましょう。</value>\n  </data>\n  <data name=\"Label_Notifications\" xml:space=\"preserve\">\n    <value>通知</value>\n  </data>\n  <data name=\"Label_NotificationOption_None\" xml:space=\"preserve\">\n    <value>None</value>\n  </data>\n  <data name=\"Label_ComfyRequiredTitle\" xml:space=\"preserve\">\n    <value>ComfyUIが必要です</value>\n  </data>\n  <data name=\"Label_ComfyRequiredDetail\" xml:space=\"preserve\">\n    <value>ComfyUIのインストールが必要です。インストールしますか？</value>\n  </data>\n  <data name=\"Error_PleaseSelectDownloadLocation\" xml:space=\"preserve\">\n    <value>ダウンロード先を選んでください。</value>\n  </data>\n  <data name=\"Label_SelectDownloadLocation\" xml:space=\"preserve\">\n    <value>ダウンロード先を選んでください:</value>\n  </data>\n  <data name=\"Label_Config\" xml:space=\"preserve\">\n    <value>設定</value>\n  </data>\n  <data name=\"Label_ToggleAutoScrolling\" xml:space=\"preserve\">\n    <value>最後まで自動スクロール</value>\n  </data>\n  <data name=\"Label_ConfirmExit\" xml:space=\"preserve\">\n    <value>終了を確認</value>\n  </data>\n  <data name=\"Label_ConfirmExitDetail\" xml:space=\"preserve\">\n    <value>本当に終了してもいいですか？現在実行中のパッケージも終了します。</value>\n  </data>\n  <data name=\"Label_Console\" xml:space=\"preserve\">\n    <value>コンソール</value>\n  </data>\n  <data name=\"Label_WebUi\" xml:space=\"preserve\">\n    <value>Web UI</value>\n  </data>\n  <data name=\"Label_Packages\" xml:space=\"preserve\">\n    <value>パッケージ</value>\n  </data>\n  <data name=\"Label_ActionCannotBeUndone\" xml:space=\"preserve\">\n    <value>このアクションは元に戻せません。</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteImages\" xml:space=\"preserve\">\n    <value>{0}枚の画像を削除してもいいですか？</value>\n  </data>\n  <data name=\"Label_CheckingHardware\" xml:space=\"preserve\">\n    <value>ハードウェアの仕様から互換性を判断しています。</value>\n  </data>\n  <data name=\"Label_EverythingLooksGood\" xml:space=\"preserve\">\n    <value>いい感じ！</value>\n  </data>\n  <data name=\"Label_NvidiaGpuRecommended\" xml:space=\"preserve\">\n    <value>快適な利用にはCUDA対応GPUを推奨します。非対応の場合一部のパッケージが動作しない場合や、動作が遅くなる場合があります。</value>\n  </data>\n  <data name=\"Label_Checkpoints\" xml:space=\"preserve\">\n    <value>Checkpoints</value>\n  </data>\n  <data name=\"Label_ModelBrowser\" xml:space=\"preserve\">\n    <value>モデルブラウザ</value>\n  </data>\n  <data name=\"Label_Workflows\" xml:space=\"preserve\">\n    <value>ワークフロー</value>\n  </data>\n  <data name=\"Label_InfiniteScrolling\" xml:space=\"preserve\">\n    <value>無限スクロール</value>\n  </data>\n  <data name=\"Label_WorkflowBrowser\" xml:space=\"preserve\">\n    <value>ワークフローブラウザ</value>\n  </data>\n  <data name=\"Action_OpenOnOpenArt\" xml:space=\"preserve\">\n    <value>OpenArtで開く</value>\n  </data>\n  <data name=\"Label_NodeDetails\" xml:space=\"preserve\">\n    <value>ノード詳細</value>\n  </data>\n  <data name=\"Label_WorkflowDescription\" xml:space=\"preserve\">\n    <value>ワークフロー概要</value>\n  </data>\n  <data name=\"Label_OpenArtBrowser\" xml:space=\"preserve\">\n    <value>OpenArtブラウザ</value>\n  </data>\n  <data name=\"Action_PreviewPreprocessor\" xml:space=\"preserve\">\n    <value>プリプロセッサをプレビュー</value>\n  </data>\n  <data name=\"TeachingTip_WebUiButtonMoved\" xml:space=\"preserve\">\n    <value>&apos;Web UIを開く&apos;ボタンがコマンドバーに移動しました</value>\n  </data>\n  <data name=\"Label_AnotherInstanceAlreadyRunning\" xml:space=\"preserve\">\n    <value>Stability Matrixは既に起動中です。終了してから、もう一度起動してください。</value>\n  </data>\n  <data name=\"Label_StabilityMatrixAlreadyRunning\" xml:space=\"preserve\">\n    <value>Stability Matrixは既に起動中です</value>\n  </data>\n  <data name=\"Label_WorkflowDeletedSuccessfully\" xml:space=\"preserve\">\n    <value>{0} 個削除しました</value>\n  </data>\n  <data name=\"Label_WorkflowDeleted\" xml:space=\"preserve\">\n    <value>ワークフローを削除しました</value>\n  </data>\n  <data name=\"Label_ErrorRetrievingWorkflows\" xml:space=\"preserve\">\n    <value>ワークフローの取得中にエラーが発生しました</value>\n  </data>\n  <data name=\"TabLabel_InstalledWorkflows\" xml:space=\"preserve\">\n    <value>インストールされたワークフロー</value>\n  </data>\n  <data name=\"Label_WorkflowImported\" xml:space=\"preserve\">\n    <value>ワークフローをインポートしました</value>\n  </data>\n  <data name=\"Label_FinishedImportingWorkflow\" xml:space=\"preserve\">\n    <value>ワークフローとカスタムノードをインポートしました</value>\n  </data>\n  <data name=\"Label_WorkflowImportComplete\" xml:space=\"preserve\">\n    <value>ワークフローとカスタムノードをインポートしました。</value>\n  </data>\n  <data name=\"TeachingTip_InferencePromptHelpButton\" xml:space=\"preserve\">\n    <value>ここをクリックしてプロンプトの構文を確認し、LoRAやEmbeddingsの使い方を確認してください。</value>\n  </data>\n  <data name=\"Label_ExtraNetworks\" xml:space=\"preserve\">\n    <value>Extra Networks (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_CLIPStrength\" xml:space=\"preserve\">\n    <value>CLIP Strength</value>\n  </data>\n  <data name=\"Label_NumberFormat\" xml:space=\"preserve\">\n    <value>Number Format</value>\n  </data>\n  <data name=\"Text_DeleteFollowingItems\" xml:space=\"preserve\">\n    <value>以下のアイテムを削除しようとしています:</value>\n  </data>\n  <data name=\"TextTemplate_DeleteFollowingCountItems\" xml:space=\"preserve\">\n    <value>{0}個のアイテムを削除しようとしています:</value>\n  </data>\n  <data name=\"Label_DeletePermanently\" xml:space=\"preserve\">\n    <value>完全に削除する</value>\n  </data>\n  <data name=\"Action_MoveToTrash\" xml:space=\"preserve\">\n    <value>ゴミ箱に送る</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteModels\" xml:space=\"preserve\">\n    <value>{0}個のモデルを削除しますか？</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad\" xml:space=\"preserve\">\n    <value>ロード時に自動的に検索</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad_Description\" xml:space=\"preserve\">\n    <value>モデルブラウザが読み込まれたら、自動的に検索を開始する</value>\n  </data>\n  <data name=\"Action_ToggleVisibility\" xml:space=\"preserve\">\n    <value>表示の切り替え</value>\n  </data>\n  <data name=\"Label_ClippingMask\" xml:space=\"preserve\">\n    <value>Clipping Mask</value>\n  </data>\n  <data name=\"Label_AppFolders\" xml:space=\"preserve\">\n    <value>App Folders</value>\n  </data>\n  <data name=\"Label_Logs\" xml:space=\"preserve\">\n    <value>Logs</value>\n  </data>\n  <data name=\"Label_AppData\" xml:space=\"preserve\">\n    <value>App Data</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToSelected\" xml:space=\"preserve\">\n    <value>{0}のバージョンが更新されました。</value>\n  </data>\n  <data name=\"Action_CopyAsBitmap\" xml:space=\"preserve\">\n    <value>bitmapにコピー</value>\n  </data>\n  <data name=\"Label_DisableUpdateCheck\" xml:space=\"preserve\">\n    <value>アップデートチェックを無効にする</value>\n  </data>\n  <data name=\"Warning_PleaseExtractFirst\" xml:space=\"preserve\">\n    <value>Stability Matrixを実行する前に、ZIPファイルからアプリを抽出してください</value>\n  </data>\n  <data name=\"Label_HistorySize\" xml:space=\"preserve\">\n    <value>履歴数</value>\n  </data>\n  <data name=\"Label_HistorySize_Description\" xml:space=\"preserve\">\n    <value>コンソールに表示されている行より上にスクロールして戻ることができる行数</value>\n  </data>\n  <data name=\"Label_ConnectAccountFailed\" xml:space=\"preserve\">\n    <value>アカウント接続に問題が発生しました</value>\n  </data>\n  <data name=\"Label_EditModelMetadata\" xml:space=\"preserve\">\n    <value>モデルメタデータの編集</value>\n  </data>\n  <data name=\"Label_NSFW\" xml:space=\"preserve\">\n    <value>NSFW</value>\n  </data>\n  <data name=\"Label_Tags\" xml:space=\"preserve\">\n    <value>タグ</value>\n  </data>\n  <data name=\"Label_VersionName\" xml:space=\"preserve\">\n    <value>バージョン名</value>\n  </data>\n  <data name=\"Label_TrainedWords\" xml:space=\"preserve\">\n    <value>言葉を学習する</value>\n  </data>\n  <data name=\"Label_PreviewImage\" xml:space=\"preserve\">\n    <value>前の画像</value>\n  </data>\n  <data name=\"Label_BatchSize\" xml:space=\"preserve\">\n    <value>Batch Size</value>\n  </data>\n  <data name=\"Label_Batches\" xml:space=\"preserve\">\n    <value>Batches</value>\n  </data>\n  <data name=\"Label_Sampler\" xml:space=\"preserve\">\n    <value>サンプラー</value>\n  </data>\n  <data name=\"Label_Scheduler\" xml:space=\"preserve\">\n    <value>スケジューラー</value>\n  </data>\n  <data name=\"Label_MaxSize\" xml:space=\"preserve\">\n    <value>最大数</value>\n  </data>\n  <data name=\"Label_UseSeparatePrompt\" xml:space=\"preserve\">\n    <value>分割プロンプトを使う</value>\n  </data>\n  <data name=\"Label_Seed\" xml:space=\"preserve\">\n    <value>シード</value>\n  </data>\n  <data name=\"Label_NegativePrompt\" xml:space=\"preserve\">\n    <value>Negative Prompt</value>\n  </data>\n  <data name=\"Label_NewFolder\" xml:space=\"preserve\">\n    <value>新しいフォルダ</value>\n  </data>\n  <data name=\"Label_CopyLinkToClipboard\" xml:space=\"preserve\">\n    <value>リンクをクリップボードにコピー</value>\n  </data>\n  <data name=\"TextTemplate_OAuthLoginTitle\" xml:space=\"preserve\">\n    <value>{0} でサインイン</value>\n    <comment>e.g. &apos;Sign in with Google&apos;</comment>\n  </data>\n  <data name=\"Text_AllowBrowserOpenAppLink\" xml:space=\"preserve\">\n    <value>ブラウザ上でこのアプリを開くように指示されますので、その通りにしてください。</value>\n  </data>\n  <data name=\"Text_OAuthLoginDescription\" xml:space=\"preserve\">\n    <value>ブラウザのリンクを開き、指示に従ってアカウントを接続してください</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Title\" xml:space=\"preserve\">\n    <value>Python Dependencies Override</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Description\" xml:space=\"preserve\">\n    <value>インストールと更新時に依存関係を追加・置換・削除</value>\n  </data>\n  <data name=\"Label_DependencySpecifiers\" xml:space=\"preserve\">\n    <value>Dependency Specifiers</value>\n  </data>\n  <data name=\"AnalyticsExample_InstallData\" xml:space=\"preserve\">\n    <value>{\n  &quot;packageName&quot;: &quot;stable-diffusion-webui&quot;,\n  &quot;packageVersion&quot;: &quot;v1.10.0&quot;,\n  &quot;isSuccess&quot;: true,\n  &quot;type&quot;: &quot;install&quot;,\n  &quot;timestamp&quot;: &quot;2024-09-04T02:14:04.1967404+00:00&quot;\n}</value>\n  </data>\n  <data name=\"Label_Analytics\" xml:space=\"preserve\">\n    <value>アナリティクス</value>\n  </data>\n  <data name=\"TextTemplate_YouCanChangeThisBehavior\" xml:space=\"preserve\">\n    <value>この動作はいつでも {0} の手順で変更できます。</value>\n    <comment>e.g. &apos;You can always change this behavior in Settings &gt; Category &gt; Item.&apos;</comment>\n  </data>\n  <data name=\"Text_AnalyticsDescription\" xml:space=\"preserve\">\n    <value>Stability Matrixの改善にご協力ください。OS, パッケージの種類など改善に必要なデータのみ匿名で送信し、あなた及びあなたのアカウントとの関連付けはいたしません、また個人データ・機密情報は決して送信しません。</value>\n  </data>\n  <data name=\"Text_AnalyticsDescriptionShort\" xml:space=\"preserve\">\n    <value>使用された機能、OSのバージョン、インストールされているパッケージの種類などについての匿名データを送信して、Stability Matrixの改善にご協力ください。</value>\n  </data>\n  <data name=\"Text_AnalyticsDataPrivacyInfo\" xml:space=\"preserve\">\n    <value>送信されたデータは、あなたまたはあなたのアカウントとは決して関連付けられず、個人データや機密情報を含みません。</value>\n  </data>\n  <data name=\"Label_UsageData\" xml:space=\"preserve\">\n    <value>使用状況のデータ</value>\n  </data>\n  <data name=\"Label_PrivacyPolicy\" xml:space=\"preserve\">\n    <value>プライバシーポリシー</value>\n  </data>\n  <data name=\"Label_ImageHidden\" xml:space=\"preserve\">\n    <value>画像を隠す</value>\n  </data>\n  <data name=\"Label_NoImageFound\" xml:space=\"preserve\">\n    <value>画像が見つかりません</value>\n  </data>\n  <data name=\"Label_HideEmptyCategories\" xml:space=\"preserve\">\n    <value>非表示のカテゴリを非表示</value>\n  </data>\n  <data name=\"Label_ShowNsfwImages\" xml:space=\"preserve\">\n    <value>NSFW画像を表示</value>\n  </data>\n  <data name=\"Label_EnableLongPaths\" xml:space=\"preserve\">\n    <value>長いファイル名を有効にする</value>\n    <comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_EnableLongPathsDescription\" xml:space=\"preserve\">\n    <value>MAX_PATH limitations を削除する(Windows で長いファイルパスを有効にする設定)</value>\n    <comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_SystemSettings\" xml:space=\"preserve\">\n    <value>システム設定</value>\n  </data>\n  <data name=\"Label_ChangesApplied\" xml:space=\"preserve\">\n    <value>変更を適用する</value>\n  </data>\n  <data name=\"Text_RestartMayBeRequiredForSystemChanges\" xml:space=\"preserve\">\n    <value>変更を有効にするには、再起動が必要になる場合があります。</value>\n  </data>\n  <data name=\"Label_ReleasesUnavailableForThisPackage\" xml:space=\"preserve\">\n    <value>このパッケージではこのリリースを利用できません。</value>\n  </data>\n  <data name=\"Text_UnexpectedError_Description\" xml:space=\"preserve\">\n    <value>この問題の詳細を、圧縮したログファイルとともにお教えください。</value>\n  </data>\n  <data name=\"Text_UnexpectedErrorRecoverable_Description\" xml:space=\"preserve\">\n    <value>この後も続けて使えますが、再起動後にすべての機能を使えるようになります。この問題の詳細を、圧縮したログファイルとともにお教えください。</value>\n  </data>\n  <data name=\"Action_ShowLogInExplorer\" xml:space=\"preserve\">\n    <value>エクスプローラでログを開く</value>\n  </data>\n  <data name=\"Action_ShowLogInFinder\" xml:space=\"preserve\">\n    <value>Finderでログを開く</value>\n  </data>\n  <data name=\"Label_ExtensionPacks\" xml:space=\"preserve\">\n    <value>Extension Packs</value>\n  </data>\n  <data name=\"Label_NoExtensionPacksFound\" xml:space=\"preserve\">\n    <value>Extension Packsが見つかりません</value>\n  </data>\n  <data name=\"Label_OpenExtensionPacksFolder\" xml:space=\"preserve\">\n    <value>Extension Packフォルダを開く</value>\n  </data>\n  <data name=\"Label_InstallExtensionPack\" xml:space=\"preserve\">\n    <value>Extension Packをインストール</value>\n  </data>\n  <data name=\"Label_AddToExistingPack\" xml:space=\"preserve\">\n    <value>Extension Packを追加</value>\n  </data>\n  <data name=\"Label_NewExtensionPack\" xml:space=\"preserve\">\n    <value>新しいExtension Pack</value>\n  </data>\n  <data name=\"Label_CreateExtensionPackExplanation\" xml:space=\"preserve\">\n    <value>拡張機能を作成するには、&quot;利用可能な拡張機能&quot;または&quot;インストール済みの拡張機能&quot;タブから欲しい拡張機能を選んで保存をクリック</value>\n  </data>\n  <data name=\"Label_ImportExtensionPacksExplanation\" xml:space=\"preserve\">\n    <value>Dataディレクトリ内のExtensionPacksフォルダに.json拡張パックファイルを追加してください。</value>\n  </data>\n  <data name=\"Label_OrWithHyphensAround\" xml:space=\"preserve\">\n    <value>- または -</value>\n  </data>\n  <data name=\"Action_OpenOnOpenModelDb\" xml:space=\"preserve\">\n    <value>OpenModelDBを開く</value>\n  </data>\n  <data name=\"Label_Wildcards\" xml:space=\"preserve\">\n    <value>Wildcards</value>\n  </data>\n  <data name=\"Text_OAuthDeviceAuthDescription\" xml:space=\"preserve\">\n    <value>Stability Matrixのアカウント認証のため、ブラウザでリンクを開いて以下のコードを入力してください。</value>\n  </data>\n  <data name=\"Action_CopyAndOpen\" xml:space=\"preserve\">\n    <value>Copy and Open</value>\n  </data>\n  <data name=\"Text_LykosAccountUpgradeNotice\" xml:space=\"preserve\">\n    <value>## 【重要】 Lykosアカウントのログアウトとセキュリティ強化のお知らせ\n\nStability MatrixとLykosアカウントの連携を、より安全で便利なシステム（OAuth 2.0 with OpenID Connect）へと変更しました。これに伴い、Lykosアカウントからログアウトされています。\n\n### アップグレードの理由は？\n\nあなたのセキュリティとプライバシーを最重視するため、今回のアップグレードで以下の改善を行いました：\n\n* **よりスムーズなログイン環境:** [account.lykos.ai](https://account.lykos.ai) に一度ログインするだけで、Stability Matrixや他のLykos AIサービスに簡単にアクセスできます。\n* **より柔軟なログイン方法:** 既存の[lykos.ai](https://lykos.ai)アカウント、または**Apple**、**GitHub**、**Google**でのサインインが可能になりました。\n* **プライバシー保護の強化:** Stability Matrixは必要な権限のみ要求します。\n* **業界標準のセキュリティ:** 安全なログインの金標準であるOAuth 2.0を採用しています。\n* **将来を見据えた準備:** これにより今後も他のアプリやサービスと安全に連携できます。\n\n### 何をすればいいの？\n\n改めて&quot;Settings&quot;から、&quot;Lykos account&quot;の横にある&quot;Connect&quot;をクリックしてください。\n\n### 今後、&quot;Lykos account&quot; の登録が必須ということ？\n\n必須ではないです！ Stability Matrixは&quot;Lykos account&quot;がなくてもお使い頂けます。&quot;Lykos account&quot;に登録連携することで追加機能を利用できます。現在はPatreonサポーター向けの開発ビルドの自動アップデートなどです。（今後も追加予定！）\n\n</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>設定に行く</value>\n  </data>\n  <data name=\"TeachingTip_PromptAmplifier\" xml:space=\"preserve\">\n    <value>Prompt Amplifierを試そう！ あなたのプロンプトを簡単にクオリティアップ！</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>Enable</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>Disable</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Lykos accountに接続</value>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>この機能を使うには、Lykos accountにサインインしてください。</value>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>もう一度サインインしてください</value>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>ログイン期限が切れました。もう一度サインインしてください。</value>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Stability Matrixをサポートする</value>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>Stability Matrixをサポートしてくれてありがとう！</value>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>**{0}**のような機能は、サポーターの皆様が利用できる特典です。Stability Matrixの開発とサーバー費用は、皆様のご支援により支えられております。</value>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>**{0}**のような機能は、サポーターレベル**{1}**以上の方が利用できる特典です。皆様のご厚情により、サーバー費用と、より高度な接続機能を維持しながら、Stability Matrixの改良を続けられております。</value>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>もしも既にPatreonで私たちをサポートしている場合は、アカウントをリンクしてください。</value>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>アカウント設定</value>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>サポートオプションを見る</value>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>Maybe Later</value>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>Status</value>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>Active</value>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>Inactive (標準接続を使用)</value>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>CivitAIなどのオンラインリポジトリからモデルを検索する際、より高速に取得できます。</value>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>実験的なサードパーティリポジトリ用の最適化です。公式な提携はなく、利用可否は変動する可能性があります。</value>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>Beta</value>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>モデル検索の高速化</value>\n  </data>\n  <data name=\"PromptAmplifier_Disclaimer\" xml:space=\"preserve\">\n    <value>### 【🪄紹介】 Prompt Amplifier  \n私達が開発している実験中のプロンプト専用AIモデル&quot;Spark&quot;が、あなたのプロンプトを増強します。\n\nPrompt Amplifierはあなたのローカル環境を**使わず**、セキュアなエンタープライズグレードのクラウドサーバで稼働しています。\n\n### ☁️ クラウドを使う理由は？\nSparkは兆レベルのパラメータを持つ基盤モデルで、膨大なパワーが必要です。ローカル環境で動作可能な機能の最大化に努めていますが、&quot;今のところ&quot;Sparkの高度な機能はクラウドを通じて利用可能です。\n\n### 🔒 プライバシーファースト\n私たちはプライバシーを最優先に考えています。([生成AI利用規約](https://lykos.ai/gen-ai-terms)) **あなたのプロンプトや出力内容は、Lykos AIや必要なクラウドインフラパートナーによるAI学習には一切使用されません。** 処理はお客様の質問に対する返答を生成するためだけに安全を重視して使われ、**その後はプロンプト内容そのものではなく、メタデータ(タイムスタンプやトークン数など)のみを保存します。** あなたのデータが販売や共有されることは決してありません。</value>\n  </data>\n</root>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.ko-KR.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>실행</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>종료</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>저장</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>취소</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>언어</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>새로운 언어 설정을 적용하려면 재실행해야 합니다</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>재실행</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>나중에 재실행</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>재실행 필요</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>알 수 없는 패키지</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>가져오기</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>패키지 종류</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>버전</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>버전 종류</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>릴리즈</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>브랜치</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>불러올 체크포인트를 여기에 끌어서 놓으세요</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>강조</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>약화</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>임베딩 / Textual Inversion</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>신경망 (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>댓글</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>확대했을 때 픽셀 그리드 보이기</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>스텝</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>스텝 - Base</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>스텝 - Refiner</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>CFG 스케일</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Denoising 강도</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>너비</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>높이</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Refiner</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>모델</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>연결</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>연결 중...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>닫기</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>연결 대기 중...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>업데이트 가능</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>후원자 되기</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Discord 서버에 참여하기</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>다운로드</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>설치</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>초기 설정 건너뛰기</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>알 수 없는 오류가 발생했습니다</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>어플리케이션 종료</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>표시 이름</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>이 이름을 가진 설치본이 이미 존재합니다.</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>다른 이름을 선택하거나 다른 설치 위치를 선택하세요.</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>고급 설정</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>커밋</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>공유 모델 폴더 전략</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>PyTorch 버전</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>완료되면 모달 닫기</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>데이터 디렉토리</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>애플리케이션 데이터(모델 체크포인트, Web UI 등)가 설치되는 위치입니다.</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>FAT32나 exFAT 드라이브를 사용할 때 오류가 발생할 수 있습니다. 보다 원활한 경험을 위해 다른 드라이브를 선택하십시오.</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>무설치 모드</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>무설치 모드에서는 모든 데이터와 설정이 애플리케이션과 같은 디렉토리에 저장됩니다. 애플리케이션과 &apos;Data&apos; 폴더를 다른 위치나 컴퓨터로 이동할 수 있습니다.</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>계속</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>이전 이미지</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>다음 이미지</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>모델 설명</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>Stability Matrix의 새로운 버전이 출시되었습니다!</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>최신 버전 가져오기 -</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>모든 버전</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>모델이나 #태그, @유저를 검색하세요</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>검색</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>정렬</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>기간</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>모델 종류</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>Base 모델</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>성인 콘텐츠 표시</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>CivitAI에서 제공하는 데이터</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>페이지</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>첫 페이지</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>이전 페이지</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>다음 페이지</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>마지막 페이지</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>이름 변경</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>삭제</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>CivitAI에서 열기</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>연결된 모델</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>로컬 모델</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>탐색기에서 보기</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>신규</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>폴더</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>불러올 파일을 여기에 끌어서 놓으세요</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>메타데이터를 포함해서 가져오기</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>로컬에서 가져올 때 연결된 메타데이터 검색</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>인덱싱...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>모델 폴더</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>카테고리</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>시작하기</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>다음 내용을 읽고 동의합니다 :</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>라이선스 계약.</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>연결된 메타데이터 찾기</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>모델 이미지 표시</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>모양</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>테마</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>체크포인트 매니저</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>종료 시 공유 체크포인트 디렉토리 심볼릭 링크 제거</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Stability Matrix를 다른 드라이브로 이동하는 데 문제가 있는 경우 이 옵션을 선택하세요</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>체크포인트 캐시 초기화</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>설치된 체크포인트 캐시를 다시 빌드합니다. 모델 브라우저에서 체크포인트가 잘못 분류된 경우 사용합니다</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>패키지 환경</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>편집</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>환경 변수</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>내장 Python</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>버전 확인</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>통합</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Discord Rich Presence</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>시스템</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>Stability Matrix를 시작 메뉴에 추가</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>현재 앱 위치를 사용하며, 앱을 이동하면 다시 실행할 수 있습니다</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Windows에서만 사용 가능</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>현재 사용자를 위해 추가</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>모든 사용자를 위해 추가</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>새 Data 디렉토리 선택</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>기존 데이터를 이동하지 않습니다</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>디렉토리 선택</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>정보</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>라이센스 및 오픈 소스 고지</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>실행을 눌러 시작하세요!</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>중지</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>입력 전송</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>입력</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>전송</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>필수 입력입니다</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>확인했나요?</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>예</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>아니요</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Web UI 열기</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix에 오신것을 환영합니다!</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>시작하려면 선호하는 인터페이스를 선택하세요</value>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>설치 중</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>실행 페이지로 이동</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>패키지 다운로드 중...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>다운로드 완료</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>설치 완료</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>필수 구성 요소 설치 중...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>패키지 요구사항 설치 중...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>탐색기에서 열기</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>Finder에서 열기</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>제거</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>업데이트 확인</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>업데이트</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>패키지 추가</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>시작하려면 패키지를 추가하세요!</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>이름</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>값</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>제거</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>상세</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>콜스택</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>내부 예외</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>검색...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>확인</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>재시도</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Python 버전 정보</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>재시작</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>삭제 확인</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>패키지 폴더와 생성된 이미지와 추가한 파일을 포함한 모든 콘텐츠가 삭제됩니다.</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>패키지 제거 중...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>패키지 제거됨</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>일부 파일을 삭제할 수 없습니다. 패키지 디렉토리에서 열려 있는 모든 파일을 닫고 다시 시도하세요.</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>잘못된 패키지 유형</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>{0} 을(를) 업데이트하는 중입니다</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>업데이트 완료</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} 을(를) 최신 버전으로 업데이트했습니다</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>{0} 업데이트 오류</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>업데이트 실패</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>브라우저에서 열기</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>패키지 설치 오류</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>브랜치</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>콘솔 출력의 끝으로 자동 스크롤</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>라이선스</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>모델 공유</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>데이터 디렉토리를 선택해주세요</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>데이터 폴더 이름</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>현재 디렉토리:</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>업데이트 후 앱이 다시 실행됩니다</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>나중에 알림</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>지금 설치</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>릴리즈 노트</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>프로젝트 열기...</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>다른 이름으로 저장...</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>기본 레이아웃 복원</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>출력 공유</value>\n  </data>\n  <data name=\"Label_BatchIndex\" xml:space=\"preserve\">\n    <value>배치 인덱스</value>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>복사</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>이미지 뷰어로 열기</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>{0}개의 이미지 선택함</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>출력 폴더</value>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>출력 종류</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>선택 해제</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>모두 선택</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>추론으로 보내기</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>Text to Image</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>Image to Image</value>\n  </data>\n  <data name=\"Label_Inpainting\" xml:space=\"preserve\">\n    <value>인페인팅</value>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>업스케일</value>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>출력 브라우저</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>1개의 이미지 선택함</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Python 패키지</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>병합하기</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>확실한가요?</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>선택한 패키지에서 생성된 모든 이미지가 공유 출력 폴더의 Consolidated 디렉토리로 이동합니다. 이 작업은 되돌릴 수 없습니다.</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>새로고침</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>업그레이드</value>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>다운그레이드</value>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>Github에서 열기</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>연결됨</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>연결 해제</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>이메일</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>사용자 이름</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>암호</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>로그인</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>회원 가입</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>암호 확인</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>API 키</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>계정</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>전처리기</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>강도</value>\n  </data>\n  <data name=\"Label_ControlWeight\" xml:space=\"preserve\">\n    <value>제어 가중치</value>\n  </data>\n  <data name=\"Label_ControlSteps\" xml:space=\"preserve\">\n    <value>제어 스텝</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>이 체크포인트를 다운로드하려면 로그인해야 합니다. 설정에서 CivitAI API 키를 입력하세요.</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>다운로드 실패</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>자동 업데이트</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>얼리 어답터를 위한 내용입니다. 프리뷰 빌드는 Dev 채널의 빌드보다 더 안정적이며 안정 버전에 더 가깝게 제공됩니다. 여려분의 피드백은 문제를 발견하고 디자인 요소를 다듬는 데 큰 도움이 됩니다.</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>기술 사용자를 위한 내용입니다. 기능 브랜치로 개발 빌드에 가장 먼저 액세스해 보세요. 새로운 기능을 실험하면서 약간의 미숙한 부분과 버그가 있을 수 있습니다.</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>업데이트</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>최신입니다</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>마지막 확인: {0}</value>\n  </data>\n  <data name=\"Action_CopyTriggerWords\" xml:space=\"preserve\">\n    <value>트리거 단어 복사</value>\n  </data>\n  <data name=\"Label_TriggerWords\" xml:space=\"preserve\">\n    <value>트리거 단어:</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>여기에서 IPAdapters 와 Textual Inversions(임베딩) 같은 추가 폴더를 활성화할 수 있습니다</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face에서 열기</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>기존 메타데이터 업데이트</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>일반</value>\n    <comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>추론</value>\n    <comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>프롬프트</value>\n    <comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_OutputImageFiles\" xml:space=\"preserve\">\n    <value>출력 이미지 파일</value>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>이미지 뷰어</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>자동완성</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>추가할 때 밑줄을 공백으로 치환</value>\n  </data>\n  <data name=\"Label_PromptTags\" xml:space=\"preserve\">\n    <value>프롬프트 태그</value>\n    <comment>Tags for image generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTagsImport\" xml:space=\"preserve\">\n    <value>프롬프트 태그 가져오기</value>\n  </data>\n  <data name=\"Label_PromptTagsDescription\" xml:space=\"preserve\">\n    <value>자동완성 제안에 사용할 태그 파일(a1111-sd-webui-tagcomplete .csv 형식 지원)</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>시스템 정보</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>애드온</value>\n    <comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>중간 이미지 저장</value>\n    <comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>설정</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>파일 선택</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>내용 덮어쓰기</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>아직 사용할 수 없습니다</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>이 기능은 추후 업데이트에서 사용할 수 있습니다</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>이미지 파일 누락</value>\n  </data>\n  <data name=\"Label_HolidayMode\" xml:space=\"preserve\">\n    <value>축제 모드</value>\n  </data>\n  <data name=\"Label_CLIPSkip\" xml:space=\"preserve\">\n    <value>CLIP 스킵</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>Image to Video</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>초당 프레임</value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>최소 CFG</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>무손실</value>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>프레임</value>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>Motion Bucket ID</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>증강 수준</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>방식</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>품질</value>\n  </data>\n  <data name=\"Label_FindInModelBrowser\" xml:space=\"preserve\">\n    <value>모델 브라우저에서 찾기</value>\n  </data>\n  <data name=\"Label_Installed\" xml:space=\"preserve\">\n    <value>설치됨</value>\n  </data>\n  <data name=\"Label_NoExtensionsFound\" xml:space=\"preserve\">\n    <value>확장 프로그램을 찾을 수 없습니다.</value>\n  </data>\n  <data name=\"Action_Hide\" xml:space=\"preserve\">\n    <value>숨기기</value>\n  </data>\n  <data name=\"Action_CopyDetails\" xml:space=\"preserve\">\n    <value>세부내용 복사</value>\n  </data>\n  <data name=\"Action_Download\" xml:space=\"preserve\">\n    <value>다운로드</value>\n  </data>\n  <data name=\"TeachingTip_DownloadsExplanation\" xml:space=\"preserve\">\n    <value>여기에서 패키지 설치 및 모델 다운로드 진행 상황을 확인하세요.</value>\n  </data>\n  <data name=\"Label_RecommendedModels\" xml:space=\"preserve\">\n    <value>추천 모델</value>\n  </data>\n  <data name=\"Label_RecommendedModelsSubText\" xml:space=\"preserve\">\n    <value>패키지 설치가 진행되는 동안 시작하는 데 도움이 되도록 몇 가지 모델을 추천해 드립니다.</value>\n  </data>\n  <data name=\"Label_Notifications\" xml:space=\"preserve\">\n    <value>알림</value>\n  </data>\n  <data name=\"Label_NotificationOption_None\" xml:space=\"preserve\">\n    <value>없음</value>\n  </data>\n  <data name=\"Label_ComfyRequiredTitle\" xml:space=\"preserve\">\n    <value>ComfyUI 필요</value>\n  </data>\n  <data name=\"Label_ComfyRequiredDetail\" xml:space=\"preserve\">\n    <value>이 패키지를 설치하려면 ComfyUI가 필요합니다. 지금 설치할까요?</value>\n  </data>\n  <data name=\"Error_PleaseSelectDownloadLocation\" xml:space=\"preserve\">\n    <value>다운로드 위치를 선택해주세요.</value>\n  </data>\n  <data name=\"Label_SelectDownloadLocation\" xml:space=\"preserve\">\n    <value>다운로드 위치 선택:</value>\n  </data>\n  <data name=\"Label_Config\" xml:space=\"preserve\">\n    <value>설정</value>\n  </data>\n  <data name=\"Label_ToggleAutoScrolling\" xml:space=\"preserve\">\n    <value>끝까지 자동 스크롤</value>\n  </data>\n  <data name=\"Label_ConfirmExit\" xml:space=\"preserve\">\n    <value>종료 확인</value>\n  </data>\n  <data name=\"Label_ConfirmExitDetail\" xml:space=\"preserve\">\n    <value>정말 종료할까요? 현재 실행 중인 패키지도 모두 닫힙니다.</value>\n  </data>\n  <data name=\"Label_Console\" xml:space=\"preserve\">\n    <value>콘솔</value>\n  </data>\n  <data name=\"Label_WebUi\" xml:space=\"preserve\">\n    <value>Web UI</value>\n  </data>\n  <data name=\"Label_Packages\" xml:space=\"preserve\">\n    <value>패키지</value>\n  </data>\n  <data name=\"Label_ActionCannotBeUndone\" xml:space=\"preserve\">\n    <value>이 작업은 되돌릴 수 없습니다.</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteImages\" xml:space=\"preserve\">\n    <value>{0}개의 이미지를 삭제할까요?</value>\n  </data>\n  <data name=\"Label_CheckingHardware\" xml:space=\"preserve\">\n    <value>호환성을 확인하기 위해 일부 하드웨어 사양을 확인하고 있습니다.</value>\n  </data>\n  <data name=\"Label_EverythingLooksGood\" xml:space=\"preserve\">\n    <value>모든게 괜찮아 보입니다!</value>\n  </data>\n  <data name=\"Label_NvidiaGpuRecommended\" xml:space=\"preserve\">\n    <value>최상의 경험을 위해 CUDA를 지원하는 GPU를 권장합니다. GPU 없이도 계속할 수 있지만 일부 패키지는 작동하지 않을 수 있으며 추론이 더 느릴 수 있습니다.</value>\n  </data>\n  <data name=\"Label_Checkpoints\" xml:space=\"preserve\">\n    <value>체크포인트</value>\n  </data>\n  <data name=\"Label_ModelBrowser\" xml:space=\"preserve\">\n    <value>모델 브라우저</value>\n  </data>\n  <data name=\"Label_Workflows\" xml:space=\"preserve\">\n    <value>워크플로우</value>\n  </data>\n  <data name=\"Label_InfiniteScrolling\" xml:space=\"preserve\">\n    <value>무한 스크롤</value>\n  </data>\n  <data name=\"Label_WorkflowBrowser\" xml:space=\"preserve\">\n    <value>워크플로우 브라우저</value>\n  </data>\n  <data name=\"Action_OpenOnOpenArt\" xml:space=\"preserve\">\n    <value>OpenArt에서 열기</value>\n  </data>\n  <data name=\"Label_NodeDetails\" xml:space=\"preserve\">\n    <value>노드 상세</value>\n  </data>\n  <data name=\"Label_WorkflowDescription\" xml:space=\"preserve\">\n    <value>워크플로우 설명</value>\n  </data>\n  <data name=\"Label_OpenArtBrowser\" xml:space=\"preserve\">\n    <value>OpenArt 브라우저</value>\n  </data>\n  <data name=\"Action_PreviewPreprocessor\" xml:space=\"preserve\">\n    <value>전처리기 미리보기</value>\n  </data>\n  <data name=\"TeachingTip_WebUiButtonMoved\" xml:space=\"preserve\">\n    <value>&apos;Web UI 열기&apos; 버튼이 명령 모음으로 이동했습니다</value>\n  </data>\n  <data name=\"Label_AnotherInstanceAlreadyRunning\" xml:space=\"preserve\">\n    <value>또 다른 Stability Matrix 인스턴스가 이미 실행 중입니다. 새 인스턴스를 시작하기 전에 닫아주세요.</value>\n  </data>\n  <data name=\"Label_StabilityMatrixAlreadyRunning\" xml:space=\"preserve\">\n    <value>Stability Matrix가 이미 실행 중입니다</value>\n  </data>\n  <data name=\"Label_WorkflowDeletedSuccessfully\" xml:space=\"preserve\">\n    <value>{0} 을(를) 성공적으로 삭제했습니다</value>\n  </data>\n  <data name=\"Label_WorkflowDeleted\" xml:space=\"preserve\">\n    <value>워크플로우 삭제됨</value>\n  </data>\n  <data name=\"Label_ErrorRetrievingWorkflows\" xml:space=\"preserve\">\n    <value>워크플로를 가져오는 중에 오류가 발생했습니다</value>\n  </data>\n  <data name=\"TabLabel_InstalledWorkflows\" xml:space=\"preserve\">\n    <value>설치된 워크플로우</value>\n  </data>\n  <data name=\"Label_WorkflowImported\" xml:space=\"preserve\">\n    <value>워크플로우 가져옴</value>\n  </data>\n  <data name=\"Label_FinishedImportingWorkflow\" xml:space=\"preserve\">\n    <value>워크플로우와 사용자 정의 노드 가져오기 완료</value>\n  </data>\n  <data name=\"Label_WorkflowImportComplete\" xml:space=\"preserve\">\n    <value>워크플로우와 사용자 정의 노드를 가져왔습니다.</value>\n  </data>\n  <data name=\"TeachingTip_InferencePromptHelpButton\" xml:space=\"preserve\">\n    <value>여기를 클릭하여 프롬프트 구문과 Lora/임베딩을 추가하는 방법을 검토하세요.</value>\n  </data>\n  <data name=\"Label_ExtraNetworks\" xml:space=\"preserve\">\n    <value>추가 신경망 (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_CLIPStrength\" xml:space=\"preserve\">\n    <value>CLIP 강도</value>\n  </data>\n  <data name=\"Label_NumberFormat\" xml:space=\"preserve\">\n    <value>숫자 형식</value>\n  </data>\n  <data name=\"Text_DeleteFollowingItems\" xml:space=\"preserve\">\n    <value>다음 항목을 삭제하려고 합니다:</value>\n  </data>\n  <data name=\"TextTemplate_DeleteFollowingCountItems\" xml:space=\"preserve\">\n    <value>다음 {0}개의 항목을 삭제하려고 합니다:</value>\n  </data>\n  <data name=\"Label_DeletePermanently\" xml:space=\"preserve\">\n    <value>영구적으로 삭제하기</value>\n  </data>\n  <data name=\"Action_MoveToTrash\" xml:space=\"preserve\">\n    <value>휴지통으로 이동</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteModels\" xml:space=\"preserve\">\n    <value>{0}개의 모델을 삭제할까요?</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad\" xml:space=\"preserve\">\n    <value>로드 시 자동 검색</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad_Description\" xml:space=\"preserve\">\n    <value>모델 브라우저 페이지를 로드할 때 자동으로 검색을 시작합니다</value>\n  </data>\n  <data name=\"Action_ToggleVisibility\" xml:space=\"preserve\">\n    <value>가시성 토글</value>\n  </data>\n  <data name=\"Label_ClippingMask\" xml:space=\"preserve\">\n    <value>클리핑 마스크</value>\n  </data>\n  <data name=\"Label_AppFolders\" xml:space=\"preserve\">\n    <value>앱 폴더</value>\n  </data>\n  <data name=\"Label_Logs\" xml:space=\"preserve\">\n    <value>로그</value>\n  </data>\n  <data name=\"Label_AppData\" xml:space=\"preserve\">\n    <value>앱 데이터</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToSelected\" xml:space=\"preserve\">\n    <value>{0} 을(를) 선택한 버전으로 업데이트했습니다</value>\n  </data>\n  <data name=\"Action_CopyAsBitmap\" xml:space=\"preserve\">\n    <value>비트맵으로 복사</value>\n  </data>\n  <data name=\"Label_DisableUpdateCheck\" xml:space=\"preserve\">\n    <value>자동 업데이트 비활성화</value>\n  </data>\n  <data name=\"Warning_PleaseExtractFirst\" xml:space=\"preserve\">\n    <value>Stability Matrix를 실행하기 전에 압축을 해제하세요</value>\n  </data>\n  <data name=\"Label_HistorySize\" xml:space=\"preserve\">\n    <value>기록 크기</value>\n  </data>\n  <data name=\"Label_ConnectAccountFailed\" xml:space=\"preserve\">\n    <value>귀하의 계정을 연결하는 데 문제가 발생했습니다</value>\n  </data>\n  <data name=\"Label_EditModelMetadata\" xml:space=\"preserve\">\n    <value>모델 메타데이터 편집</value>\n  </data>\n  <data name=\"Label_NSFW\" xml:space=\"preserve\">\n    <value>성인물</value>\n  </data>\n  <data name=\"Label_Tags\" xml:space=\"preserve\">\n    <value>태그</value>\n  </data>\n  <data name=\"Label_VersionName\" xml:space=\"preserve\">\n    <value>버전 이름</value>\n  </data>\n  <data name=\"Label_TrainedWords\" xml:space=\"preserve\">\n    <value>학습된 단어</value>\n  </data>\n  <data name=\"Label_PreviewImage\" xml:space=\"preserve\">\n    <value>미리보기 이미지</value>\n  </data>\n  <data name=\"Label_BatchSize\" xml:space=\"preserve\">\n    <value>배치 크기</value>\n  </data>\n  <data name=\"Label_Batches\" xml:space=\"preserve\">\n    <value>배치</value>\n  </data>\n  <data name=\"Label_Sampler\" xml:space=\"preserve\">\n    <value>샘플러</value>\n  </data>\n  <data name=\"Label_Scheduler\" xml:space=\"preserve\">\n    <value>스케쥴러</value>\n  </data>\n  <data name=\"Label_MaxSize\" xml:space=\"preserve\">\n    <value>최대 크기</value>\n  </data>\n  <data name=\"Label_UseSeparatePrompt\" xml:space=\"preserve\">\n    <value>별도의 프롬프트 사용</value>\n  </data>\n  <data name=\"Label_Seed\" xml:space=\"preserve\">\n    <value>시드</value>\n  </data>\n  <data name=\"Label_NegativePrompt\" xml:space=\"preserve\">\n    <value>부정 프롬프트</value>\n  </data>\n  <data name=\"Label_NewFolder\" xml:space=\"preserve\">\n    <value>새 폴더</value>\n  </data>\n  <data name=\"Label_CopyLinkToClipboard\" xml:space=\"preserve\">\n    <value>클립보드에 링크 복사</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>설정으로 이동</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>활성화</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>비활성화</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Lykos 계정 연결</value>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>연결된 기능을 사용하려면 Lykos 계정으로 로그인하세요.</value>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>다시 로그인해주세요</value>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>로그인 유효 기간이 만료되었습니다. 계속하려면 다시 로그인해주세요.</value>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Stability Matrix 후원</value>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>Stability Matrix의 후원자가 되어 주셔서 감사합니다!</value>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>후원자분들께는 **{0}** 기능을 포함한 다양한 혜택이 제공됩니다. 회원님의 기여는 서버 비용을 충당하고 Stability Matrix 개발을 지원하는 데 도움이 됩니다.</value>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>**{0}** 기능은 **{1}** 후원자 등급(또는 그 이상)부터 이용 가능한 혜택입니다. 회원님의 기여는 더 고급 연결 기능을 위한 서버 비용을 충당하는 데 도움이 되며 모든 사용자를 위해 Stability Matrix를 계속 개선할 수 있게 해줍니다.</value>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>이미 Patreon에서 후원하고 계시다면, 계속 진행하려면 계정을 연결해 주세요.</value>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>계정 설정</value>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>후원 옵션 보기</value>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>나중에</value>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>상태</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>활성</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>비활성 (표준 연결 사용 중)</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>Civitai와 같은 온라인 리포지토리에서 모델을 탐색할 때 더 빠른 검색 결과를 경험해 보세요。</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>타사 리포지토리에 대한 실험적인 최적화입니다. 공식적으로 제휴되지 않았으며, 사용 가능 여부는 달라질 수 있습니다。</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>베타</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>가속 모델 탐색</value>\n    <comment>Fuzzy</comment>\n  </data>\n</root>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.pt-BR.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>Iniciar</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>Sair</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>Salvar</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>Cancelar</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>Idioma</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>É necessário reiniciar para que a nova opção de idioma entre em vigor</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>Reiniciar</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>Reiniciar Mais Tarde</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>Reinicialização Necessária</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>Pacote Desconhecido</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>Importar</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>Tipo de Pacote</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>Versão</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>Tipo de Versão</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>Lançamentos</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>Ramificações</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>Arraste e Solte os checkpoints aqui para importa-los</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>Ênfase</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>Desenfatizar</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>Embeddings / Inversão Textual</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>Redes (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>Comentários</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>Exibir grade de pixels com altos níveis de zoom</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>Etapas</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>Etapas - Base</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>Etapas - Refinador</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>Escala CFG</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Intensidade de Redução de Ruído</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>Largura</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>Altura</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Refinador</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>Modelo</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>Conectar</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>Conectando...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>Fechar</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>Aguardando conexão...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>Atualização Disponível</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>Seja um apoiador</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Junte-se ao servidor do Discord</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>Downloads</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>Instalar</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>Pular configuração inicial</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>Ocorreu um erro</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>Sair do Stability Matrix</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>Nome de Exibição</value>\n    <comment>Can I get some context on this?</comment>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>Já existe uma instalação do Stability Matrix com este nome.</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>Escolha outro nome ou local de instalação.</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>Opções Avançadas</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>Commit</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>Uso Compartilhado da Pasta de Modelos</value>\n    <comment>Please, Can I get some context?</comment>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>Versão do PyTorch</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>Fechar esta janela ao terminar</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>Diretório de Dados</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>Aqui serão instalados os dados do Stability Matrix (checkpoints dos modelos, interfaces web, etc.).</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>O uso de unidades FAT32 ou exFAT pode causar erros. Selecione outra unidade para evitar problemas.</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>Modo Portátil</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>No Modo Portátil, todos os dados e configurações serão armazenados na mesma pasta do programa. Você poderá mover o programa e a pasta &apos;Dados&apos; para outro local ou computador.</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>Continuar</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>Imagem Anterior</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>Próxima Imagem</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>Descrição do Modelo</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>Uma nova versão do Stability Matrix está disponível!</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>Importar a versão mais recente -</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>Todas as Versões</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>Buscar modelos, #tags ou @usuários</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>Pesquisar</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>Classificar</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>Período</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>Tipo de Modelo</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>Modelo Base</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>Mostrar Conteúdo NSFW</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>Dados fornecidos pela CivitAI</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>Página</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>Primeira Página</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>Página Anterior</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>Próxima Página</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>Última Página</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>Renomear</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>Excluir</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>Abrir no CivitAI</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>Modelo Conectado</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>Modelo Local</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>Mostrar no Explorador de Arquivos</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>Novo</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>Pasta</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>Solte o arquivo aqui para importar</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>Importar com Metadados</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>Buscar metadados conectados em novas importações locais</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>Indexando...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>Pasta de Modelos</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>Categorias</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>Vamos começar</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>Li e concordo com os</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>Contrato de Licença.</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>Buscar metadados conectados</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>Mostrar Imagens do Modelo</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>Aparência</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>Tema</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>Gerenciador de checkpoints</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>Remover links simbólicos do diretório de checkpoints compartilhados ao desligar</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Selecione esta opção se houver problemas ao mover o Stability Matrix para outra unidade</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>Redefinir cache de checkpoints</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>Reconstrói o cache de checkpoints instalados. Use esta opção se os checkpoints estiverem com os nomes errados no Navegador de Modelos</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>Ambiente do Pacote</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>Editar</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>Variáveis de Ambiente</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>Python Embutido</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>Verificar Versão</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>Integrações</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Discord Rich Presence</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>Sistema</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>Adicionar o Stability Matrix ao menu Iniciar</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>Usa a localização atual do aplicativo, você pode executar isso novamente se mover o aplicativo</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Disponível apenas para Windows</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>Adicionar para o usuário atual</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>Adicionar para todos os usuários</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>Selecionar nova pasta de dados</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>Os dados existentes não serão movidos. É necessário reiniciar o aplicativo.</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>Selecionar pasta</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>Sobre</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>Licença e Informações sobre Código Aberto</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>Clique em &quot;Iniciar&quot; para começar!</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>Parar</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>Enviar Entrada</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>Entrada</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>Enviar</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>Entrada obrigatória</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>Confirmar?</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>Sim</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>Não</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Abrir interface web</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>Bem-vindo ao Stability Matrix!</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>Escolha a interface que você prefere para começar</value>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>Instalando</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>Prosseguindo para a tela de início</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>Baixando pacote...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>Download concluído</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>Instalação completa</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>Instalando pré-requisitos...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>Instalando as dependências do pacote...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>Abrir no Explorador de Arquivos</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>Abrir no Finder</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>Desinstalar</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>Verificar se há atualizações</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>Atualizar</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>Adicionar Pacote</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>Adicione um pacote para iniciar!</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>Nome</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>Valor</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>Remover</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>Detalhes</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>Callstack</value>\n    <comment>Referring to the term in the programming, no translation is necessary, as the word Callstack is widely known</comment>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>Exceção interna</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>Pesquisar...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>OK</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>Tentar novamente</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Informações sobre a Versão do Python</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>Reiniciar</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>Confirmar Exclusão</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>Esta ação excluirá a pasta do pacote e todo o seu conteúdo, incluindo imagens geradas e arquivos adicionados por você.</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>Desinstalando o pacote...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>Pacote desinstalado com sucesso</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>Não foi possível excluir alguns arquivos. Feche qualquer arquivo aberto na pasta do pacote e tente novamente.</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>Tipo de Pacote Inválido</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>Atualizando {0}</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>Atualização concluída</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} foi atualizado para a última versão</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>Erro na atualização de {0}</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>Falha na atualização</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>Abrir no Navegador</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>Erro ao instalar o pacote</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>Ramificação</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>Ir automaticamente para o fim do console</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>Licença</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>Compartilhar Modelos</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>Escolha um diretório de dados</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>Nome da pasta de dados</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>Pasta atual:</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>O Stability Matrix será reiniciado após ser atualizado</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>Lembrar mais tarde</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>Instalar agora</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>Notas da versão</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>Abrir Projeto...</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>Salvar como...</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>Restaurar Layout Padrão</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>Compartilhar resultados</value>\n  </data>\n  <data name=\"Label_BatchIndex\" xml:space=\"preserve\">\n    <value>Número do lote</value>\n    <comment>Batch is also a word useded in PT-BR tech community</comment>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>Copiar</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>Abrir no visualizador de imagens</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>{0} imagem(ns) selecionada(s)</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>Pasta de destino</value>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>Formato de saída</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>Desmarcar tudo</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>Selecionar tudo</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>Enviar para Inferência</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>Texto para Imagem</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>Imagem para Imagem</value>\n  </data>\n  <data name=\"Label_Inpainting\" xml:space=\"preserve\">\n    <value>Inpainting</value>\n    <comment>There is no word that directly translates &apos;Inpainting&apos; into PT-BR, the closest would be &apos;image reconstruction&apos;. So I decided to keep the original term.</comment>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>Ampliar</value>\n    <comment>There is no word that directly translates &apos;upscale&apos; into PT-BR, the closest would be &apos;Enhance Image&apos;. So I decided to keep the original term</comment>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>Gerenciador de Resultados</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>1 imagem selecionada</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Pacotes do Python</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>Agrupar</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>Tem certeza?</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>Esta ação moverá todas as imagens geradas dos pacotes selecionados para a pasta &apos;Agrupadas&apos; dentro da pasta de resultados compartilhados. Esta ação é irreversível.</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>Atualizar</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>Atualizar</value>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>Voltar para versão anterior</value>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>Abrir no GitHub</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>Conectado</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>Desconectar</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>Email</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>Nome de usuário</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>Senha</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>Entrar</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>Inscrever-se</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>Confirmar Senha</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>Chave de API</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>Contas</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>Pré-processador</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>Intensidade</value>\n  </data>\n  <data name=\"Label_ControlWeight\" xml:space=\"preserve\">\n    <value>Influência do Controle</value>\n  </data>\n  <data name=\"Label_ControlSteps\" xml:space=\"preserve\">\n    <value>Nível de Controle</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>É necessário estar logado para baixar este checkpoint. Insira uma chave de API do CivitAI nas configurações.</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>Download Falhou</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>Atualizações Automáticas</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>Para quem gosta de testar novidades. As versões de prévia são mais estáveis que as do canal Dev e ficam disponíveis próximo aos lançamentos oficiais. Seu feedback é muito importante para identificarmos problemas e aprimorarmos o design.</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>Para usuários avançados. Seja o primeiro a testar nossas versões de desenvolvimento com novos recursos experimentais. Pode haver alguns erros e instabilidades.</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>Atualizações</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>Tudo em dia</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>Verificado pela última vez em: {0}</value>\n  </data>\n  <data name=\"Action_CopyTriggerWords\" xml:space=\"preserve\">\n    <value>Copiar Palavras de Ativação</value>\n  </data>\n  <data name=\"Label_TriggerWords\" xml:space=\"preserve\">\n    <value>Palavras de ativação:</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>Pastas adicionais, como IPAdapters e Embeddings (Inversões Textuais), podem ser ativadas aqui</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>Abrir no Hugging Face</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>Atualizar Metadados</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>Geral</value>\n    <comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>Inferência</value>\n    <comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>Prompt</value>\n    <comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_OutputImageFiles\" xml:space=\"preserve\">\n    <value>Imagens geradas</value>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>Visualizador de Imagens</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>Preenchimento automático</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>Substituir _ (underline) por espaços ao completar</value>\n  </data>\n  <data name=\"Label_PromptTags\" xml:space=\"preserve\">\n    <value>Tags de Prompt</value>\n    <comment>Tags for image generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTagsImport\" xml:space=\"preserve\">\n    <value>Importar tags de prompt</value>\n  </data>\n  <data name=\"Label_PromptTagsDescription\" xml:space=\"preserve\">\n    <value>Arquivo com tags para ser usado como sugestões (formato .csv do a1111-sd-webui-tagcomplete)</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>Informações do Sistema</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>Addons</value>\n    <comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>Salvar imagens intermediárias</value>\n    <comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>Configurações</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>Selecionar arquivo</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>Substituir conteúdo</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>Ainda não disponível</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>Recurso disponível em breve</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>Arquivo de imagem não encontrado</value>\n  </data>\n  <data name=\"Label_HolidayMode\" xml:space=\"preserve\">\n    <value>Modo Feriado</value>\n    <comment>Do we have some context?</comment>\n  </data>\n  <data name=\"Label_CLIPSkip\" xml:space=\"preserve\">\n    <value>Ignorar CLIP</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>Imagem para Vídeo</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>Quadros por Segundo</value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>CFG Mínimo</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>Sem perdas</value>\n    <comment>There is no term that translates directly into PT-BR. The word &apos;lossless&apos; is widely used.</comment>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>Quadros</value>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>ID do Grupo de Movimento</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>Nível de Ampliação</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>Método</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>Qualidade</value>\n  </data>\n  <data name=\"Label_FindInModelBrowser\" xml:space=\"preserve\">\n    <value>Procurar no Navegador de Modelos</value>\n  </data>\n  <data name=\"Label_Installed\" xml:space=\"preserve\">\n    <value>Instalado</value>\n  </data>\n  <data name=\"Label_NoExtensionsFound\" xml:space=\"preserve\">\n    <value>Nenhuma extensão foi encontrada.</value>\n  </data>\n  <data name=\"Action_Hide\" xml:space=\"preserve\">\n    <value>Ocultar</value>\n  </data>\n  <data name=\"Action_CopyDetails\" xml:space=\"preserve\">\n    <value>Copiar Detalhes</value>\n  </data>\n  <data name=\"Action_Download\" xml:space=\"preserve\">\n    <value>Baixar</value>\n  </data>\n  <data name=\"TeachingTip_DownloadsExplanation\" xml:space=\"preserve\">\n    <value>Confira aqui o progresso da instalação dos seus pacotes e do download dos modelos.</value>\n  </data>\n  <data name=\"Label_RecommendedModels\" xml:space=\"preserve\">\n    <value>Modelos recomendados</value>\n  </data>\n  <data name=\"Label_RecommendedModelsSubText\" xml:space=\"preserve\">\n    <value>Enquanto instalamos seu pacote, veja alguns modelos recomendados para dar os primeiros passos.</value>\n  </data>\n  <data name=\"Label_Notifications\" xml:space=\"preserve\">\n    <value>Notificações</value>\n  </data>\n  <data name=\"Label_NotificationOption_None\" xml:space=\"preserve\">\n    <value>Nenhum</value>\n  </data>\n  <data name=\"Label_ComfyRequiredTitle\" xml:space=\"preserve\">\n    <value>ComfyUI Obrigatório</value>\n  </data>\n  <data name=\"Label_ComfyRequiredDetail\" xml:space=\"preserve\">\n    <value>O ComfyUI é necessário para instalar este pacote. Deseja instalá-lo?</value>\n  </data>\n  <data name=\"Error_PleaseSelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Escolha um local para salvar o download.</value>\n  </data>\n  <data name=\"Label_SelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Local para salvar:</value>\n  </data>\n  <data name=\"Label_Config\" xml:space=\"preserve\">\n    <value>Configuração</value>\n  </data>\n  <data name=\"Label_ToggleAutoScrolling\" xml:space=\"preserve\">\n    <value>Ir para o fim automaticamente</value>\n  </data>\n  <data name=\"Label_ConfirmExit\" xml:space=\"preserve\">\n    <value>Confirmar saída</value>\n  </data>\n  <data name=\"Label_ConfirmExitDetail\" xml:space=\"preserve\">\n    <value>Tem certeza que quer sair? Todos os pacotes em execução serão fechados.</value>\n  </data>\n  <data name=\"Label_Console\" xml:space=\"preserve\">\n    <value>Console</value>\n  </data>\n  <data name=\"Label_WebUi\" xml:space=\"preserve\">\n    <value>Interface Web</value>\n  </data>\n  <data name=\"Label_Packages\" xml:space=\"preserve\">\n    <value>Pacotes</value>\n  </data>\n  <data name=\"Label_ActionCannotBeUndone\" xml:space=\"preserve\">\n    <value>Esta ação não poderá ser desfeita.</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteImages\" xml:space=\"preserve\">\n    <value>Tem certeza de que deseja excluir {0} imagens?</value>\n  </data>\n  <data name=\"Label_CheckingHardware\" xml:space=\"preserve\">\n    <value>Verificando a compatibilidade do hardware...</value>\n  </data>\n  <data name=\"Label_EverythingLooksGood\" xml:space=\"preserve\">\n    <value>Tudo parece estar certo!</value>\n  </data>\n  <data name=\"Label_NvidiaGpuRecommended\" xml:space=\"preserve\">\n    <value>Para uma experiência otimizada, recomendamos uma GPU com suporte a CUDA. É possível continuar sem uma GPU, mas alguns pacotes podem apresentar falhas e a inferência pode demorar mais.</value>\n  </data>\n  <data name=\"Label_Checkpoints\" xml:space=\"preserve\">\n    <value>Checkpoints</value>\n  </data>\n  <data name=\"Label_ModelBrowser\" xml:space=\"preserve\">\n    <value>Gerenciador de Modelos</value>\n  </data>\n  <data name=\"Label_Workflows\" xml:space=\"preserve\">\n    <value>Fluxos de Trabalho</value>\n  </data>\n  <data name=\"Label_InfiniteScrolling\" xml:space=\"preserve\">\n    <value>Rolagem infinita</value>\n  </data>\n  <data name=\"Label_WorkflowBrowser\" xml:space=\"preserve\">\n    <value>Gerenciador de Fluxos de Trabalho</value>\n  </data>\n  <data name=\"Action_OpenOnOpenArt\" xml:space=\"preserve\">\n    <value>Abrir no OpenArt</value>\n  </data>\n  <data name=\"Label_NodeDetails\" xml:space=\"preserve\">\n    <value>Detalhes do nó</value>\n  </data>\n  <data name=\"Label_WorkflowDescription\" xml:space=\"preserve\">\n    <value>Descrição do fluxo de trabalho</value>\n  </data>\n  <data name=\"Label_OpenArtBrowser\" xml:space=\"preserve\">\n    <value>Explorar OpenArt</value>\n  </data>\n  <data name=\"Action_PreviewPreprocessor\" xml:space=\"preserve\">\n    <value>Visualizar Pré-processador</value>\n  </data>\n  <data name=\"TeachingTip_WebUiButtonMoved\" xml:space=\"preserve\">\n    <value>O botão &apos;Abrir Interface Web&apos; agora está na barra de comandos</value>\n  </data>\n  <data name=\"Label_AnotherInstanceAlreadyRunning\" xml:space=\"preserve\">\n    <value>Outra instância do Stability Matrix está aberta. Feche-a antes de abrir uma nova.</value>\n  </data>\n  <data name=\"Label_StabilityMatrixAlreadyRunning\" xml:space=\"preserve\">\n    <value>O Stability Matrix já está aberto</value>\n  </data>\n  <data name=\"Label_WorkflowDeletedSuccessfully\" xml:space=\"preserve\">\n    <value>{0} excluído(a) com sucesso</value>\n  </data>\n  <data name=\"Label_WorkflowDeleted\" xml:space=\"preserve\">\n    <value>Fluxo de trabalho excluído</value>\n  </data>\n  <data name=\"Label_ErrorRetrievingWorkflows\" xml:space=\"preserve\">\n    <value>Erro ao carregar fluxos de trabalho</value>\n  </data>\n  <data name=\"TabLabel_InstalledWorkflows\" xml:space=\"preserve\">\n    <value>Fluxos de trabalho instalados</value>\n  </data>\n  <data name=\"Label_WorkflowImported\" xml:space=\"preserve\">\n    <value>Fluxo de trabalho importado</value>\n  </data>\n  <data name=\"Label_FinishedImportingWorkflow\" xml:space=\"preserve\">\n    <value>Fluxo de trabalho e nós personalizados importados com sucesso</value>\n  </data>\n  <data name=\"Label_WorkflowImportComplete\" xml:space=\"preserve\">\n    <value>Fluxo de trabalho e nós personalizados importados com sucesso.</value>\n  </data>\n  <data name=\"TeachingTip_InferencePromptHelpButton\" xml:space=\"preserve\">\n    <value>Clique aqui para ver a sintaxe do prompt e como usar LoRAs/Embeddings.</value>\n  </data>\n  <data name=\"Label_ExtraNetworks\" xml:space=\"preserve\">\n    <value>Redes Extras (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_CLIPStrength\" xml:space=\"preserve\">\n    <value>Intensidade CLIP</value>\n  </data>\n  <data name=\"Label_NumberFormat\" xml:space=\"preserve\">\n    <value>Formato numérico</value>\n  </data>\n  <data name=\"Text_DeleteFollowingItems\" xml:space=\"preserve\">\n    <value>Você está prestes a apagar os seguintes itens:</value>\n  </data>\n  <data name=\"TextTemplate_DeleteFollowingCountItems\" xml:space=\"preserve\">\n    <value>Você está prestes a apagar os seguintes {0} itens:</value>\n  </data>\n  <data name=\"Label_DeletePermanently\" xml:space=\"preserve\">\n    <value>Excluir permanentemente</value>\n  </data>\n  <data name=\"Action_MoveToTrash\" xml:space=\"preserve\">\n    <value>Mover para a lixeira</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteModels\" xml:space=\"preserve\">\n    <value>Tem certeza que deseja apagar {0} modelos?</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad\" xml:space=\"preserve\">\n    <value>Pesquisar ao carregar</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad_Description\" xml:space=\"preserve\">\n    <value>Pesquisar automaticamente ao abrir o gerenciador de modelos</value>\n  </data>\n  <data name=\"Action_ToggleVisibility\" xml:space=\"preserve\">\n    <value>Alternar visibilidade</value>\n  </data>\n  <data name=\"Label_ClippingMask\" xml:space=\"preserve\">\n    <value>Máscara de corte</value>\n  </data>\n  <data name=\"Label_AppFolders\" xml:space=\"preserve\">\n    <value>Pastas do programa</value>\n  </data>\n  <data name=\"Label_Logs\" xml:space=\"preserve\">\n    <value>Logs</value>\n  </data>\n  <data name=\"Label_AppData\" xml:space=\"preserve\">\n    <value>Dados do programa</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToSelected\" xml:space=\"preserve\">\n    <value>{0} atualizado para a versão escolhida</value>\n  </data>\n  <data name=\"Action_CopyAsBitmap\" xml:space=\"preserve\">\n    <value>Copiar como bitmap</value>\n  </data>\n  <data name=\"Label_DisableUpdateCheck\" xml:space=\"preserve\">\n    <value>Desativar atualização automática</value>\n  </data>\n  <data name=\"Warning_PleaseExtractFirst\" xml:space=\"preserve\">\n    <value>EXTRAIA O PROGRAMA DO ARQUIVO ZIP ANTES DE EXECUTAR O STABILITY MATRIX</value>\n  </data>\n  <data name=\"Label_HistorySize\" xml:space=\"preserve\">\n    <value>Tamanho do Histórico</value>\n  </data>\n  <data name=\"Label_HistorySize_Description\" xml:space=\"preserve\">\n    <value>O número de linhas acima daquelas exibidas no console em que você pode rolar de volta</value>\n  </data>\n  <data name=\"Label_ConnectAccountFailed\" xml:space=\"preserve\">\n    <value>Nós tivemos algum problema para conectar sua conta</value>\n  </data>\n  <data name=\"Label_EditModelMetadata\" xml:space=\"preserve\">\n    <value>Editar Metadados do Modelo</value>\n  </data>\n  <data name=\"Label_NSFW\" xml:space=\"preserve\">\n    <value>NSFW</value>\n  </data>\n  <data name=\"Label_Tags\" xml:space=\"preserve\">\n    <value>Tags</value>\n  </data>\n  <data name=\"Label_VersionName\" xml:space=\"preserve\">\n    <value>Nome de Versão</value>\n  </data>\n  <data name=\"Label_TrainedWords\" xml:space=\"preserve\">\n    <value>Palavras Treinadas</value>\n  </data>\n  <data name=\"Label_PreviewImage\" xml:space=\"preserve\">\n    <value>Pré-visualizar imagem</value>\n  </data>\n  <data name=\"Label_BatchSize\" xml:space=\"preserve\">\n    <value>Tamanho do Lote</value>\n  </data>\n  <data name=\"Label_Batches\" xml:space=\"preserve\">\n    <value>Lotes</value>\n  </data>\n  <data name=\"Label_Sampler\" xml:space=\"preserve\">\n    <value>Amostrador</value>\n  </data>\n  <data name=\"Label_Scheduler\" xml:space=\"preserve\">\n    <value>Agendador</value>\n  </data>\n  <data name=\"Label_MaxSize\" xml:space=\"preserve\">\n    <value>Tamanho máximo</value>\n  </data>\n  <data name=\"Label_UseSeparatePrompt\" xml:space=\"preserve\">\n    <value>Usar prompt separado</value>\n  </data>\n  <data name=\"Label_Seed\" xml:space=\"preserve\">\n    <value>Semente</value>\n  </data>\n  <data name=\"Label_NegativePrompt\" xml:space=\"preserve\">\n    <value>Prompt Negativo</value>\n  </data>\n  <data name=\"Label_NewFolder\" xml:space=\"preserve\">\n    <value>Nova Pasta</value>\n  </data>\n  <data name=\"Label_CopyLinkToClipboard\" xml:space=\"preserve\">\n    <value>Copiar Link para a Área de Transferência</value>\n  </data>\n  <data name=\"TextTemplate_OAuthLoginTitle\" xml:space=\"preserve\">\n    <value>Entrar com {0}</value>\n    <comment>e.g. &apos;Sign in with Google&apos;</comment>\n  </data>\n  <data name=\"Text_AllowBrowserOpenAppLink\" xml:space=\"preserve\">\n    <value>Permita que seu navegador abra este aplicativo quando solicitado.</value>\n  </data>\n  <data name=\"Text_OAuthLoginDescription\" xml:space=\"preserve\">\n    <value>Abra o link no seu navegador e siga as instruções para conectar sua conta.</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Title\" xml:space=\"preserve\">\n    <value>Substituir dependências do Python</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Description\" xml:space=\"preserve\">\n    <value>Adicione, substitua ou remova dependências para instalação e atualização</value>\n  </data>\n  <data name=\"Label_DependencySpecifiers\" xml:space=\"preserve\">\n    <value>Especificadores de dependência</value>\n  </data>\n  <data name=\"AnalyticsExample_InstallData\" xml:space=\"preserve\">\n    <value>{\n  &quot;packageName&quot;: &quot;stable-diffusion-webui&quot;,\n  &quot;packageVersion&quot;: &quot;v1.10.0&quot;,\n  &quot;isSuccess&quot;: true,\n  &quot;type&quot;: &quot;install&quot;,\n  &quot;timestamp&quot;: &quot;2024-09-04T02:14:04.1967404+00:00&quot;\n}</value>\n  </data>\n  <data name=\"Label_Analytics\" xml:space=\"preserve\">\n    <value>Dados Analíticos</value>\n  </data>\n  <data name=\"TextTemplate_YouCanChangeThisBehavior\" xml:space=\"preserve\">\n    <value>Você sempre pode alterar esse comportamento em {0}.</value>\n    <comment>e.g. &apos;You can always change this behavior in Settings &gt; Category &gt; Item.&apos;</comment>\n  </data>\n  <data name=\"Text_AnalyticsDescription\" xml:space=\"preserve\">\n    <value>Ajude-nos a melhorar o Stability Matrix enviando dados anônimos sobre recursos usados, versões de sistema operacional, tipos de pacotes instalados, etc. Os dados enviados nunca serão associados a você ou à sua conta e não incluirão dados pessoais ou qualquer informação sensível.</value>\n  </data>\n  <data name=\"Text_AnalyticsDescriptionShort\" xml:space=\"preserve\">\n    <value>Ajude-nos a melhorar o Stability Matrix enviando dados anônimos sobre recursos usados, versões de sistema operacional, tipos de pacotes instalados, etc.</value>\n  </data>\n  <data name=\"Text_AnalyticsDataPrivacyInfo\" xml:space=\"preserve\">\n    <value>Os dados enviados nunca serão associados a você ou à sua conta e não incluirão dados pessoais ou qualquer informação sensível.</value>\n  </data>\n  <data name=\"Label_UsageData\" xml:space=\"preserve\">\n    <value>Dados de Uso</value>\n  </data>\n  <data name=\"Label_PrivacyPolicy\" xml:space=\"preserve\">\n    <value>Política de Privacidade</value>\n  </data>\n  <data name=\"Label_ImageHidden\" xml:space=\"preserve\">\n    <value>Imagem ocultada</value>\n  </data>\n  <data name=\"Label_NoImageFound\" xml:space=\"preserve\">\n    <value>Nenhuma imagem encontrada</value>\n  </data>\n  <data name=\"Label_HideEmptyCategories\" xml:space=\"preserve\">\n    <value>Ocultar categorias vazias</value>\n  </data>\n  <data name=\"Label_ShowNsfwImages\" xml:space=\"preserve\">\n    <value>Mostrar imagens NSFW</value>\n  </data>\n  <data name=\"Label_EnableLongPaths\" xml:space=\"preserve\">\n    <value>Habilitar caminhos longos</value>\n    <comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_EnableLongPathsDescription\" xml:space=\"preserve\">\n    <value>Remover limitações MAX_PATH de funções comuns de arquivo e diretório do Win32</value>\n    <comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_SystemSettings\" xml:space=\"preserve\">\n    <value>Configurações do Sistema</value>\n  </data>\n  <data name=\"Label_ChangesApplied\" xml:space=\"preserve\">\n    <value>Alterações Aplicadas</value>\n  </data>\n  <data name=\"Text_RestartMayBeRequiredForSystemChanges\" xml:space=\"preserve\">\n    <value>Uma reinicialização pode ser necessária para que as alterações do sistema tenham efeito.</value>\n  </data>\n  <data name=\"Label_ReleasesUnavailableForThisPackage\" xml:space=\"preserve\">\n    <value>Este pacote não possui versões disponíveis.</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>Ir para Configurações</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>Ativar</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>Desativar</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Conectar sua conta Lykos</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>Entre com sua conta Lykos para usar os recursos conectados.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>Faça login novamente</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>Seu login expirou. Faça login novamente para continuar.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Apoiar Stability Matrix</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>Obrigado por apoiar o Stability Matrix!</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>Recursos como **{0}** são um dos muitos benefícios disponíveis para nossos apoiadores. Sua contribuição nos ajuda a cobrir os custos do servidor e apoia o desenvolvimento do Stability Matrix.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>Recursos como **{0}** estão disponíveis no nível de apoiador **{1}** (ou superior). Sua contribuição nos ajuda a cobrir os custos do servidor para recursos conectados mais avançados e nos permite continuar melhorando o Stability Matrix para todos.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>Se você já nos apoia no Patreon, por favor, vincule sua conta para continuar.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>Configurações da conta</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>Ver opções de apoio</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>Mais tarde</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>Status</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>Ativo</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>Inativo (Usando conexão padrão)</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>Experimente resultados de busca mais rápidos ao navegar por modelos de repositórios online, como o Civitai.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>Otimização experimental para repositórios de terceiros. Não afiliado oficialmente; a disponibilidade pode variar.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>Beta</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>Busca Acelerada de Modelos</value>\n    <comment>Fuzzy</comment>\n  </data>\n</root>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.pt-PT.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>Executar</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>Encerrar</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>Salvar</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>Cancelar</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>Idioma</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>Reiniciar</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>Reiniciar</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>Reiniciar mais tarde</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>Necessário Reiniciar</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>Pacote Desconhecido</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>Importar</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>Tipo do Pacote</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>Versão</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>Tipo da Versão</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>Versões</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>Ramificações</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>Arraste e Solte os Checkpoints aqui para Importar</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>Enfatizar</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>Desenfatizar</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>Emebeddings / Inversão Textual</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>Networks (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>Comentários</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>Exibir Pixels Grid com níveis de Zoom</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>Passos</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>Passos - Base</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>Passos - Refinamento</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>Escala de CFG</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Nível de Denoising</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>Largura</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>Altura</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Refinamento</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>Modelo</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>Conectar</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>Conectando...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>Fechar</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>Aguardando pela conecção</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>Novo Update disponível</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>Torne-se um colaborador no Patreon</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Junte-se ao nosso canal no Discord</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>Downloads</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>Instalação</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>Pular as configurações iniciais</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>Um erro inesperado ocorreu</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>Sair da Aplicação</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>Nome de Exibição</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>Uma instalação com este nome já existe</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>Por favor escolha um novo nome ou escolha um novo local para a instalação</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>Opções Avançadas</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>Salvar alterações</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>Estratégia de Modelo de Pasta compartilhado</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>Versão to PyTorch</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>Fechar diálogo quando finalizar</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>DIretório de Dados</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>Este local é onde os dados do aplicativo (modelos de Checkpoints, Interfaces Web, etc.) serão instaldos</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>Você poderá encontrar erros quando usar discos rígidos com formatação FAT32 ou exFAT. Escolha outro tipo de HD  para uma experiência melhor.</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>Modo Portátil</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>Com o Modo Portátil, todos os dados e configurações serão salvos no mesmo diretório da aplicação. Você poderá mover a aplicação com suas pastas de dados para um local diferente em seu computador.</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>Continuar</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>Imagem Anterior</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>Próxima Imagem</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>Descrição do Modelo</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>Uma nova versão do Stability Matrix está disponível</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>Importar última versão</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>Todas as Versões</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>Pesquisar Modelos, #tags, ou @users</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>Pesquisar</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>Filtrar</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>Período</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>Tipo do Modelo</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>Modelo Base</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>Mostar Conteúdo NSFW (adulto)</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>Dados fornecidos pela CivitAI</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>Página</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>Primeira Página</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>Página Anterior</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>Próxima Página</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>Última Página</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>Renomear</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>Apagar</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>Abrir em CivitAI</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>Modelo Conectado</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>Modelo Local</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>Exibir no Explorer</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>Novo</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>Pasta</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>Coloque o arquivo aqui para Importar</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>Importar com Metadados</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>Procurar por Metadados conectados em novos locais de importação</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>Indexando...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>Pasta de Modelos</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>Categorias</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>Vamos começar!</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>Ei lí e concordei com o</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>Acordo de Licenciamento</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>Procurar Metadados Conectados</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>Exibir imagens dos Modelos</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>Aparência</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>Tema</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>Gerenciador de Checkpoint</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>Remove os links simbólico compartilhados do diretorio de Checkpoints  ao encerrar</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Selecione esta opção caso você esteja encontrando problemas ao mover o Stability Matrix para outro HD</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>Resetar o cache de Checkpoints</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>Recriar o cache de Checkpoints instalados. Use caso os Checkpoints estiverem com a descrição incorreta no Selecionador de Modelos</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>Ambiente do Pacote de Instalação</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>Editar</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>Variáveis de Ambiente</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>Python Embutido</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>Verificar Versão</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>Integrações</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Presença no Discord</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>Sistema</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>Adicionar o Stability Matrix ao Menu Iniciar</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>Utilizar o local atual do aplicativo. Você poderá executar esta opção novamente caso você mover o aplicativo</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Apenas disponível no Windows</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>Adicionar para o Usuário Atual</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>Adicionar para todos os Usuários</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>Selecionar novo Diretório de Dados</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>Não mover os dados atuais</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>Selecionar o Diretório</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>Sobre</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>Avisos de licença e código aberto</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>Clique em Iniciar para começar!</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>Parar</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>Enviar comando</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>Input</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>Enviar</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>Input necessário</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>Confirma?</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>Sim</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>Não</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Abrir a Interface Web</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>Bem-vindo ao Stability Matrix!</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>Escolha sua interface preferida e clique em Instalar para começar</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>Instalando</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>Prosseguindo para a Página de Inicial</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>Baixando pacote...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>Download completo</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>Instalação completa</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>Instalando pré-requisitos...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>Instalando requisitos de pacote...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>Abrir no Explorer</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>Abrir no Finder</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>Desinstalar</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>Verificar se existem atualizações</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>Atualizar</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>Adicionar um pacote</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>Adicione um pacote para iniciar!</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>Nome</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>Valor</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>Remover</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>Detalhes</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>Pilha de chamadas</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>Exceção interna</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>Procurar...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>OK</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>Tentar novamente</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Informações da versão do Python</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>Reiniciar</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>Confirmar exclusão</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>Isso excluirá a pasta do pacote e todo o seu conteúdo, incluindo quaisquer imagens e arquivos gerados que você possa ter adicionado.</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>Desinstalando pacote...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>Pacote desinstalado</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>Alguns arquivos não puderam ser excluídos. Feche todos os arquivos abertos no diretório do pacote e tente novamente.</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>Tipo de pacote inválido</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>Atualizando {0}</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>Atualização completa</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} foi atualizado para a versão mais recente</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>Erro ao atualizar {0}</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>A Atualização falhou</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>Abrir no navegador</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>Erro ao instalar o pacote</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>Branch</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>Rolar automaticamente até o final da saída do console</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>Licença</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>Compartilhamento de modelo</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>Selecione um diretório de dados</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>Nome da pasta de dados</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>Diretório atual:</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>O aplicativo será reiniciado após a atualização</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>Lembre-me mais tarde</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>instale agora</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>Notas de versão</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>Abrir Projeto...</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>Salvar como...</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>Restaurar layout padrão</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>Usar Pasta de saída Compartilhada </value>\n  </data>\n  <data name=\"Label_BatchIndex\" xml:space=\"preserve\">\n    <value>Índice de lote</value>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>Copiar</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>Abrir no Visualizador de Imagens</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>{0} imagens selecionadas</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>Pasta de saída</value>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>Tipo de saída</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>Limpar Seleção</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>Selecionar tudo</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>Enviar para inferência</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>Texto para imagem</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>Imagem para imagem</value>\n  </data>\n  <data name=\"Label_Inpainting\" xml:space=\"preserve\">\n    <value>Inpainting (Pintar sobre a imagem)</value>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>Aumentar o tamanho da Imagem</value>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>Navegador de Exibição</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>1 imagem selecionada</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Pacotes Python</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>Consolidar</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>Tem certeza?</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>Isso moverá todas as imagens geradas dos pacotes selecionados para o diretório Consolidado da pasta de saídas compartilhadas. Essa ação não pode ser desfeita.</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>Atualizar</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>Upgrade</value>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>Downgrade</value>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>Abrir no GitHub</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>Conectado</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>Desconectar</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>E-mail</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>Nome de usuário</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>Senha</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>Conectar</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>Cadastrar Conta</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>Confirme sua senha</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>Chave API</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>Contas</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>Pré-processador</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>Força a aplicar</value>\n  </data>\n  <data name=\"Label_ControlWeight\" xml:space=\"preserve\">\n    <value>Controle de Potência a aplicar</value>\n  </data>\n  <data name=\"Label_ControlSteps\" xml:space=\"preserve\">\n    <value>Número de Etapas de controle</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>Você deve estar logado para baixar este ponto de verificação. Insira uma chave de API CivitAI nas configurações.</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>O Download falhou</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>Atualizações automáticas</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>Para os usuários Beta. Os Builds (compilações) de pré-visualização serão mais confiáveis do que as do canal Dev e estarão disponíveis mais próximas de versões estáveis. Seu feedback nos ajudará muito a descobrir problemas e aprimorar os elementos do design.</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>Para usuários técnicos. Seja o primeiro a acessar nossos Builds (compilações) de desenvolvimento a partir das versões com novos recursos assim que estes estiverem disponíveis. Podem haver alguns Bugs e pequenos problemas à medida que experimentamos novos recursos.</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>Atualizações</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>Você está atualizado</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>Última verificação: {0}</value>\n  </data>\n  <data name=\"Action_CopyTriggerWords\" xml:space=\"preserve\">\n    <value>Copiar palavras que executam ações</value>\n  </data>\n  <data name=\"Label_TriggerWords\" xml:space=\"preserve\">\n    <value>Palavras que executam ações:</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>Pastas adicionais como IPAdapters e TextualInversions (embeddings) podem ser habilitadas aqui</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>Abrir no Hugging Face</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>Atualizar metadados existentes</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>Em geral</value>\n    <comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>Inferência</value>\n    <comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>Prompt</value>\n    <comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_OutputImageFiles\" xml:space=\"preserve\">\n    <value>Arquivos de imagem de saída</value>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>Visualizador de imagens</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>Preenchimento automático</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>Substitua caracteres sublinhados por espaços ao inserir conclusões</value>\n  </data>\n  <data name=\"Label_PromptTags\" xml:space=\"preserve\">\n    <value>Etiquetas de Prompt</value>\n    <comment>Tags for image generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTagsImport\" xml:space=\"preserve\">\n    <value>Importar Tags de Prompt</value>\n  </data>\n  <data name=\"Label_PromptTagsDescription\" xml:space=\"preserve\">\n    <value>Arquivo de tags a ser usado para Sugestão de Completions (suporta o formato .csv a1111-sd-webui-tagcomplete)</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>Informação do sistema</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>Complementos</value>\n    <comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>Salvar imagem intermediária</value>\n    <comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>Configurações</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>Selecione o arquivo</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>Substituir conteúdo</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>Não disponível ainda</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>Este recurso estará disponível em uma atualização futura</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>Arquivo de imagem não encontrado</value>\n  </data>\n  <data name=\"Label_HolidayMode\" xml:space=\"preserve\">\n    <value>Modo Feriado</value>\n  </data>\n  <data name=\"Label_CLIPSkip\" xml:space=\"preserve\">\n    <value>Pular CLIPE</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>Imagem para vídeo</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>Quadros por segundo</value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>CFG mínimo</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>Sem perdas</value>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>Frames</value>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>ID do Motion Bucket</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>Nível de aumento</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>Método</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>Qualidade</value>\n  </data>\n  <data name=\"Label_CLIPStrength\" xml:space=\"preserve\">\n    <value>CLIP Força a aplicar</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>Ir para as Definições</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>Ativar</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>Desativar</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Ligar a sua conta Lykos</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>Inicie sessão com a sua conta Lykos para usar as funcionalidades ligadas.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>Inicie sessão novamente</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>O seu início de sessão expirou. Inicie sessão novamente para continuar.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Apoiar o Stability Matrix</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>Obrigado por apoiar o Stability Matrix!</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>Funcionalidades como **{0}** são um dos muitos benefícios disponíveis para os nossos apoiantes. A sua contribuição ajuda-nos a cobrir os custos do servidor e apoia o desenvolvimento do Stability Matrix.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>Funcionalidades como **{0}** estão disponíveis no nível de apoiante **{1}** (ou superior). A sua contribuição ajuda-nos a cobrir os custos do servidor para funcionalidades conectadas mais avançadas e permite-nos continuar a melhorar o Stability Matrix para todos.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>Se já nos apoia no Patreon, por favor, associe a sua conta para continuar.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>Definições da conta</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>Ver opções de apoio</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>Mais tarde</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>Estado</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>Ativo</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>Inativo (A utilizar ligação standard)</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>Experimente resultados de pesquisa mais rápidos ao navegar por modelos de repositórios online, como o Civitai.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>Otimização experimental para repositórios de terceiros. Sem afiliação oficial; a disponibilidade pode variar.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>Beta</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>Pesquisa Acelerada de Modelos</value>\n    <comment>Fuzzy</comment>\n  </data>\n</root>"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>Launch</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>Quit</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>Save</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>Cancel</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>Language</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>Relaunch is required for new language option to take effect</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>Relaunch</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>Relaunch Later</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>Relaunch Required</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>Unknown Package</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>Import</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>Package Type</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>Version</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>Version Type</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>Releases</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>Branches</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>Drag &amp; Drop checkpoints here to import</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>Emphasis</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>Deemphasis</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>Embeddings / Textual Inversion</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>Networks (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>Comments</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>Show pixel grid at high zoom levels</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>Steps</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>Steps - Base</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>Steps - Refiner</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>CFG Scale</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Denoising Strength</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>Width</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>Height</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Refiner</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>Model</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>Connect</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>Connecting...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>Close</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>Waiting to connect...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>Update Available</value>\n  </data>\n  <data name=\"Label_EarlyAccessUpdateAvailable\" xml:space=\"preserve\">\n    <value>Early Access Update Available</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>Become a Patron</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Join Discord Server</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>Downloads</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>Install</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>Skip first-time setup</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>An unexpected error occurred</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>Exit Application</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>Display Name</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>An installation with this name already exists.</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>Please choose a different name or select a different install location.</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>Advanced Options</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>Commit</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>Shared Model Folder Strategy</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>PyTorch Version</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>Close dialog when finished</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>Data Directory</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>This is where application data (model checkpoints, web UIs, etc.) will be installed.</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>You may encounter errors when using a FAT32 or exFAT drive. Select a different drive for a smoother experience.</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>Portable Mode</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>In Portable Mode, all data and settings will be stored in the same directory as the application. You will be able to move the application with its &apos;Data&apos; folder to a different location or computer.</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>Continue</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>Previous Image</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>Next Image</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>Model Description</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>A new version of Stability Matrix is available!</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>Import Latest -</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>All Versions</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>Search models, #tags, or @users</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>Search</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>Sort</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>Period</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>Model Type</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>Base Model</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>Show NSFW Content</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>Data provided by CivitAI</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>Page</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>First Page</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>Previous Page</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>Next Page</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>Last Page</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>Rename</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>Delete</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>Open on CivitAI</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>Connected Model</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>Local Model</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>Show in Explorer</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>New</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>Folder</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>Drop file here to import</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>Import with Metadata</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>Search for connected metadata on new local imports</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>Indexing...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>Models Folder</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>Categories</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>Let&apos;s get started</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>I have read and agree to the</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>License Agreement.</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>Find Connected Metadata</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>Show Model Images</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>Appearance</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>Theme</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>Checkpoint Manager</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>Remove shared folder symbolic links on shutdown</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Removes both model and output folder symbolic links when closing Stability Matrix. Select this option if you're having problems moving Stability Matrix to another drive</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>Reset Checkpoints Cache</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>Rebuilds the installed checkpoints cache. Use if checkpoints are incorrectly labeled in the Model Browser</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>Package Environment</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>Edit</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>Environment Variables</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>Embedded Python</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>Check Version</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>Integrations</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Discord Rich Presence</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>System</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>Add Stability Matrix to the Start Menu</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>Uses the current app location, you can run this again if you move the app</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Only available on Windows</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>Add for Current User</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>Add for All Users</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>Select new Data Directory</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>Does not move existing data. Requires application restart.</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>Select Directory</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>About</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>License and Open Source Notices</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>Click Launch to get started!</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>Stop</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>Send Input</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>Input</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>Send</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>Input required</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>Confirm?</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>Yes</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>No</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Open Web UI</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>Welcome to Stability Matrix!</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>Choose your preferred interface to get started</value>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>Installing</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>Proceeding to Launch page</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>Downloading package...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>Download complete</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>Installation complete</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>Installing prerequisites...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>Installing package requirements...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>Open in Explorer</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>Open in Finder</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>Uninstall</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>Check for Updates</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>Update</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>Add Package</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>Add a package to get started!</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>Name</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>Value</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>Remove</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>Details</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>Callstack</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>Inner exception</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>Search...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>OK</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>Retry</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Python Version Info</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>Restart</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>Confirm Delete</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>This will delete the package folder and all its contents, including any generated images and files you may have added.</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>Uninstalling package...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>Package Uninstalled</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>Some files could not be deleted. Please close any open files in the package directory and try again.</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>Invalid Package type</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>Updating {0}</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>Update complete</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} has been updated to the latest version</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>Error updating {0}</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>Update failed</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>Open in Browser</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>Error installing package</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>Branch</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>Automatically scroll to end of console output</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>License</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>Model Sharing</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>Please Select a Data Directory</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>Data Folder Name</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>Current directory:</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>The app will relaunch after updating</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>Remind Me Later</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>Install Now</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>Release Notes</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>Open Project...</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>Save As...</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>Restore Default Layout</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>Output Sharing</value>\n  </data>\n  <data name=\"Label_BatchIndex\" xml:space=\"preserve\">\n    <value>Batch Index</value>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>Copy</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>Open in Image Viewer</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>{0} images selected</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>Output Folder</value>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>Output Type</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>Clear Selection</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>Select All</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>Send to Inference</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>Text to Image</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>Image to Image</value>\n  </data>\n  <data name=\"Label_Inpainting\" xml:space=\"preserve\">\n    <value>Inpainting</value>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>Upscale</value>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>Output Browser</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>1 image selected</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>Preprocessor</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>Strength</value>\n  </data>\n  <data name=\"Label_ControlWeight\" xml:space=\"preserve\">\n    <value>Control Weight</value>\n  </data>\n  <data name=\"Label_ControlSteps\" xml:space=\"preserve\">\n    <value>Control Steps</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Python Packages</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>Consolidate</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>Are you sure?</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>This will move all generated images from the selected packages to the Consolidated directory of the shared outputs folder. This action cannot be undone.</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>Refresh</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>Upgrade</value>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>Downgrade</value>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>Open on GitHub</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>Connected</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>Disconnect</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>Email</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>Username</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>Password</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>Login</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>Signup</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>Confirm Password</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>API Key</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>Accounts</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>You must be logged in to download this checkpoint. Please enter a CivitAI API Key in the settings.</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>Download Failed</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>Auto Updates</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>For early adopters. Preview builds will be more reliable than those from the Dev channel, and will be available closer to stable releases. Your feedback will help us greatly in discovering issues and polishing design elements.</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>For technical users. Be the first to access our Development builds from feature branches as soon as they are available. There may be some rough edges and bugs as we experiment with new features.</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>Updates</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>You're up to date</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>Last checked: {0}</value>\n  </data>\n  <data name=\"Action_CopyTriggerWords\" xml:space=\"preserve\">\n    <value>Copy Trigger Words</value>\n  </data>\n  <data name=\"Label_TriggerWords\" xml:space=\"preserve\">\n    <value>Trigger words:</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>Additional folders such as IPAdapters and TextualInversions (embeddings) can be enabled here</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>Open on Hugging Face</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>Update Existing Metadata</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>General</value><comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>Inference</value><comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>Prompt</value><comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_OutputImageFiles\" xml:space=\"preserve\">\n    <value>Output Image Files</value>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>Image Viewer</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>Auto Completion</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>Replace underscores with spaces when inserting completions</value>\n  </data>\n  <data name=\"Label_PromptTags\" xml:space=\"preserve\">\n    <value>Prompt Tags</value><comment>Tags for image generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTagsImport\" xml:space=\"preserve\">\n    <value>Import Prompt tags</value>\n  </data>\n  <data name=\"Label_PromptTagsDescription\" xml:space=\"preserve\">\n    <value>Tags file to use for suggesting completions (Supports the a1111-sd-webui-tagcomplete .csv format)</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>System Information</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>Addons</value><comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>Save Intermediate Image</value><comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>Settings</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>Select File</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>Replace Contents</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>Not yet available</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>Feature will be available in a future update</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>Missing Image File</value>\n  </data>\n  <data name=\"Label_HolidayMode\" xml:space=\"preserve\">\n    <value>Holiday Mode</value>\n  </data>\n  <data name=\"Label_CLIPSkip\" xml:space=\"preserve\">\n    <value>CLIP Skip</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>Image to Video</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>Frames Per Second</value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>Min CFG</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>Lossless</value>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>Frames</value>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>Motion Bucket ID</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>Augmentation Level</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>Method</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>Quality</value>\n  </data>\n  <data name=\"Label_FindInModelBrowser\" xml:space=\"preserve\">\n    <value>Find in Model Browser</value>\n  </data>\n  <data name=\"Label_Installed\" xml:space=\"preserve\">\n    <value>Installed</value>\n  </data>\n  <data name=\"Label_NoExtensionsFound\" xml:space=\"preserve\">\n    <value>No extensions found.</value>\n  </data>\n  <data name=\"Action_Hide\" xml:space=\"preserve\">\n    <value>Hide</value>\n  </data>\n  <data name=\"Action_CopyDetails\" xml:space=\"preserve\">\n    <value>Copy Details</value>\n  </data>\n  <data name=\"Label_Notifications\" xml:space=\"preserve\">\n    <value>Notifications</value>\n  </data>\n  <data name=\"Label_NotificationOption_None\" xml:space=\"preserve\">\n    <value>None</value>\n  </data>\n  <data name=\"Action_Download\" xml:space=\"preserve\">\n    <value>Download</value>\n  </data>\n  <data name=\"TeachingTip_DownloadsExplanation\" xml:space=\"preserve\">\n    <value>Check the progress of your package installations and model downloads here.</value>\n  </data>\n  <data name=\"Label_RecommendedModels\" xml:space=\"preserve\">\n    <value>Recommended Models</value>\n  </data>\n  <data name=\"Label_RecommendedModelsSubText\" xml:space=\"preserve\">\n    <value>While your package is installing, here are some models we recommend to help you get started.</value>\n  </data>\n  <data name=\"Label_ComfyRequiredTitle\" xml:space=\"preserve\">\n    <value>ComfyUI Required</value>\n  </data>\n  <data name=\"Label_ComfyRequiredDetail\" xml:space=\"preserve\">\n    <value>ComfyUI is required to install this package. Would you like to install it now?</value>\n  </data>\n  <data name=\"Error_PleaseSelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Please select a download location.</value>\n  </data>\n  <data name=\"Label_SelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Select Download Location:</value>\n  </data>\n  <data name=\"Label_Workflows\" xml:space=\"preserve\">\n    <value>Workflows</value>\n  </data>\n  <data name=\"Label_InfiniteScrolling\" xml:space=\"preserve\">\n    <value>Infinite Scrolling</value>\n  </data>\n  <data name=\"Label_WorkflowBrowser\" xml:space=\"preserve\">\n    <value>Workflow Browser</value>\n  </data>\n  <data name=\"Label_Config\" xml:space=\"preserve\">\n    <value>Config</value>\n  </data>\n  <data name=\"Action_OpenOnOpenArt\" xml:space=\"preserve\">\n    <value>Open on OpenArt</value>\n  </data>\n  <data name=\"Label_NodeDetails\" xml:space=\"preserve\">\n    <value>Node Details</value>\n  </data>\n  <data name=\"Label_WorkflowDescription\" xml:space=\"preserve\">\n    <value>Workflow Description</value>\n  </data>\n  <data name=\"Label_OpenArtBrowser\" xml:space=\"preserve\">\n    <value>OpenArt Browser</value>\n  </data>\n  <data name=\"Action_PreviewPreprocessor\" xml:space=\"preserve\">\n    <value>Preview Preprocessor</value>\n  </data>\n  <data name=\"Label_ToggleAutoScrolling\" xml:space=\"preserve\">\n    <value>Auto-Scroll to End</value>\n  </data>\n  <data name=\"Label_ConfirmExit\" xml:space=\"preserve\">\n    <value>Confirm Exit</value>\n  </data>\n  <data name=\"Label_ConfirmExitDetail\" xml:space=\"preserve\">\n    <value>Are you sure you want to exit? This will also close any currently running packages.</value>\n  </data>\n  <data name=\"Label_Console\" xml:space=\"preserve\">\n    <value>Console</value>\n  </data>\n  <data name=\"Label_WebUi\" xml:space=\"preserve\">\n    <value>Web UI</value>\n  </data>\n  <data name=\"Label_Packages\" xml:space=\"preserve\">\n    <value>Packages</value>\n  </data>\n  <data name=\"Label_ActionCannotBeUndone\" xml:space=\"preserve\">\n    <value>This action cannot be undone.</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteImages\" xml:space=\"preserve\">\n    <value>Are you sure you want to delete {0} images?</value>\n  </data>\n  <data name=\"Label_CheckingHardware\" xml:space=\"preserve\">\n    <value>We're checking some hardware specifications to determine compatibility.</value>\n  </data>\n  <data name=\"Label_EverythingLooksGood\" xml:space=\"preserve\">\n    <value>Everything looks good!</value>\n  </data>\n  <data name=\"Label_NvidiaGpuRecommended\" xml:space=\"preserve\">\n    <value>We recommend a GPU with CUDA support for the best experience. You can continue without one, but some packages may not work, and inference may be slower.</value>\n  </data>\n  <data name=\"Label_Checkpoints\" xml:space=\"preserve\">\n    <value>Checkpoints</value>\n  </data>\n  <data name=\"Label_ModelBrowser\" xml:space=\"preserve\">\n    <value>Model Browser</value>\n  </data>\n  <data name=\"TeachingTip_WebUiButtonMoved\" xml:space=\"preserve\">\n    <value>The 'Open Web UI' button has moved to the command bar</value>\n  </data>\n  <data name=\"Label_AnotherInstanceAlreadyRunning\" xml:space=\"preserve\">\n    <value>Another instance of Stability Matrix is already running. Please close it before starting a new one.</value>\n  </data>\n  <data name=\"Label_StabilityMatrixAlreadyRunning\" xml:space=\"preserve\">\n    <value>Stability Matrix is already running</value>\n  </data>\n  <data name=\"Label_WorkflowDeletedSuccessfully\" xml:space=\"preserve\">\n    <value>{0} deleted successfully</value>\n  </data>\n  <data name=\"Label_WorkflowDeleted\" xml:space=\"preserve\">\n    <value>Workflow Deleted</value>\n  </data>\n  <data name=\"Label_ErrorRetrievingWorkflows\" xml:space=\"preserve\">\n    <value>Error retrieving workflows</value>\n  </data>\n  <data name=\"TabLabel_InstalledWorkflows\" xml:space=\"preserve\">\n    <value>Installed Workflows</value>\n  </data>\n  <data name=\"Label_WorkflowImported\" xml:space=\"preserve\">\n    <value>Workflow Imported</value>\n  </data>\n  <data name=\"Label_FinishedImportingWorkflow\" xml:space=\"preserve\">\n    <value>Finished importing workflow and custom nodes</value>\n  </data>\n  <data name=\"Label_WorkflowImportComplete\" xml:space=\"preserve\">\n    <value>The workflow and custom nodes have been imported.</value>\n  </data>\n  <data name=\"TeachingTip_InferencePromptHelpButton\" xml:space=\"preserve\">\n    <value>Click here to review prompt syntax and how to include Lora / Embeddings.</value>\n  </data>\n  <data name=\"Label_ExtraNetworks\" xml:space=\"preserve\">\n    <value>Extra Networks (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_CLIPStrength\" xml:space=\"preserve\">\n    <value>CLIP Strength</value>\n  </data>\n  <data name=\"Label_NumberFormat\" xml:space=\"preserve\">\n    <value>Number Format</value>\n  </data>\n  <data name=\"Text_DeleteFollowingItems\" xml:space=\"preserve\">\n    <value>You are about to delete the following items:</value>\n  </data>\n  <data name=\"TextTemplate_DeleteFollowingCountItems\" xml:space=\"preserve\">\n    <value>You are about to delete the following {0} items:</value>\n  </data>\n  <data name=\"Label_DeletePermanently\" xml:space=\"preserve\">\n    <value>Delete Permanently</value>\n  </data>\n  <data name=\"Action_MoveToTrash\" xml:space=\"preserve\">\n    <value>Move to Trash</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteModels\" xml:space=\"preserve\">\n    <value>Are you sure you want to delete {0} models?</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad\" xml:space=\"preserve\">\n    <value>Auto-Search on Load</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad_Description\" xml:space=\"preserve\">\n    <value>Automatically initiate a search when the model browser page is loaded</value>\n  </data>\n  <data name=\"Action_ToggleVisibility\" xml:space=\"preserve\">\n    <value>Toggle Visibility</value>\n  </data>\n  <data name=\"Label_ClippingMask\" xml:space=\"preserve\">\n    <value>Clipping Mask</value>\n  </data>\n  <data name=\"Label_AppFolders\" xml:space=\"preserve\">\n    <value>App Folders</value>\n  </data>\n  <data name=\"Label_Logs\" xml:space=\"preserve\">\n    <value>Logs</value>\n  </data>\n  <data name=\"Label_AppData\" xml:space=\"preserve\">\n    <value>App Data</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToSelected\" xml:space=\"preserve\">\n    <value>{0} has been updated to the selected version</value>\n  </data>\n  <data name=\"Action_CopyAsBitmap\" xml:space=\"preserve\">\n    <value>Copy as Bitmap</value>\n  </data>\n  <data name=\"Label_DisableUpdateCheck\" xml:space=\"preserve\">\n    <value>Disable Update Check</value>\n  </data>\n  <data name=\"Warning_PleaseExtractFirst\" xml:space=\"preserve\">\n    <value>PLEASE EXTRACT THE APP FROM THE ZIP FILE BEFORE RUNNING STABILITY MATRIX</value>\n  </data>\n  <data name=\"Label_HistorySize\" xml:space=\"preserve\">\n    <value>History Size</value>\n  </data>\n  <data name=\"Label_HistorySize_Description\" xml:space=\"preserve\">\n    <value>The number of lines above the ones displayed in the console you can scroll back to</value>\n  </data>\n  <data name=\"Label_ConnectAccountFailed\" xml:space=\"preserve\">\n    <value>We had some trouble connecting your account</value>\n  </data>\n  <data name=\"Label_EditModelMetadata\" xml:space=\"preserve\">\n    <value>Edit Model Metadata</value>\n  </data>\n  <data name=\"Label_NSFW\" xml:space=\"preserve\">\n    <value>NSFW</value>\n  </data>\n  <data name=\"Label_Tags\" xml:space=\"preserve\">\n    <value>Tags</value>\n  </data>\n  <data name=\"Label_VersionName\" xml:space=\"preserve\">\n    <value>Version Name</value>\n  </data>\n  <data name=\"Label_TrainedWords\" xml:space=\"preserve\">\n    <value>Trained Words</value>\n  </data>\n  <data name=\"Label_PreviewImage\" xml:space=\"preserve\">\n    <value>Preview Image</value>\n  </data>\n  <data name=\"Label_BatchSize\" xml:space=\"preserve\">\n    <value>Batch Size</value>\n  </data>\n  <data name=\"Label_Batches\" xml:space=\"preserve\">\n    <value>Batches</value>\n  </data>\n  <data name=\"Label_Sampler\" xml:space=\"preserve\">\n    <value>Sampler</value>\n  </data>\n  <data name=\"Label_Scheduler\" xml:space=\"preserve\">\n    <value>Scheduler</value>\n  </data>\n  <data name=\"Label_MaxSize\" xml:space=\"preserve\">\n    <value>Max Size</value>\n  </data>\n  <data name=\"Label_UseSeparatePrompt\" xml:space=\"preserve\">\n    <value>Use Separate Prompt</value>\n  </data>\n  <data name=\"Label_Seed\" xml:space=\"preserve\">\n    <value>Seed</value>\n  </data>\n  <data name=\"Label_NegativePrompt\" xml:space=\"preserve\">\n    <value>Negative Prompt</value>\n  </data>\n  <data name=\"Label_NewFolder\" xml:space=\"preserve\">\n    <value>New Folder</value>\n  </data>\n  <data name=\"Label_CopyLinkToClipboard\" xml:space=\"preserve\">\n    <value>Copy Link to Clipboard</value>\n  </data>\n  <data name=\"TextTemplate_OAuthLoginTitle\" xml:space=\"preserve\">\n    <value>Sign in with {0}</value><comment>e.g. 'Sign in with Google'</comment>\n  </data>\n  <data name=\"Text_AllowBrowserOpenAppLink\" xml:space=\"preserve\">\n    <value>Please allow your browser to open this app when prompted to continue.</value>\n  </data>\n  <data name=\"Text_OAuthLoginDescription\" xml:space=\"preserve\">\n    <value>Open the link in your browser and follow the instructions to connect your account.</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Title\" xml:space=\"preserve\">\n    <value>Python Dependencies Override</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Description\" xml:space=\"preserve\">\n    <value>Add, replace, or remove dependencies for install and update</value>\n  </data>\n  <data name=\"Label_DependencySpecifiers\" xml:space=\"preserve\">\n    <value>Dependency Specifiers</value>\n  </data>\n  <data name=\"AnalyticsExample_InstallData\" xml:space=\"preserve\">\n    <value>{\n  \"packageName\": \"stable-diffusion-webui\",\n  \"packageVersion\": \"v1.10.0\",\n  \"isSuccess\": true,\n  \"type\": \"install\",\n  \"timestamp\": \"2024-09-04T02:14:04.1967404+00:00\"\n}</value>\n  </data>\n  <data name=\"Label_Analytics\" xml:space=\"preserve\">\n    <value>Analytics</value>\n  </data>\n  <data name=\"TextTemplate_YouCanChangeThisBehavior\" xml:space=\"preserve\">\n    <value>You can always change this behavior in {0}.</value><comment>e.g. 'You can always change this behavior in Settings &gt; Category &gt; Item.'</comment>\n  </data>\n  <data name=\"Text_AnalyticsDescription\" xml:space=\"preserve\">\n    <value>Help us improve Stability Matrix by sending anonymous data about features used, operating system versions, types of packages installed, etc. The data sent is never associated with you or your account and will not include personal data or any sensitive information.</value>\n  </data>\n  <data name=\"Text_AnalyticsDescriptionShort\" xml:space=\"preserve\">\n    <value>Help us improve Stability Matrix by sending anonymous data about features used, operating system versions, types of packages installed, etc.</value>\n  </data>\n  <data name=\"Text_AnalyticsDataPrivacyInfo\" xml:space=\"preserve\">\n    <value>The data sent is never associated with you or your account and will not include personal data or any sensitive information.</value>\n  </data>\n  <data name=\"Label_UsageData\" xml:space=\"preserve\">\n    <value>Usage data</value>\n  </data>\n  <data name=\"Label_PrivacyPolicy\" xml:space=\"preserve\">\n    <value>Privacy Policy</value>\n  </data>\n  <data name=\"Label_ImageHidden\" xml:space=\"preserve\">\n    <value>Image Hidden</value>\n  </data>\n  <data name=\"Label_NoImageFound\" xml:space=\"preserve\">\n    <value>No Image Found</value>\n  </data>\n  <data name=\"Label_HideEmptyCategories\" xml:space=\"preserve\">\n    <value>Hide Empty Categories</value>\n  </data>\n  <data name=\"Label_ShowNsfwImages\" xml:space=\"preserve\">\n    <value>Show NSFW Images</value>\n  </data>\n  <data name=\"Label_EnableLongPaths\" xml:space=\"preserve\">\n    <value>Enable Long Paths</value><comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_EnableLongPathsDescription\" xml:space=\"preserve\">\n    <value>Remove MAX_PATH limitations from common Win32 file and directory functions</value><comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_SystemSettings\" xml:space=\"preserve\">\n    <value>System Settings</value>\n  </data>\n  <data name=\"Label_ChangesApplied\" xml:space=\"preserve\">\n    <value>Changes Applied</value>\n  </data>\n  <data name=\"Text_RestartMayBeRequiredForSystemChanges\" xml:space=\"preserve\">\n    <value>A restart may be required for system changes to take effect.</value>\n  </data>\n  <data name=\"Label_ReleasesUnavailableForThisPackage\" xml:space=\"preserve\">\n    <value>Releases are unavailable for this package.</value>\n  </data>\n  <data name=\"Text_UnexpectedError_Description\" xml:space=\"preserve\">\n    <value>Please let us know about this issue with the details below and attach the zipped log files.</value>\n  </data>\n  <data name=\"Text_UnexpectedErrorRecoverable_Description\" xml:space=\"preserve\">\n    <value>You can continue, but full functionality will be available after restart. Please let us know about this issue with the details below and attach the zipped log files.</value>\n  </data>\n  <data name=\"Action_ShowLogInExplorer\" xml:space=\"preserve\">\n    <value>Show Log in Explorer</value>\n  </data>\n  <data name=\"Action_ShowLogInFinder\" xml:space=\"preserve\">\n    <value>Show Log in Finder</value>\n  </data>\n  <data name=\"Label_ExtensionPacks\" xml:space=\"preserve\">\n    <value>Extension Packs</value>\n  </data>\n  <data name=\"Label_NoExtensionPacksFound\" xml:space=\"preserve\">\n    <value>No Extension Packs Found</value>\n  </data>\n  <data name=\"Label_OpenExtensionPacksFolder\" xml:space=\"preserve\">\n    <value>Open Extension Packs Folder</value>\n  </data>\n  <data name=\"Label_InstallExtensionPack\" xml:space=\"preserve\">\n    <value>Install Extension Pack</value>\n  </data>\n  <data name=\"Label_AddToExistingPack\" xml:space=\"preserve\">\n    <value>Add to Existing Pack</value>\n  </data>\n  <data name=\"Label_NewExtensionPack\" xml:space=\"preserve\">\n    <value>New Extension Pack</value>\n  </data>\n  <data name=\"Label_CreateExtensionPackExplanation\" xml:space=\"preserve\">\n    <value>To create one, simply select the extensions you want from the 'Available Extensions' or 'Installed Extensions' tab and click 'Save'</value>\n  </data>\n  <data name=\"Label_ImportExtensionPacksExplanation\" xml:space=\"preserve\">\n    <value>Add a .json extension pack file to the ExtensionPacks folder within your Data directory.</value>\n  </data>\n  <data name=\"Label_OrWithHyphensAround\" xml:space=\"preserve\">\n    <value>- or -</value>\n  </data>\n  <data name=\"Text_OAuthDeviceAuthDescription\" xml:space=\"preserve\">\n    <value>Open the link in your browser and enter the following code to authorize your account with Stability Matrix.</value>\n  </data>\n  <data name=\"Action_CopyAndOpen\" xml:space=\"preserve\">\n    <value>Copy and Open</value>\n  </data>\n  <data name=\"Action_OpenOnOpenModelDb\" xml:space=\"preserve\">\n    <value>Open on OpenModelDB</value>\n  </data>\n  <data name=\"Label_Wildcards\" xml:space=\"preserve\">\n    <value>Wildcards</value>\n  </data>\n  <data name=\"Text_LykosAccountUpgradeNotice\" xml:space=\"preserve\">\n    <value>## Heads Up: Lykos Account Sign-Out &amp; Security Upgrade\n\nWe've made some important behind-the-scenes improvements to how Stability Matrix handles Lykos accounts, upgrading to a more secure and convenient login system **(OAuth 2.0 with OpenID Connect)**. Because of this, you've been signed out of your Lykos account.\n\n### Why the change?\n\nYour security and privacy are important to us. This upgrade brings:\n\n*   **Streamlined Experience:** Sign in once to [account.lykos.ai](https://account.lykos.ai) to connect with Stability Matrix and other Lykos AI services.\n*   **More Ways to Sign In:** Use your existing [lykos.ai](https://lykos.ai) account, or sign in with **Apple**, **GitHub**, or **Google**.\n*   **Enhanced Privacy:** Stability Matrix only requests the permissions it needs.\n*   **Industry Standard Security:** We're using OAuth 2.0, the gold standard for secure logins.\n*   **Ready for the Future:** This allows for secure connections with other apps and services.\n\n### What do I need to do?\n\nClick the **\"Go to Settings\"** button, then click **\"Connect\"** next to **\"Lykos account\"**.\n\n### Is a Lykos account required?\n\nNope! Stability Matrix is still fully functional without one. But, your Lykos account enables some extra connected features, such as automatic updates to development builds for our Patreon supporters (and more to come!).\n</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>Go to Settings</value>\n  </data>\n  <data name=\"TeachingTip_PromptAmplifier\" xml:space=\"preserve\">\n    <value>Try out the new Prompt Amplifier! Enhance your prompts for better results!</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>Enable</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>Disable</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Connect your Lykos Account</value>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>Sign in with your Lykos Account to use connected features.</value>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>Please Sign In Again</value>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>Your login has expired. Please sign in again to continue.</value>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Support Stability Matrix</value>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>Thank you for being a Stability Matrix supporter!</value>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>Features like **{0}** are one of many benefits available to our supporters. Your contribution helps us cover server costs and supports Stability Matrix development.</value>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>Features like **{0}** are available at the **{1}** supporter level (or higher). Your contribution helps us cover server costs for more advanced connected features and allows us to keep improving Stability Matrix for everyone.</value>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>If you&apos;re already supporting us on Patreon, please link your account to continue.</value>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>Account Settings</value>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>View Support Options</value>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>Maybe Later</value>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>Status</value>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>Active</value>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>Inactive (Using standard connection)</value>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>Experience faster search results when browsing models from online repositories, such as Civitai.</value>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>Experimental optimization for third-party repositories. Not officially affiliated; availability may vary.</value>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>Beta</value>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>Accelerated Model Discovery</value>\n  </data>\n  <data name=\"PromptAmplifier_Disclaimer\" xml:space=\"preserve\">\n    <value>### 🪄 Introducing: Prompt Amplifier  \nOur AI assistant, powered by our experimental Spark model, will generate creative expansions of your prompt.\n\nThe Prompt Amplifier runs in our secure, enterprise-grade cloud environment—it does **not** run locally on your machine.\n\n### ☁️️ Why Cloud?  \nThe Spark model operates at a scale comparable to trillion-parameter foundation models, requiring substantial computational power. While we’re committed to maximizing locally-runnable features, Spark’s advanced capabilities are available **now** via our cloud infrastructure.\n\n### 🔒 Privacy First\nWe prioritize your privacy ([Gen AI Terms](&lt;https://lykos.ai/gen-ai-terms&gt;)). **Your prompts/outputs are NEVER used for AI training by Lykos AI or our necessary cloud infrastructure partners.** Secure processing occurs solely to generate your amplification, **after which we only retain metadata (like timestamps and token counts), not the prompt content itself.** Your data is never sold or shared.</value>\n  </data>\n  <data name=\"Label_ShowUnsupportedPythonVersions\" xml:space=\"preserve\">\n    <value>Show Unsupported Python Versions</value>\n  </data>\n  <data name=\"Label_UnsupportedPythonVersionDetails\" xml:space=\"preserve\">\n    <value>You may encounter problems with some packages when using unsupported Python versions</value>\n  </data>\n  <data name=\"Label_UnsupportedPythonVersionWarningDescription\" xml:space=\"preserve\">\n    <value>This will show all available Python versions, including those that are not supported by Stability Matrix. Are you sure?</value>\n  </data>\n  <data name=\"Label_PythonVersionWarningTitle\" xml:space=\"preserve\">\n    <value>Unsupported Python Versions</value>\n  </data>\n  <data name=\"Label_HuggingFaceLoginRequired\" xml:space=\"preserve\">\n    <value>You must be logged in to download this checkpoint. Please enter a Hugging Face Access Token in the settings.</value>\n  </data>\n  <data name=\"Label_LoginRequired\" xml:space=\"preserve\">\n    <value>Login required to download this model</value>\n  </data>\n  <data name=\"Watermark_EnterPackageName\" xml:space=\"preserve\">\n    <value>Enter package name</value>\n  </data>\n  <data name=\"Validation_PackageNameCannotBeEmpty\" xml:space=\"preserve\">\n    <value>Package name cannot be empty</value>\n  </data>\n  <data name=\"Description_RenamePackage\" xml:space=\"preserve\">\n    <value>Enter a new name for '{0}'</value>\n  </data>\n  <data name=\"ValidationError_PackageExists\" xml:space=\"preserve\">\n    <value>Package named '{0}' already exists</value>\n  </data>\n  <data name=\"Label_BulkDownloadStarted\" xml:space=\"preserve\">\n    <value>Bulk Download Started</value>\n  </data>\n  <data name=\"Label_BulkDownloadStartedMessage\" xml:space=\"preserve\">\n    <value>{0} files have started downloading. Check the Downloads tab for progress.</value>\n  </data>\n  <data name=\"Label_DownloadStarted\" xml:space=\"preserve\">\n    <value>Download Started</value>\n  </data>\n  <data name=\"Label_DownloadWillBeSavedToLocation\" xml:space=\"preserve\">\n    <value>{0} will be saved to {1}</value>\n  </data>\n  <data name=\"Label_Author\" xml:space=\"preserve\">\n    <value>Author</value>\n  </data>\n  <data name=\"Label_Hash\" xml:space=\"preserve\">\n    <value>Hash</value>\n  </data>\n  <data name=\"Label_LastUpdatedAt\" xml:space=\"preserve\">\n    <value>Last Updated</value>\n  </data>\n  <data name=\"Label_FileNamePattern\" xml:space=\"preserve\">\n    <value>File Name Pattern</value>\n  </data>\n  <data name=\"Label_Files\" xml:space=\"preserve\">\n    <value>Files</value>\n  </data>\n  <data name=\"Label_InferenceDefaultsHeader\" xml:space=\"preserve\">\n    <value>Inference Defaults</value>\n  </data>\n  <data name=\"Tooltip_InferenceDefaults\" xml:space=\"preserve\">\n    <value>When enabled, these settings will be applied automatically when this model is selected in the Inference tab</value>\n  </data>\n  <data name=\"Label_Show\" xml:space=\"preserve\">\n    <value>Show</value>\n  </data>\n  <data name=\"Label_EarlyAccessModels\" xml:space=\"preserve\">\n    <value>Early Access Models</value>\n  </data>\n  <data name=\"Label_NsfwContent\" xml:space=\"preserve\">\n    <value>NSFW Content</value>\n  </data>\n  <data name=\"Label_NonModelFiles\" xml:space=\"preserve\">\n    <value>Non-Model Files</value>\n  </data>\n  <data name=\"Label_InstalledModels\" xml:space=\"preserve\">\n    <value>Installed Models</value>\n  </data>\n  <data name=\"Label_DownloadAllFilesAllVersions\" xml:space=\"preserve\">\n    <value>Download All Files (All Versions)</value>\n  </data>\n  <data name=\"Label_View\" xml:space=\"preserve\">\n    <value>View</value>\n  </data>\n  <data name=\"Label_Filter\" xml:space=\"preserve\">\n    <value>Filter</value>\n  </data>\n</root>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.ru-ru.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>Запустить</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>Выйти</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>Сохранить</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>Отмена</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>Язык</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>Для применения нового языка, требуется перезапуск</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>Перезапустить</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>Перезапустить позже</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>Требуется перезапуск</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>Неизвестный пакет</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>Импорт</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>Тип пакета</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>Версия</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>Тип версии</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>Релизы</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>Ветки</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>Перетащите модели сюда для импорта</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>Усиление акцента</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>Ослабление акцента</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>Embeddings / Textual Inversion</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>Сети (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>Комментарии</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>Отображать пиксельную сетку при больших уровнях масштабирования</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>Шаги</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>Базовые шаги</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>Шаги корректора</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>Соответствие промпту</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Сила шумоподавления</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>Ширина</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>Высота</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Корректор</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>Модель</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>Соединиться</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>Соединение...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>Закрыть</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>Ожидание соединения...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>Доступно обновление</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>Поддержать на Патреоне</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Присоединиться к Дискорду</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>Загрузки</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>Установить</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>Пропустить первоначальную настройку</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>Произошла неизвестная ошибка</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>Выйти из программы</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>Название</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>Установка с таким названием уже существует.</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>Выберите другое название или другой путь установки.</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>Дополнительные параметры</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>Коммит</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>Метод общего хранилища моделей</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>Версия PyTorch</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>Закрыть диалоговое окно после завершения</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>Папка с данными</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>Сюда будут установлены данные приложения (контрольные точки модели, веб-интерфейсы и так далее).</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>При использовании диска FAT32 или exFAT могут возникнуть ошибки. Выберите другой диск для более удобной работы.</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>Переносной режим</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>В переносном режиме все данные и настройки будут храниться в той же папке, что и приложение. Можно перемещать приложение вместе с его папкой «Data» в другое место или на другой компьютер.</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>Продолжить</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>Пред. изображение</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>След. изображение</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>Описание модели</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>Доступна новая версия Stability Matrix!</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>Импорт последней -</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>Все версии</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>Поиск моделей, #тегов или @авторов</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>Поиск</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>Сортировка</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>Промежуток</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>Тип модели</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>Базовая модель</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>Показывать контент с NSFW</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>Данные предоставлены CivitAI</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>Страница</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>Первая страница</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>Предыдущая страница</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>Следущая страница</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>Последняя страница</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>Переименовать</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>Удалить</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>Открыть на CivitAI</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>Связанная модель</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>Локальная модель</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>Открыть в проводнике</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>Новая...</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>Папка</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>Перетащите файл сюда для импорта</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>Импорт с метаданными</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>Поиск связанных метаданных при новом локальном импорте</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>Индексирование...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>Папка с моделями</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>Категории</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>Давайте приступим</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>Я прочёл и согласен с</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>Лицензионным соглашением.</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>Найти связанные метаданные</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>Показать изображения модели</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>Внешний вид</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>Тема</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>Менеджер моделей</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>Удалить символические ссылки на каталог общих моделей при завершении работы</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Выберите эту настройку если у вас возникают проблемы с перемещением Stability Matrix на другой диск</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>Сбросить кэш моделей</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>Перестраивает кэш установленных моделей. Используется, если модели неправильно обозначены в обозревателе моделей</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>Окружение пакета</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>Изменить</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>Переменные среды</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>Встроенный Python</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>Проверить версию</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>Интеграции</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Discord Rich Presence</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>Система</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>Добавить ярлык Stability Matrix в меню &quot;Пуск&quot;</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>Использует текущее местоположение приложения, вы можете запустить это снова, если переместите приложение</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Доступно только на Windows</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>Добавить для текущего пользователя</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>Добавить для всех</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>Выберите новую папку с данными (Data)</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>Не перемещает существующие данные. Требуется перезапуск приложения.</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>Выберите папку</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>О программе</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>Уведомления о лицензиях и открытом исходном коде</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>Нажмите «Запустить», чтобы начать!</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>Остановить</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>Послать команду</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>Команда</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>Отправить</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>Требуется команда</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>Подтвердить?</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>Да</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>Нет</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Открыть веб-интерфейс</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>Добро пожаловать в Stability Matrix!</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>Выберите предпочитаемый интерфейс, чтобы начать</value>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>Установка</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>Переход на страницу запуска</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>Скачивание пакета...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>Скачивание завершено</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>Установка завершена</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>Установка необходимых компонентов...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>Установка требований к пакету...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>Открыть в проводнике</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>Открыть в файндере</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>Удалить</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>Проверить обновления</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>Обновить</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>Добавить пакет</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>Добавьте пакет, чтобы начать!</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>Переменная</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>Значение</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>Удалить</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>Подробнее</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>Стэк вызовов (Callstack)</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>Внутреннее исключение (Inner exception)</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>Поиск...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>ОК</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>Повторить</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Информация о версии Python</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>Перезапустить</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>Подтвердите удаление</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>Это приведет к удалению папки пакета и всего её содержимого, включая любые сгенерированные картинки и файлы, которые вы, возможно, добавили.</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>Удаление пакета...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>Пакет удалён</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>Некоторые файлы не удалось удалить. Пожалуйста, закройте все открытые файлы в каталоге пакетов и повторите попытку.</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>Неправильный тип пакета</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>Обновление {0}</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>Обновление завершено</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} был обновлён до последней версии</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>Ошибка обновления {0}</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>Ошибка обновления</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>Открыть в браузере</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>Ошибка установки пакета</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>Ветка</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>Автоматически прокручивать в конец</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>Лицензия</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>Расшаривание моделей...</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>Выберите папку с данными</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>Название папки с данными</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>Текущая папка:</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>После обновления приложение будет перезапущено</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>Напомнить позже</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>Установить</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>Что нового</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>Открыть проект...</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>Сохранить как...</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>Восстановить вид по умолчанию</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>Вывод результатов</value>\n  </data>\n  <data name=\"Label_BatchIndex\" xml:space=\"preserve\">\n    <value>Индекс пакета</value>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>Копировать</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>Открыть в просмотрщике изображений</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>Выбрано {0} изображений</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>Папка вывода</value>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>Тип вывода</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>Очистить секцию</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>Выбрать всё</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>Отправить в Инференс</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>Текст в изображение</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>Изображение в изображение</value>\n  </data>\n  <data name=\"Label_Inpainting\" xml:space=\"preserve\">\n    <value>Инпейнтинг</value>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>Увеличить разрешение</value>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>Браузер выходных данных</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>Выбрано 1 изображение</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Пакеты Python</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>Консолидация</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>Вы уверены?</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>В результате все сгенерированные изображения из выбранных пакетов будут перемещены в каталог консолидированной общей папки outputs. Это действие нельзя отменить.</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>Обновить</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>Улучшить</value>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>Понизить</value>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>Открыть на GitHub</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>Подключено</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>Отключить</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>Эл. почта</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>Имя пользователя</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>Пароль</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>Логин</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>Зарегистрироваться</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>Подтвердить пароль</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>Ключ API</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>Учётные записи</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>Препроцессор</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>Интенсивность</value>\n  </data>\n  <data name=\"Label_ControlWeight\" xml:space=\"preserve\">\n    <value>Контроль значимости</value>\n  </data>\n  <data name=\"Label_ControlSteps\" xml:space=\"preserve\">\n    <value>Контроль шагов</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>Необходимо авторизоваться, чтобы скачать эту модель. Введите ключ API CivitAI в настройках.</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>Скачивание не удалось</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>Автообновления</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>Для ранних последователей. Предварительные сборки будут более надёжными, чем из канала разработки, и будут доступны ближе к стабильным релизам. Ваши отзывы очень помогут нам в выявлении проблем и доработке элементов дизайна.</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>Для технических пользователей. Получите доступ к нашим сборкам для разработки из ветвей функций, как только они станут доступны. В процессе экспериментов с новыми функциями могут возникнуть некоторые неровности и ошибки.</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>Обновления</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>Установлена последняя версия</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>Последняя проверка: {0}</value>\n  </data>\n  <data name=\"Action_CopyTriggerWords\" xml:space=\"preserve\">\n    <value>Копировать слова-триггеры</value>\n  </data>\n  <data name=\"Label_TriggerWords\" xml:space=\"preserve\">\n    <value>Слова-триггеры:</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>Здесь можно включить дополнительные папки, такие как IPAdapters и TextualInversions (вложения)</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>Открыть на Hugging Face</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>Обновить существующие метаданные</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>Основные</value>\n    <comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>Инференс</value>\n    <comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>Промпт</value>\n    <comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_OutputImageFiles\" xml:space=\"preserve\">\n    <value>Файлы выходных изображений</value>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>Просмотрщик изображений</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>Автозавершение</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>Заменяйте символы подчеркивания пробелами при вставке завершений</value>\n  </data>\n  <data name=\"Label_PromptTags\" xml:space=\"preserve\">\n    <value>Теги промпта</value>\n    <comment>Tags for image generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTagsImport\" xml:space=\"preserve\">\n    <value>Импорт тегов промпта</value>\n  </data>\n  <data name=\"Label_PromptTagsDescription\" xml:space=\"preserve\">\n    <value>Файл тегов для предложения завершений (поддерживает формат .csv a1111-sd-webui-tagcomplete)</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>Информация о системе</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>Дополнения</value>\n    <comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>Сохранить промежуточное изображение</value>\n    <comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>Настройки</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>Выбрать файл</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>Заменить содержимое</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>Пока недоступно</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>Функция будет доступна в одном из будущих обновлений</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>Отсутствует файл изображения</value>\n  </data>\n  <data name=\"Label_HolidayMode\" xml:space=\"preserve\">\n    <value>Праздничный режим</value>\n  </data>\n  <data name=\"Label_CLIPSkip\" xml:space=\"preserve\">\n    <value>Пропуск CLIP</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>Изображение в видео</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>Кадров в Секунду</value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>Мин CFG</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>Без потерь</value>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>Кадры</value>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>Интенсивность движения (Motion Bucket ID)</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>Уровень аугментации</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>Метод</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>Качество</value>\n  </data>\n  <data name=\"Label_FindInModelBrowser\" xml:space=\"preserve\">\n    <value>Найти в обозревателе моделей</value>\n  </data>\n  <data name=\"Label_Installed\" xml:space=\"preserve\">\n    <value>Установлено</value>\n  </data>\n  <data name=\"Label_NoExtensionsFound\" xml:space=\"preserve\">\n    <value>Расширения не найдены.</value>\n  </data>\n  <data name=\"Action_Hide\" xml:space=\"preserve\">\n    <value>Скрыть</value>\n  </data>\n  <data name=\"Action_CopyDetails\" xml:space=\"preserve\">\n    <value>Копировать подробности</value>\n  </data>\n  <data name=\"Action_Download\" xml:space=\"preserve\">\n    <value>Скачать</value>\n  </data>\n  <data name=\"TeachingTip_DownloadsExplanation\" xml:space=\"preserve\">\n    <value>Здесь можно проверить прогресс установки и скачивания пакетов и моделей.</value>\n  </data>\n  <data name=\"Label_RecommendedModels\" xml:space=\"preserve\">\n    <value>Рекомендуемые модели</value>\n  </data>\n  <data name=\"Label_RecommendedModelsSubText\" xml:space=\"preserve\">\n    <value>Пока ваш пакет устонавливается, вот несколько рекомендуемых моделей для того чтобы помочь вам начать.</value>\n  </data>\n  <data name=\"Label_Notifications\" xml:space=\"preserve\">\n    <value>Уведомления</value>\n  </data>\n  <data name=\"Label_NotificationOption_None\" xml:space=\"preserve\">\n    <value>Ничего</value>\n  </data>\n  <data name=\"Label_ComfyRequiredTitle\" xml:space=\"preserve\">\n    <value>Требуется ComfyUI</value>\n  </data>\n  <data name=\"Label_ComfyRequiredDetail\" xml:space=\"preserve\">\n    <value>Для установки этого пакета требуется ComfyUI. Установить?</value>\n  </data>\n  <data name=\"Error_PleaseSelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Пожалуйста выберите место скачивания.</value>\n  </data>\n  <data name=\"Label_SelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Выберите место скачивания:</value>\n  </data>\n  <data name=\"Label_Config\" xml:space=\"preserve\">\n    <value>Конфигурация</value>\n  </data>\n  <data name=\"Label_ToggleAutoScrolling\" xml:space=\"preserve\">\n    <value>Автопрокрутка в конец</value>\n  </data>\n  <data name=\"Label_ConfirmExit\" xml:space=\"preserve\">\n    <value>Подтвердить выход</value>\n  </data>\n  <data name=\"Label_ConfirmExitDetail\" xml:space=\"preserve\">\n    <value>Вы точно хотите выйти? Это также закроет все использующиеся пакеты.</value>\n  </data>\n  <data name=\"Label_Console\" xml:space=\"preserve\">\n    <value>Консоль</value>\n  </data>\n  <data name=\"Label_WebUi\" xml:space=\"preserve\">\n    <value>Веб интерфейс</value>\n  </data>\n  <data name=\"Label_Packages\" xml:space=\"preserve\">\n    <value>Пакеты</value>\n  </data>\n  <data name=\"Label_ActionCannotBeUndone\" xml:space=\"preserve\">\n    <value>Это действие не может быть отменено.</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteImages\" xml:space=\"preserve\">\n    <value>Вы уверены что хотите удалить {0} картинок?</value>\n  </data>\n  <data name=\"Label_CheckingHardware\" xml:space=\"preserve\">\n    <value>Мы проверяем спецификации вашего компьютера, чтобы определить совместимость.</value>\n  </data>\n  <data name=\"Label_EverythingLooksGood\" xml:space=\"preserve\">\n    <value>Всё выглядит хорошо!</value>\n  </data>\n  <data name=\"Label_NvidiaGpuRecommended\" xml:space=\"preserve\">\n    <value>Мы рекомендуем графический процессор с поддержкой CUDA для наилучшего опыта. Вы можете продолжить без него, но некоторые пакетов могут не работать и вывод будет медленнее.</value>\n  </data>\n  <data name=\"Label_Checkpoints\" xml:space=\"preserve\">\n    <value>Модели</value>\n  </data>\n  <data name=\"Label_ModelBrowser\" xml:space=\"preserve\">\n    <value>Поиск моделей</value>\n  </data>\n  <data name=\"Label_Workflows\" xml:space=\"preserve\">\n    <value>Рабочие процессы</value>\n  </data>\n  <data name=\"Label_InfiniteScrolling\" xml:space=\"preserve\">\n    <value>Бесконечная прокрутка</value>\n  </data>\n  <data name=\"Label_WorkflowBrowser\" xml:space=\"preserve\">\n    <value>Обозреватель рабочих процессов</value>\n  </data>\n  <data name=\"Action_OpenOnOpenArt\" xml:space=\"preserve\">\n    <value>Открыть в OpenArt</value>\n  </data>\n  <data name=\"Label_NodeDetails\" xml:space=\"preserve\">\n    <value>Сведения об узле</value>\n  </data>\n  <data name=\"Label_WorkflowDescription\" xml:space=\"preserve\">\n    <value>Описание рабочего процесса</value>\n  </data>\n  <data name=\"Label_OpenArtBrowser\" xml:space=\"preserve\">\n    <value>Обозреватель OpenArt</value>\n  </data>\n  <data name=\"Action_PreviewPreprocessor\" xml:space=\"preserve\">\n    <value>Препроцессор предпросмотра</value>\n  </data>\n  <data name=\"TeachingTip_WebUiButtonMoved\" xml:space=\"preserve\">\n    <value>Кнопка «Открыть веб-интерфейс» перемещена на панель команд</value>\n  </data>\n  <data name=\"Label_AnotherInstanceAlreadyRunning\" xml:space=\"preserve\">\n    <value>Уже запущен другой экземпляр Stability Matrix. Закройте его, прежде чем запускать новый.</value>\n  </data>\n  <data name=\"Label_StabilityMatrixAlreadyRunning\" xml:space=\"preserve\">\n    <value>Stability Matrix уже запущен</value>\n  </data>\n  <data name=\"Label_WorkflowDeletedSuccessfully\" xml:space=\"preserve\">\n    <value>{0} успешно удалён</value>\n  </data>\n  <data name=\"Label_WorkflowDeleted\" xml:space=\"preserve\">\n    <value>Рабочий процесс удалён</value>\n  </data>\n  <data name=\"Label_ErrorRetrievingWorkflows\" xml:space=\"preserve\">\n    <value>Не удалось загрузить рабочие процессы</value>\n  </data>\n  <data name=\"TabLabel_InstalledWorkflows\" xml:space=\"preserve\">\n    <value>Установленные Workflows</value>\n  </data>\n  <data name=\"Label_WorkflowImported\" xml:space=\"preserve\">\n    <value>Workflow импортирован</value>\n  </data>\n  <data name=\"Label_FinishedImportingWorkflow\" xml:space=\"preserve\">\n    <value>Импорт workflow и пользовательских узлов завершён</value>\n  </data>\n  <data name=\"Label_WorkflowImportComplete\" xml:space=\"preserve\">\n    <value>Workflow и пользовательские узлы были импортированы.</value>\n  </data>\n  <data name=\"TeachingTip_InferencePromptHelpButton\" xml:space=\"preserve\">\n    <value>Нажмите здесь, чтобы ознакомиться с синтаксисом промпта и как включить Lora / Embeddings.</value>\n  </data>\n  <data name=\"Label_ExtraNetworks\" xml:space=\"preserve\">\n    <value>Дополнительные сети (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_CLIPStrength\" xml:space=\"preserve\">\n    <value>Интенсивность CLIP</value>\n  </data>\n  <data name=\"Label_NumberFormat\" xml:space=\"preserve\">\n    <value>Формат числа</value>\n  </data>\n  <data name=\"Text_DeleteFollowingItems\" xml:space=\"preserve\">\n    <value>Вы удалите следующие файлы:</value>\n  </data>\n  <data name=\"TextTemplate_DeleteFollowingCountItems\" xml:space=\"preserve\">\n    <value>Вы удалите следующие {0} файлов:</value>\n  </data>\n  <data name=\"Label_DeletePermanently\" xml:space=\"preserve\">\n    <value>Безвозвратно Удалить</value>\n  </data>\n  <data name=\"Action_MoveToTrash\" xml:space=\"preserve\">\n    <value>Отправить в Корзину</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteModels\" xml:space=\"preserve\">\n    <value>Удалить модели {0}?</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad\" xml:space=\"preserve\">\n    <value>Автопоиск при загрузке</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad_Description\" xml:space=\"preserve\">\n    <value>Автоматически запускать поиск при загрузке страницы браузера модели</value>\n  </data>\n  <data name=\"Action_ToggleVisibility\" xml:space=\"preserve\">\n    <value>Переключить Видимость</value>\n  </data>\n  <data name=\"Label_ClippingMask\" xml:space=\"preserve\">\n    <value>Маска отсечения</value>\n  </data>\n  <data name=\"Label_AppFolders\" xml:space=\"preserve\">\n    <value>Папки приложения</value>\n  </data>\n  <data name=\"Label_Logs\" xml:space=\"preserve\">\n    <value>Логи</value>\n  </data>\n  <data name=\"Label_AppData\" xml:space=\"preserve\">\n    <value>Данные приложения</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToSelected\" xml:space=\"preserve\">\n    <value>{0} обновлён до выбранной версии</value>\n  </data>\n  <data name=\"Action_CopyAsBitmap\" xml:space=\"preserve\">\n    <value>Копировать как растровое изображение</value>\n  </data>\n  <data name=\"Label_DisableUpdateCheck\" xml:space=\"preserve\">\n    <value>Отключить проверку обновлений</value>\n  </data>\n  <data name=\"Warning_PleaseExtractFirst\" xml:space=\"preserve\">\n    <value>ПОЖАЛУЙСТА ИЗВЛЕКИТЕ ПРИЛОЖЕНИЕ ИЗ АРХИВА ПЕРЕЗ ЗАПУСКОМ STABILITY MATRIX</value>\n  </data>\n  <data name=\"Label_HistorySize\" xml:space=\"preserve\">\n    <value>Размер истории</value>\n  </data>\n  <data name=\"Label_HistorySize_Description\" xml:space=\"preserve\">\n    <value>Количество дополнительных строк консоли, доступных при прокрутке вверх</value>\n  </data>\n  <data name=\"Label_ConnectAccountFailed\" xml:space=\"preserve\">\n    <value>Произошла проблема с подключением вашего аккаунта</value>\n  </data>\n  <data name=\"Label_EditModelMetadata\" xml:space=\"preserve\">\n    <value>Изменить Метаданные Модели</value>\n  </data>\n  <data name=\"Label_NSFW\" xml:space=\"preserve\">\n    <value>NSFW</value>\n  </data>\n  <data name=\"Label_Tags\" xml:space=\"preserve\">\n    <value>Тэги</value>\n  </data>\n  <data name=\"Label_VersionName\" xml:space=\"preserve\">\n    <value>Название Версии</value>\n  </data>\n  <data name=\"Label_TrainedWords\" xml:space=\"preserve\">\n    <value>Обученные слова</value>\n  </data>\n  <data name=\"Label_PreviewImage\" xml:space=\"preserve\">\n    <value>Превью</value>\n  </data>\n  <data name=\"Label_BatchSize\" xml:space=\"preserve\">\n    <value>Размер сетки</value>\n  </data>\n  <data name=\"Label_Batches\" xml:space=\"preserve\">\n    <value>Количество</value>\n  </data>\n  <data name=\"Label_Sampler\" xml:space=\"preserve\">\n    <value>Алгоритм</value>\n  </data>\n  <data name=\"Label_Scheduler\" xml:space=\"preserve\">\n    <value>Планировщик</value>\n  </data>\n  <data name=\"Label_MaxSize\" xml:space=\"preserve\">\n    <value>Максимальный Размер</value>\n  </data>\n  <data name=\"Label_UseSeparatePrompt\" xml:space=\"preserve\">\n    <value>Использовать отдельный промпт</value>\n  </data>\n  <data name=\"Label_Seed\" xml:space=\"preserve\">\n    <value>Сид</value>\n  </data>\n  <data name=\"Label_NegativePrompt\" xml:space=\"preserve\">\n    <value>Негативный промпт</value>\n  </data>\n  <data name=\"Label_NewFolder\" xml:space=\"preserve\">\n    <value>Новая Папка</value>\n  </data>\n  <data name=\"Label_CopyLinkToClipboard\" xml:space=\"preserve\">\n    <value>Копировать Ссылку в Буфер Обмена</value>\n  </data>\n  <data name=\"TextTemplate_OAuthLoginTitle\" xml:space=\"preserve\">\n    <value>Войти с помощью {0}</value>\n    <comment>e.g. &apos;Sign in with Google&apos;</comment>\n  </data>\n  <data name=\"Text_AllowBrowserOpenAppLink\" xml:space=\"preserve\">\n    <value>Разрешите вашему браузеру открыть это приложение, когда вам будет предложено продолжить.</value>\n  </data>\n  <data name=\"Text_OAuthLoginDescription\" xml:space=\"preserve\">\n    <value>Откройте ссылку в браузере и следуйте инструкциям по подключению учётной записи.</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Title\" xml:space=\"preserve\">\n    <value>Переопределение зависимостей Python</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Description\" xml:space=\"preserve\">\n    <value>Добавление, замена или удаление зависимостей для установки и обновления</value>\n  </data>\n  <data name=\"Label_DependencySpecifiers\" xml:space=\"preserve\">\n    <value>Спецификаторы зависимостей</value>\n  </data>\n  <data name=\"AnalyticsExample_InstallData\" xml:space=\"preserve\">\n    <value>{\n  &quot;packageName&quot;: &quot;stable-diffusion-webui&quot;,\n  &quot;packageVersion&quot;: &quot;v1.10.0&quot;,\n  &quot;isSuccess&quot;: true,\n  &quot;type&quot;: &quot;install&quot;,\n  &quot;timestamp&quot;: &quot;2024-09-04T02:14:04.1967404+00:00&quot;\n}</value>\n  </data>\n  <data name=\"Label_Analytics\" xml:space=\"preserve\">\n    <value>Аналитика</value>\n  </data>\n  <data name=\"TextTemplate_YouCanChangeThisBehavior\" xml:space=\"preserve\">\n    <value>Вы всегда можете изменить это поведение в {0}.</value>\n    <comment>e.g. &apos;You can always change this behavior in Settings &gt; Category &gt; Item.&apos;</comment>\n  </data>\n  <data name=\"Text_AnalyticsDescription\" xml:space=\"preserve\">\n    <value>Помогите нам улучшить Stability Matrix, отправляя анонимные данные об используемых функциях, версиях операционной системы, типах установленных пакетов и так далее. Отправленные данные никогда не будут связаны с вами или вашей учётной записью и не будут включать личные данные или какую-либо конфиденциальную информацию.</value>\n  </data>\n  <data name=\"Text_AnalyticsDescriptionShort\" xml:space=\"preserve\">\n    <value>Помогите нам улучшить Stability Matrix, отправляя анонимные данные об используемых функциях, версиях операционной системы, типах установленных пакетов и так далее.</value>\n  </data>\n  <data name=\"Text_AnalyticsDataPrivacyInfo\" xml:space=\"preserve\">\n    <value>Отправляемые данные никогда не будут связаны с вами или вашей учётной записью и не будут содержать личных данных или какой-либо конфиденциальной информации.</value>\n  </data>\n  <data name=\"Label_UsageData\" xml:space=\"preserve\">\n    <value>Данные использования</value>\n  </data>\n  <data name=\"Label_PrivacyPolicy\" xml:space=\"preserve\">\n    <value>Политика конфиденциальности</value>\n  </data>\n  <data name=\"Label_ImageHidden\" xml:space=\"preserve\">\n    <value>Скрытое изображение</value>\n  </data>\n  <data name=\"Label_NoImageFound\" xml:space=\"preserve\">\n    <value>Изображение не найдено</value>\n  </data>\n  <data name=\"Label_HideEmptyCategories\" xml:space=\"preserve\">\n    <value>Скрыть пустые категории</value>\n  </data>\n  <data name=\"Label_ShowNsfwImages\" xml:space=\"preserve\">\n    <value>Показывать изображения с NSFW</value>\n  </data>\n  <data name=\"Label_EnableLongPaths\" xml:space=\"preserve\">\n    <value>Включить длинные пути</value>\n    <comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_EnableLongPathsDescription\" xml:space=\"preserve\">\n    <value>Уберает ограничения MAX_PATH из общих функций файлов и каталогов Win32</value>\n    <comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_SystemSettings\" xml:space=\"preserve\">\n    <value>Системные настройки</value>\n  </data>\n  <data name=\"Label_ChangesApplied\" xml:space=\"preserve\">\n    <value>Внесённые изменения</value>\n  </data>\n  <data name=\"Text_RestartMayBeRequiredForSystemChanges\" xml:space=\"preserve\">\n    <value>Чтобы изменения в системе применились, может потребоваться перезагрузка.</value>\n  </data>\n  <data name=\"Label_ReleasesUnavailableForThisPackage\" xml:space=\"preserve\">\n    <value>Релизы для этого пакета недоступны.</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>Перейти к настройкам</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>Включить</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>Отключить</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Подключить аккаунт Lykos</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>Войдите в свой аккаунт Lykos, чтобы использовать подключенные функции.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>Пожалуйста, войдите снова</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>Срок действия вашего входа истёк. Пожалуйста, войдите снова, чтобы продолжить.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Поддержка Stability Matrix</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>Спасибо за поддержку Stability Matrix!</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>Такие функции, как **{0}**, — это одно из многих преимуществ, доступных нашим сторонникам. Ваш вклад помогает нам покрывать расходы на серверы и поддерживает разработку Stability Matrix.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>Такие функции, как **{0}**, доступны на уровне поддержки **{1}** (или выше). Ваш вклад помогает нам покрывать расходы на серверы для более продвинутых подключенных функций и позволяет нам постоянно улучшать Stability Matrix для всех.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>Если Вы уже поддерживаете нас на Patreon, пожалуйста, привяжите свою учетную запись, чтобы продолжить.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>Настройки учетной записи</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>Посмотреть варианты поддержки</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>Позже</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>Статус</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>Активно</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>Неактивно (Используется стандартное соединение)</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>Получайте более быстрые результаты поиска при просмотре моделей из онлайн-репозиториев, таких как Civitai.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>Экспериментальная оптимизация для сторонних репозиториев. Официально не аффилирован; доступность может изменяться.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>Бета</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>Ускоренный поиск моделей</value>\n    <comment>Fuzzy</comment>\n  </data>\n</root>"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.tr-TR.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>Başlat</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>Çıkış</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>Kaydet</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>İptal</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>Dil</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>Yeni dil seçeneğinin etkili olması için yeniden başlatma gerekir.</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>Yeniden başlat</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>Daha Sonra Yeniden Başlat</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>Yeniden Başlatma Gerekli</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>Bilinmeyen Paket</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>İçe Aktar</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>Paket Türü</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>Sürüm</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>Sürüm Türü</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>Sürümler</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>Dallar</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>İçe aktarmak için chekpoints&apos;leri buraya sürükleyip bırakın.</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>Vurgu</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>Vurguyu Kaldırma</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>Emebeddings / Textual Inversion</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>Networks (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>Yorumlar</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>Yüksek yakınlaştırma seviyesinde piksel ızgarasını göster</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>Adımlar</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>Adımlar - Temel</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>Adımlar - İyileştirici</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>CFG Ölçeği</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Gürültü Azaltma Gücü</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>Genişlik</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>Yükseklik</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>İyileştirici</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>Model</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>Bağlan</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>Bağlanıyor...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>Kapat</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>Bağlanmayı bekliyor...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>Güncelleme Mevcut</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>Sponsor Ol</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Discord Sunucumuza Katıl</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>İndirmeler</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>Yükle</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>İlk kurulum işlemini atla</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>Beklenmeyen bir hata oluştu</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>Uygulamadan Çık</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>Görünen Ad</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>Bu ada sahip bir yükleme zaten mevcut.</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>Lütfen farklı bir ad seçin veya başka bir yükleme konumu seçin.</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>Gelişmiş Seçenekler</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>İşlemek</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>Paylaşılan Model Klasör Stratejisi</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>PyTorch Sürümü</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>Bittiğinde iletişim kutusunu kapat</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>Veri Klasörü</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>Model chekpoints, Lora&apos;ların, web UI&apos;lerin, ayarların vb. kurulacağı yer burasıdır.</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>FAT32 veya exFAT sürücü kullanırken hatalarla karşılaşabilirsiniz. Daha sorunsuz bir deneyim için farklı bir sürücü seçin.</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>Taşınabilir Mod</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>Taşınabilir Modda tüm veriler ve ayarlar uygulamayla aynı dizinde saklanacaktır. Uygulamayı &apos;Veri&apos; klasörüyle birlikte farklı bir konuma veya bilgisayara taşıyabileceksiniz.</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>Devam</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>Önceki Resim</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>Sonraki Resim</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>Model Açıklaması</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>Stabilite Matrisi için yeni bir sürüm mevcut!</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>En Yeniyi İçe Aktar -</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>Tüm Sürümler</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>Modelleri,Tagları (#tag) veya Kullanıcıları (@user) Buradan Arayabilirsin</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>Ara</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>Sırala</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>Sıralama Tarihi</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>Model Türü</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>Model Altyapısı</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>NSFW İçerik Göster</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>CivitAI tarafından sağlanan veriler</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>Sayfa</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>İlk Sayfa</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>Önceki Sayfa</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>Sonraki Sayfa</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>Son Sayfa</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>Yeniden Adlandır</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>Sil</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>CivitAI&apos;de Aç</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>Bağlı Model</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>Yerel Model</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>Explorer&apos;da göster</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>Yeni</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>Klasör</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>Dosyayı içeri aktarmak için lütfen sürükleyip işaretlenen alana bırakın.</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>Dosyaları metadata ile içeri aktar</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>Yeni yerel içe aktarmalar için bağlı meta veri arayın</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>İndeksleniyor...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>Model Klasörü</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>Kategoriler</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>Başlayalım</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>Lisans Sözleşmesini Okudum ve Kabul Ediyorum.</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>Lisans Sözleşmesi.</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>Metadata&apos;larını bul</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>Model Resimlerini Göster</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>Görünüm</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>Tema</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>Checkpoint Yöneticisi</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>Kapatma sırasında paylaşılan Checkpoint dizini sembolik bağlantılarını kaldırın</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Stabilite Matrisini başka bir sürücüye taşımada sorun yaşıyorsanız bu seçeneği seçin</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>Checkpoints Önbelleğini Sıfırla</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>Kurulu checkpoints önbelleğini yeniden oluştur. Model tarayıcısında checkpointler yanlış etiketlenmişse kullanın</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>Paket Ortamı</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>Düzenle</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>Ortam Değişkenleri</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>Gömülü Python</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>Sürümü Kontrol Et</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>Entegrasyonlar</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Discord Etkinliğini StabilityMatrix Olarak Göster.</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>Sistem</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>Stability Matrix&apos;i başlat menüsüne ekle</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>Mevcut uygulama konumu kullanılır, uygulamayı taşırsanız tekrar çalıştırabilirsiniz</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Yalnızca Windows&apos;ta kullanılabilir</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>Geçerli Kullanıcı için Ekle</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>Tüm Kullanıcılar İçin Ekle</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>Yeni Dosya Dizinini Seç</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>Bu işlem mevcut veriyi yeni dizine taşımaz.</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>Dizin Seçin</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>Hakkında</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>Lisans ve Açık Kaynak Bildirimleri</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>Başlamak için Başlat&apos;a tıklayın!</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>Durdur</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>Giriş Gönder</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>Giriş</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>Gönder</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>Giriş gerekiyor</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>Onaylamak?</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>Evet</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>Hayır</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Web Arayüzünü Aç</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix&apos;e Hoş Geldiniz!</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>Tercih ettiğiniz arayüzü seçin ve başlamak için Yükle&apos;ye tıklayın</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>Yükleniyor</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>Başlatma sayfasına geçiliyor</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>Paket indiriliyor...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>İndirme tamamlandı</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>Kurulum tamamlandı</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>Önkoşullar kuruluyor...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>Paket gereksinimleri kuruluyor...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>Explorer&apos;da Aç</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>Finder&apos;da Aç</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>Kaldır</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>Güncellemeleri Kontrol Et</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>Güncelle</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>Paket Ekle</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>Başlamak için bir paket ekle!</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>Ad</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>Değer</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>Kaldır</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>Ayrıntılar</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>Çağrı Yığını</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>İç istisna</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>Ara...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>Tamam</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>Tekrar Dene</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Python Sürüm Bilgisi</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>Yeniden Başlat</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>Silmeyi Onayla</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>Bu, paket klasörünü ve içindeki tüm içeriği, eklediğiniz tüm oluşturulmuş resimleri ve dosyaları silecek.</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>Paket Kaldırılıyor...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>Paket Kaldırıldı</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>Bazı dosyalar silinemedi. Lütfen paket klasöründeki açık dosyaları kapatın ve tekrar deneyin.</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>Geçersiz Paket Türü</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>{0} Güncelleniyor</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>Güncelleme tamamlandı</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0}, en son sürüme güncellendi</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>{0} güncelleme hatası</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>Güncelleme başarısız oldu</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>Tarayıcıda Aç</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>Paket kurulum hatası</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>Dal</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>Konsol çıktısının sonuna otomatik olarak kaydır</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>Lisans</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>Model Paylaşımı</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>Lütfen Bir Veri Klasörü Seçin</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>Veri Klasörü Adı</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>Geçerli klasör:</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>Uygulama güncelleme sonrası yeniden başlatılacaktır</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>Daha Sonra Hatırlat</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>Şimdi Yükle</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>Sürüm Notları</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>Projeyi Aç...</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>Farklı Kaydet...</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>Varsayılan Düzeni Geri Yükle</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>Çıktı Paylaşımı</value>\n  </data>\n  <data name=\"Label_BatchIndex\" xml:space=\"preserve\">\n    <value>Toplu İndeks</value>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>Kopyala</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>Resim Görüntüleyici&apos;de aç</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>{0} resim seçildi</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>Çıktı Klasörü</value>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>Çıktı Türü</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>Seçimi Temizle</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>Tümünü Seç</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>Üreticiye Gönder</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>Metinden Resime</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>Resimden Resime</value>\n  </data>\n  <data name=\"Label_Inpainting\" xml:space=\"preserve\">\n    <value>Inpainting</value>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>Upscale</value>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>Medya Kitaplığı</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>1 resim seçildi</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Python Paketleri</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>Birleştir</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>Emin misiniz?</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>Bu, seçilen paketlerden tüm oluşturulmuş resimleri, paylaşılan çıktılar klasörünün Konsolide dizinine taşıyacak. Bu işlem geri alınamaz.</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>Yenile</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>Yükselt</value>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>Düşür</value>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>GitHub&apos;da Aç</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>Bağlandı</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>Bağlantıyı Kes</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>E-posta</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>Kullanıcı Adı</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>Parola</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>Giriş</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>Kaydol</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>Parolayı Onayla</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>API Key</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>Hesaplar</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>Önişlemci</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>Kuvvet</value>\n  </data>\n  <data name=\"Label_ControlWeight\" xml:space=\"preserve\">\n    <value>Kontrol Ağırlığı</value>\n  </data>\n  <data name=\"Label_ControlSteps\" xml:space=\"preserve\">\n    <value>Kontrol Adımları</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>Bu kontrol noktasını indirmek için giriş yapmalısınız. Lütfen ayarlara bir CivitAI API Anahtarı girin.</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>İndirme Başarısız</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>Otomatik Güncellemeler</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>Erken benimseyenler için. Önizleme yapıları, Geliştirme kanalındakilerden daha güvenilir olacak ve kararlı sürümlere daha yakın olacak. Geri bildiriminiz sorunları keşfetmemizde ve tasarım öğelerini geliştirmemizde bize büyük ölçüde yardımcı olacaktır.</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>Teknik kullanıcılar için. Geliştirme yapılarımıza, kullanıma sunuldukları anda özellik dallarından ilk erişen siz olun. Yeni özellikleri denedikçe bazı pürüzlü noktalar ve hatalar olabilir.</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>Güncellemeler</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>Güncelsin</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>Son kontrol: {0}</value>\n  </data>\n  <data name=\"Action_CopyTriggerWords\" xml:space=\"preserve\">\n    <value>Tetikleyici Kelimeleri Kopyala</value>\n  </data>\n  <data name=\"Label_TriggerWords\" xml:space=\"preserve\">\n    <value>Tetikleyici kelimeler:</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>IPAdapters ve TextualInversions (embeddings) gibi ek klasörler burada etkinleştirilebilir</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face&apos;de aç</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>Mevcut Meta Verileri Güncelle</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>Genel</value>\n    <comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>Üretici</value>\n    <comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>Komut</value>\n    <comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_OutputImageFiles\" xml:space=\"preserve\">\n    <value>Çıktı Resim Dosyaları</value>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>Resim görüntüleyici</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>Otomatik Tamamlama</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>Tamamlamaları eklerken alt çizgileri boşluklarla değiştirin</value>\n  </data>\n  <data name=\"Label_PromptTags\" xml:space=\"preserve\">\n    <value>Komut Etiketleri</value>\n    <comment>Tags for image generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTagsImport\" xml:space=\"preserve\">\n    <value>Komut etiketlerini içe aktar</value>\n  </data>\n  <data name=\"Label_PromptTagsDescription\" xml:space=\"preserve\">\n    <value>Tamamlamaları önermek için kullanılacak etiket dosyası (a1111-sd-webui-tagcomplete .csv formatını destekler)</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>Sistem bilgisi</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>Eklentiler</value>\n    <comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>Ara Resmi Kaydet</value>\n    <comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>Ayarlar</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>Dosya Seç</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>İçeriği Değiştir</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>Henüz uygun değil</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>Özellik gelecekteki bir güncellemede kullanıma sunulacak</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>Eksik Resim Dosyası</value>\n  </data>\n  <data name=\"Label_HolidayMode\" xml:space=\"preserve\">\n    <value>Tatil Modu</value>\n  </data>\n  <data name=\"Label_CLIPSkip\" xml:space=\"preserve\">\n    <value>CLIP Atla</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>Resimden Videoya</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>Saniyedeki Kare Sayısı</value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>Min. CFG</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>Kayıpsız</value>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>Çerçeveler</value>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>Motion Bucket ID</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>Arttırma Seviyesi</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>Yöntem</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>Kalite</value>\n  </data>\n  <data name=\"Label_FindInModelBrowser\" xml:space=\"preserve\">\n    <value>Model Tarayıcıda Bul</value>\n  </data>\n  <data name=\"Label_Installed\" xml:space=\"preserve\">\n    <value>Kurulmuş</value>\n  </data>\n  <data name=\"Label_NoExtensionsFound\" xml:space=\"preserve\">\n    <value>Uzantı bulunamadı.</value>\n  </data>\n  <data name=\"Action_Hide\" xml:space=\"preserve\">\n    <value>Gizle</value>\n  </data>\n  <data name=\"Action_CopyDetails\" xml:space=\"preserve\">\n    <value>Ayrıntıları Kopyala</value>\n  </data>\n  <data name=\"Action_Download\" xml:space=\"preserve\">\n    <value>İndir</value>\n  </data>\n  <data name=\"TeachingTip_DownloadsExplanation\" xml:space=\"preserve\">\n    <value>Paket kurulumlarınızın ve model indirmelerinizin ilerlemesini buradan kontrol edin.</value>\n  </data>\n  <data name=\"Label_RecommendedModels\" xml:space=\"preserve\">\n    <value>Önerilen Modeller</value>\n  </data>\n  <data name=\"Label_RecommendedModelsSubText\" xml:space=\"preserve\">\n    <value>Paketiniz yüklenirken başlamanıza yardımcı olması için önerdiğimiz bazı modeller aşağıda verilmiştir.</value>\n  </data>\n  <data name=\"Label_Notifications\" xml:space=\"preserve\">\n    <value>Bildirimler</value>\n  </data>\n  <data name=\"Label_NotificationOption_None\" xml:space=\"preserve\">\n    <value>Hiçbiri</value>\n  </data>\n  <data name=\"Label_ComfyRequiredTitle\" xml:space=\"preserve\">\n    <value>ComfyUI Gerekli</value>\n  </data>\n  <data name=\"Label_ComfyRequiredDetail\" xml:space=\"preserve\">\n    <value>Bu paketi kurmak için ComfyUI gereklidir. Şimdi yüklemek ister misiniz?</value>\n  </data>\n  <data name=\"Error_PleaseSelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Lütfen bir indirme konumu seçin.</value>\n  </data>\n  <data name=\"Label_SelectDownloadLocation\" xml:space=\"preserve\">\n    <value>İndirme Konumunu Seçin:</value>\n  </data>\n  <data name=\"Label_Config\" xml:space=\"preserve\">\n    <value>Yapılandırma</value>\n  </data>\n  <data name=\"Label_ToggleAutoScrolling\" xml:space=\"preserve\">\n    <value>Otomatik Sona Kaydır</value>\n  </data>\n  <data name=\"Label_ConfirmExit\" xml:space=\"preserve\">\n    <value>Çıkışı Onayla</value>\n  </data>\n  <data name=\"Label_ConfirmExitDetail\" xml:space=\"preserve\">\n    <value>Çıkmak istediğine emin misin? Bu aynı zamanda şu anda çalışan tüm paketleri de kapatacaktır.</value>\n  </data>\n  <data name=\"Label_Console\" xml:space=\"preserve\">\n    <value>Konsol</value>\n  </data>\n  <data name=\"Label_WebUi\" xml:space=\"preserve\">\n    <value>Web arayüzü</value>\n  </data>\n  <data name=\"Label_Packages\" xml:space=\"preserve\">\n    <value>Paketler</value>\n  </data>\n  <data name=\"Label_ActionCannotBeUndone\" xml:space=\"preserve\">\n    <value>Bu eylem geri alınamaz.</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteImages\" xml:space=\"preserve\">\n    <value>{0} görseli silmek istediğinizden emin misiniz?</value>\n  </data>\n  <data name=\"Label_CheckingHardware\" xml:space=\"preserve\">\n    <value>Uyumluluğu belirlemek için bazı donanım özelliklerini kontrol ediyoruz.</value>\n  </data>\n  <data name=\"Label_EverythingLooksGood\" xml:space=\"preserve\">\n    <value>Her şey iyi gözüküyor!</value>\n  </data>\n  <data name=\"Label_NvidiaGpuRecommended\" xml:space=\"preserve\">\n    <value>En iyi deneyim için CUDA destekli bir GPU öneriyoruz. Olmadan da devam edebilirsiniz ancak bazı paketler çalışmayabilir ve üretimler daha yavaş olabilir.</value>\n  </data>\n  <data name=\"Label_Checkpoints\" xml:space=\"preserve\">\n    <value>Kontrol noktaları</value>\n  </data>\n  <data name=\"Label_ModelBrowser\" xml:space=\"preserve\">\n    <value>Model Tarayıcı</value>\n  </data>\n  <data name=\"Label_Workflows\" xml:space=\"preserve\">\n    <value>İş akışları</value>\n  </data>\n  <data name=\"Label_InfiniteScrolling\" xml:space=\"preserve\">\n    <value>Sonsuz Kaydırma</value>\n  </data>\n  <data name=\"Label_WorkflowBrowser\" xml:space=\"preserve\">\n    <value>İş Akışı Tarayıcısı</value>\n  </data>\n  <data name=\"Action_OpenOnOpenArt\" xml:space=\"preserve\">\n    <value>OpenArt&apos;ta aç</value>\n  </data>\n  <data name=\"Label_NodeDetails\" xml:space=\"preserve\">\n    <value>Düğüm Ayrıntıları</value>\n  </data>\n  <data name=\"Label_WorkflowDescription\" xml:space=\"preserve\">\n    <value>İş Akışı Açıklaması</value>\n  </data>\n  <data name=\"Label_OpenArtBrowser\" xml:space=\"preserve\">\n    <value>OpenArt Tarayıcı</value>\n  </data>\n  <data name=\"Action_PreviewPreprocessor\" xml:space=\"preserve\">\n    <value>Ön İşlemciyi Önizle</value>\n  </data>\n  <data name=\"TeachingTip_WebUiButtonMoved\" xml:space=\"preserve\">\n    <value>&apos;Web Kullanıcı Arayüzünü Aç&apos; düğmesi komut çubuğuna taşındı</value>\n  </data>\n  <data name=\"Label_AnotherInstanceAlreadyRunning\" xml:space=\"preserve\">\n    <value>Stability Matrixin başka bir örneği zaten çalışıyor. Yenisini başlatmadan önce lütfen kapatın.</value>\n  </data>\n  <data name=\"Label_StabilityMatrixAlreadyRunning\" xml:space=\"preserve\">\n    <value>Stability Matrix zaten çalışıyor</value>\n  </data>\n  <data name=\"Label_WorkflowDeletedSuccessfully\" xml:space=\"preserve\">\n    <value>{0} başarıyla silindi</value>\n  </data>\n  <data name=\"Label_WorkflowDeleted\" xml:space=\"preserve\">\n    <value>İş Akışı Silindi</value>\n  </data>\n  <data name=\"Label_ErrorRetrievingWorkflows\" xml:space=\"preserve\">\n    <value>İş akışları alınırken hata oluştu</value>\n  </data>\n  <data name=\"TabLabel_InstalledWorkflows\" xml:space=\"preserve\">\n    <value>Yüklü İş Akışları</value>\n  </data>\n  <data name=\"Label_WorkflowImported\" xml:space=\"preserve\">\n    <value>İş Akışı İçeri Aktarıldı</value>\n  </data>\n  <data name=\"Label_FinishedImportingWorkflow\" xml:space=\"preserve\">\n    <value>İş akışının ve özel düğümlerin içe aktarılması tamamlandı</value>\n  </data>\n  <data name=\"Label_WorkflowImportComplete\" xml:space=\"preserve\">\n    <value>İş akışı ve özel düğümler içe aktarıldı.</value>\n  </data>\n  <data name=\"TeachingTip_InferencePromptHelpButton\" xml:space=\"preserve\">\n    <value>Bilgi istemi sözdizimini ve Lora / Embeddings&apos;in nasıl ekleneceğini incelemek için burayı tıklayın.</value>\n  </data>\n  <data name=\"Label_ExtraNetworks\" xml:space=\"preserve\">\n    <value>Extra Networks (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_CLIPStrength\" xml:space=\"preserve\">\n    <value>CLIP Kuvveti</value>\n  </data>\n  <data name=\"Label_NumberFormat\" xml:space=\"preserve\">\n    <value>Sayı Formatı</value>\n  </data>\n  <data name=\"Text_DeleteFollowingItems\" xml:space=\"preserve\">\n    <value>Şu öğeleri silmek üzeresiniz:</value>\n  </data>\n  <data name=\"TextTemplate_DeleteFollowingCountItems\" xml:space=\"preserve\">\n    <value>Şu {0} öğeyi silmek üzeresiniz:</value>\n  </data>\n  <data name=\"Label_DeletePermanently\" xml:space=\"preserve\">\n    <value>Kalıcı olarak silin</value>\n  </data>\n  <data name=\"Action_MoveToTrash\" xml:space=\"preserve\">\n    <value>Çöp Kutusuna Taşı</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteModels\" xml:space=\"preserve\">\n    <value>{0} modelini silmek istediğinizden emin misiniz?</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad\" xml:space=\"preserve\">\n    <value>Yükleme Sırasında Otomatik Arama</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad_Description\" xml:space=\"preserve\">\n    <value>Model tarayıcı sayfası yüklendiğinde otomatik olarak bir arama başlatma</value>\n  </data>\n  <data name=\"Action_ToggleVisibility\" xml:space=\"preserve\">\n    <value>Görünürlüğü Değiştir</value>\n  </data>\n  <data name=\"Label_ClippingMask\" xml:space=\"preserve\">\n    <value>Kırpma Maskesi</value>\n  </data>\n  <data name=\"Label_AppFolders\" xml:space=\"preserve\">\n    <value>Uygulama Klasörleri</value>\n  </data>\n  <data name=\"Label_Logs\" xml:space=\"preserve\">\n    <value>Günlükler</value>\n  </data>\n  <data name=\"Label_AppData\" xml:space=\"preserve\">\n    <value>Uygulama Verileri</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToSelected\" xml:space=\"preserve\">\n    <value>{0} seçilen sürüme güncellendi</value>\n  </data>\n  <data name=\"Action_CopyAsBitmap\" xml:space=\"preserve\">\n    <value>Bitmap Olarak Kopyala</value>\n  </data>\n  <data name=\"Label_DisableUpdateCheck\" xml:space=\"preserve\">\n    <value>Güncelleme Kontrolünü Devre Dışı Bırak</value>\n  </data>\n  <data name=\"Warning_PleaseExtractFirst\" xml:space=\"preserve\">\n    <value>STABILITE MATRISINI ÇALIŞTIRMADAN ÖNCE LÜTFEN UYGULAMAYI ZIP DOSYASINDAN ÇIKARIN</value>\n  </data>\n  <data name=\"Label_HistorySize\" xml:space=\"preserve\">\n    <value>Geçmiş Boyutu</value>\n  </data>\n  <data name=\"Label_HistorySize_Description\" xml:space=\"preserve\">\n    <value>Konsolda görüntülenen satırların üzerinde geri kaydırabileceğiniz satır sayısı</value>\n  </data>\n  <data name=\"Label_ConnectAccountFailed\" xml:space=\"preserve\">\n    <value>Hesabınızı bağlarken bazı sorunlar yaşadık</value>\n  </data>\n  <data name=\"Label_EditModelMetadata\" xml:space=\"preserve\">\n    <value>Model Meta Verilerini Düzenleme</value>\n  </data>\n  <data name=\"Label_NSFW\" xml:space=\"preserve\">\n    <value>NSFW</value>\n  </data>\n  <data name=\"Label_Tags\" xml:space=\"preserve\">\n    <value>Etiketler</value>\n  </data>\n  <data name=\"Label_VersionName\" xml:space=\"preserve\">\n    <value>Sürüm Adı</value>\n  </data>\n  <data name=\"Label_TrainedWords\" xml:space=\"preserve\">\n    <value>Eğitilmiş Kelimeler</value>\n  </data>\n  <data name=\"Label_PreviewImage\" xml:space=\"preserve\">\n    <value>Önizleme Görüntüsü</value>\n  </data>\n  <data name=\"Label_BatchSize\" xml:space=\"preserve\">\n    <value>Toplu İş Boyutu</value>\n  </data>\n  <data name=\"Label_Batches\" xml:space=\"preserve\">\n    <value>Toplu İşler</value>\n  </data>\n  <data name=\"Label_Sampler\" xml:space=\"preserve\">\n    <value>Örnekleyici</value>\n  </data>\n  <data name=\"Label_Scheduler\" xml:space=\"preserve\">\n    <value>Programlayıcı</value>\n  </data>\n  <data name=\"Label_MaxSize\" xml:space=\"preserve\">\n    <value>Maksimum Boyut</value>\n  </data>\n  <data name=\"Label_UseSeparatePrompt\" xml:space=\"preserve\">\n    <value>Ayrı İstem Kullanın</value>\n  </data>\n  <data name=\"Label_Seed\" xml:space=\"preserve\">\n    <value>Seed</value>\n  </data>\n  <data name=\"Label_NegativePrompt\" xml:space=\"preserve\">\n    <value>Olumsuz İstem</value>\n  </data>\n  <data name=\"Label_NewFolder\" xml:space=\"preserve\">\n    <value>Yeni Klasör</value>\n  </data>\n  <data name=\"Label_CopyLinkToClipboard\" xml:space=\"preserve\">\n    <value>Bağlantıyı Panoya Kopyala</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>Ayarlara Git</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>Etkinleştir</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>Devre Dışı Bırak</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Lykos Hesabınızı Bağlayın</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>Bağlı özellikleri kullanmak için Lykos hesabınızla oturum açın.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>Lütfen Tekrar Oturum Açın</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>Oturumunuzun süresi doldu. Devam etmek için lütfen tekrar oturum açın.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Stability Matrix Desteği</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>Stability Matrix destekçisi olduğunuz için teşekkür ederiz!</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>**{0}** gibi özellikler, destekçilerimize sunulan birçok avantajdan biridir. Katkınız, sunucu maliyetlerimizi karşılamamıza ve Stability Matrix&apos;in geliştirilmesini desteklememize yardımcı olur.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>**{0}** gibi özellikler, **{1}** destekçi seviyesinde (veya üzeri) kullanılabilir. Katkınız, daha gelişmiş bağlantılı özellikler için sunucu maliyetlerimizi karşılamamıza yardımcı olur ve Stability Matrix&apos;i herkes için sürekli iyileştirmemize olanak tanır.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>Bizi zaten Patreon&apos;da destekliyorsanız, devam etmek için lütfen hesabınızı bağlayın.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>Hesap Ayarları</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>Destek Seçeneklerini Görüntüle</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>Daha Sonra</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>Durum</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>Etkin</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>Etkin değil (Standart bağlantı kullanılıyor)</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>Civitai gibi çevrimiçi depolardan modellere göz atarken daha hızlı arama sonuçları elde edin.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>Üçüncü taraf depolar için deneysel optimizasyon. Resmi olarak bağlı değildir; kullanılabilirlik değişiklik gösterebilir.</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>Beta</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>Hızlandırılmış Model Arama</value>\n    <comment>Fuzzy</comment>\n  </data>\n</root>"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.uk-UA.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>Запустити</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>Вийти</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>Зберегти</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>Скасувати</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>Мова</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>Щобзастосувати нові налаштування мови потрібен перезапуск</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>Перезапустити</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>Перезапустити пізніше</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>Потрібен перезапуск</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>Невідомий Пакунок</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>Імпортувати</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>Тип пакунку</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>Версія</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>Тип версії</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>Релізи</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>Вітки</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>Щоб імпортувати чекпоїнти, перетягніть їх сюди</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>Акцент</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>Зменшення акценту</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>Ембедінґи / Текстова інверсія</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>Мережі (LoRA / LyCORIS)</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>Коментарі</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>Показувати сітку пікселів при наближенні</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>Кроки</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>Кроки - Основні</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>Кроки - Уточнювач</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>Масштаб CFG</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>Ступінь шумозаглушення</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>Ширина</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>Висота</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Уточнювач</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>ВАЕ</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>Модель</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>Під&apos;єднатися</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>Під&apos;єднання...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>Закрити</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>Очікування підключення...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>Доступне оновлення</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>Підтримати на Patreon</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>Приєднатися до Discord-сервера</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>Завантаження</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>Встановити</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>Пропустити початкове налаштування</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>Виникла неочікувана помилка</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>Закрити програму</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>Відображуване ім’я</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>Інсталяція з цією назвою вже існує.</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>Будь ласка, оберіть іншу назву або локацію для встановлення.</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>Просунуті налаштування</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>Коміт</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>Стратегія спільної папки моделей</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>Версія PyTorch</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>Закрити після завершення</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>Папка даних</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>Тут буде розміщено дані програми (контрольні точки моделей, веб-інтерфейси тощо).</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>При роботі з дисками FAT32 або exFAT можливі помилки. Рекомендуємо обрати інший диск для більш стабільної роботи.</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>Портативний режим</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>У портативному режимі всі дані та налаштування зберігатимуться у тій же папці, що й програма. Ви зможете переміщувати програму разом із папкою «Data» в інше місце або на інший комп’ютер.</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>Продовжити</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>Попереднє зображення</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>Наступне зображення</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>Опис моделі</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>Доступна нова версія Stability Matrix!</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>Імпортувати останнє -</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>Всі версії</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>Шукати моделі, #теги чи @користувачів</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>Шукати</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>Сортування</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>Період</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>Тип моделі</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>Базова модель</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>Показувати контент 18+</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>Дані передано від CivitAI</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>Сторінка</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>Перша сторінка</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>Попередня сторінка</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>Наступна сторінка</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>Остання сторінка</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>Перейменувати</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>Видалити</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>Відкрити на CivitAI</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>Підключена модель</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>Локальна модель</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>Показати в файловому менеджері</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>Новий</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>Папка</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>Перетягніть файл сюди для імпорту</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>Імпортувати з метаданими</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>Шукати пов’язані метадані при новому локальному імпорті</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>Індексація...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>Папка з моделями</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>Категорії</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>Почнімо</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>Я прочитав(-ла) та погоджуюся з</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>Ліцензійною Угодою.</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>Знайти пов&apos;язані метадані</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>Показати зображення моделі</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>Зовнішній вигляд</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>Тема</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>Менеджер чекпоїнтів</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>Видаляти символічні посилання на спільну папку контрольних точок при завершенні роботи</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>Виберіть цю опцію, якщо у вас виникають проблеми з переміщенням Stability Matrix на інший диск</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>Скинути кеш контрольних точок</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>Перебудовує кеш встановлених контрольних точок. Використовуйте, якщо контрольні точки некоректно позначені у переглядачі моделей</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>Середовище пакету</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>Змінити</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>Змінні середовища</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>Вбудований Python</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>Перевірити версію</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>Інтеграції</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Rich Presense з Discord</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>Система</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>Додати Stability Matrix до меню запуску</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>Використовує поточне розташування програми, ви можете запустити це знову, якщо перемістите програму</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>Доступно тільки на Windows -_-</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>Додати для поточного юзера</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>Додати для всіх юзерів</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>Обрати новий каталог даних</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>Не переміщує існуючі дані. Потребує перезапуску програми.</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>Обрати каталог</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>Про програму</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>Ліцензія та відкритий код</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>Натисніть Запустити щоб почати!</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>Зупинити</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>Надіслати введення</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>Введення</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>Надіслати</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>Необхідне введення</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>Підтвердити?</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>Так</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>Ні</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>Відкрити веб-інтерфейс</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>Вітаємо у Stability Matrix!</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>Оберіть бажаний інтерфейс, щоб почати</value>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>Йде інсталляція</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>Переходимо до сторінки запуску</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>Завантаження пакунка...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>Завантаження завершене</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>Встановлення завершене</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>Інсталяція залежностей...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>Встановлюємо залежності...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>Відкрити в файловому провіднику</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>Відкрити в Finder</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>Видалити</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>Перевірити наявність оновлень</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>Оновити</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>Додати пакунок</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>Щоб почати, додайте пакунок!</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>Назва</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>Значення</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>Прибрати</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>Деталі</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>Стек викликів</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>Внутрішня помилка</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>Пошук...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>ОК</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>Повторити</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Інформація про версію Python</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>Перезапустити</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>Підтвердити видалення</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>Ця дія видалить папку пакунка разом із усіма файлами та зображеннями, які там є.</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>Видаляємо пакунок...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>Пакунок видалено</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>Не вдалося видалити деякі файли. Переконайтеся, що всі файли у папці пакету закриті, і повторіть спробу.</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>Неправильний тип пакунка</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>Оновлюємо {0}</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>Оновлення завершене</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} оновлено до останньої версії</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>Помилка під час оновлення {0}</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>Оновлення не вдалося</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>Відкрити в браузері</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>Помилка встановлення пакету</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>Гілка</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>Автоматично прокручувати до кінця консолі</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>Ліцензія</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>Обмін моделями</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>Будь ласка, оберіть папку з даними</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>Назва каталогу даних</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>Поточна директорія:</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>Програма перезапуститься після оновлення</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>Нагадати пізніше</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>Встановити зараз</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>Примітки до випуску</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>Відкрити проєкт...</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>Зберегти як...</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>Відновити макет за замовчуванням</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>Спільний вихідний каталог</value>\n  </data>\n  <data name=\"Label_BatchIndex\" xml:space=\"preserve\">\n    <value>Індекс партії</value>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>Копіювати</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>Відкрити у переглядачі зображень</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>Вибрано {0} зображень</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>Вихідний каталог</value>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>Вихідний тип</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>Очистити вибір</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>Вибрати все</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>Надіслати на обробку</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>Текст в зображення</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>Зображення в зображення</value>\n  </data>\n  <data name=\"Label_Inpainting\" xml:space=\"preserve\">\n    <value>Інпейнтінг</value>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>Апскейл</value>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>Результати</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>Обрано 1 зображення</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Пакунки Python</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>Об&apos;єднати</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>Ви впевнені?</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>Ця дія перемістить усі згенеровані зображення з вибраних пакетів до папки “Consolidated” у спільному каталозі результатів. Цю операцію неможливо скасувати.</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>Оновити</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>Оновити</value>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>Повернути попередню версію</value>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>Відкрити на GitHub</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>З&apos;єднано</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>Від&apos;єднатися</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>Email</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>Юзернейм</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>Пароль</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>Увійти</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>Створити аккаунт</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>Підтвердити пароль</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>Ключ APi</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>Аккаунти</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>Препроцесор</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>Потужність</value>\n  </data>\n  <data name=\"Label_ControlWeight\" xml:space=\"preserve\">\n    <value>Контрольна вага</value>\n  </data>\n  <data name=\"Label_ControlSteps\" xml:space=\"preserve\">\n    <value>Контрольні кроки</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>Щоб завантажити цей чекпоінт, ви повинні увійти в систему. Будь ласка, введіть ключ API CivitAI в налаштуваннях.</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>Завантаження невдале</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>Автоматичні оновлення</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>Для ранніх користувачів. Попередні збірки будуть надійнішими, ніж у каналі Dev, і виходитимуть ближче до стабільних релізів. Ваші відгуки допоможуть нам швидше знаходити помилки та вдосконалювати елементи дизайну.</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>Для технічних користувачів. Отримуйте першими доступ до наших збірок для розробників з гілок функцій, щойно вони стануть доступними. У процесі експериментів з новими функціями можуть з&apos;явитися деякі шорсткості та помилки.</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>Оновлення</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>Все оновлено</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>В останнє перевірено: {0}</value>\n  </data>\n  <data name=\"Action_CopyTriggerWords\" xml:space=\"preserve\">\n    <value>Копіювати тріґерні слова</value>\n  </data>\n  <data name=\"Label_TriggerWords\" xml:space=\"preserve\">\n    <value>Слова-тріґери:</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>Додаткові папки, такі як IPAdapters та TextualInversions (вбудовування), можна ввімкнути тут</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>Відкрити на Hugging Face</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>Оновити існуючі метадані</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>Загальні</value>\n    <comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>Генерація</value>\n    <comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>Промпт</value>\n    <comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_OutputImageFiles\" xml:space=\"preserve\">\n    <value>Вихідні файли зображень</value>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>Переглядач зображень</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>Підказки при введенні</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>Замінюйте нижні підкреслення на пробіли під час автозаповнення</value>\n  </data>\n  <data name=\"Label_PromptTags\" xml:space=\"preserve\">\n    <value>Теги підказки</value>\n    <comment>Tags for image generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTagsImport\" xml:space=\"preserve\">\n    <value>Імпортувати теги підказки</value>\n  </data>\n  <data name=\"Label_PromptTagsDescription\" xml:space=\"preserve\">\n    <value>Помічає файл для підказок при автодоповненні промпта (підтримується формат a1111-sd-webui-tagcomplete .csv)</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>Інформація про систему</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>Аддони</value>\n    <comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>Зберегти проміжне зображення</value>\n    <comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>Налаштування</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>Обрати файл</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>Замінити вміст</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>Поки що недоступно</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>Функція буде доступна в наступному оновленні</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>Відсутній файл зображення</value>\n  </data>\n  <data name=\"Label_HolidayMode\" xml:space=\"preserve\">\n    <value>Різдвяний режим</value>\n  </data>\n  <data name=\"Label_CLIPSkip\" xml:space=\"preserve\">\n    <value>Пропуск CLIP</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>Зображення в відео</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>Кадри в секунду</value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>Min CFG</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>Без втрат</value>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>Рамки</value>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>Motion Bucket ID</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>Рівень аугментації</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>Метод</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>Якість</value>\n  </data>\n  <data name=\"Label_FindInModelBrowser\" xml:space=\"preserve\">\n    <value>Знайти в браузері моделей</value>\n  </data>\n  <data name=\"Label_Installed\" xml:space=\"preserve\">\n    <value>Встановлено</value>\n  </data>\n  <data name=\"Label_NoExtensionsFound\" xml:space=\"preserve\">\n    <value>Розширень не знайдено.</value>\n  </data>\n  <data name=\"Action_Hide\" xml:space=\"preserve\">\n    <value>Приховати</value>\n  </data>\n  <data name=\"Action_CopyDetails\" xml:space=\"preserve\">\n    <value>Копіювати деталі</value>\n  </data>\n  <data name=\"Action_Download\" xml:space=\"preserve\">\n    <value>Завантажити</value>\n  </data>\n  <data name=\"TeachingTip_DownloadsExplanation\" xml:space=\"preserve\">\n    <value>Перевіряйте прогргес завантажень тут.</value>\n  </data>\n  <data name=\"Label_RecommendedModels\" xml:space=\"preserve\">\n    <value>Рекомендовані моделі</value>\n  </data>\n  <data name=\"Label_RecommendedModelsSubText\" xml:space=\"preserve\">\n    <value>Поки ваш пакунок встановлюється, ось деякі моделі, які ми рекомендуємо для початку.</value>\n  </data>\n  <data name=\"Label_Notifications\" xml:space=\"preserve\">\n    <value>Сповіщення</value>\n  </data>\n  <data name=\"Label_NotificationOption_None\" xml:space=\"preserve\">\n    <value>Жодних</value>\n  </data>\n  <data name=\"Label_ComfyRequiredTitle\" xml:space=\"preserve\">\n    <value>Потрібен ComfyUA</value>\n  </data>\n  <data name=\"Label_ComfyRequiredDetail\" xml:space=\"preserve\">\n    <value>Щоб встовити цей пакунок потрібен ComfyUI. Хочете встановити зараз?</value>\n  </data>\n  <data name=\"Error_PleaseSelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Будь ласка, оберіть директорію для завантаження.</value>\n  </data>\n  <data name=\"Label_SelectDownloadLocation\" xml:space=\"preserve\">\n    <value>Оберіть директорію для завантаження:</value>\n  </data>\n  <data name=\"Label_Config\" xml:space=\"preserve\">\n    <value>Конфіґ</value>\n  </data>\n  <data name=\"Label_ToggleAutoScrolling\" xml:space=\"preserve\">\n    <value>Автоматично прокручувати до кінця</value>\n  </data>\n  <data name=\"Label_ConfirmExit\" xml:space=\"preserve\">\n    <value>Підтвердити вихід</value>\n  </data>\n  <data name=\"Label_ConfirmExitDetail\" xml:space=\"preserve\">\n    <value>Ви впевнені, що хочете вийти? Це також закриє всі запущені пакети.</value>\n  </data>\n  <data name=\"Label_Console\" xml:space=\"preserve\">\n    <value>Консоль</value>\n  </data>\n  <data name=\"Label_WebUi\" xml:space=\"preserve\">\n    <value>Веб-інтерфейс</value>\n  </data>\n  <data name=\"Label_Packages\" xml:space=\"preserve\">\n    <value>Пакунки</value>\n  </data>\n  <data name=\"Label_ActionCannotBeUndone\" xml:space=\"preserve\">\n    <value>Цю дію не можна скасувати.</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteImages\" xml:space=\"preserve\">\n    <value>Ви впевнені, що хочете видалити {0} зображень?</value>\n  </data>\n  <data name=\"Label_CheckingHardware\" xml:space=\"preserve\">\n    <value>Ми перевіряємо деякі специфікації обладнання, щоб визначити сумісність.</value>\n  </data>\n  <data name=\"Label_EverythingLooksGood\" xml:space=\"preserve\">\n    <value>Все виглядає чудово!</value>\n  </data>\n  <data name=\"Label_NvidiaGpuRecommended\" xml:space=\"preserve\">\n    <value>Ми рекомендуємо використовувати відеокарту з підтримкою CUDA для найкращої роботи. Ви можете продовжувати без неї, але деякі пакети можуть не працювати, а генерація може бути повільнішим.</value>\n  </data>\n  <data name=\"Label_Checkpoints\" xml:space=\"preserve\">\n    <value>Чекпоїнти</value>\n  </data>\n  <data name=\"Label_ModelBrowser\" xml:space=\"preserve\">\n    <value>Браузер моделей</value>\n  </data>\n  <data name=\"Label_Workflows\" xml:space=\"preserve\">\n    <value>Воркфлоу</value>\n  </data>\n  <data name=\"Label_InfiniteScrolling\" xml:space=\"preserve\">\n    <value>Нескінченна прокрутка</value>\n  </data>\n  <data name=\"Label_WorkflowBrowser\" xml:space=\"preserve\">\n    <value>Браузер воркфлоу</value>\n  </data>\n  <data name=\"Action_OpenOnOpenArt\" xml:space=\"preserve\">\n    <value>Відкрити на OpenArt</value>\n  </data>\n  <data name=\"Label_NodeDetails\" xml:space=\"preserve\">\n    <value>Деталі вузла</value>\n  </data>\n  <data name=\"Label_WorkflowDescription\" xml:space=\"preserve\">\n    <value>Опис робочого процесу</value>\n  </data>\n  <data name=\"Label_OpenArtBrowser\" xml:space=\"preserve\">\n    <value>OpenArt Browser</value>\n  </data>\n  <data name=\"Action_PreviewPreprocessor\" xml:space=\"preserve\">\n    <value>Попередній перегляд Препроцесор</value>\n  </data>\n  <data name=\"TeachingTip_WebUiButtonMoved\" xml:space=\"preserve\">\n    <value>Кнопка &quot;Відкрити веб-інтерфейс&quot; перемістилася в командну панель</value>\n  </data>\n  <data name=\"Label_AnotherInstanceAlreadyRunning\" xml:space=\"preserve\">\n    <value>Інший екземпляр Stability Matrix вже запущено. Будь ласка, закрийте його, перш ніж запускати новий.</value>\n  </data>\n  <data name=\"Label_StabilityMatrixAlreadyRunning\" xml:space=\"preserve\">\n    <value>Матриця стабільності вже працює</value>\n  </data>\n  <data name=\"Label_WorkflowDeletedSuccessfully\" xml:space=\"preserve\">\n    <value>{0} видалено успішно</value>\n  </data>\n  <data name=\"Label_WorkflowDeleted\" xml:space=\"preserve\">\n    <value>Робочий процес Видалено</value>\n  </data>\n  <data name=\"Label_ErrorRetrievingWorkflows\" xml:space=\"preserve\">\n    <value>Виправлення помилок у робочих процесах</value>\n  </data>\n  <data name=\"TabLabel_InstalledWorkflows\" xml:space=\"preserve\">\n    <value>Встановлені робочі процеси</value>\n  </data>\n  <data name=\"Label_WorkflowImported\" xml:space=\"preserve\">\n    <value>Імпортований робочий процес</value>\n  </data>\n  <data name=\"Label_FinishedImportingWorkflow\" xml:space=\"preserve\">\n    <value>Завершено імпорт робочих процесів та користувацьких вузлів</value>\n  </data>\n  <data name=\"Label_WorkflowImportComplete\" xml:space=\"preserve\">\n    <value>Робочий процес і кастомні вузли були імпортовані.</value>\n  </data>\n  <data name=\"TeachingTip_InferencePromptHelpButton\" xml:space=\"preserve\">\n    <value>Натисніть тут, щоб переглянути синтаксис підказки і те, як включити Lora / Embeddings.</value>\n  </data>\n  <data name=\"Label_ExtraNetworks\" xml:space=\"preserve\">\n    <value>Додаткові мережі (Lora / LyCORIS)</value>\n  </data>\n  <data name=\"Label_CLIPStrength\" xml:space=\"preserve\">\n    <value>Сила CLIP</value>\n  </data>\n  <data name=\"Label_NumberFormat\" xml:space=\"preserve\">\n    <value>Формат чисел</value>\n  </data>\n  <data name=\"Text_DeleteFollowingItems\" xml:space=\"preserve\">\n    <value>Ви збираєтеся видалити наступні елементи:</value>\n  </data>\n  <data name=\"TextTemplate_DeleteFollowingCountItems\" xml:space=\"preserve\">\n    <value>Ви збираєтеся видалити наступні {0} елементів:</value>\n  </data>\n  <data name=\"Label_DeletePermanently\" xml:space=\"preserve\">\n    <value>Видалити назавжди</value>\n  </data>\n  <data name=\"Action_MoveToTrash\" xml:space=\"preserve\">\n    <value>Перемістити в кошик</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteModels\" xml:space=\"preserve\">\n    <value>Ви впевнені, що хочете видалити стільки моделей: {0}?</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad\" xml:space=\"preserve\">\n    <value>Автоматичний пошук при завантаженні</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad_Description\" xml:space=\"preserve\">\n    <value>Автоматично запускати пошук при завантаженні сторінки браузера моделі</value>\n  </data>\n  <data name=\"Action_ToggleVisibility\" xml:space=\"preserve\">\n    <value>Переключити видимість</value>\n  </data>\n  <data name=\"Label_ClippingMask\" xml:space=\"preserve\">\n    <value>Маска для обрізання</value>\n  </data>\n  <data name=\"Label_AppFolders\" xml:space=\"preserve\">\n    <value>Папки додатків</value>\n  </data>\n  <data name=\"Label_Logs\" xml:space=\"preserve\">\n    <value>Логи</value>\n  </data>\n  <data name=\"Label_AppData\" xml:space=\"preserve\">\n    <value>Дані програми</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToSelected\" xml:space=\"preserve\">\n    <value>{0} оновлено до вибраної версії</value>\n  </data>\n  <data name=\"Action_CopyAsBitmap\" xml:space=\"preserve\">\n    <value>Копіювати як растрове зображення</value>\n  </data>\n  <data name=\"Label_DisableUpdateCheck\" xml:space=\"preserve\">\n    <value>Вимкнути перевірку оновлень</value>\n  </data>\n  <data name=\"Warning_PleaseExtractFirst\" xml:space=\"preserve\">\n    <value>Будь ласка, розпакуйте додаток з ZIP-архіву перед запуском Stability Matrix</value>\n  </data>\n  <data name=\"Label_HistorySize\" xml:space=\"preserve\">\n    <value>Розмір історії</value>\n  </data>\n  <data name=\"Label_HistorySize_Description\" xml:space=\"preserve\">\n    <value>Кількість рядків над тими, що відображаються в консолі, до яких можна прокрутити назад</value>\n  </data>\n  <data name=\"Label_ConnectAccountFailed\" xml:space=\"preserve\">\n    <value>У нас виникли проблеми з підключенням вашого облікового запису</value>\n  </data>\n  <data name=\"Label_EditModelMetadata\" xml:space=\"preserve\">\n    <value>Редагування метаданих моделі</value>\n  </data>\n  <data name=\"Label_NSFW\" xml:space=\"preserve\">\n    <value>NSFW</value>\n  </data>\n  <data name=\"Label_Tags\" xml:space=\"preserve\">\n    <value>Теги</value>\n  </data>\n  <data name=\"Label_VersionName\" xml:space=\"preserve\">\n    <value>Назва версії</value>\n  </data>\n  <data name=\"Label_TrainedWords\" xml:space=\"preserve\">\n    <value>Треновані слова</value>\n  </data>\n  <data name=\"Label_PreviewImage\" xml:space=\"preserve\">\n    <value>Попередній перегляд зображення</value>\n  </data>\n  <data name=\"Label_BatchSize\" xml:space=\"preserve\">\n    <value>Розмір партії</value>\n  </data>\n  <data name=\"Label_Batches\" xml:space=\"preserve\">\n    <value>Партії</value>\n  </data>\n  <data name=\"Label_Sampler\" xml:space=\"preserve\">\n    <value>Семплер</value>\n  </data>\n  <data name=\"Label_Scheduler\" xml:space=\"preserve\">\n    <value>Планувальник</value>\n  </data>\n  <data name=\"Label_MaxSize\" xml:space=\"preserve\">\n    <value>Максимальний розмір</value>\n  </data>\n  <data name=\"Label_UseSeparatePrompt\" xml:space=\"preserve\">\n    <value>Використовувати окремий промпт</value>\n  </data>\n  <data name=\"Label_Seed\" xml:space=\"preserve\">\n    <value>Зернятко</value>\n  </data>\n  <data name=\"Label_NegativePrompt\" xml:space=\"preserve\">\n    <value>Негативний промпт</value>\n  </data>\n  <data name=\"Label_NewFolder\" xml:space=\"preserve\">\n    <value>Нова папка</value>\n  </data>\n  <data name=\"Label_CopyLinkToClipboard\" xml:space=\"preserve\">\n    <value>Копіювати посилання в буфер обміну</value>\n  </data>\n  <data name=\"TextTemplate_OAuthLoginTitle\" xml:space=\"preserve\">\n    <value>Увійдіть через {0}</value>\n    <comment>e.g. &apos;Sign in with Google&apos;</comment>\n  </data>\n  <data name=\"Text_AllowBrowserOpenAppLink\" xml:space=\"preserve\">\n    <value>Будь ласка, дозвольте вашому браузеру відкрити цей додаток, коли з&apos;явиться запит на продовження.</value>\n  </data>\n  <data name=\"Text_OAuthLoginDescription\" xml:space=\"preserve\">\n    <value>Відкрийте посилання у вашому браузері та дотримуйтесь інструкцій, щоб підключити свій обліковий запис.</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Title\" xml:space=\"preserve\">\n    <value>Перевизначення залежностей Python</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Description\" xml:space=\"preserve\">\n    <value>Додавання, заміна або видалення залежностей для встановлення та оновлення</value>\n  </data>\n  <data name=\"Label_DependencySpecifiers\" xml:space=\"preserve\">\n    <value>Специфікатори залежностей</value>\n  </data>\n  <data name=\"AnalyticsExample_InstallData\" xml:space=\"preserve\">\n    <value>{\n  &quot;packageName&quot;: &quot;stable-diffusion-webui&quot;,\n  &quot;packageVersion&quot;: &quot;v1.10.0&quot;,\n  &quot;isSuccess&quot;: true,\n  &quot;type&quot;: &quot;install&quot;,\n  &quot;timestamp&quot;: &quot;2024-09-04T02:14:04.1967404+00:00&quot;\n}</value>\n  </data>\n  <data name=\"Label_Analytics\" xml:space=\"preserve\">\n    <value>Аналітика</value>\n  </data>\n  <data name=\"TextTemplate_YouCanChangeThisBehavior\" xml:space=\"preserve\">\n    <value>Ви завжди можете змінити цю поведінку в {0}.</value>\n    <comment>e.g. &apos;You can always change this behavior in Settings &gt; Category &gt; Item.&apos;</comment>\n  </data>\n  <data name=\"Text_AnalyticsDescription\" xml:space=\"preserve\">\n    <value>Допоможіть нам покращити Stability Matrix, надіславши анонімні дані про використовувані функції, версії операційної системи, типи встановлених пакунків тощо. Надіслані дані ніколи не будуть пов&apos;язані з вами або вашим обліковим записом і не міститимуть особистих даних або будь-якої конфіденційної інформації.</value>\n  </data>\n  <data name=\"Text_AnalyticsDescriptionShort\" xml:space=\"preserve\">\n    <value>Допоможіть нам покращити Stability Matrix, надіславши анонімні дані про використовувані функції, версії операційної системи, типи встановлених пакунків тощо.</value>\n  </data>\n  <data name=\"Text_AnalyticsDataPrivacyInfo\" xml:space=\"preserve\">\n    <value>Надіслані дані ніколи не будуть пов&apos;язані з вами або вашим обліковим записом і не будуть містити особистих даних або будь-якої конфіденційної інформації.</value>\n  </data>\n  <data name=\"Label_UsageData\" xml:space=\"preserve\">\n    <value>Дані про використання</value>\n  </data>\n  <data name=\"Label_PrivacyPolicy\" xml:space=\"preserve\">\n    <value>Політика конфіденційності</value>\n  </data>\n  <data name=\"Label_ImageHidden\" xml:space=\"preserve\">\n    <value>Приховане зображення</value>\n  </data>\n  <data name=\"Label_NoImageFound\" xml:space=\"preserve\">\n    <value>Зображення не знайдено</value>\n  </data>\n  <data name=\"Label_HideEmptyCategories\" xml:space=\"preserve\">\n    <value>Приховати порожні категорії</value>\n  </data>\n  <data name=\"Label_ShowNsfwImages\" xml:space=\"preserve\">\n    <value>Показати зображення NSFW</value>\n  </data>\n  <data name=\"Label_EnableLongPaths\" xml:space=\"preserve\">\n    <value>Увімкнути довгі шляхи</value>\n    <comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_EnableLongPathsDescription\" xml:space=\"preserve\">\n    <value>Видалити обмеження MAX_PATH зі звичайних функцій файлів і каталогів Win32</value>\n    <comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_SystemSettings\" xml:space=\"preserve\">\n    <value>Налаштування системи</value>\n  </data>\n  <data name=\"Label_ChangesApplied\" xml:space=\"preserve\">\n    <value>Застосовані зміни</value>\n  </data>\n  <data name=\"Text_RestartMayBeRequiredForSystemChanges\" xml:space=\"preserve\">\n    <value>Для того, щоб зміни в системі набули чинності, може знадобитися перезавантаження.</value>\n  </data>\n  <data name=\"Label_ReleasesUnavailableForThisPackage\" xml:space=\"preserve\">\n    <value>Випуски для цього пакунка недоступні.</value>\n  </data>\n  <data name=\"Text_UnexpectedError_Description\" xml:space=\"preserve\">\n    <value>Будь ласка, повідомте нам про цю проблему, вказавши деталі нижче, і прикріпіть заархівовані файли журналів.</value>\n  </data>\n  <data name=\"Text_UnexpectedErrorRecoverable_Description\" xml:space=\"preserve\">\n    <value>Ви можете продовжити роботу, але повна функціональність буде доступна після перезапуску. Будь ласка, повідомте нам про цю проблему, вказавши деталі нижче, і прикріпіть заархівовані файли журналів.</value>\n  </data>\n  <data name=\"Action_ShowLogInExplorer\" xml:space=\"preserve\">\n    <value>Показати лог у файловому провіднику</value>\n  </data>\n  <data name=\"Action_ShowLogInFinder\" xml:space=\"preserve\">\n    <value>Показати лог у Finder</value>\n  </data>\n  <data name=\"Label_ExtensionPacks\" xml:space=\"preserve\">\n    <value>Пакети розширення</value>\n  </data>\n  <data name=\"Label_NoExtensionPacksFound\" xml:space=\"preserve\">\n    <value>Розширення не знайдено</value>\n  </data>\n  <data name=\"Label_OpenExtensionPacksFolder\" xml:space=\"preserve\">\n    <value>Відкрити папку з пакетами розширень</value>\n  </data>\n  <data name=\"Label_InstallExtensionPack\" xml:space=\"preserve\">\n    <value>Інсталяція пакета розширень</value>\n  </data>\n  <data name=\"Label_AddToExistingPack\" xml:space=\"preserve\">\n    <value>Додати до існуючого пакету</value>\n  </data>\n  <data name=\"Label_NewExtensionPack\" xml:space=\"preserve\">\n    <value>Новий пакет розширень</value>\n  </data>\n  <data name=\"Label_CreateExtensionPackExplanation\" xml:space=\"preserve\">\n    <value>вЩоб створити його, просто виберіть потрібні розширення у вкладці &quot;Доступні розширення&quot; або &quot;Встановлені розширення&quot; та натисніть &quot;Зберегти&quot;</value>\n  </data>\n  <data name=\"Label_ImportExtensionPacksExplanation\" xml:space=\"preserve\">\n    <value>Додайте файл пакету розширень .json до теки ExtensionPacks у вашому каталозі Data.</value>\n  </data>\n  <data name=\"Label_OrWithHyphensAround\" xml:space=\"preserve\">\n    <value>- або -</value>\n  </data>\n  <data name=\"Action_OpenOnOpenModelDb\" xml:space=\"preserve\">\n    <value>Відкрити на OpenModelDB</value>\n  </data>\n  <data name=\"Label_Wildcards\" xml:space=\"preserve\">\n    <value>Підстановочні знаки</value>\n  </data>\n  <data name=\"Text_OAuthDeviceAuthDescription\" xml:space=\"preserve\">\n    <value>Відкрийте посилання у вашому браузері та введіть наступний код для авторизації вашого облікового запису в Stability Matrix.</value>\n  </data>\n  <data name=\"Action_CopyAndOpen\" xml:space=\"preserve\">\n    <value>Скопіювати та відкрити</value>\n  </data>\n  <data name=\"Text_LykosAccountUpgradeNotice\" xml:space=\"preserve\">\n    <value>## Увага: вихід з облікового запису Lykos та оновлення системи безпеки\n\nМи внесли деякі важливі покращення в те, як Stability Matrix працює з акаунтами Lykos, оновивши їх до більш безпечної та зручної системи входу **(OAuth 2.0 з OpenID Connect)**. У зв&apos;язку з цим ви вийшли зі свого облікового запису Lykos.\n\n### Чому відбулися зміни?\n\nВаша безпека і конфіденційність важливі для нас. Це оновлення принесе:\n\n*   **Спрощений досвід:** Увійдіть один раз на [account.lykos.ai](https://account.lykos.ai), щоб підключитися до Stability Matrix та інших сервісів Lykos AI.\n*   **Більше способів входу:** Використовуйте свій існуючий обліковий запис [lykos.ai](https://lykos.ai) або увійдіть за допомогою **Apple**, **GitHub** або **Google**.\n*   **Покращена конфіденційність:** Stability Matrix запитує лише необхідні дозволи.\n*   **Безпека за галузевим стандартом:** Ми використовуємо OAuth 2.0, золотий стандарт для безпечних входів.\n*   **Готовність до майбутнього:** Це забезпечує безпечне з&apos;єднання з іншими програмами та службами.\n\n### Що мені потрібно зробити?\n\nНатисніть кнопку **&quot;Перейти до налаштувань &quot;**, потім натисніть **&quot;Підключитися &quot;** поруч з **&quot;Обліковим записом Lykos &quot;**.\n\n### Чи потрібен обліковий запис Lykos?\n\nНі! Stability Matrix повноцінно функціонує і без нього. Але ваш обліковий запис Lykos дозволяє використовувати деякі додаткові підключені функції, такі як автоматичне оновлення збірок розробки для наших підписників Patreon (і багато іншого!).\n</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>Перейти до Налаштувань</value>\n  </data>\n  <data name=\"TeachingTip_PromptAmplifier\" xml:space=\"preserve\">\n    <value>Спробуйте новий підсилювач промпту! Покращуйте свої промпти для кращих результатів!</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>Увімкнути</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>Вимкнути</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>Підключіть свій обліковий запис Lykos</value>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>Увійдіть у свій обліковий запис Lykos, щоб користуватися функціями підключення.</value>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>Будь ласка, увійдіть ще раз</value>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>Ваш логін закінчився. Будь ласка, увійдіть знову, щоб продовжити.</value>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>Підтримка Stability Matrix</value>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>Дякуємо, що підтримуєте Stability Matrix!</value>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>Такі функції, як **{0}**, є однією з багатьох переваг, доступних для наших донатерів. Ваш внесок допомагає нам покривати витрати на сервер і підтримує розвиток Stability Matrix.</value>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>Такі функції, як **{0}**, доступні на рівні **{1}** (або вище). Ваш внесок допомагає нам покривати витрати на сервер для більш просунутих підключених функцій і дозволяє нам продовжувати покращувати Stability Matrix для всіх.</value>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>Якщо ви вже підтримуєте нас на Patreon, будь ласка, прив&apos;яжіть свій акаунт, щоб продовжити.</value>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>Налаштування облікового запису</value>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>Переглянути параметри підтримки</value>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>Може, пізніше</value>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>Статус</value>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>Активний</value>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>Неактивний (Використовує стандартне з&apos;єднання)</value>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>Скористайтеся швидшими результатами пошуку, переглядаючи моделі з онлайн-репозиторіїв, таких як CivitAI.</value>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>Експериментальна оптимізація для сторонніх репозиторіїв. Офіційно не пов&apos;язана з нами; доступність може змінюватися.</value>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>Бета</value>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>Прискорене відкриття моделі</value>\n  </data>\n  <data name=\"PromptAmplifier_Disclaimer\" xml:space=\"preserve\">\n    <value>### Представляємо: Підсилювач реплік\nНаш помічник зі штучним інтелектом, заснований на експериментальній моделі Spark, генерує творчі варіанти ваших підказок.\n\nPrompt Amplifier працює в нашому захищеному хмарному середовищі корпоративного рівня - він не працює локально на вашому комп&apos;ютері.\n\n### ☁️️ Чому хмарне середовище?  \nМодель Spark працює в масштабі, порівнянному з фундаментальними моделями з трильйонами параметрів, що вимагає значних обчислювальних потужностей. Хоча ми прагнемо максимізувати можливості локального запуску, розширені можливості Spark доступні **тепер** через нашу хмарну інфраструктуру.\n\n### Конфіденційність понад усе\nМи ставимо на перше місце вашу конфіденційність ([Умови Gen AI] (&lt;https://lykos.ai/gen-ai-terms&gt;)). **Ваші підказки/вихідні дані НІКОЛИ не використовуються для навчання ШІ компанією Lykos AI або нашими партнерами з хмарної інфраструктури.** Безпечна обробка відбувається виключно для генерації вашого посилення, після чого ми зберігаємо лише метадані (наприклад, часові мітки та кількість токенів), а не сам вміст підказок.** Ваші дані ніколи не продаються та не передаються іншим особам.</value>\n  </data>\n  <data name=\"Label_ShowUnsupportedPythonVersions\" xml:space=\"preserve\">\n    <value>Показати непідтримувані версії Python</value>\n  </data>\n  <data name=\"Label_UnsupportedPythonVersionDetails\" xml:space=\"preserve\">\n    <value>При використанні непідтримуваних версій Python можуть виникнути проблеми з деякими пакунками</value>\n  </data>\n  <data name=\"Label_UnsupportedPythonVersionWarningDescription\" xml:space=\"preserve\">\n    <value>Буде показано всі доступні версії Python, включно з тими, які не підтримуються Stability Matrix. Ви впевнені?</value>\n  </data>\n  <data name=\"Label_PythonVersionWarningTitle\" xml:space=\"preserve\">\n    <value>Непідтримувані версії Python</value>\n  </data>\n  <data name=\"Label_HuggingFaceLoginRequired\" xml:space=\"preserve\">\n    <value>Ви повинні увійти, щоб завантажити цей чекпоїнт. Будь ласка, додайте токен Hugging Face в налаштуваннях.</value>\n  </data>\n  <data name=\"Label_LoginRequired\" xml:space=\"preserve\">\n    <value>Для завантаження цієї моделі потрібен логін</value>\n  </data>\n  <data name=\"Watermark_EnterPackageName\" xml:space=\"preserve\">\n    <value>Введіть назву пакета</value>\n  </data>\n  <data name=\"Validation_PackageNameCannotBeEmpty\" xml:space=\"preserve\">\n    <value>Назва пакунка не може бути порожньою</value>\n  </data>\n  <data name=\"Description_RenamePackage\" xml:space=\"preserve\">\n    <value>Введіть нове ім&apos;я для &apos;{0}&apos;</value>\n  </data>\n  <data name=\"ValidationError_PackageExists\" xml:space=\"preserve\">\n    <value>Пакунок з назвою &apos;{0}&apos; вже існує</value>\n  </data>\n  <data name=\"Label_BulkDownloadStarted\" xml:space=\"preserve\">\n    <value>Розпочато масове завантаження</value>\n  </data>\n  <data name=\"Label_BulkDownloadStartedMessage\" xml:space=\"preserve\">\n    <value>{0} файли почали завантажуватися. Перевірте прогрес на вкладці Завантаження.</value>\n  </data>\n  <data name=\"Label_DownloadStarted\" xml:space=\"preserve\">\n    <value>Завантаження розпочато</value>\n  </data>\n  <data name=\"Label_DownloadWillBeSavedToLocation\" xml:space=\"preserve\">\n    <value>{0} буде збережено в {1}</value>\n  </data>\n  <data name=\"Label_Author\" xml:space=\"preserve\">\n    <value>Автор</value>\n  </data>\n  <data name=\"Label_Hash\" xml:space=\"preserve\">\n    <value>Хеш</value>\n  </data>\n  <data name=\"Label_LastUpdatedAt\" xml:space=\"preserve\">\n    <value>Останнє оновлення</value>\n  </data>\n  <data name=\"Label_FileNamePattern\" xml:space=\"preserve\">\n    <value>Шаблон імен файлів</value>\n  </data>\n  <data name=\"Label_Files\" xml:space=\"preserve\">\n    <value>Файли</value>\n  </data>\n  <data name=\"Label_InferenceDefaultsHeader\" xml:space=\"preserve\">\n    <value>Висновок за замовчуванням</value>\n  </data>\n  <data name=\"Tooltip_InferenceDefaults\" xml:space=\"preserve\">\n    <value>Якщо увімкнено, ці налаштування будуть застосовані автоматично, коли цю модель буде обрано на вкладці Висновки</value>\n  </data>\n  <data name=\"Label_Show\" xml:space=\"preserve\">\n    <value>Показати</value>\n  </data>\n  <data name=\"Label_EarlyAccessModels\" xml:space=\"preserve\">\n    <value>Моделі раннього доступу</value>\n  </data>\n  <data name=\"Label_NsfwContent\" xml:space=\"preserve\">\n    <value>Контент 18+</value>\n  </data>\n  <data name=\"Label_NonModelFiles\" xml:space=\"preserve\">\n    <value>Файли, що не є моделями</value>\n  </data>\n  <data name=\"Label_InstalledModels\" xml:space=\"preserve\">\n    <value>Встановлені моделі</value>\n  </data>\n  <data name=\"Label_DownloadAllFilesAllVersions\" xml:space=\"preserve\">\n    <value>Завантажити всі файли (всі версії)</value>\n  </data>\n  <data name=\"Label_View\" xml:space=\"preserve\">\n    <value>Вигляд</value>\n  </data>\n  <data name=\"Label_Filter\" xml:space=\"preserve\">\n    <value>Фільтр</value>\n  </data>\n</root>"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.zh-Hans.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>启动</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>退出</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>保存</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>取消</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>语言</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>需要重新启动才能使新语言选项生效</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>重新启动</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>稍后重新启动</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>需要重新启动</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>未知包</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>导入</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>包类型</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>版本</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>版本类型</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>发布</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>分支</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>将模型拖放到此处进行导入</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>加权</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>减权</value>\n    <comment>I am not sure about this, need to know what background are we talking about</comment>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>嵌入 / 文本倒置</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>网络（Lora / LyCORIS）</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>评论</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>在高缩放级别下显示像素网格</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>迭代步数</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>迭代步数 - Base</value>\n    <comment>base and refiner are words that closely relates to the model SDXL (base model and refiner model appears in the checkpoint&apos;s file）so I think it will be ok the keep it the original text. Or, the translation for base can be &quot;基础模型&quot;(which means base model to be more precise)，and the refiner will be &quot;精炼模型&quot; (which means refiner model to be more precise)</comment>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>迭代步数 - Refiner</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>提示词引导系数</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>重绘幅度</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>宽度</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>高度</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>精炼</value>\n    <comment>need to know where does this appear in the ai</comment>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>模型</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>连接</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>连接中...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>关闭</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>等待连接...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>可更新</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>成为赞助商</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>加入 Discord 服务器</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>下载</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>安装</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>跳过首次设置</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>发生意外错误</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>退出程序</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>显示名称</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>已存在使用此名称的安装。</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>请选择不同的名称或不同的安装位置。</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>高级选项</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>提交</value>\n    <comment>are we talking about submit something? if there was the case, then &quot;提交&quot; seems proper</comment>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>共享模型文件夹方案</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>PyTorch版本</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>完成后关闭对话框</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>数据目录</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>这里将安装模型，LORA，Web UIs，设置等等。</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>使用 FAT32 或 exFAT 硬盘时可能会遭遇错误。请选择不同的硬盘，以获得更流畅的体验。</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>便携模式</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>在便携模式下，所有数据和设置都将存储在与应用程序相同的目录中。您可以将应用程序及其 &quot;Data &quot;文件夹移动到不同的位置或电脑上。</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>继续</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>上一张图片</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>下一张图片</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>模型描述</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>新版 Stability Matrix 已发布！</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>导入最新版本</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>所有版本</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>搜索模型、#标签或@用户</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>搜索</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>分类</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>期间</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>模型类型</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>基础模型</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>显示 NSFW 内容</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>数据由 CivitAI 提供</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>页次</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>第一页</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>上一页</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>下一页</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>最后一页</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>重新命名</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>删除</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>在 CivitAI 上打开</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>已连接 CivitAI 元数据</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>本地模型</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>在资源管理器中显示</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>新</value>\n    <comment>not sure where this word appears, need more information</comment>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>文件夹</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>将文件放入此处以导入</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>导入时获取元数据</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>搜索本地新导入模型的元数据</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>正在索引...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>模型文件夹</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>类别</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>让我们开始吧</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>我已阅读并同意</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>许可协议。</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>查找连接的元数据</value>\n    <comment>not sure where it appears, need more information</comment>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>显示模型图片</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>外观</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>主题</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>模型管理器</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>关闭时删除共享模型目录的符号链接</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>如果您在将 Stability Matrix 移至另一个驱动器时遇到问题，请选择此选项</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>重置模型缓存</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>重建已安装模型的缓存。如果模型在模型浏览器中被错误标注，则使用此功能</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>软件包环境</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>编辑</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>环境变量</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>嵌入式 Python</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>检查版本</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>集成</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Discord Rich Presence</value>\n    <comment>this seems to be a product name in discord and i can not find a good translation for it</comment>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>系统</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>将 Stability Matrix 添加到开始菜单</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>使用当前应用程序的位置，如果应用程序移动了，可以再次运行此功能</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>仅适用于Windows</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>为当前用户添加</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>为所有用户添加</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>选择新数据目录</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>不移动现有数据</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>选择目录</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>关于</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n    <comment>I think this is the product, and should not directly translate to Chinese right?</comment>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>许可证和开放源代码声明</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>点击 &quot;启动 &quot;开始！</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>停止</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>发送输入</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>输入</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>发送</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>需要输入</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>确认？</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>是</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>否</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>打开 Web UI</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>欢迎使用 Stability Matrix！</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>选择您喜欢的交互界面，然后点击安装开始使用</value>\n    <comment>Fuzzy</comment>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>正在安装</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>正在进入启动页面</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>正在下载软件包...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>下载完毕</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>安装完毕</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>正在安装文件依赖...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>正在安装软件包依赖...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>在资源管理器中打开</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>在 Finder 中打开</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>卸载</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>检查更新</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>更新</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>添加软件包</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>添加软件包即可开始使用！</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>名称</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>值</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>移除</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>详细信息</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>调用栈</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>内部异常</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>搜索...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>好</value>\n    <comment>need to know more background</comment>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>重试</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Python版本信息</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>重新启动</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>确认删除</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>操作将会删除软件包文件夹及其所有的内容，包括任何已生成的图像和您可能已添加的文件。</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>软件包卸载中...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>软件包已卸载</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>某些文件无法被删除。请关闭软件包目录中所有打开的文件，然后重试。</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>无效软件包类型</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>正在更新 {0}</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>更新完毕</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} 已更新至最新版本</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>更新出错 {0}</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>更新失败</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>在浏览器中打开</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>安装软件包出错</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>分支</value>\n  </data>\n  <data name=\"Label_AutoScrollToEnd\" xml:space=\"preserve\">\n    <value>自动滚动到控制台输出的底部。</value>\n  </data>\n  <data name=\"Label_License\" xml:space=\"preserve\">\n    <value>许可协议</value>\n  </data>\n  <data name=\"Label_SharedModelStrategyShort\" xml:space=\"preserve\">\n    <value>模型共享</value>\n  </data>\n  <data name=\"Label_PleaseSelectDataDirectory\" xml:space=\"preserve\">\n    <value>请选择软件数据存放目录</value>\n  </data>\n  <data name=\"Label_DataFolderName\" xml:space=\"preserve\">\n    <value>数据文件夹名称</value>\n  </data>\n  <data name=\"Label_CurrentDirectory\" xml:space=\"preserve\">\n    <value>当前目录：</value>\n  </data>\n  <data name=\"Text_AppWillRelaunchAfterUpdate\" xml:space=\"preserve\">\n    <value>应用将在更新后重新启动</value>\n  </data>\n  <data name=\"Action_RemindMeLater\" xml:space=\"preserve\">\n    <value>稍后再提醒</value>\n  </data>\n  <data name=\"Action_InstallNow\" xml:space=\"preserve\">\n    <value>立即安装</value>\n  </data>\n  <data name=\"Label_ReleaseNotes\" xml:space=\"preserve\">\n    <value>发布说明</value>\n  </data>\n  <data name=\"Action_OpenProjectEllipsis\" xml:space=\"preserve\">\n    <value>打开项目...</value>\n  </data>\n  <data name=\"Action_SaveAsEllipsis\" xml:space=\"preserve\">\n    <value>另存为...</value>\n  </data>\n  <data name=\"Action_RestoreDefaultLayout\" xml:space=\"preserve\">\n    <value>恢复默认布局</value>\n  </data>\n  <data name=\"Label_UseSharedOutputFolder\" xml:space=\"preserve\">\n    <value>输出共享</value>\n  </data>\n  <data name=\"Label_BatchIndex\" xml:space=\"preserve\">\n    <value>批处理索引</value>\n  </data>\n  <data name=\"Action_Copy\" xml:space=\"preserve\">\n    <value>复制</value>\n  </data>\n  <data name=\"Action_OpenInViewer\" xml:space=\"preserve\">\n    <value>在图像查看器中打开</value>\n  </data>\n  <data name=\"Label_NumImagesSelected\" xml:space=\"preserve\">\n    <value>已选择 {0} 张图片</value>\n  </data>\n  <data name=\"Label_OutputFolder\" xml:space=\"preserve\">\n    <value>输出文件夹</value>\n  </data>\n  <data name=\"Label_OutputType\" xml:space=\"preserve\">\n    <value>输出类型</value>\n  </data>\n  <data name=\"Action_ClearSelection\" xml:space=\"preserve\">\n    <value>清除选择</value>\n  </data>\n  <data name=\"Action_SelectAll\" xml:space=\"preserve\">\n    <value>全选</value>\n  </data>\n  <data name=\"Action_SendToInference\" xml:space=\"preserve\">\n    <value>发送到推理</value>\n  </data>\n  <data name=\"Label_TextToImage\" xml:space=\"preserve\">\n    <value>文本到图像</value>\n  </data>\n  <data name=\"Label_ImageToImage\" xml:space=\"preserve\">\n    <value>图像到图像</value>\n  </data>\n  <data name=\"Label_Inpainting\" xml:space=\"preserve\">\n    <value>图像修复</value>\n  </data>\n  <data name=\"Label_Upscale\" xml:space=\"preserve\">\n    <value>放大</value>\n  </data>\n  <data name=\"Label_OutputsPageTitle\" xml:space=\"preserve\">\n    <value>输出浏览器</value>\n  </data>\n  <data name=\"Label_OneImageSelected\" xml:space=\"preserve\">\n    <value>已选择 1 张图片</value>\n  </data>\n  <data name=\"Label_PythonPackages\" xml:space=\"preserve\">\n    <value>Python 包</value>\n  </data>\n  <data name=\"Action_Consolidate\" xml:space=\"preserve\">\n    <value>合并</value>\n  </data>\n  <data name=\"Label_AreYouSure\" xml:space=\"preserve\">\n    <value>你确定吗？</value>\n  </data>\n  <data name=\"Label_ConsolidateExplanation\" xml:space=\"preserve\">\n    <value>这将会将所有选中的包生成的图像移动到共享输出文件夹的“合并”目录中。此操作无法撤销。</value>\n  </data>\n  <data name=\"Action_Refresh\" xml:space=\"preserve\">\n    <value>刷新</value>\n  </data>\n  <data name=\"Action_Upgrade\" xml:space=\"preserve\">\n    <value>升级</value>\n  </data>\n  <data name=\"Action_Downgrade\" xml:space=\"preserve\">\n    <value>降级</value>\n  </data>\n  <data name=\"Action_OpenGithub\" xml:space=\"preserve\">\n    <value>在 GitHub 上打开</value>\n  </data>\n  <data name=\"Label_Connected\" xml:space=\"preserve\">\n    <value>已连接</value>\n  </data>\n  <data name=\"Action_Disconnect\" xml:space=\"preserve\">\n    <value>断开连接</value>\n  </data>\n  <data name=\"Label_Email\" xml:space=\"preserve\">\n    <value>电子邮件</value>\n  </data>\n  <data name=\"Label_Username\" xml:space=\"preserve\">\n    <value>用户名</value>\n  </data>\n  <data name=\"Label_Password\" xml:space=\"preserve\">\n    <value>密码</value>\n  </data>\n  <data name=\"Action_Login\" xml:space=\"preserve\">\n    <value>登录</value>\n  </data>\n  <data name=\"Action_Signup\" xml:space=\"preserve\">\n    <value>注册</value>\n  </data>\n  <data name=\"Label_ConfirmPassword\" xml:space=\"preserve\">\n    <value>确认密码</value>\n  </data>\n  <data name=\"Label_ApiKey\" xml:space=\"preserve\">\n    <value>API 密钥</value>\n  </data>\n  <data name=\"Label_Accounts\" xml:space=\"preserve\">\n    <value>账户</value>\n  </data>\n  <data name=\"Label_Preprocessor\" xml:space=\"preserve\">\n    <value>预处理</value>\n  </data>\n  <data name=\"Label_Strength\" xml:space=\"preserve\">\n    <value>强度</value>\n  </data>\n  <data name=\"Label_ControlWeight\" xml:space=\"preserve\">\n    <value>控制权重</value>\n  </data>\n  <data name=\"Label_ControlSteps\" xml:space=\"preserve\">\n    <value>控制步骤</value>\n  </data>\n  <data name=\"Label_CivitAiLoginRequired\" xml:space=\"preserve\">\n    <value>您必须登录才能下载此检查点。请在设置中输入 CivitAI API 密钥。</value>\n  </data>\n  <data name=\"Label_DownloadFailed\" xml:space=\"preserve\">\n    <value>下载失败</value>\n  </data>\n  <data name=\"Label_AutoUpdates\" xml:space=\"preserve\">\n    <value>自动更新</value>\n  </data>\n  <data name=\"Label_UpdatesPreviewChannelDescription\" xml:space=\"preserve\">\n    <value>适用于早期采用者。预览构建比开发频道更可靠，并且将更接近稳定版本发布。您的反馈将极大地帮助我们发现问题和完善设计元素。</value>\n  </data>\n  <data name=\"Label_UpdatesDevChannelDescription\" xml:space=\"preserve\">\n    <value>适用于技术用户。一旦可用，您将首先访问来自功能分支的开发构建。由于我们正在实验新功能，可能会有一些粗糙的边缘和错误。</value>\n  </data>\n  <data name=\"Label_Updates\" xml:space=\"preserve\">\n    <value>更新</value>\n  </data>\n  <data name=\"Label_YouAreUpToDate\" xml:space=\"preserve\">\n    <value>您已更新到最新版本</value>\n  </data>\n  <data name=\"TextTemplate_LastChecked\" xml:space=\"preserve\">\n    <value>上次检查：{0}</value>\n  </data>\n  <data name=\"Action_CopyTriggerWords\" xml:space=\"preserve\">\n    <value>复制触发词</value>\n  </data>\n  <data name=\"Label_TriggerWords\" xml:space=\"preserve\">\n    <value>触发词：</value>\n  </data>\n  <data name=\"TeachingTip_MoreCheckpointCategories\" xml:space=\"preserve\">\n    <value>可以在此启用其他文件夹，如 IPAdapters 和 TextualInversions（嵌入）</value>\n  </data>\n  <data name=\"Action_OpenOnHuggingFace\" xml:space=\"preserve\">\n    <value>在 Hugging Face 上打开</value>\n  </data>\n  <data name=\"Action_UpdateExistingMetadata\" xml:space=\"preserve\">\n    <value>更新现有元数据</value>\n  </data>\n  <data name=\"Label_General\" xml:space=\"preserve\">\n    <value>常规</value>\n    <comment>A general settings category</comment>\n  </data>\n  <data name=\"Label_Inference\" xml:space=\"preserve\">\n    <value>推理</value>\n    <comment>The Inference feature page</comment>\n  </data>\n  <data name=\"Label_Prompt\" xml:space=\"preserve\">\n    <value>提示</value>\n    <comment>A settings category for Inference generation prompts</comment>\n  </data>\n  <data name=\"Label_OutputImageFiles\" xml:space=\"preserve\">\n    <value>输出图像文件</value>\n  </data>\n  <data name=\"Label_ImageViewer\" xml:space=\"preserve\">\n    <value>图像查看器</value>\n  </data>\n  <data name=\"Label_AutoCompletion\" xml:space=\"preserve\">\n    <value>自动补全</value>\n  </data>\n  <data name=\"Label_CompletionReplaceUnderscoresWithSpaces\" xml:space=\"preserve\">\n    <value>插入补全时用空格替换下划线</value>\n  </data>\n  <data name=\"Label_PromptTags\" xml:space=\"preserve\">\n    <value>提示标签</value>\n    <comment>Tags for image generation prompts</comment>\n  </data>\n  <data name=\"Label_PromptTagsImport\" xml:space=\"preserve\">\n    <value>导入提示标签</value>\n  </data>\n  <data name=\"Label_PromptTagsDescription\" xml:space=\"preserve\">\n    <value>用于建议补全的标签文件（支持a1111-sd-webui-tagcomplete .csv格式）</value>\n  </data>\n  <data name=\"Label_SystemInformation\" xml:space=\"preserve\">\n    <value>系统信息</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Label_HuggingFace\" xml:space=\"preserve\">\n    <value>Hugging Face</value>\n  </data>\n  <data name=\"Label_Addons\" xml:space=\"preserve\">\n    <value>插件</value>\n    <comment>Inference Sampler Addons</comment>\n  </data>\n  <data name=\"Label_SaveIntermediateImage\" xml:space=\"preserve\">\n    <value>保存中间图像</value>\n    <comment>Inference module step to save an intermediate image</comment>\n  </data>\n  <data name=\"Label_Settings\" xml:space=\"preserve\">\n    <value>设置</value>\n  </data>\n  <data name=\"Action_SelectFile\" xml:space=\"preserve\">\n    <value>选择文件</value>\n  </data>\n  <data name=\"Action_ReplaceContents\" xml:space=\"preserve\">\n    <value>替换内容</value>\n  </data>\n  <data name=\"Label_WipFeature\" xml:space=\"preserve\">\n    <value>尚未可用</value>\n  </data>\n  <data name=\"Label_WipFeatureDescription\" xml:space=\"preserve\">\n    <value>功能将在未来的更新中可用</value>\n  </data>\n  <data name=\"Label_MissingImageFile\" xml:space=\"preserve\">\n    <value>缺少图像文件</value>\n  </data>\n  <data name=\"Label_HolidayMode\" xml:space=\"preserve\">\n    <value>节日模式</value>\n  </data>\n  <data name=\"Label_CLIPSkip\" xml:space=\"preserve\">\n    <value>CLIP 跳过</value>\n  </data>\n  <data name=\"Label_ImageToVideo\" xml:space=\"preserve\">\n    <value>图像转视频</value>\n  </data>\n  <data name=\"Label_Fps\" xml:space=\"preserve\">\n    <value>每秒帧数</value>\n  </data>\n  <data name=\"Label_MinCfg\" xml:space=\"preserve\">\n    <value>最小 CFG</value>\n  </data>\n  <data name=\"Label_Lossless\" xml:space=\"preserve\">\n    <value>无损</value>\n  </data>\n  <data name=\"Label_Frames\" xml:space=\"preserve\">\n    <value>帧数</value>\n  </data>\n  <data name=\"Label_MotionBucketId\" xml:space=\"preserve\">\n    <value>运动桶 ID</value>\n  </data>\n  <data name=\"Label_AugmentationLevel\" xml:space=\"preserve\">\n    <value>增强级别</value>\n  </data>\n  <data name=\"Label_VideoOutputMethod\" xml:space=\"preserve\">\n    <value>方法</value>\n  </data>\n  <data name=\"Label_VideoQuality\" xml:space=\"preserve\">\n    <value>质量</value>\n  </data>\n  <data name=\"Label_FindInModelBrowser\" xml:space=\"preserve\">\n    <value>在模型浏览器中查找</value>\n  </data>\n  <data name=\"Label_Installed\" xml:space=\"preserve\">\n    <value>已安装</value>\n  </data>\n  <data name=\"Label_NoExtensionsFound\" xml:space=\"preserve\">\n    <value>未找到扩展。</value>\n  </data>\n  <data name=\"Action_Hide\" xml:space=\"preserve\">\n    <value>隐藏</value>\n  </data>\n  <data name=\"Action_CopyDetails\" xml:space=\"preserve\">\n    <value>复制详情</value>\n  </data>\n  <data name=\"Action_Download\" xml:space=\"preserve\">\n    <value>下载</value>\n  </data>\n  <data name=\"TeachingTip_DownloadsExplanation\" xml:space=\"preserve\">\n    <value>在此处检查您的包安装和模型下载进度。</value>\n  </data>\n  <data name=\"Label_RecommendedModels\" xml:space=\"preserve\">\n    <value>推荐模型</value>\n  </data>\n  <data name=\"Label_RecommendedModelsSubText\" xml:space=\"preserve\">\n    <value>当您的包正在安装时，这里有一些我们推荐的模型，可以帮助您开始。</value>\n  </data>\n  <data name=\"Label_Notifications\" xml:space=\"preserve\">\n    <value>通知</value>\n  </data>\n  <data name=\"Label_NotificationOption_None\" xml:space=\"preserve\">\n    <value>无</value>\n  </data>\n  <data name=\"Label_ComfyRequiredTitle\" xml:space=\"preserve\">\n    <value>需要 ComfyUI</value>\n  </data>\n  <data name=\"Label_ComfyRequiredDetail\" xml:space=\"preserve\">\n    <value>安装此包需要 ComfyUI。您现在想安装它吗？</value>\n  </data>\n  <data name=\"Error_PleaseSelectDownloadLocation\" xml:space=\"preserve\">\n    <value>请选择下载位置。</value>\n  </data>\n  <data name=\"Label_SelectDownloadLocation\" xml:space=\"preserve\">\n    <value>选择下载位置：</value>\n  </data>\n  <data name=\"Label_Config\" xml:space=\"preserve\">\n    <value>配置</value>\n  </data>\n  <data name=\"Label_ToggleAutoScrolling\" xml:space=\"preserve\">\n    <value>自动滚动到底部</value>\n  </data>\n  <data name=\"Label_ConfirmExit\" xml:space=\"preserve\">\n    <value>确认退出</value>\n  </data>\n  <data name=\"Label_ConfirmExitDetail\" xml:space=\"preserve\">\n    <value>您确定要退出吗？这将关闭所有当前正在运行的包。</value>\n  </data>\n  <data name=\"Label_Console\" xml:space=\"preserve\">\n    <value>控制台</value>\n  </data>\n  <data name=\"Label_WebUi\" xml:space=\"preserve\">\n    <value>Web UI</value>\n  </data>\n  <data name=\"Label_Packages\" xml:space=\"preserve\">\n    <value>包</value>\n  </data>\n  <data name=\"Label_ActionCannotBeUndone\" xml:space=\"preserve\">\n    <value>此操作无法撤销。</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteImages\" xml:space=\"preserve\">\n    <value>您确定要删除 {0} 张图像吗？</value>\n  </data>\n  <data name=\"Label_CheckingHardware\" xml:space=\"preserve\">\n    <value>我们正在检查一些硬件规格以确定兼容性。</value>\n  </data>\n  <data name=\"Label_EverythingLooksGood\" xml:space=\"preserve\">\n    <value>一切看起来都很好！</value>\n  </data>\n  <data name=\"Label_NvidiaGpuRecommended\" xml:space=\"preserve\">\n    <value>我们推荐具有 CUDA 支持的 GPU 以获得最佳体验。您可以继续不使用 GPU，但某些包可能无法工作，推理速度可能会较慢。</value>\n  </data>\n  <data name=\"Label_Checkpoints\" xml:space=\"preserve\">\n    <value>检查点</value>\n  </data>\n  <data name=\"Label_ModelBrowser\" xml:space=\"preserve\">\n    <value>模型浏览器</value>\n  </data>\n  <data name=\"Label_Workflows\" xml:space=\"preserve\">\n    <value>工作流</value>\n  </data>\n  <data name=\"Label_InfiniteScrolling\" xml:space=\"preserve\">\n    <value>无限滚动</value>\n  </data>\n  <data name=\"Label_WorkflowBrowser\" xml:space=\"preserve\">\n    <value>工作流浏览器</value>\n  </data>\n  <data name=\"Action_OpenOnOpenArt\" xml:space=\"preserve\">\n    <value>在 OpenArt 上打开</value>\n  </data>\n  <data name=\"Label_NodeDetails\" xml:space=\"preserve\">\n    <value>节点详情</value>\n  </data>\n  <data name=\"Label_WorkflowDescription\" xml:space=\"preserve\">\n    <value>工作流描述</value>\n  </data>\n  <data name=\"Label_OpenArtBrowser\" xml:space=\"preserve\">\n    <value>OpenArt 浏览器</value>\n  </data>\n  <data name=\"Action_PreviewPreprocessor\" xml:space=\"preserve\">\n    <value>预览预处理</value>\n  </data>\n  <data name=\"TeachingTip_WebUiButtonMoved\" xml:space=\"preserve\">\n    <value>&apos;打开 Web UI&apos;按钮已移动到命令栏</value>\n  </data>\n  <data name=\"Label_AnotherInstanceAlreadyRunning\" xml:space=\"preserve\">\n    <value>另一个 Stability Matrix 实例已经在运行。请在启动新实例之前关闭它。</value>\n  </data>\n  <data name=\"Label_StabilityMatrixAlreadyRunning\" xml:space=\"preserve\">\n    <value>Stability Matrix 已经在运行</value>\n  </data>\n  <data name=\"Label_WorkflowDeletedSuccessfully\" xml:space=\"preserve\">\n    <value>{0} 删除成功</value>\n  </data>\n  <data name=\"Label_WorkflowDeleted\" xml:space=\"preserve\">\n    <value>工作流已删除</value>\n  </data>\n  <data name=\"Label_ErrorRetrievingWorkflows\" xml:space=\"preserve\">\n    <value>获取工作流时出错</value>\n  </data>\n  <data name=\"TabLabel_InstalledWorkflows\" xml:space=\"preserve\">\n    <value>已安装工作流</value>\n  </data>\n  <data name=\"Label_WorkflowImported\" xml:space=\"preserve\">\n    <value>工作流已导入</value>\n  </data>\n  <data name=\"Label_FinishedImportingWorkflow\" xml:space=\"preserve\">\n    <value>完成导入工作流和自定义节点</value>\n  </data>\n  <data name=\"Label_WorkflowImportComplete\" xml:space=\"preserve\">\n    <value>工作流和自定义节点已导入。</value>\n  </data>\n  <data name=\"TeachingTip_InferencePromptHelpButton\" xml:space=\"preserve\">\n    <value>点击此处查看提示语法以及如何包含Lora / Embeddings。</value>\n  </data>\n  <data name=\"Label_ExtraNetworks\" xml:space=\"preserve\">\n    <value>额外网络（Lora / LyCORIS）</value>\n  </data>\n  <data name=\"Label_CLIPStrength\" xml:space=\"preserve\">\n    <value>CLIP 强度</value>\n  </data>\n  <data name=\"Label_NumberFormat\" xml:space=\"preserve\">\n    <value>数字格式</value>\n  </data>\n  <data name=\"Text_DeleteFollowingItems\" xml:space=\"preserve\">\n    <value>您即将删除以下项目：</value>\n  </data>\n  <data name=\"TextTemplate_DeleteFollowingCountItems\" xml:space=\"preserve\">\n    <value>您即将删除以下 {0} 个项目：</value>\n  </data>\n  <data name=\"Label_DeletePermanently\" xml:space=\"preserve\">\n    <value>永久删除</value>\n  </data>\n  <data name=\"Action_MoveToTrash\" xml:space=\"preserve\">\n    <value>移动到回收站</value>\n  </data>\n  <data name=\"Label_AreYouSureDeleteModels\" xml:space=\"preserve\">\n    <value>您确定要删除 {0} 个模型吗？</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad\" xml:space=\"preserve\">\n    <value>加载时自动搜索</value>\n  </data>\n  <data name=\"Label_AutoSearchOnLoad_Description\" xml:space=\"preserve\">\n    <value>加载模型浏览器页面时自动启动搜索</value>\n  </data>\n  <data name=\"Action_ToggleVisibility\" xml:space=\"preserve\">\n    <value>切换可见性</value>\n  </data>\n  <data name=\"Label_ClippingMask\" xml:space=\"preserve\">\n    <value>裁剪遮罩</value>\n  </data>\n  <data name=\"Label_AppFolders\" xml:space=\"preserve\">\n    <value>应用程序文件夹</value>\n  </data>\n  <data name=\"Label_Logs\" xml:space=\"preserve\">\n    <value>日志</value>\n  </data>\n  <data name=\"Label_AppData\" xml:space=\"preserve\">\n    <value>应用程序数据</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToSelected\" xml:space=\"preserve\">\n    <value>{0} 已更新为选定版本</value>\n  </data>\n  <data name=\"Action_CopyAsBitmap\" xml:space=\"preserve\">\n    <value>复制为位图</value>\n  </data>\n  <data name=\"Label_DisableUpdateCheck\" xml:space=\"preserve\">\n    <value>禁用更新检查</value>\n  </data>\n  <data name=\"Warning_PleaseExtractFirst\" xml:space=\"preserve\">\n    <value>请在运行 Stability Matrix 之前从ZIP文件中提取应用程序</value>\n  </data>\n  <data name=\"Label_HistorySize\" xml:space=\"preserve\">\n    <value>历史记录大小</value>\n  </data>\n  <data name=\"Label_HistorySize_Description\" xml:space=\"preserve\">\n    <value>您可以在控制台中向上滚动以查看的行数</value>\n  </data>\n  <data name=\"Label_ConnectAccountFailed\" xml:space=\"preserve\">\n    <value>我们连接您的账户时遇到了一些问题</value>\n  </data>\n  <data name=\"Label_EditModelMetadata\" xml:space=\"preserve\">\n    <value>编辑模型元数据</value>\n  </data>\n  <data name=\"Label_NSFW\" xml:space=\"preserve\">\n    <value>NSFW</value>\n  </data>\n  <data name=\"Label_Tags\" xml:space=\"preserve\">\n    <value>标签</value>\n  </data>\n  <data name=\"Label_VersionName\" xml:space=\"preserve\">\n    <value>版本名称</value>\n  </data>\n  <data name=\"Label_TrainedWords\" xml:space=\"preserve\">\n    <value>训练词汇</value>\n  </data>\n  <data name=\"Label_PreviewImage\" xml:space=\"preserve\">\n    <value>预览图像</value>\n  </data>\n  <data name=\"Label_BatchSize\" xml:space=\"preserve\">\n    <value>批次大小</value>\n  </data>\n  <data name=\"Label_Batches\" xml:space=\"preserve\">\n    <value>批次</value>\n  </data>\n  <data name=\"Label_Sampler\" xml:space=\"preserve\">\n    <value>采样器</value>\n  </data>\n  <data name=\"Label_Scheduler\" xml:space=\"preserve\">\n    <value>调度器</value>\n  </data>\n  <data name=\"Label_MaxSize\" xml:space=\"preserve\">\n    <value>最大尺寸</value>\n  </data>\n  <data name=\"Label_UseSeparatePrompt\" xml:space=\"preserve\">\n    <value>使用单独提示</value>\n  </data>\n  <data name=\"Label_Seed\" xml:space=\"preserve\">\n    <value>种子</value>\n  </data>\n  <data name=\"Label_NegativePrompt\" xml:space=\"preserve\">\n    <value>负面提示</value>\n  </data>\n  <data name=\"Label_NewFolder\" xml:space=\"preserve\">\n    <value>新建文件夹</value>\n  </data>\n  <data name=\"Label_CopyLinkToClipboard\" xml:space=\"preserve\">\n    <value>复制链接到剪贴板</value>\n  </data>\n  <data name=\"TextTemplate_OAuthLoginTitle\" xml:space=\"preserve\">\n    <value>使用 {0} 登录</value>\n    <comment>e.g. &apos;Sign in with Google&apos;</comment>\n  </data>\n  <data name=\"Text_AllowBrowserOpenAppLink\" xml:space=\"preserve\">\n    <value>请允许您的浏览器在提示时打开此应用程序。</value>\n  </data>\n  <data name=\"Text_OAuthLoginDescription\" xml:space=\"preserve\">\n    <value>在浏览器中打开链接并按照说明连接您的账户。</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Title\" xml:space=\"preserve\">\n    <value>Python 依赖项覆盖</value>\n  </data>\n  <data name=\"Label_PythonDependenciesOverride_Description\" xml:space=\"preserve\">\n    <value>为安装和更新添加、替换或删除依赖项</value>\n  </data>\n  <data name=\"Label_DependencySpecifiers\" xml:space=\"preserve\">\n    <value>依赖项说明符</value>\n  </data>\n  <data name=\"AnalyticsExample_InstallData\" xml:space=\"preserve\">\n    <value>{\n  &quot;packageName&quot;: &quot;stable-diffusion-webui&quot;,\n  &quot;packageVersion&quot;: &quot;v1.10.0&quot;,\n  &quot;isSuccess&quot;: true,\n  &quot;type&quot;: &quot;install&quot;,\n  &quot;timestamp&quot;: &quot;2024-09-04T02:14:04.1967404+00:00&quot;\n}</value>\n  </data>\n  <data name=\"Label_Analytics\" xml:space=\"preserve\">\n    <value>分析</value>\n  </data>\n  <data name=\"TextTemplate_YouCanChangeThisBehavior\" xml:space=\"preserve\">\n    <value>您可以在 {0} 中随时更改此行为。</value>\n    <comment>e.g. &apos;You can always change this behavior in Settings &gt; Category &gt; Item.&apos;</comment>\n  </data>\n  <data name=\"Text_AnalyticsDescription\" xml:space=\"preserve\">\n    <value>通过发送有关使用功能、操作系统版本、安装的软件包类型等匿名数据来帮助我们改进 Stability Matrix。发送的数据永远不会与您或您的账户关联，也不会包含个人数据或任何敏感信息。</value>\n  </data>\n  <data name=\"Text_AnalyticsDescriptionShort\" xml:space=\"preserve\">\n    <value>通过发送有关使用功能、操作系统版本、安装的软件包类型等匿名数据来帮助我们改进 Stability Matrix。</value>\n  </data>\n  <data name=\"Text_AnalyticsDataPrivacyInfo\" xml:space=\"preserve\">\n    <value>发送的数据永远不会与您或您的账户关联，也不会包含个人数据或任何敏感信息。</value>\n  </data>\n  <data name=\"Label_UsageData\" xml:space=\"preserve\">\n    <value>使用数据</value>\n  </data>\n  <data name=\"Label_PrivacyPolicy\" xml:space=\"preserve\">\n    <value>隐私政策</value>\n  </data>\n  <data name=\"Label_ImageHidden\" xml:space=\"preserve\">\n    <value>图像已隐藏</value>\n  </data>\n  <data name=\"Label_NoImageFound\" xml:space=\"preserve\">\n    <value>未找到图像</value>\n  </data>\n  <data name=\"Label_HideEmptyCategories\" xml:space=\"preserve\">\n    <value>隐藏空类别</value>\n  </data>\n  <data name=\"Label_ShowNsfwImages\" xml:space=\"preserve\">\n    <value>显示 NSFW 图像</value>\n  </data>\n  <data name=\"Label_EnableLongPaths\" xml:space=\"preserve\">\n    <value>启用长路径</value>\n    <comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_EnableLongPathsDescription\" xml:space=\"preserve\">\n    <value>从常见的 Win32 文件和目录函数中移除 MAX_PATH 限制</value>\n    <comment>(Setting to enable long file paths on windows)</comment>\n  </data>\n  <data name=\"Label_SystemSettings\" xml:space=\"preserve\">\n    <value>系统设置</value>\n  </data>\n  <data name=\"Label_ChangesApplied\" xml:space=\"preserve\">\n    <value>更改已应用</value>\n  </data>\n  <data name=\"Text_RestartMayBeRequiredForSystemChanges\" xml:space=\"preserve\">\n    <value>系统更改可能需要重启才能生效。</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>前往设置</value>\n  </data>\n  <data name=\"Text_LykosAccountUpgradeNotice\" xml:space=\"preserve\">\n    <value>## 重要提示：Lykos 帐户登出和安全升级\n\n我们对 Stability Matrix 处理 Lykos 帐户的方式进行了一些重要的幕后改进，升级到更安全、更便捷的登录系统 **(OAuth 2.0 with OpenID Connect)**。 因此，您已登出 Lykos 帐户。\n\n### 为什么进行此更改？\n\n您的安全和隐私对我们很重要。 此升级带来：\n\n*   **简化的体验：** 登录 [account.lykos.ai](https://account.lykos.ai) 一次即可连接到 Stability Matrix 和其他 Lykos AI 服务。\n*   **更多登录方式：** 使用您现有的 [lykos.ai](https://lykos.ai) 帐户，或使用 **Apple**、**GitHub** 或 **Google** 登录。\n*   **增强的隐私：** Stability Matrix 仅请求其所需的权限。\n*   **行业标准安全性：** 我们正在使用 OAuth 2.0，这是安全登录的黄金标准。\n*   **为未来做好准备：** 这允许与其他应用程序和服务进行安全连接。\n\n### 我需要做什么？\n\n单击**“前往设置”**按钮，然后单击**“Lykos 帐户”**旁边的**“连接”**。\n\n### Lykos 帐户是必需的吗？\n\n不，Stability Matrix 在没有帐户的情况下仍然可以正常运行。 但是，您的 Lykos 帐户可以启用一些额外的连接功能，例如为我们的 Patreon 支持者自动更新到开发版本（以及更多即将推出的功能）。</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>启用</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>停用</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>连接您的 Lykos 账户</value>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>请使用您的 Lykos 账户登录以使用连接功能。</value>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>请重新登录</value>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>您的登录已过期。请重新登录以继续。</value>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>支持 Stability Matrix</value>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>感谢您支持 Stability Matrix！</value>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>**{0}** 等功能是我们为支持者提供的众多权益之一。您的贡献有助于我们支付服务器成本并支持 Stability Matrix 的开发。</value>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>**{0}** 等功能需达到 **{1}** 支持者等级（或更高）方可使用。您的贡献有助于我们支付更高级连接功能的服务器成本，并让我们能够为大家不断改进 Stability Matrix。</value>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>如果您已在 Patreon 上支持我们，请关联您的账户以继续。</value>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>账户设置</value>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>查看支持选项</value>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>以后再说</value>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>状态</value>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>启用</value>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>未启用（使用标准连接）</value>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>从 Civitai 等在线存储库浏览模型时，体验更快的搜索结果。</value>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>针对第三方存储库的实验性优化。非官方合作；可用性可能有所不同。</value>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>测试版</value>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>加速模型搜索</value>\n  </data>\n  <data name=\"PromptAmplifier_Disclaimer\" xml:space=\"preserve\">\n    <value>### ✨ 新功能上线：Prompt Amplifier\n\n这是我们的 AI 助手，它使用实验性的 Spark 模型，能为您的提示生成更具创意的扩展。\n\n请注意：Prompt Amplifier 会在我们安全的云端环境中运行，**不会**在您的本地电脑上运行。\n\n### ☁️ 为何使用云端？\n\nSpark 模型规模庞大，算力需求堪比万亿参数的基础模型。我们重视本地功能，但 Spark 的高级能力需要云端支持，现在即可通过我们的云服务使用。\n\n### 🔒 隐私至上\n\n我们极其重视您的隐私（详情请参阅[生成式 AI 条款](https://lykos.ai/gen-ai-terms)）。Lykos AI 及必要的云服务提供商**绝不会**使用您的提示或生成结果来训练 AI 模型。我们仅在为确保功能正常运行时处理您的数据，**处理完成后也只保留必要的元数据（如时间、Token 数量），不保留提示内容本身**。您的数据绝不会被出售或共享。</value>\n  </data>\n</root>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Languages/Resources.zh-Hant.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <data name=\"Action_Launch\" xml:space=\"preserve\">\n    <value>啟動</value>\n  </data>\n  <data name=\"Action_Quit\" xml:space=\"preserve\">\n    <value>退出</value>\n  </data>\n  <data name=\"Action_Save\" xml:space=\"preserve\">\n    <value>保存</value>\n  </data>\n  <data name=\"Action_Cancel\" xml:space=\"preserve\">\n    <value>取消</value>\n  </data>\n  <data name=\"Label_Language\" xml:space=\"preserve\">\n    <value>語言</value>\n  </data>\n  <data name=\"Text_RelaunchRequiredToApplyLanguage\" xml:space=\"preserve\">\n    <value>需要重新啟動才能使新語言選項生效</value>\n  </data>\n  <data name=\"Action_Relaunch\" xml:space=\"preserve\">\n    <value>重新啟動</value>\n  </data>\n  <data name=\"Action_RelaunchLater\" xml:space=\"preserve\">\n    <value>稍後重新啟動</value>\n  </data>\n  <data name=\"Label_RelaunchRequired\" xml:space=\"preserve\">\n    <value>需要重新啟動</value>\n  </data>\n  <data name=\"Label_UnknownPackage\" xml:space=\"preserve\">\n    <value>未知包</value>\n  </data>\n  <data name=\"Action_Import\" xml:space=\"preserve\">\n    <value>導入</value>\n  </data>\n  <data name=\"Label_PackageType\" xml:space=\"preserve\">\n    <value>包類型</value>\n  </data>\n  <data name=\"Label_Version\" xml:space=\"preserve\">\n    <value>版本</value>\n  </data>\n  <data name=\"Label_VersionType\" xml:space=\"preserve\">\n    <value>版本類型</value>\n  </data>\n  <data name=\"Label_Releases\" xml:space=\"preserve\">\n    <value>發布</value>\n  </data>\n  <data name=\"Label_Branches\" xml:space=\"preserve\">\n    <value>分支</value>\n  </data>\n  <data name=\"Label_DragAndDropCheckpointsHereToImport\" xml:space=\"preserve\">\n    <value>將模型拖放到此處進行導入</value>\n  </data>\n  <data name=\"Label_Emphasis\" xml:space=\"preserve\">\n    <value>強調</value>\n  </data>\n  <data name=\"Label_Deemphasis\" xml:space=\"preserve\">\n    <value>輕描淡寫</value>\n  </data>\n  <data name=\"Label_EmbeddingsOrTextualInversion\" xml:space=\"preserve\">\n    <value>嵌入 / 文本倒置</value>\n  </data>\n  <data name=\"Label_NetworksLoraOrLycoris\" xml:space=\"preserve\">\n    <value>網絡（Lora / LyCORIS）</value>\n  </data>\n  <data name=\"Label_Comments\" xml:space=\"preserve\">\n    <value>評論</value>\n  </data>\n  <data name=\"Label_ShowPixelGridAtHighZoomLevels\" xml:space=\"preserve\">\n    <value>在高縮放級別下顯示像素網格</value>\n  </data>\n  <data name=\"Label_Steps\" xml:space=\"preserve\">\n    <value>迭代步數</value>\n  </data>\n  <data name=\"Label_StepsBase\" xml:space=\"preserve\">\n    <value>迭代步數 - Base</value>\n  </data>\n  <data name=\"Label_StepsRefiner\" xml:space=\"preserve\">\n    <value>迭代步數 - Refiner</value>\n  </data>\n  <data name=\"Label_CFGScale\" xml:space=\"preserve\">\n    <value>提示詞引導係數</value>\n  </data>\n  <data name=\"Label_DenoisingStrength\" xml:space=\"preserve\">\n    <value>重繪幅度</value>\n  </data>\n  <data name=\"Label_Width\" xml:space=\"preserve\">\n    <value>寬度</value>\n  </data>\n  <data name=\"Label_Height\" xml:space=\"preserve\">\n    <value>高度</value>\n  </data>\n  <data name=\"Label_Refiner\" xml:space=\"preserve\">\n    <value>Refiner</value>\n  </data>\n  <data name=\"Label_VAE\" xml:space=\"preserve\">\n    <value>VAE</value>\n  </data>\n  <data name=\"Label_Model\" xml:space=\"preserve\">\n    <value>模型</value>\n  </data>\n  <data name=\"Action_Connect\" xml:space=\"preserve\">\n    <value>連接</value>\n  </data>\n  <data name=\"Label_ConnectingEllipsis\" xml:space=\"preserve\">\n    <value>連接中...</value>\n  </data>\n  <data name=\"Action_Close\" xml:space=\"preserve\">\n    <value>關閉</value>\n  </data>\n  <data name=\"Label_WaitingToConnectEllipsis\" xml:space=\"preserve\">\n    <value>等待連接...</value>\n  </data>\n  <data name=\"Label_UpdateAvailable\" xml:space=\"preserve\">\n    <value>可更新</value>\n  </data>\n  <data name=\"Label_BecomeAPatron\" xml:space=\"preserve\">\n    <value>成為贊助商</value>\n  </data>\n  <data name=\"Label_JoinDiscord\" xml:space=\"preserve\">\n    <value>加入Discord 服務器</value>\n  </data>\n  <data name=\"Label_Downloads\" xml:space=\"preserve\">\n    <value>下載</value>\n  </data>\n  <data name=\"Action_Install\" xml:space=\"preserve\">\n    <value>安裝</value>\n  </data>\n  <data name=\"Label_SkipSetup\" xml:space=\"preserve\">\n    <value>跳過首次設置</value>\n  </data>\n  <data name=\"Label_UnexpectedErrorOccurred\" xml:space=\"preserve\">\n    <value>發生意外錯誤</value>\n  </data>\n  <data name=\"Action_ExitApplication\" xml:space=\"preserve\">\n    <value>退出程序</value>\n  </data>\n  <data name=\"Label_DisplayName\" xml:space=\"preserve\">\n    <value>顯示名稱</value>\n  </data>\n  <data name=\"Label_InstallationWithThisNameExists\" xml:space=\"preserve\">\n    <value>已存在使用此名稱的安裝。</value>\n  </data>\n  <data name=\"Label_PleaseChooseDifferentName\" xml:space=\"preserve\">\n    <value>請選擇不同的名稱或不同的安裝位置。</value>\n  </data>\n  <data name=\"Label_AdvancedOptions\" xml:space=\"preserve\">\n    <value>高級選項</value>\n  </data>\n  <data name=\"Label_Commit\" xml:space=\"preserve\">\n    <value>提交</value>\n  </data>\n  <data name=\"Label_SharedModelFolderStrategy\" xml:space=\"preserve\">\n    <value>共享模型文件夾方案</value>\n  </data>\n  <data name=\"Label_PyTorchVersion\" xml:space=\"preserve\">\n    <value>PyTorch版本</value>\n  </data>\n  <data name=\"Label_CloseDialogWhenFinished\" xml:space=\"preserve\">\n    <value>完成後關閉對話框</value>\n  </data>\n  <data name=\"Label_DataDirectory\" xml:space=\"preserve\">\n    <value>數據目錄</value>\n  </data>\n  <data name=\"Label_DataDirectoryExplanation\" xml:space=\"preserve\">\n    <value>這裡將安裝模型，LORA，Web UIs，設置等等。</value>\n  </data>\n  <data name=\"Label_FatWarning\" xml:space=\"preserve\">\n    <value>使用 FAT32或exFAT硬盤時可能會遭遇錯誤。請選擇不同的硬盤，以獲得更流暢的體驗。</value>\n  </data>\n  <data name=\"Label_PortableMode\" xml:space=\"preserve\">\n    <value>便攜模式</value>\n  </data>\n  <data name=\"Label_PortableModeExplanation\" xml:space=\"preserve\">\n    <value>在便攜模式下，所有數據和設置都將存儲在與應用程序相同的目錄中。您可以將應用程序及其 &quot;Data &quot;文件夾移動到不同的位置或電腦上。</value>\n  </data>\n  <data name=\"Action_Continue\" xml:space=\"preserve\">\n    <value>繼續</value>\n  </data>\n  <data name=\"Label_PreviousImage\" xml:space=\"preserve\">\n    <value>上一張圖片</value>\n  </data>\n  <data name=\"Label_NextImage\" xml:space=\"preserve\">\n    <value>下一張圖片</value>\n  </data>\n  <data name=\"Label_ModelDescription\" xml:space=\"preserve\">\n    <value>模型描述</value>\n  </data>\n  <data name=\"Label_NewVersionAvailable\" xml:space=\"preserve\">\n    <value>新版Stability Matrix已發布！</value>\n  </data>\n  <data name=\"Label_ImportLatest\" xml:space=\"preserve\">\n    <value>導入最新版本</value>\n  </data>\n  <data name=\"Label_AllVersions\" xml:space=\"preserve\">\n    <value>所有版本</value>\n  </data>\n  <data name=\"Label_ModelSearchWatermark\" xml:space=\"preserve\">\n    <value>搜索模型、#標籤或@用戶</value>\n  </data>\n  <data name=\"Action_Search\" xml:space=\"preserve\">\n    <value>搜尋</value>\n  </data>\n  <data name=\"Label_Sort\" xml:space=\"preserve\">\n    <value>分類</value>\n  </data>\n  <data name=\"Label_TimePeriod\" xml:space=\"preserve\">\n    <value>期間</value>\n  </data>\n  <data name=\"Label_ModelType\" xml:space=\"preserve\">\n    <value>模型類型</value>\n  </data>\n  <data name=\"Label_BaseModel\" xml:space=\"preserve\">\n    <value>基礎模型</value>\n  </data>\n  <data name=\"Label_ShowNsfwContent\" xml:space=\"preserve\">\n    <value>顯示 NSFW 內容</value>\n  </data>\n  <data name=\"Label_DataProvidedByCivitAi\" xml:space=\"preserve\">\n    <value>數據由 CivitAI 提供</value>\n  </data>\n  <data name=\"Label_Page\" xml:space=\"preserve\">\n    <value>頁次</value>\n  </data>\n  <data name=\"Label_FirstPage\" xml:space=\"preserve\">\n    <value>第一頁</value>\n  </data>\n  <data name=\"Label_PreviousPage\" xml:space=\"preserve\">\n    <value>上一頁</value>\n  </data>\n  <data name=\"Label_NextPage\" xml:space=\"preserve\">\n    <value>下一頁</value>\n  </data>\n  <data name=\"Label_LastPage\" xml:space=\"preserve\">\n    <value>最後一頁</value>\n  </data>\n  <data name=\"Action_Rename\" xml:space=\"preserve\">\n    <value>重新命名</value>\n  </data>\n  <data name=\"Action_Delete\" xml:space=\"preserve\">\n    <value>刪除</value>\n  </data>\n  <data name=\"Action_OpenOnCivitAi\" xml:space=\"preserve\">\n    <value>在 CivitAI上打開</value>\n  </data>\n  <data name=\"Label_ConnectedModel\" xml:space=\"preserve\">\n    <value>已連接CivitAI元數據</value>\n  </data>\n  <data name=\"Label_LocalModel\" xml:space=\"preserve\">\n    <value>本地模型</value>\n  </data>\n  <data name=\"Action_ShowInExplorer\" xml:space=\"preserve\">\n    <value>在資源管理器中顯示</value>\n  </data>\n  <data name=\"Action_New\" xml:space=\"preserve\">\n    <value>新</value>\n  </data>\n  <data name=\"Label_Folder\" xml:space=\"preserve\">\n    <value>文件夾</value>\n  </data>\n  <data name=\"Label_DropFileToImport\" xml:space=\"preserve\">\n    <value>將文件放入此處以導入</value>\n  </data>\n  <data name=\"Label_ImportAsConnected\" xml:space=\"preserve\">\n    <value>導入時獲取元數據</value>\n  </data>\n  <data name=\"Label_ImportAsConnectedExplanation\" xml:space=\"preserve\">\n    <value>搜索本地新導入模型的元數據</value>\n  </data>\n  <data name=\"Label_Indexing\" xml:space=\"preserve\">\n    <value>正在索引...</value>\n  </data>\n  <data name=\"Label_ModelsFolder\" xml:space=\"preserve\">\n    <value>模型文件夾</value>\n  </data>\n  <data name=\"Label_Categories\" xml:space=\"preserve\">\n    <value>類別</value>\n  </data>\n  <data name=\"Label_LetsGetStarted\" xml:space=\"preserve\">\n    <value>讓我們開始吧</value>\n  </data>\n  <data name=\"Label_ReadAndAgree\" xml:space=\"preserve\">\n    <value>我已閱讀並同意</value>\n  </data>\n  <data name=\"Label_LicenseAgreement\" xml:space=\"preserve\">\n    <value>許可協議。</value>\n  </data>\n  <data name=\"Label_FindConnectedMetadata\" xml:space=\"preserve\">\n    <value>查找連接的元數據</value>\n  </data>\n  <data name=\"Label_ShowModelImages\" xml:space=\"preserve\">\n    <value>顯示模型圖片</value>\n  </data>\n  <data name=\"Label_Appearance\" xml:space=\"preserve\">\n    <value>外觀</value>\n  </data>\n  <data name=\"Label_Theme\" xml:space=\"preserve\">\n    <value>主題</value>\n  </data>\n  <data name=\"Label_CheckpointManager\" xml:space=\"preserve\">\n    <value>模型管理器</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown\" xml:space=\"preserve\">\n    <value>關閉時刪除共享模型目錄的符號鏈接</value>\n  </data>\n  <data name=\"Label_RemoveSymlinksOnShutdown_Details\" xml:space=\"preserve\">\n    <value>如果您在將 Stability Matrix 移至另一個驅動器時遇到問題，請選擇此選項</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache\" xml:space=\"preserve\">\n    <value>重置模型緩存</value>\n  </data>\n  <data name=\"Label_ResetCheckpointsCache_Details\" xml:space=\"preserve\">\n    <value>重建已安裝模型的緩存。如果模型在模型瀏覽器中被錯誤標註，則使用此功能</value>\n  </data>\n  <data name=\"Label_PackageEnvironment\" xml:space=\"preserve\">\n    <value>軟件包環境</value>\n  </data>\n  <data name=\"Action_Edit\" xml:space=\"preserve\">\n    <value>編輯</value>\n  </data>\n  <data name=\"Label_EnvironmentVariables\" xml:space=\"preserve\">\n    <value>環境變量</value>\n  </data>\n  <data name=\"Label_EmbeddedPython\" xml:space=\"preserve\">\n    <value>嵌入式Python</value>\n  </data>\n  <data name=\"Action_CheckVersion\" xml:space=\"preserve\">\n    <value>檢查版本</value>\n  </data>\n  <data name=\"Label_Integrations\" xml:space=\"preserve\">\n    <value>集成</value>\n  </data>\n  <data name=\"Label_DiscordRichPresence\" xml:space=\"preserve\">\n    <value>Discord Rich Presence</value>\n  </data>\n  <data name=\"Label_System\" xml:space=\"preserve\">\n    <value>系統</value>\n  </data>\n  <data name=\"Label_AddToStartMenu\" xml:space=\"preserve\">\n    <value>將Stability Matrix添加到開始菜單</value>\n  </data>\n  <data name=\"Label_AddToStartMenu_Details\" xml:space=\"preserve\">\n    <value>使用當前應用程序的位置，如果應用程序移動了，可以再次運行此功能</value>\n  </data>\n  <data name=\"Label_OnlyAvailableOnWindows\" xml:space=\"preserve\">\n    <value>僅適用於 Windows</value>\n  </data>\n  <data name=\"Action_AddForCurrentUser\" xml:space=\"preserve\">\n    <value>為當前用戶添加</value>\n  </data>\n  <data name=\"Action_AddForAllUsers\" xml:space=\"preserve\">\n    <value>為所有用戶添加</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory\" xml:space=\"preserve\">\n    <value>選擇新數據目錄</value>\n  </data>\n  <data name=\"Label_SelectNewDataDirectory_Details\" xml:space=\"preserve\">\n    <value>不移動現有數據</value>\n  </data>\n  <data name=\"Action_SelectDirectory\" xml:space=\"preserve\">\n    <value>選擇目錄</value>\n  </data>\n  <data name=\"Label_About\" xml:space=\"preserve\">\n    <value>關於</value>\n  </data>\n  <data name=\"Label_StabilityMatrix\" xml:space=\"preserve\">\n    <value>Stability Matrix</value>\n  </data>\n  <data name=\"Label_LicenseAndOpenSourceNotices\" xml:space=\"preserve\">\n    <value>許可與開源告示</value>\n  </data>\n  <data name=\"TeachingTip_ClickLaunchToGetStarted\" xml:space=\"preserve\">\n    <value>點擊 &quot;啟動 &quot;開始！</value>\n  </data>\n  <data name=\"Action_Stop\" xml:space=\"preserve\">\n    <value>停止</value>\n  </data>\n  <data name=\"Action_SendInput\" xml:space=\"preserve\">\n    <value>發送輸入</value>\n  </data>\n  <data name=\"Label_Input\" xml:space=\"preserve\">\n    <value>輸入</value>\n  </data>\n  <data name=\"Action_Send\" xml:space=\"preserve\">\n    <value>發送</value>\n  </data>\n  <data name=\"Label_InputRequired\" xml:space=\"preserve\">\n    <value>需要輸入</value>\n  </data>\n  <data name=\"Label_ConfirmQuestion\" xml:space=\"preserve\">\n    <value>確認？</value>\n  </data>\n  <data name=\"Action_Yes\" xml:space=\"preserve\">\n    <value>是</value>\n  </data>\n  <data name=\"Label_No\" xml:space=\"preserve\">\n    <value>否</value>\n  </data>\n  <data name=\"Action_OpenWebUI\" xml:space=\"preserve\">\n    <value>打開Web UI</value>\n  </data>\n  <data name=\"Text_WelcomeToStabilityMatrix\" xml:space=\"preserve\">\n    <value>歡迎使用Stability Matrix！</value>\n  </data>\n  <data name=\"Text_OneClickInstaller_SubHeader\" xml:space=\"preserve\">\n    <value>選擇您喜歡的交互界面，然後點擊安裝開始使用</value>\n  </data>\n  <data name=\"Label_Installing\" xml:space=\"preserve\">\n    <value>正在安裝</value>\n  </data>\n  <data name=\"Text_ProceedingToLaunchPage\" xml:space=\"preserve\">\n    <value>正在進入啟動頁面</value>\n  </data>\n  <data name=\"Progress_DownloadingPackage\" xml:space=\"preserve\">\n    <value>正在下載軟件包...</value>\n  </data>\n  <data name=\"Progress_DownloadComplete\" xml:space=\"preserve\">\n    <value>下載完畢</value>\n  </data>\n  <data name=\"Progress_InstallationComplete\" xml:space=\"preserve\">\n    <value>安裝完畢</value>\n  </data>\n  <data name=\"Progress_InstallingPrerequisites\" xml:space=\"preserve\">\n    <value>正在安裝文件依賴...</value>\n  </data>\n  <data name=\"Progress_InstallingPackageRequirements\" xml:space=\"preserve\">\n    <value>正在安裝軟件包依賴...</value>\n  </data>\n  <data name=\"Action_OpenInExplorer\" xml:space=\"preserve\">\n    <value>在資源管理器中打開</value>\n  </data>\n  <data name=\"Action_OpenInFinder\" xml:space=\"preserve\">\n    <value>在 Finder中打開</value>\n  </data>\n  <data name=\"Action_Uninstall\" xml:space=\"preserve\">\n    <value>卸載</value>\n  </data>\n  <data name=\"Action_CheckForUpdates\" xml:space=\"preserve\">\n    <value>檢查更新</value>\n  </data>\n  <data name=\"Action_Update\" xml:space=\"preserve\">\n    <value>更新</value>\n  </data>\n  <data name=\"Action_AddPackage\" xml:space=\"preserve\">\n    <value>添加軟件包</value>\n  </data>\n  <data name=\"TeachingTip_AddPackageToGetStarted\" xml:space=\"preserve\">\n    <value>添加軟件包即可開始使用！</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Name\" xml:space=\"preserve\">\n    <value>名稱</value>\n  </data>\n  <data name=\"Label_EnvVarsTable_Value\" xml:space=\"preserve\">\n    <value>值</value>\n  </data>\n  <data name=\"Action_Remove\" xml:space=\"preserve\">\n    <value>移除</value>\n  </data>\n  <data name=\"Label_Details\" xml:space=\"preserve\">\n    <value>詳細信息</value>\n  </data>\n  <data name=\"Label_Callstack\" xml:space=\"preserve\">\n    <value>調用棧</value>\n  </data>\n  <data name=\"Label_InnerException\" xml:space=\"preserve\">\n    <value>內部異常</value>\n  </data>\n  <data name=\"Label_SearchEllipsis\" xml:space=\"preserve\">\n    <value>搜尋...</value>\n  </data>\n  <data name=\"Action_OK\" xml:space=\"preserve\">\n    <value>好的</value>\n  </data>\n  <data name=\"Action_Retry\" xml:space=\"preserve\">\n    <value>重試</value>\n  </data>\n  <data name=\"Label_PythonVersionInfo\" xml:space=\"preserve\">\n    <value>Python版本信息</value>\n  </data>\n  <data name=\"Action_Restart\" xml:space=\"preserve\">\n    <value>重新啟動</value>\n  </data>\n  <data name=\"Label_ConfirmDelete\" xml:space=\"preserve\">\n    <value>確認刪除</value>\n  </data>\n  <data name=\"Text_PackageUninstall_Details\" xml:space=\"preserve\">\n    <value>操作將會刪除軟件包文件夾及其所有的內容，包括任何已生成的圖像和您可能已添加的文件。</value>\n  </data>\n  <data name=\"Progress_UninstallingPackage\" xml:space=\"preserve\">\n    <value>軟件包卸載中...</value>\n  </data>\n  <data name=\"Label_PackageUninstalled\" xml:space=\"preserve\">\n    <value>軟件包已卸載</value>\n  </data>\n  <data name=\"Text_SomeFilesCouldNotBeDeleted\" xml:space=\"preserve\">\n    <value>某些文件無法被刪除。請關閉軟件包目錄中所有打開的文件，然後重試。</value>\n  </data>\n  <data name=\"Label_InvalidPackageType\" xml:space=\"preserve\">\n    <value>無效軟件包類型</value>\n  </data>\n  <data name=\"TextTemplate_UpdatingPackage\" xml:space=\"preserve\">\n    <value>正在更新 {0}</value>\n  </data>\n  <data name=\"Progress_UpdateComplete\" xml:space=\"preserve\">\n    <value>更新完畢</value>\n  </data>\n  <data name=\"TextTemplate_PackageUpdatedToLatest\" xml:space=\"preserve\">\n    <value>{0} 已更新至最新版本</value>\n  </data>\n  <data name=\"TextTemplate_ErrorUpdatingPackage\" xml:space=\"preserve\">\n    <value>更新出錯 {0}</value>\n  </data>\n  <data name=\"Progress_UpdateFailed\" xml:space=\"preserve\">\n    <value>更新失敗</value>\n  </data>\n  <data name=\"Action_OpenInBrowser\" xml:space=\"preserve\">\n    <value>在瀏覽器中打開</value>\n  </data>\n  <data name=\"Label_ErrorInstallingPackage\" xml:space=\"preserve\">\n    <value>安裝軟件包出錯</value>\n  </data>\n  <data name=\"Label_Branch\" xml:space=\"preserve\">\n    <value>分支</value>\n  </data>\n  <data name=\"Label_CivitAi\" xml:space=\"preserve\">\n    <value>CivitAI</value>\n  </data>\n  <data name=\"Action_GoToSettings\" xml:space=\"preserve\">\n    <value>前往設定</value>\n  </data>\n  <data name=\"Text_LykosAccountUpgradeNotice\" xml:space=\"preserve\">\n    <value>## 重要提示：Lykos 帳戶登出和安全性升級\n\n我們對 Stability Matrix 處理 Lykos 帳戶的方式進行了一些重要的幕後改進，升級到更安全、更便捷的登入系統 **(OAuth 2.0 with OpenID Connect)**。 因此，您已登出 Lykos 帳戶。\n\n### 為什麼進行此變更？\n\n您的安全和隱私對我們很重要。 此升級帶來：\n\n*   **簡化的體驗：** 登入 [account.lykos.ai](https://account.lykos.ai) 一次即可連接到 Stability Matrix 和其他 Lykos AI 服務。\n*   **更多登入方式：** 使用您現有的 [lykos.ai](https://lykos.ai) 帳戶，或使用 **Apple**、**GitHub** 或 **Google** 登入。\n*   **增強的隱私：** Stability Matrix 僅請求其所需的權限。\n*   **行業標準安全性：** 我們正在使用 OAuth 2.0，這是安全登入的黃金標準。\n*   **為未來做好準備：** 這允許與其他應用程式和服務進行安全連接。\n\n### 我需要做什麼？\n\n點擊**「前往設定」**按鈕，然後點擊**「Lykos 帳戶」**旁邊的**「連線」**。\n\n### Lykos 帳戶是必需的嗎？\n\n不，Stability Matrix 在沒有帳戶的情況下仍然可以正常運作。 但是，您的 Lykos 帳戶可以啟用一些額外的連接功能，例如為我們的 Patreon 支持者自動更新到開發版本（以及更多即將推出的功能）。</value>\n  </data>\n  <data name=\"Action_Enable\" xml:space=\"preserve\">\n    <value>啟用</value>\n  </data>\n  <data name=\"Action_Disable\" xml:space=\"preserve\">\n    <value>停用</value>\n  </data>\n  <data name=\"Text_Login_ConnectTitle\" xml:space=\"preserve\">\n    <value>連接您的 Lykos 帳戶</value>\n  </data>\n  <data name=\"Text_Login_ConnectDescription\" xml:space=\"preserve\">\n    <value>請使用您的 Lykos 帳戶登入以使用連接功能。</value>\n  </data>\n  <data name=\"Text_Login_ExpiredTitle\" xml:space=\"preserve\">\n    <value>請重新登入</value>\n  </data>\n  <data name=\"Text_Login_ExpiredDescription\" xml:space=\"preserve\">\n    <value>您的登入已過期。請重新登入以繼續。</value>\n  </data>\n  <data name=\"Sponsorship_Title\" xml:space=\"preserve\">\n    <value>支持 Stability Matrix</value>\n  </data>\n  <data name=\"Sponsorship_ExistingSupporterPreamble\" xml:space=\"preserve\">\n    <value>感謝您支持 Stability Matrix！</value>\n  </data>\n  <data name=\"Sponsorship_ReqAnyTier\" xml:space=\"preserve\">\n    <value>**{0}** 等功能是我們為支持者提供的眾多權益之一。您的貢獻有助於我們支付伺服器成本並支持 Stability Matrix 的開發。</value>\n  </data>\n  <data name=\"Sponsorship_ReqSpecificTier\" xml:space=\"preserve\">\n    <value>**{0}** 等功能需達到 **{1}** 支持者等級（或更高）方可使用。您的貢獻有助於我們支付更進階連接功能的伺服器成本，並讓我們能夠為大家不斷改進 Stability Matrix。</value>\n  </data>\n  <data name=\"Sponsorship_ConnectPatreonPrompt\" xml:space=\"preserve\">\n    <value>如果您已在 Patreon 上支持我們，請關聯您的帳戶以繼續。</value>\n  </data>\n  <data name=\"Label_AccountSettings\" xml:space=\"preserve\">\n    <value>帳戶設定</value>\n  </data>\n  <data name=\"Action_ViewSupportOptions\" xml:space=\"preserve\">\n    <value>檢視支持選項</value>\n  </data>\n  <data name=\"Action_MaybeLater\" xml:space=\"preserve\">\n    <value>稍後再說</value>\n  </data>\n  <data name=\"Label_Status\" xml:space=\"preserve\">\n    <value>狀態</value>\n  </data>\n  <data name=\"ModelDiscovery_Active\" xml:space=\"preserve\">\n    <value>啟用</value>\n  </data>\n  <data name=\"ModelDiscovery_Inactive\" xml:space=\"preserve\">\n    <value>未啟用（使用標準連線）</value>\n  </data>\n  <data name=\"ModelDiscovery_Description\" xml:space=\"preserve\">\n    <value>從 Civitai 等線上儲存庫瀏覽模型時，體驗更快的搜尋結果。</value>\n  </data>\n  <data name=\"ModelDiscovery_Disclaimer\" xml:space=\"preserve\">\n    <value>針對第三方儲存庫的實驗性最佳化。非官方合作；可用性可能有所不同。</value>\n  </data>\n  <data name=\"Label_Beta\" xml:space=\"preserve\">\n    <value>測試版</value>\n  </data>\n  <data name=\"ModelDiscovery_Title\" xml:space=\"preserve\">\n    <value>加速模型搜尋</value>\n  </data>\n  <data name=\"PromptAmplifier_Disclaimer\" xml:space=\"preserve\">\n    <value>### ✨ 新功能上線：Prompt Amplifier\n\n這是我們的 AI 助理，它使用實驗性的 Spark 模型，能為您的提示生成更具創意的擴充。\n\n請注意：Prompt Amplifier 會在我們安全的雲端環境中執行，**不會**在您的本機電腦上執行。\n\n### ☁️ 為何使用雲端？\n\nSpark 模型規模龐大，算力需求堪比兆級參數的基礎模型。我們重視本機功能，但 Spark 的進階能力需要雲端支援，現在即可透過我們的雲端服務使用。\n\n### 🔒 隱私至上\n\n我們極其重視您的隱私（詳情請參閱[生成式 AI 條款](https://lykos.ai/gen-ai-terms)）。Lykos AI 及必要的雲端服務提供者**絕不會**使用您的提示或生成結果來訓練 AI 模型。我們僅在為確保功能正常運作時處理您的資料，**處理完成後也只保留必要的元數據（如時間、Token 數量），不保留提示內容本身**。您的資料絕不會被出售或分享。</value>\n  </data>\n</root>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Logging/RichNLogTheme.cs",
    "content": "﻿using System.Collections;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing NLog;\nusing NLog.MessageTemplates;\nusing NLog.Targets;\n\nnamespace StabilityMatrix.Avalonia.Logging;\n\n// Define our own theme styles, inspired by Serilog.Expressions\npublic enum RichNLogThemeStyle\n{\n    /// <summary>\n    /// Foreground for main message text.\n    /// </summary>\n    Text,\n\n    /// <summary>\n    /// Foreground for logger name.\n    /// </summary>\n    LogArea,\n\n    /// <summary>\n    /// Boilerplate like timestamp, logger name parts\n    /// </summary>\n    SecondaryText,\n    TertiaryText,\n\n    /// <summary>\n    /// For errors, missing values\n    /// </summary>\n    Invalid,\n    Null,\n    Name,\n    String,\n    Number,\n    Boolean,\n    Scalar,\n    LevelVerbose,\n    LevelDebug,\n    LevelInformation,\n    LevelWarning,\n    LevelError,\n    LevelFatal,\n    // Add more specific styles if needed, e.g., SourceContext, RequestPath, etc.\n}\n\n[Localizable(false)]\npublic class RichNLogTheme\n{\n    // Map styles to ANSI codes (e.g. \"\\x1b[38;5;0253m\")\n    public Dictionary<RichNLogThemeStyle, string> Styles { get; set; } = new();\n\n    // Method to get style, falling back to default if not found\n    public string GetAnsiStyle(RichNLogThemeStyle style) =>\n        Styles.TryGetValue(style, out var ansi) ? ansi : string.Empty;\n\n    public void ApplyStyle(TextWriter writer, RichNLogThemeStyle style, string text)\n    {\n        var ansi = GetAnsiStyle(style);\n        if (!string.IsNullOrEmpty(ansi))\n            writer.Write(ansi);\n        writer.Write(text);\n        if (!string.IsNullOrEmpty(ansi))\n            writer.Write(AnsiReset);\n    }\n\n    // Using ANSI 256-color codes (like \\x1b[38;5;XXXm)\n    // Reference: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 (ANSI codes)\n\n    public static RichNLogTheme Simple { get; } =\n        new RichNLogTheme\n        {\n            Styles = new Dictionary<RichNLogThemeStyle, string>\n            {\n                // --- Log Level Styles ---\n                [RichNLogThemeStyle.LevelVerbose] = \"\\e[37m\", // White/Light Gray (Subtle)\n                [RichNLogThemeStyle.LevelDebug] = \"\\e[32;1m\", // Bold Green\n                [RichNLogThemeStyle.LevelInformation] = \"\\e[36;1m\", // Bold Cyan\n                [RichNLogThemeStyle.LevelWarning] = \"\\e[33;1m\", // Bold Yellow\n                [RichNLogThemeStyle.LevelError] = \"\\e[31;1m\", // Bold Red\n                [RichNLogThemeStyle.LevelFatal] = \"\\e[31;1m\\e[48;5;0238m\", // Bold black on red background\n                // --- Value Styles ---\n                [RichNLogThemeStyle.Null] = \"\\e[34m\", // Blue\n                [RichNLogThemeStyle.Name] = \"\\e[37m\", // White (or default)\n                [RichNLogThemeStyle.String] = \"\\e[36m\", // Cyan\n                [RichNLogThemeStyle.Number] = \"\\e[35m\", // Magenta\n                [RichNLogThemeStyle.Boolean] = \"\\e[34m\", // Blue\n                [RichNLogThemeStyle.Scalar] = \"\\e[38;5;0079m\", // Purple/Magenta\n                // --- Text Styles ---\n                [RichNLogThemeStyle.Text] = \"\\e[38;5;0253m\", // Near white\n                [RichNLogThemeStyle.SecondaryText] = \"\\e[38;5;0246m\", // Medium gray\n                [RichNLogThemeStyle.TertiaryText] = \"\\e[90m\", // Bright Black (Gray)\n            },\n        };\n    public static RichNLogTheme Default { get; } =\n        new RichNLogTheme\n        {\n            Styles = new Dictionary<RichNLogThemeStyle, string>\n            {\n                // --- Text Styles ---\n                { RichNLogThemeStyle.Text, \"\\e[38;5;0253m\" }, // Near white\n                { RichNLogThemeStyle.SecondaryText, \"\\e[38;5;0246m\" }, // Medium gray (Boilerplate like timestamp, logger name parts)\n                { RichNLogThemeStyle.TertiaryText, \"\\e[38;5;0242m\" }, // Darker gray (Punctuation like braces, colons, commas)\n                { RichNLogThemeStyle.Invalid, \"\\e[33;1m\" }, // Bold Yellow (For errors, missing values - standard ANSI bright)\n                // --- Value Styles ---\n                { RichNLogThemeStyle.Null, \"\\e[38;5;0038m\" }, // Blue/Purple (For `null` keyword)\n                { RichNLogThemeStyle.Name, \"\\e[37m\" }, // Light Cyan/Blue (Property names, logger name)\n                { RichNLogThemeStyle.String, \"\\e[38;5;0216m\" }, // Orange/Pink (String literals)\n                { RichNLogThemeStyle.Number, \"\\e[38;5;151m\" }, // Light Green (Numeric values)\n                { RichNLogThemeStyle.Boolean, \"\\e[38;5;0038m\" }, // Blue/Purple (For `true`/`false`)\n                { RichNLogThemeStyle.Scalar, \"\\e[38;5;0079m\" }, // Purple/Magenta (Other scalar types like Guid, DateTime)\n                // --- Log Level Styles ---\n                { RichNLogThemeStyle.LevelVerbose, \"\\e[37m\" }, // White/Light Gray (Subtle)\n                { RichNLogThemeStyle.LevelDebug, \"\\e[37m\" }, // White/Light Gray (Subtle) - Same as Verbose\n                { RichNLogThemeStyle.LevelInformation, \"\\e[37;1m\" }, // Bold White (Default, noticeable)\n                { RichNLogThemeStyle.LevelWarning, \"\\e[38;5;0229m\" }, // Bright Yellow/Gold\n                { RichNLogThemeStyle.LevelError, \"\\e[38;5;0197m\" }, // Bright Red foreground\n                { RichNLogThemeStyle.LevelFatal, \"\\e[38;5;0197m\\e[48;5;0238m\" }, // Bright Red foreground on dark gray background\n\n                // --- Custom Semantic Styles (Optional examples) ---\n                // { RichNLogThemeStyle.SourceContext, \"\\x1b[38;5;0248m\" }, // Example: slightly different gray for source context\n                // { RichNLogThemeStyle.HttpRequest,   \"\\x1b[38;5;0111m\" }, // Example: a distinct color for HTTP request paths\n            },\n        };\n\n    public static RichNLogTheme Code { get; } =\n        new()\n        {\n            Styles = new Dictionary<RichNLogThemeStyle, string>\n            {\n                [RichNLogThemeStyle.Text] = \"\\e[38;5;0253m\",\n                [RichNLogThemeStyle.SecondaryText] = \"\\e[38;5;0246m\",\n                [RichNLogThemeStyle.TertiaryText] = \"\\e[38;5;0242m\",\n                [RichNLogThemeStyle.Invalid] = \"\\e[33;1m\",\n                [RichNLogThemeStyle.Null] = \"\\e[38;5;0038m\",\n                [RichNLogThemeStyle.Name] = \"\\e[38;5;0081m\",\n                [RichNLogThemeStyle.String] = \"\\e[38;5;0216m\",\n                [RichNLogThemeStyle.Number] = \"\\e[38;5;151m\",\n                [RichNLogThemeStyle.Boolean] = \"\\e[38;5;0038m\",\n                [RichNLogThemeStyle.Scalar] = \"\\e[38;5;0079m\",\n                [RichNLogThemeStyle.LevelVerbose] = \"\\e[38;5;8m\",\n                [RichNLogThemeStyle.LevelDebug] = \"\\e[37m\",\n                [RichNLogThemeStyle.LevelInformation] = \"\\e[37;1m\",\n                [RichNLogThemeStyle.LevelWarning] = \"\\e[38;5;0229m\",\n                [RichNLogThemeStyle.LevelError] = \"\\e[38;5;0197m\\e[48;5;0238m\",\n                [RichNLogThemeStyle.LevelFatal] = \"\\e[38;5;0197m\\e[48;5;0238m\",\n            },\n        };\n\n    public static RichNLogTheme Code2 { get; } =\n        new()\n        {\n            Styles = new Dictionary<RichNLogThemeStyle, string>\n            {\n                [RichNLogThemeStyle.Text] = \"\\e[38;5;0015m\",\n                [RichNLogThemeStyle.SecondaryText] = \"\\e[38;5;0246m\",\n                [RichNLogThemeStyle.TertiaryText] = \"\\e[38;5;0242m\",\n                [RichNLogThemeStyle.LogArea] = \"\\e[37;1m\",\n                [RichNLogThemeStyle.Invalid] = \"\\e[33;1m\",\n                [RichNLogThemeStyle.Null] = \"\\e[38;5;0038m\",\n                [RichNLogThemeStyle.Name] = \"\\e[38;5;0081m\",\n                [RichNLogThemeStyle.String] = \"\\e[38;5;0216m\",\n                [RichNLogThemeStyle.Number] = \"\\e[38;5;151m\",\n                [RichNLogThemeStyle.Boolean] = \"\\e[38;5;0038m\",\n                [RichNLogThemeStyle.Scalar] = \"\\e[38;5;0079m\",\n                // --- Log Level Styles ---\n                [RichNLogThemeStyle.LevelVerbose] = \"\\e[37m\", // White/Light Gray (Subtle)\n                [RichNLogThemeStyle.LevelDebug] = \"\\e[32;1m\", // Bold Green\n                [RichNLogThemeStyle.LevelInformation] = \"\\e[36;1m\", // Bold Cyan\n                [RichNLogThemeStyle.LevelWarning] = \"\\e[33;1m\", // Bold Yellow\n                [RichNLogThemeStyle.LevelError] = \"\\e[31;1m\", // Bold Red\n                [RichNLogThemeStyle.LevelFatal] = \"\\e[31;1m\\e[48;5;0238m\", // Bold black on red background\n            },\n        };\n\n    public static RichNLogTheme Literate { get; } =\n        new()\n        {\n            Styles = new Dictionary<RichNLogThemeStyle, string>\n            {\n                [RichNLogThemeStyle.Text] = \"\\e[38;5;0015m\",\n                [RichNLogThemeStyle.SecondaryText] = \"\\e[38;5;0007m\",\n                [RichNLogThemeStyle.TertiaryText] = \"\\e[38;5;0008m\",\n                [RichNLogThemeStyle.Invalid] = \"\\e[38;5;0011m\",\n                [RichNLogThemeStyle.Null] = \"\\e[38;5;0027m\",\n                [RichNLogThemeStyle.Name] = \"\\e[38;5;0007m\",\n                [RichNLogThemeStyle.String] = \"\\e[38;5;0045m\",\n                [RichNLogThemeStyle.Number] = \"\\e[38;5;0200m\",\n                [RichNLogThemeStyle.Boolean] = \"\\e[38;5;0027m\",\n                [RichNLogThemeStyle.Scalar] = \"\\e[38;5;0085m\",\n                [RichNLogThemeStyle.LevelVerbose] = \"\\e[38;5;0007m\",\n                [RichNLogThemeStyle.LevelDebug] = \"\\e[38;5;0007m\",\n                [RichNLogThemeStyle.LevelInformation] = \"\\e[38;5;0015m\",\n                [RichNLogThemeStyle.LevelWarning] = \"\\e[38;5;0011m\",\n                [RichNLogThemeStyle.LevelError] = \"\\e[38;5;0015m\\e[48;5;0196m\",\n                [RichNLogThemeStyle.LevelFatal] = \"\\e[38;5;0015m\\e[48;5;0196m\",\n            },\n        };\n\n    public const string AnsiReset = \"\\e[0m\";\n}\n\n[Localizable(false)]\n[Target(\"RichConsole\")]\npublic sealed class RichConsoleTarget : TargetWithLayout\n{\n    public RichNLogTheme Theme { get; set; } = RichNLogTheme.Default; // Allow theme customization\n\n    public RichConsoleTarget()\n        : this(\"RichConsoleTarget\") { }\n\n    public RichConsoleTarget(string name)\n    {\n        Name = name;\n    }\n\n    protected override void Write(LogEventInfo logEvent)\n    {\n        // Using Console.Out directly for simplicity. Consider locking or async for production.\n        var writer = Console.Out;\n        var theme = Theme; // Use the configured theme\n\n        // --- Timestamp ---\n        ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, logEvent.TimeStamp.ToString(\"HH:mm:ss.fff\"));\n        writer.Write(\" \");\n\n        // --- Level ---\n        RenderLevel(writer, logEvent.Level);\n        writer.Write(\" \");\n\n        // --- Logger Name ---\n        var formattedLoggerName = logEvent.LoggerName?.Split(\".\").Last();\n        if (formattedLoggerName != null)\n        {\n            ApplyStyle(writer, RichNLogThemeStyle.LogArea, formattedLoggerName);\n            ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \":\");\n            writer.Write(\" \");\n        }\n\n        // --- Message Template and Parameters ---\n        RenderMessage(writer, logEvent, theme);\n        writer.Write(\" \");\n\n        // --- Properties (Similar to Layout.FromMethod, but with styling) ---\n        // Exclude any already rendered properties\n        /*var templateParamNames = logEvent\n            .MessageTemplateParameters\n            .Select(p => p.Name)\n            .Where(name => !string.IsNullOrEmpty(name))\n            .ToHashSet();\n        var renderProperties = logEvent\n            .Properties\n            .Where(p => p.Key is string && !templateParamNames.Contains(p.Key))\n            .ToDictionary(p => p.Key, p => p.Value);\n\n        RenderPropertiesBlock(writer, renderProperties, theme);*/\n        // RenderProperties(writer, renderProperties);\n\n        // --- Exception ---\n        if (logEvent.Exception != null)\n        {\n            writer.WriteLine();\n            // Potentially style different parts of the exception/stack trace\n            ApplyStyle(writer, RichNLogThemeStyle.Invalid, logEvent.Exception.ToString());\n        }\n\n        writer.WriteLine(); // Final newline for the log entry\n    }\n\n    private void RenderLevel(TextWriter writer, LogLevel level)\n    {\n        RichNLogThemeStyle style;\n        string levelText;\n\n        if (level == LogLevel.Trace)\n        {\n            style = RichNLogThemeStyle.LevelVerbose;\n            levelText = \"TRACE\";\n        }\n        else if (level == LogLevel.Debug)\n        {\n            style = RichNLogThemeStyle.LevelDebug;\n            levelText = \"DEBUG\";\n        }\n        else if (level == LogLevel.Info)\n        {\n            style = RichNLogThemeStyle.LevelInformation;\n            levelText = \"INFO\";\n        }\n        else if (level == LogLevel.Warn)\n        {\n            style = RichNLogThemeStyle.LevelWarning;\n            levelText = \"WARN\";\n        }\n        else if (level == LogLevel.Error)\n        {\n            style = RichNLogThemeStyle.LevelError;\n            levelText = \"ERROR\";\n        }\n        else if (level == LogLevel.Fatal)\n        {\n            style = RichNLogThemeStyle.LevelFatal;\n            levelText = \"FATAL\";\n        }\n        else\n        {\n            style = RichNLogThemeStyle.SecondaryText;\n            levelText = level.ToString().ToUpperInvariant();\n        }\n\n        // Fixed width\n        levelText = levelText.PadRight(5);\n\n        ApplyStyle(writer, style, levelText);\n    }\n\n    /*private void RenderMessage(TextWriter writer, LogEventInfo logEvent)\n    {\n        var template = logEvent.Message ?? logEvent.FormattedMessage ?? \"\"; // Fallback\n        var properties = logEvent.Properties; // Primarily use properties for named lookup\n\n        var lastIndex = 0;\n        while (lastIndex < template.Length)\n        {\n            var openBrace = template.IndexOf('{', lastIndex);\n\n            // End of template or no more placeholders\n            if (openBrace == -1)\n            {\n                if (lastIndex < template.Length)\n                {\n                    ApplyStyle(writer, RichNLogThemeStyle.Text, template.Substring(lastIndex));\n                }\n                break;\n            }\n\n            // Handle escaped brace {{\n            if (openBrace + 1 < template.Length && template[openBrace + 1] == '{')\n            {\n                ApplyStyle(\n                    writer,\n                    RichNLogThemeStyle.Text,\n                    template.Substring(lastIndex, openBrace - lastIndex + 1)\n                ); // Render text + single {\n                lastIndex = openBrace + 2; // Skip {{\n                continue;\n            }\n\n            // Render literal text before the placeholder\n            if (openBrace > lastIndex)\n            {\n                ApplyStyle(\n                    writer,\n                    RichNLogThemeStyle.Text,\n                    template.Substring(lastIndex, openBrace - lastIndex)\n                );\n            }\n\n            // Find the matching closing brace }\n            var closeBraceScan = openBrace + 1;\n            var closeBrace = -1;\n            var braceDepth = 0; // Handle nested braces within format specifiers if needed (simple case here)\n            while (closeBraceScan < template.Length)\n            {\n                if (\n                    closeBraceScan + 1 < template.Length\n                    && template[closeBraceScan] == '}'\n                    && template[closeBraceScan + 1] == '}'\n                )\n                {\n                    // Skip escaped }}\n                    closeBraceScan += 2;\n                    continue;\n                }\n\n                if (template[closeBraceScan] == '{')\n                    braceDepth++;\n                else if (template[closeBraceScan] == '}')\n                {\n                    if (braceDepth == 0)\n                    {\n                        closeBrace = closeBraceScan;\n                        break;\n                    }\n                    braceDepth--;\n                }\n                closeBraceScan++;\n            }\n\n            if (closeBrace == -1) // Malformed - no closing brace found\n            {\n                ApplyStyle(writer, RichNLogThemeStyle.Invalid, template.Substring(openBrace)); // Render rest as invalid\n                lastIndex = template.Length;\n                break;\n            }\n\n            // Extract placeholder content (e.g., \"UserId:N5\" or \"0,10\")\n            var placeholderContent = template.Substring(openBrace + 1, closeBrace - openBrace - 1);\n\n            // Basic parsing for name, format, and alignment (alignment not used here yet)\n            var name = placeholderContent;\n            string? format = null;\n            // int? alignment = null; // Alignment parsing would go here\n\n            var formatColon = placeholderContent.IndexOf(':');\n            var alignmentComma = placeholderContent.IndexOf(','); // Handle alignment separator ','\n\n            if (formatColon != -1 && (alignmentComma == -1 || formatColon < alignmentComma)) // Colon before comma or no comma\n            {\n                name = placeholderContent.Substring(0, formatColon);\n                format = placeholderContent.Substring(formatColon + 1);\n                // Further parse alignment out of 'name' if comma exists before colon\n                if (alignmentComma != -1 && alignmentComma < formatColon)\n                {\n                    name = placeholderContent.Substring(0, alignmentComma);\n                    // Parse alignment value if needed\n                }\n            }\n            else if (alignmentComma != -1) // Comma but no colon, or comma before colon\n            {\n                name = placeholderContent.Substring(0, alignmentComma);\n                // Parse alignment value if needed\n                // format might be after comma if no colon, or just alignment exists\n            }\n\n            // Attempt to find the value primarily using the name from Properties\n            object? value = null;\n            var valueFound = false;\n            if (properties != null && properties.TryGetValue(name, out var propValue))\n            {\n                value = propValue;\n                valueFound = true;\n            }\n            // Add positional fallback if needed (e.g., if name is purely numeric and not in properties)\n            else if (\n                int.TryParse(name, out var positionalIndex)\n                && logEvent.Parameters != null\n                && positionalIndex >= 0\n                && positionalIndex < logEvent.Parameters.Length\n            )\n            {\n                value = logEvent.Parameters[positionalIndex];\n                valueFound = true;\n            }\n\n            // Render the value using styling and format\n            if (valueFound)\n            {\n                RenderPropertyValue(writer, value, format);\n            }\n            else // Value not found - render placeholder text as invalid/missing\n            {\n                ApplyStyle(writer, RichNLogThemeStyle.Invalid, $\"{{{placeholderContent}}}\");\n            }\n\n            lastIndex = closeBrace + 1; // Move past the processed placeholder\n        }\n    }*/\n\n    // --- Render Message (Parsing Template) ---\n    private void RenderMessage(TextWriter writer, LogEventInfo logEvent, RichNLogTheme theme)\n    {\n        var template = logEvent.Message ?? logEvent.FormattedMessage ?? \"\";\n        var properties = logEvent.Properties;\n\n        var lastIndex = 0;\n        // Keep track of positional parameter index if needed for fallback\n        // int positionalParamIndex = 0;\n\n        while (lastIndex < template.Length)\n        {\n            var openBrace = template.IndexOf('{', lastIndex);\n            if (openBrace == -1)\n            {\n                if (lastIndex < template.Length)\n                    theme.ApplyStyle(writer, RichNLogThemeStyle.Text, template.Substring(lastIndex));\n                break;\n            }\n\n            if (openBrace + 1 < template.Length && template[openBrace + 1] == '{') // Escaped {{\n            {\n                theme.ApplyStyle(\n                    writer,\n                    RichNLogThemeStyle.Text,\n                    template.Substring(lastIndex, openBrace - lastIndex + 1)\n                );\n                lastIndex = openBrace + 2;\n                continue;\n            }\n\n            if (openBrace > lastIndex)\n                theme.ApplyStyle(\n                    writer,\n                    RichNLogThemeStyle.Text,\n                    template.Substring(lastIndex, openBrace - lastIndex)\n                );\n\n            var closeBrace = FindClosingBrace(template, openBrace + 1);\n\n            if (closeBrace == -1) // Malformed\n            {\n                theme.ApplyStyle(writer, RichNLogThemeStyle.Invalid, template.Substring(openBrace));\n                lastIndex = template.Length;\n                break;\n            }\n\n            var placeholderContent = template.Substring(openBrace + 1, closeBrace - openBrace - 1);\n            ParsePlaceholder(placeholderContent, out var name, out var format, out var captureType); // Pass captureType out\n\n            object? value = null;\n            var valueFound = properties != null && properties.TryGetValue(name, out value);\n            // Add positional fallback logic here if required, using logEvent.Parameters and positionalParamIndex\n\n            if (valueFound)\n            {\n                // Pass captureType and format to RenderPropertyValue\n                RenderPropertyValue(writer, value, theme, format, captureType);\n            }\n            else\n            {\n                theme.ApplyStyle(writer, RichNLogThemeStyle.Invalid, $\"{{{placeholderContent}}}\");\n            }\n\n            lastIndex = closeBrace + 1;\n            // if (valueFound && IsPositional(name)) positionalParamIndex++; // Increment if positional logic used\n        }\n    }\n\n    private static void ParsePlaceholder(\n        string content,\n        out string name,\n        out string? format,\n        out CaptureType captureType\n    )\n    {\n        // Defaults\n        captureType = CaptureType.Normal;\n        name = content; // Assume full content is name initially\n        format = null;\n\n        if (string.IsNullOrEmpty(content))\n            return; // Empty placeholder? Handle gracefully.\n\n        var nameStart = 0;\n\n        // Check for capture type sigil at the beginning\n        if (content[0] == '@')\n        {\n            captureType = CaptureType.Serialize;\n            nameStart = 1;\n        }\n        else if (content[0] == '$')\n        {\n            captureType = CaptureType.Stringify;\n            nameStart = 1;\n        }\n        // No sigil means CaptureType.Normal\n\n        // Find format/alignment after the name part\n        var formatColon = -1;\n        var alignmentComma = -1;\n\n        for (var i = nameStart; i < content.Length; ++i)\n        {\n            if (content[i] == ':')\n            {\n                formatColon = i;\n                break; // Found format first\n            }\n\n            if (content[i] == ',')\n            {\n                alignmentComma = i;\n                // Don't break, format might come after alignment\n            }\n        }\n\n        var nameEnd = content.Length; // Default end of name is end of content\n\n        if (formatColon != -1 && (alignmentComma == -1 || formatColon < alignmentComma))\n        {\n            // Format found, and it comes before any alignment or there's no alignment\n            nameEnd = formatColon;\n            format = content[(formatColon + 1)..];\n        }\n        else if (alignmentComma != -1)\n        {\n            // Alignment found, and it comes before any format or there's no format\n            nameEnd = alignmentComma;\n            // Format might be after alignment comma if no colon was found earlier\n            if (formatColon == -1 && alignmentComma + 1 < content.Length)\n            {\n                // format = content.Substring(alignmentComma + 1); // Basic assumption\n            }\n            // TODO: Handle parsing the alignment value itself if needed\n        }\n\n        // Extract the name based on start index and calculated end\n        name = content.Substring(nameStart, nameEnd - nameStart);\n    }\n\n    /// <summary>\n    /// Finds the index of the matching closing brace '}' for an opening brace '{',\n    /// starting the search from a specified position within a string template.\n    /// Correctly handles nested braces and escaped closing braces ('}}').\n    /// </summary>\n    /// <param name=\"template\">The string template to search within.</param>\n    /// <param name=\"startIndex\">The index within the template immediately after the opening brace for which the corresponding closing brace should be found.</param>\n    /// <returns>The index of the matching closing brace, or -1 if no matching closing brace is found before the end of the template.</returns>\n    private static int FindClosingBrace(string template, int startIndex)\n    {\n        var braceDepth = 0;\n        for (var i = startIndex; i < template.Length; ++i)\n        {\n            if (i + 1 < template.Length && template[i] == '}' && template[i + 1] == '}')\n            {\n                i++; // Skip escaped }}\n                continue;\n            }\n\n            if (template[i] == '{')\n                braceDepth++;\n            else if (template[i] == '}')\n            {\n                if (braceDepth == 0)\n                    return i;\n                braceDepth--;\n            }\n        }\n\n        return -1; // Not found\n    }\n\n    private void RenderProperties(TextWriter writer, IDictionary<object, object> properties)\n    {\n        // Filter out internal/unwanted properties if needed\n        var propsToRender = properties.Where(p => p.Key is string key && key != \"CallerMemberName\").ToList();\n\n        if (!propsToRender.Any())\n            return;\n\n        writer.Write(\" \"); // Space before properties start\n        ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \"{ \"); // Opening brace\n\n        var firstProp = true;\n        foreach (var prop in propsToRender)\n        {\n            if (!firstProp)\n                ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \", \");\n            firstProp = false;\n\n            // Style the property name\n            ApplyStyle(writer, RichNLogThemeStyle.Name, prop.Key.ToString() ?? \"null\");\n            ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \"=\");\n\n            // Style the property value based on its type\n            RenderPropertyValue(writer, prop.Value);\n        }\n\n        ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \" }\"); // Closing brace\n    }\n\n    // --- Render Properties Block (Optional alternative to inline rendering) ---\n    private void RenderPropertiesBlock(\n        TextWriter writer,\n        IReadOnlyDictionary<object, object> properties,\n        RichNLogTheme theme\n    )\n    {\n        var propsToRender = properties\n            ?.Where(p =>\n                p.Key is string key /* && key != \"SomeInternalProp\" */\n            ) // Filter if needed\n            .ToList();\n\n        if (propsToRender == null || !propsToRender.Any())\n            return;\n\n        writer.Write(\" \"); // Separator\n        theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \"{\");\n        var firstProp = true;\n        foreach (var prop in propsToRender)\n        {\n            if (!firstProp)\n                theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \", \");\n            firstProp = false;\n\n            theme.ApplyStyle(writer, RichNLogThemeStyle.Name, prop.Key.ToString() ?? \"null\");\n            theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \": \"); // Use colon-space\n\n            // Render property value, assuming complex types should be serialized\n            RenderPropertyValue(writer, prop.Value, theme, null, CaptureType.Serialize);\n        }\n\n        theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \"}\");\n    }\n\n    // --- Render Individual Property Value (Recursive) ---\n    private void RenderPropertyValue(\n        TextWriter writer,\n        object? value,\n        RichNLogTheme theme,\n        string? format = null,\n        CaptureType captureType = CaptureType.Normal,\n        int maxDepth = 5,\n        int currentDepth = 0\n    )\n    {\n        // --- Handle Base Cases and Known Scalars ---\n        // Accept intrinsic types when 1. not in serialize mode or 2. if we're in a nested context\n        var renderIntrinsic = captureType != CaptureType.Serialize || currentDepth > 0;\n\n        switch (value)\n        {\n            case null:\n                theme.ApplyStyle(writer, RichNLogThemeStyle.Null, \"null\");\n                return;\n            case string s when renderIntrinsic:\n                theme.ApplyStyle(writer, RichNLogThemeStyle.String, $\"\\\"{EscapeString(s)}\\\"\"); // Add escaping\n                return;\n            case bool b when renderIntrinsic:\n                theme.ApplyStyle(\n                    writer,\n                    RichNLogThemeStyle.Boolean,\n                    (format?.ToLowerInvariant() == \"u\") ? (b ? \"TRUE\" : \"FALSE\") : (b ? \"true\" : \"false\")\n                );\n                return;\n            // Handle numeric types applying format\n            case (\n                int\n                or uint\n                or long\n                or ulong\n                or decimal\n                or byte\n                or sbyte\n                or short\n                or ushort\n                or float\n                or double\n            )\n            and IFormattable formattableNum when renderIntrinsic:\n                string numStr;\n                try\n                {\n                    numStr = formattableNum.ToString(\n                        format,\n                        System.Globalization.CultureInfo.InvariantCulture\n                    );\n                }\n                catch (FormatException)\n                {\n                    numStr =\n                        formattableNum.ToString(null, System.Globalization.CultureInfo.InvariantCulture)\n                        ?? \"null\";\n                }\n\n                theme.ApplyStyle(writer, RichNLogThemeStyle.Number, numStr);\n                return;\n            // Handle DateTime types applying format\n            case DateTime dt when value is IFormattable formattableDt && renderIntrinsic:\n                string dtStr;\n                try\n                {\n                    dtStr = formattableDt.ToString(\n                        string.IsNullOrEmpty(format) ? \"O\" : format,\n                        System.Globalization.CultureInfo.InvariantCulture\n                    );\n                }\n                catch (FormatException)\n                {\n                    dtStr = formattableDt.ToString(\"O\", System.Globalization.CultureInfo.InvariantCulture);\n                }\n\n                theme.ApplyStyle(writer, RichNLogThemeStyle.Scalar, $\"\\\"{EscapeString(dtStr)}\\\"\");\n                return;\n            case DateTimeOffset dto when value is IFormattable formattableDto && renderIntrinsic:\n                string dtoStr;\n                try\n                {\n                    dtoStr = formattableDto.ToString(\n                        string.IsNullOrEmpty(format) ? \"O\" : format,\n                        System.Globalization.CultureInfo.InvariantCulture\n                    );\n                }\n                catch (FormatException)\n                {\n                    dtoStr = formattableDto.ToString(\"O\", System.Globalization.CultureInfo.InvariantCulture);\n                }\n\n                theme.ApplyStyle(writer, RichNLogThemeStyle.Scalar, $\"\\\"{EscapeString(dtoStr)}\\\"\");\n                return;\n            // Add Guid, TimeSpan etc. if needed\n        }\n\n        // --- Handle Recursive Cases (Collections, Dictionaries, Objects) or Forced Serialization ---\n        if (currentDepth >= maxDepth)\n        {\n            theme.ApplyStyle(writer, RichNLogThemeStyle.Invalid, \"\\\"...\\\"\"); // Indicate recursion limit\n            return;\n        }\n\n        // Check for Dictionary\n        if (value is IDictionary dict)\n        {\n            theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \"{ \");\n            var firstItem = true;\n            foreach (DictionaryEntry entry in dict)\n            {\n                if (!firstItem)\n                    theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \", \");\n                firstItem = false;\n                // Render key as string (usually)\n                RenderPropertyValue(\n                    writer,\n                    entry.Key?.ToString(),\n                    theme,\n                    null,\n                    CaptureType.Normal,\n                    maxDepth,\n                    currentDepth + 1\n                );\n                theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \": \");\n                // Render value recursively\n                RenderPropertyValue(\n                    writer,\n                    entry.Value,\n                    theme,\n                    null,\n                    captureType,\n                    maxDepth,\n                    currentDepth + 1\n                );\n            }\n\n            theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \" }\");\n            return;\n        }\n\n        // Check for non-string IEnumerable (Collections/Arrays)\n        if (value is IEnumerable collection and not string)\n        {\n            theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \"[\");\n            var firstItem = true;\n            foreach (var item in collection)\n            {\n                if (!firstItem)\n                    theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \", \");\n                firstItem = false;\n                // Render item recursively\n                RenderPropertyValue(writer, item, theme, null, captureType, maxDepth, currentDepth + 1);\n            }\n\n            theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \"]\");\n            return;\n        }\n\n        // --- Stringify: Render as string with quotes ---\n        if (captureType == CaptureType.Stringify)\n        {\n            // Use ToString() and render as string\n            theme.ApplyStyle(\n                writer,\n                RichNLogThemeStyle.String,\n                $\"\\\"{EscapeString(value.ToString() ?? \"\")}\\\"\"\n            );\n            return;\n        }\n\n        // --- Normal: Render as scalar ---\n        if (captureType == CaptureType.Normal)\n        {\n            // Use ToString() and render as scalar\n            theme.ApplyStyle(writer, RichNLogThemeStyle.Scalar, value.ToString() ?? \"null\");\n            return;\n        }\n\n        // If we got here, it's likely CaptureType.Serialize, OR it's a complex object we didn't handle above\n        // Render as an object using public properties\n        theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \"{ \");\n        try\n        {\n            var properties = value\n                ?.GetType()\n                .GetProperties(\n                    System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance\n                )\n                .Where(p => p.CanRead && p.GetIndexParameters().Length == 0) // Exclude indexers\n                .ToList();\n            var firstProp = true;\n            if (properties != null)\n            {\n                foreach (var prop in properties)\n                {\n                    if (!firstProp)\n                    {\n                        theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \", \");\n                    }\n\n                    firstProp = false;\n                    theme.ApplyStyle(writer, RichNLogThemeStyle.Name, prop.Name);\n                    theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \": \");\n                    var propValue = prop.GetValue(value);\n                    RenderPropertyValue(\n                        writer,\n                        propValue,\n                        theme,\n                        null,\n                        captureType,\n                        maxDepth,\n                        currentDepth + 1\n                    );\n                }\n            }\n        }\n        catch (Exception ex)\n        {\n            // Handle reflection errors, cyclical references might cause stack overflow without depth check\n            /*if (!firstProp)\n                theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \", \");*/\n\n            theme.ApplyStyle(writer, RichNLogThemeStyle.Invalid, $\"\\\"<Error: {ex.GetType().Name}>\\\"\");\n        }\n\n        theme.ApplyStyle(writer, RichNLogThemeStyle.TertiaryText, \" }\");\n    }\n\n    private void RenderPropertyValue(TextWriter writer, object? value, string? format = null)\n    {\n        RichNLogThemeStyle style;\n        string textValue;\n        var quoted = false;\n\n        switch (value)\n        {\n            case null:\n                style = RichNLogThemeStyle.Null;\n                textValue = \"null\";\n                break;\n            case string s:\n                style = RichNLogThemeStyle.String;\n                textValue = s;\n                quoted = true;\n                break;\n            // Handle numeric types - Apply format if present\n            case int\n            or uint\n            or long\n            or ulong\n            or decimal\n            or byte\n            or sbyte\n            or short\n            or ushort\n            or float\n            or double when value is IFormattable formattableValue:\n                style = RichNLogThemeStyle.Number;\n                try\n                {\n                    textValue = formattableValue.ToString(\n                        format,\n                        System.Globalization.CultureInfo.InvariantCulture\n                    );\n                }\n                catch (FormatException)\n                {\n                    textValue =\n                        formattableValue.ToString(null, System.Globalization.CultureInfo.InvariantCulture)\n                        ?? \"null\";\n                } // Fallback on format error\n\n                break;\n            case bool b:\n                style = RichNLogThemeStyle.Boolean;\n                // Booleans usually don't have formats, but handle just in case\n                textValue =\n                    (format?.ToLowerInvariant() == \"u\") ? (b ? \"TRUE\" : \"FALSE\") : (b ? \"true\" : \"false\");\n                break;\n            // Handle DateTime types - Apply format if present\n            case DateTime dt when value is IFormattable formattableDt:\n                style = RichNLogThemeStyle.Scalar;\n                try\n                {\n                    textValue = formattableDt.ToString(\n                        string.IsNullOrEmpty(format) ? \"O\" : format,\n                        System.Globalization.CultureInfo.InvariantCulture\n                    );\n                }\n                catch (FormatException)\n                {\n                    textValue = formattableDt.ToString(\n                        \"O\",\n                        System.Globalization.CultureInfo.InvariantCulture\n                    );\n                } // Fallback\n\n                quoted = true;\n                break;\n            case DateTimeOffset dto when value is IFormattable formattableDto:\n                style = RichNLogThemeStyle.Scalar;\n                try\n                {\n                    textValue = formattableDto.ToString(\n                        string.IsNullOrEmpty(format) ? \"O\" : format,\n                        System.Globalization.CultureInfo.InvariantCulture\n                    );\n                }\n                catch (FormatException)\n                {\n                    textValue = formattableDto.ToString(\n                        \"O\",\n                        System.Globalization.CultureInfo.InvariantCulture\n                    );\n                } // Fallback\n\n                quoted = true;\n                break;\n            // Add other specific types if needed (Guid, TimeSpan, etc.)\n            default:\n                style = RichNLogThemeStyle.Scalar;\n                // Attempt to apply format if the object is IFormattable\n                if (!string.IsNullOrEmpty(format) && value is IFormattable formattableObj)\n                {\n                    try\n                    {\n                        textValue = formattableObj.ToString(\n                            format,\n                            System.Globalization.CultureInfo.InvariantCulture\n                        );\n                    }\n                    catch (FormatException)\n                    {\n                        textValue = value?.ToString() ?? \"null\";\n                    } // Fallback\n                }\n                else\n                {\n                    textValue = value?.ToString() ?? \"null\";\n                }\n\n                quoted = true;\n                break;\n        }\n\n        // Apply quoting if necessary (basic) - Needs escaping logic for real use\n        if (quoted)\n            textValue = $\"\\\"{textValue.Replace(\"\\\"\", \"\\\\\\\"\")}\\\"\";\n\n        ApplyStyle(writer, style, textValue);\n    }\n\n    // --- Helper for basic string escaping ---\n    private string EscapeString(string? s)\n    {\n        if (s == null)\n            return string.Empty;\n        return s.Replace(\"\\\\\", \"\\\\\\\\\").Replace(\"\\\"\", \"\\\\\\\"\"); // Basic escaping\n    }\n\n    private void ApplyStyle(TextWriter writer, RichNLogThemeStyle style, string text)\n    {\n        var ansi = Theme.GetAnsiStyle(style);\n        if (!string.IsNullOrEmpty(ansi))\n            writer.Write(ansi);\n        writer.Write(text);\n        if (!string.IsNullOrEmpty(ansi))\n            writer.Write(RichNLogTheme.AnsiReset);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/MarkupExtensions/EnumValuesExtension.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Avalonia.Markup.Xaml;\n\nnamespace StabilityMatrix.Avalonia.MarkupExtensions;\n\npublic class EnumValuesExtension<TEnum> : MarkupExtension\n    where TEnum : struct, Enum\n{\n    public TEnum? MinValue { get; set; }\n\n    public TEnum? MaxValue { get; set; }\n\n    public bool NoDefault { get; set; }\n\n    public override IEnumerable<TEnum> ProvideValue(IServiceProvider serviceProvider)\n    {\n        var values = Enum.GetValues<TEnum>().AsEnumerable();\n\n        if (NoDefault)\n        {\n            values = values.Where(value => !EqualityComparer<TEnum>.Default.Equals(value, default));\n        }\n\n        if (MinValue is not null)\n        {\n            values = values.Where(value => Comparer<TEnum>.Default.Compare(value, MinValue.Value) >= 0);\n        }\n\n        if (MaxValue is not null)\n        {\n            values = values.Where(value => Comparer<TEnum>.Default.Compare(value, MaxValue.Value) <= 0);\n        }\n\n        return values;\n    }\n}\n\npublic class EnumValuesExtension : MarkupExtension\n{\n    public Type? Type { get; set; }\n\n    public int? MinValue { get; set; }\n\n    public int? MaxValue { get; set; }\n\n    public bool NoDefault { get; set; }\n\n    public override IEnumerable<Enum> ProvideValue(IServiceProvider serviceProvider)\n    {\n        if (Type is null)\n        {\n            return [];\n        }\n\n        var values = Enum.GetValues(Type).Cast<Enum>().AsEnumerable();\n\n        if (NoDefault)\n        {\n            values = values.Where(value => value.CompareTo(default) != 0);\n        }\n\n        if (MinValue is not null)\n        {\n            values = values.Where(value => value.CompareTo(MinValue.Value) >= 0);\n        }\n\n        if (MaxValue is not null)\n        {\n            values = values.Where(value => value.CompareTo(MaxValue.Value) <= 0);\n        }\n\n        return values;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/MarkupExtensions/TernaryExtension.cs",
    "content": "﻿using System.Globalization;\nusing Avalonia;\nusing Avalonia.Data;\nusing Avalonia.Data.Converters;\nusing Avalonia.Markup.Xaml;\n\nnamespace StabilityMatrix.Avalonia.MarkupExtensions;\n\n/// <summary>\n/// Provides a ternary conditional logic within XAML.\n/// Usage: {ext:Ternary Condition={Binding SomeBool}, True='ValueIfTrue', False={Binding ValueIfFalse}}\n/// </summary>\npublic class TernaryExtension : MarkupExtension\n{\n    /// <summary>\n    /// Gets or sets the condition to evaluate. Can be a constant or a binding.\n    /// This is the default constructor argument.\n    /// </summary>\n    public object? Condition { get; set; }\n\n    /// <summary>\n    /// Gets or sets the value to return if the condition evaluates to true.\n    /// Can be a constant or a binding.\n    /// </summary>\n    public object? True { get; set; }\n\n    /// <summary>\n    /// Gets or sets the value to return if the condition evaluates to false.\n    /// Can be a constant or a binding.\n    /// </summary>\n    public object? False { get; set; }\n\n    public TernaryExtension() { }\n\n    public TernaryExtension(object condition)\n    {\n        Condition = condition;\n    }\n\n    public override object ProvideValue(IServiceProvider serviceProvider)\n    {\n        // --- Input Validation ---\n        // Basic check if Condition is provided (either via constructor or property)\n        if (Condition == null)\n        {\n            // In Avalonia 11+, MarkupExtension exceptions during ProvideValue might not\n            // always surface easily. Returning UnsetValue or null might be alternatives,\n            // but throwing helps identify the XAML issue during development.\n            throw new InvalidOperationException(\"TernaryExtension requires a Condition.\");\n            // return AvaloniaProperty.UnsetValue; // Or return null;\n        }\n        // Optional: Add checks for True/False if they are mandatory in your use case\n\n        // --- Identify Bindings vs Constants ---\n        var conditionBinding = Condition as IBinding;\n        var trueBinding = True as IBinding;\n        var falseBinding = False as IBinding;\n\n        // --- Case 1: All inputs are constants ---\n        if (conditionBinding == null && trueBinding == null && falseBinding == null)\n        {\n            // Evaluate the condition directly\n            var conditionResult = EvaluateCondition(Condition);\n            return conditionResult ? True : False;\n        }\n\n        // --- Case 2: At least one input is a binding ---\n        var multiBinding = new MultiBinding\n        {\n            Converter = TernaryConverter.Instance // Use a shared instance\n            // Mode = BindingMode.OneWay // Typically OneWay is sufficient\n        };\n\n        var converterParameters = new TernaryConverterParameters();\n        var bindingIndex = 0;\n\n        // Process Condition\n        if (conditionBinding != null)\n        {\n            multiBinding.Bindings.Add(conditionBinding);\n            converterParameters.ConditionIndex = bindingIndex++;\n        }\n        else\n        {\n            converterParameters.ConstantCondition = Condition;\n            converterParameters.ConditionIndex = -1; // Indicate constant\n        }\n\n        // Process True value\n        if (trueBinding != null)\n        {\n            multiBinding.Bindings.Add(trueBinding);\n            converterParameters.TrueValueIndex = bindingIndex++;\n        }\n        else\n        {\n            converterParameters.ConstantTrueValue = True;\n            converterParameters.TrueValueIndex = -1; // Indicate constant\n        }\n\n        // Process False value\n        if (falseBinding != null)\n        {\n            multiBinding.Bindings.Add(falseBinding);\n            converterParameters.FalseValueIndex = bindingIndex++;\n        }\n        else\n        {\n            converterParameters.ConstantFalseValue = False;\n            converterParameters.FalseValueIndex = -1; // Indicate constant\n        }\n\n        multiBinding.ConverterParameter = converterParameters;\n\n        // Return the MultiBinding instance. Avalonia will handle its evaluation.\n        // Note: In Avalonia 11+, you might need to explicitly provide the target provider\n        // if the default resolution isn't sufficient, but usually, this works.\n        // For MultiBinding, ProvideValue typically returns the binding instance itself.\n        return multiBinding;\n        // If targeting Avalonia 11+ and facing issues, you might explore if\n        // MultiBinding needs explicit target info from serviceProvider, but start with this.\n    }\n\n    /// <summary>\n    /// Helper to evaluate the condition object.\n    /// Treats null, false, \"false\" (case-insensitive), and 0 as false. Everything else is true.\n    /// Adjust this logic if you need different truthiness rules.\n    /// </summary>\n    private static bool EvaluateCondition(object? conditionValue)\n    {\n        if (conditionValue == null)\n            return false;\n        if (conditionValue is bool b)\n            return b;\n        if (conditionValue is string s)\n            return !string.Equals(s, \"false\", StringComparison.OrdinalIgnoreCase) && !string.Equals(s, \"0\");\n        if (conditionValue is int i)\n            return i != 0;\n        if (conditionValue is double d)\n            return d != 0.0;\n        if (conditionValue is float f)\n            return f != 0.0f;\n        // Add other numeric types if needed (decimal, long, etc.)\n\n        // Default: Treat non-null, non-specific types as true\n        return true;\n    }\n\n    /// <summary>\n    /// Internal class to hold parameters for the converter.\n    /// </summary>\n    private class TernaryConverterParameters\n    {\n        public int ConditionIndex { get; set; }\n        public int TrueValueIndex { get; set; }\n        public int FalseValueIndex { get; set; }\n\n        public object? ConstantCondition { get; set; }\n        public object? ConstantTrueValue { get; set; }\n        public object? ConstantFalseValue { get; set; }\n    }\n\n    /// <summary>\n    /// Converter used by the MultiBinding within TernaryExtension.\n    /// </summary>\n    private class TernaryConverter : IMultiValueConverter\n    {\n        /// <summary>\n        /// Shared instance to avoid unnecessary allocations.\n        /// </summary>\n        public static TernaryConverter Instance { get; } = new TernaryConverter();\n\n        public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)\n        {\n            if (parameter is not TernaryConverterParameters parameters)\n            {\n                // Should not happen if ProvideValue is correct\n                return AvaloniaProperty.UnsetValue; // Or BindingOperations.DoNothing or null\n            }\n\n            try\n            {\n                // --- Get Values ---\n                // Retrieve values from the binding results or the stored constants\n                var conditionValue =\n                    parameters.ConditionIndex >= 0\n                        ? values[parameters.ConditionIndex]\n                        : parameters.ConstantCondition;\n\n                var trueValue =\n                    parameters.TrueValueIndex >= 0\n                        ? values[parameters.TrueValueIndex]\n                        : parameters.ConstantTrueValue;\n\n                var falseValue =\n                    parameters.FalseValueIndex >= 0\n                        ? values[parameters.FalseValueIndex]\n                        : parameters.ConstantFalseValue;\n\n                // --- Evaluate Condition ---\n                var conditionResult = EvaluateCondition(conditionValue);\n\n                // --- Return Result ---\n                return conditionResult ? trueValue : falseValue;\n            }\n            catch\n            {\n                // Error during conversion (e.g., index out of bounds if ProvideValue logic failed)\n                return AvaloniaProperty.UnsetValue; // Or BindingOperations.DoNothing or null\n            }\n        }\n\n        // ConvertBack is not typically needed or meaningful for this type of extension\n        public object[]? ConvertBack(\n            object? value,\n            Type[] targetTypes,\n            object? parameter,\n            CultureInfo culture\n        )\n        {\n            throw new NotSupportedException(\"TernaryExtension does not support ConvertBack.\");\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/AdvancedObservableList.cs",
    "content": "﻿using System.Collections.Specialized;\nusing Avalonia.Collections;\nusing Avalonia.Threading;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\n/// <summary>\n/// Observable AvaloniaList supporting child item deletion requests.\n/// </summary>\npublic class AdvancedObservableList<T> : AvaloniaList<T>\n{\n    /// <inheritdoc />\n    public AdvancedObservableList()\n    {\n        CollectionChanged += CollectionChangedEventRegistrationHandler;\n    }\n\n    /// <inheritdoc />\n    public AdvancedObservableList(IEnumerable<T> items)\n        : base(items)\n    {\n        CollectionChanged += CollectionChangedEventRegistrationHandler;\n    }\n\n    private void CollectionChangedEventRegistrationHandler(object? sender, NotifyCollectionChangedEventArgs e)\n    {\n        if (e.OldItems != null)\n        {\n            foreach (var item in e.OldItems)\n            {\n                TryUnregisterRemovableListItem((T)item);\n            }\n        }\n        if (e.NewItems != null)\n        {\n            foreach (var item in e.NewItems)\n            {\n                TryRegisterRemovableListItem((T)item);\n            }\n        }\n    }\n\n    private void OnItemRemoveRequested(object? sender, EventArgs e)\n    {\n        if (sender is T item)\n        {\n            Dispatcher.UIThread.Post(() => Remove(item));\n        }\n    }\n\n    private bool TryRegisterRemovableListItem(T item)\n    {\n        if (item is IRemovableListItem removableListItem)\n        {\n            removableListItem.ParentListRemoveRequested += OnItemRemoveRequested;\n            return true;\n        }\n        return false;\n    }\n\n    private bool TryUnregisterRemovableListItem(T item)\n    {\n        if (item is IRemovableListItem removableListItem)\n        {\n            removableListItem.ParentListRemoveRequested -= OnItemRemoveRequested;\n            return true;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/AppArgs.cs",
    "content": "﻿using CommandLine;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\n/// <summary>\n/// Command line arguments passed to the application.\n/// </summary>\npublic class AppArgs\n{\n    /// <summary>\n    /// Whether to enable debug mode\n    /// </summary>\n    [Option(\"debug\", HelpText = \"Enable debug mode\")]\n    public bool DebugMode { get; set; }\n\n    /// <summary>\n    /// Whether to use the exception dialog while debugger is attached.\n    /// When no debugger is attached, the exception dialog is always used.\n    /// </summary>\n    [Option(\"debug-exception-dialog\", HelpText = \"Use exception dialog while debugger is attached\")]\n    public bool DebugExceptionDialog { get; set; }\n\n    /// <summary>\n    /// Whether to use Sentry when a debugger is attached.\n    /// </summary>\n    [Option(\"debug-sentry\", HelpText = \"Use Sentry when debugger is attached\")]\n    public bool DebugSentry { get; set; }\n\n    /// <summary>\n    /// Whether to force show the one-click install dialog.\n    /// </summary>\n    [Option(\"debug-one-click-install\", HelpText = \"Force show the one-click install dialog\")]\n    public bool DebugOneClickInstall { get; set; }\n\n    /// <summary>\n    /// Whether to disable Sentry.\n    /// </summary>\n    [Option(\"no-sentry\", HelpText = \"Disable Sentry\")]\n    public bool NoSentry { get; set; }\n\n    /// <summary>\n    /// Whether to disable window chrome effects\n    /// </summary>\n    [Option(\"no-window-chrome-effects\", HelpText = \"Disable window chrome effects\")]\n    public bool NoWindowChromeEffects { get; set; }\n\n    /// <summary>\n    /// Flag to indicate if we should reset the saved window position back to (O,0)\n    /// </summary>\n    [Option(\"reset-window-position\", HelpText = \"Reset the saved window position back to (0,0)\")]\n    public bool ResetWindowPosition { get; set; }\n\n    /// <summary>\n    /// Flag to enable the splash screen on startup\n    /// </summary>\n    [Option(\"splash-screen\", HelpText = \"Enable the startup splash screen\")]\n    public bool IsSplashScreenEnabled { get; set; }\n\n    /// <summary>\n    /// Flag for disabling hardware acceleration / GPU rendering\n    /// </summary>\n    [Option(\"disable-gpu-rendering\", HelpText = \"Disable hardware acceleration / GPU rendering\")]\n    public bool DisableGpuRendering { get; set; }\n\n    /// <summary>\n    /// Flag to use OpenGL rendering\n    /// </summary>\n    [Option(\"opengl\", HelpText = \"Prefer OpenGL rendering\")]\n    public bool UseOpenGlRendering { get; set; }\n\n    /// <summary>\n    /// Flag to use Vulkan rendering\n    /// </summary>\n    [Option(\"vulkan\", HelpText = \"Prefer Vulkan rendering\")]\n    public bool UseVulkanRendering { get; set; }\n\n    /// <summary>\n    /// Override global app home directory\n    /// Defaults to (%APPDATA%|~/.config)/StabilityMatrix\n    /// </summary>\n    [Option(\"home-dir\", HelpText = \"Override global app home directory\")]\n    public string? HomeDirectoryOverride { get; set; }\n\n    /// <summary>\n    /// Override data directory\n    /// This takes precedence over relative portable directory and global directory\n    /// </summary>\n    [Option(\"data-dir\", HelpText = \"Override data directory\")]\n    public string? DataDirectoryOverride { get; set; }\n\n    /// <summary>\n    /// Launch an installed package on startup\n    /// Can use package ID or name\n    /// </summary>\n    [Option(\"launch-package\", HelpText = \"Package ID or name to launch on startup\")]\n    public string? LaunchPackageName { get; set; }\n\n    /// <summary>\n    /// Custom Uri protocol handler\n    /// This will send the Uri to the running instance of the app via IPC and exit\n    /// </summary>\n    [Option(\"uri\", Hidden = true)]\n    public string? Uri { get; set; }\n\n    /// <summary>\n    /// If provided, the app will wait for the process with this PID to exit\n    /// before starting up. Mainly used by the updater.\n    /// </summary>\n    [Option(\"wait-for-exit-pid\", Hidden = true)]\n    public int? WaitForExitPid { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/AvaloniaResource.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text;\nusing Avalonia.Platform;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic readonly record struct AvaloniaResource(\n    Uri UriPath,\n    UnixFileMode WriteUnixFileMode = UnixFileMode.None\n)\n{\n    /// <summary>\n    /// File name component of the Uri path.\n    /// </summary>\n    public string FileName => Path.GetFileName(UriPath.ToString());\n\n    /// <summary>\n    /// File path relative to the 'Assets' folder.\n    /// </summary>\n    public Uri RelativeAssetPath =>\n        new Uri(\"avares://StabilityMatrix.Avalonia/Assets/\").MakeRelativeUri(UriPath);\n\n    public AvaloniaResource(string uriPath, UnixFileMode writeUnixFileMode = UnixFileMode.None)\n        : this(new Uri(uriPath), writeUnixFileMode) { }\n\n    /// <summary>\n    /// Opens a stream to this resource.\n    /// </summary>\n    public Stream Open() => AssetLoader.Open(UriPath);\n\n    public async Task<string> ReadAsStringAsync()\n    {\n        await using var stream = AssetLoader.Open(UriPath);\n        // Utf8 reader\n        using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);\n        // Read all text\n        return await reader.ReadToEndAsync();\n    }\n\n    /// <summary>\n    /// Extracts this resource to a target file path.\n    /// </summary>\n    public async Task ExtractTo(FilePath outputPath, bool overwrite = true)\n    {\n        if (outputPath.Exists)\n        {\n            // Skip if not overwriting\n            if (!overwrite)\n                return;\n            // Otherwise delete the file\n            outputPath.Delete();\n        }\n        var stream = AssetLoader.Open(UriPath);\n        await using var fileStream = File.Create(outputPath);\n        await stream.CopyToAsync(fileStream);\n        // Write permissions\n        if (!Compat.IsWindows && Compat.IsUnix && WriteUnixFileMode != UnixFileMode.None)\n        {\n            File.SetUnixFileMode(outputPath, WriteUnixFileMode);\n        }\n    }\n\n    /// <summary>\n    /// Extracts this resource to the output directory.\n    /// </summary>\n    public Task ExtractToDir(DirectoryPath outputDir, bool overwrite = true)\n    {\n        return ExtractTo(outputDir.JoinFile(FileName), overwrite);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/CheckpointCategory.cs",
    "content": "﻿using System.Collections.ObjectModel;\nusing CommunityToolkit.Mvvm.ComponentModel;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic partial class CheckpointCategory : TreeViewDirectory\n{\n    [ObservableProperty]\n    private int count;\n\n    [ObservableProperty]\n    private bool isExpanded;\n\n    [ObservableProperty]\n    private string tooltip;\n\n    public new ObservableCollection<CheckpointCategory> SubDirectories { get; set; } = new();\n\n    public IEnumerable<CheckpointCategory> Flatten()\n    {\n        yield return this;\n\n        foreach (var subDirectory in SubDirectories)\n        {\n            foreach (var nestedSubDirectory in subDirectory.Flatten())\n            {\n                yield return nestedSubDirectory;\n            }\n        }\n    }\n\n    public string GetId() => $@\"{Name};{Flatten().Count()}\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/CommandItem.cs",
    "content": "﻿using System.Diagnostics.Contracts;\nusing System.Runtime.CompilerServices;\nusing System.Text.RegularExpressions;\nusing System.Windows.Input;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic partial record CommandItem\n{\n    public ICommand Command { get; init; }\n\n    public string DisplayName { get; init; }\n\n    public CommandItem(ICommand command, [CallerArgumentExpression(\"command\")] string? commandName = null)\n    {\n        Command = command;\n        DisplayName = commandName == null ? \"\" : ProcessName(commandName);\n    }\n\n    [Pure]\n    private static string ProcessName(string name)\n    {\n        name = name.StripEnd(\"Command\");\n\n        name = SpaceTitleCaseRegex().Replace(name, \"$1 $2\");\n\n        return name;\n    }\n\n    [GeneratedRegex(\"([a-z])_?([A-Z])\")]\n    private static partial Regex SpaceTitleCaseRegex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/ContentDialogValueResult.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing FluentAvalonia.UI.Controls;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic record ContentDialogValueResult<T>(ContentDialogResult Result, [property: AllowNull] T Value)\n{\n    public bool IsNone => Result == ContentDialogResult.None;\n\n    public bool IsPrimary => Result == ContentDialogResult.Primary;\n\n    public bool IsSecondary => Result == ContentDialogResult.Secondary;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/DirectionalNavigationEventArgs.cs",
    "content": "﻿using System;\nusing System.Numerics;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic class DirectionalNavigationEventArgs : EventArgs\n{\n    private Vector2 Direction { get; }\n\n    public DirectionalNavigationEventArgs(Vector2 direction)\n    {\n        Direction = direction;\n    }\n\n    public static DirectionalNavigationEventArgs Up => new(new Vector2(0, -1));\n    public static DirectionalNavigationEventArgs Down => new(new Vector2(0, 1));\n\n    public bool IsNext => Direction.X > 0 || Direction.Y > 0;\n    public bool IsPrevious => Direction.X < 0 || Direction.Y < 0;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/HuggingFace/HuggingFaceModelType.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.Models.HuggingFace;\n\n[JsonConverter(typeof(DefaultUnknownEnumConverter<HuggingFaceModelType>))]\npublic enum HuggingFaceModelType\n{\n    [Description(\"Base Models\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.StableDiffusion)]\n    BaseModel,\n\n    [Description(\"CLIP / Text Encoders\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.TextEncoders)]\n    Clip,\n\n    [Description(\"ControlNets (SD1.5)\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.ControlNet)]\n    ControlNet,\n\n    [Description(\"ControlNets (Diffusers SD1.5)\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.ControlNet)]\n    DiffusersControlNet,\n\n    [Description(\"ControlNets (SDXL)\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.ControlNet)]\n    ControlNetXl,\n\n    [Description(\"IP Adapters\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.IpAdapter)]\n    IpAdapter,\n\n    [Description(\"IP Adapters (Diffusers SD1.5)\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.IpAdapters15)]\n    DiffusersIpAdapter,\n\n    [Description(\"IP Adapters (Diffusers SDXL)\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.IpAdaptersXl)]\n    DiffusersIpAdapterXl,\n\n    [Description(\"CLIP Vision\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.ClipVision)]\n    DiffusersClipVision,\n\n    [Description(\"T2I Adapters\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.T2IAdapter)]\n    T2IAdapter,\n\n    [Description(\"T2I Adapters (Diffusers)\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.T2IAdapter)]\n    DiffusersT2IAdapter,\n\n    [Description(\"Ultralytics/Segmentation Models\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.Ultralytics)]\n    Ultralytics,\n\n    [Description(\"SAM Models\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.Sams)]\n    Sams,\n\n    [Description(\"UNet-Only Models\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.DiffusionModels)]\n    Unet,\n\n    [Description(\"VAE\")]\n    [ConvertTo<SharedFolderType>(SharedFolderType.VAE)]\n    Vae,\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/HuggingFace/HuggingfaceItem.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models.HuggingFace;\n\npublic class HuggingfaceItem\n{\n    public required HuggingFaceModelType ModelCategory { get; set; }\n    public required string ModelName { get; set; }\n    public required string RepositoryPath { get; set; }\n    public required string[] Files { get; set; }\n    public required string LicenseType { get; set; }\n    public string? LicensePath { get; set; }\n    public string? Subfolder { get; set; }\n    public bool LoginRequired { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/IInfinitelyScroll.cs",
    "content": "﻿using System.Threading.Tasks;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic interface IInfinitelyScroll\n{\n    Task LoadNextPageAsync();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/IJsonLoadableState.cs",
    "content": "﻿using System.Text.Json.Nodes;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic interface IJsonLoadableState\n{\n    void LoadStateFromJsonObject(JsonObject state, int version);\n\n    void LoadStateFromJsonObject(JsonObject state);\n\n    JsonObject SaveStateToJsonObject();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/IParametersLoadableState.cs",
    "content": "﻿using StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic interface IParametersLoadableState\n{\n    void LoadStateFromParameters(GenerationParameters parameters);\n\n    GenerationParameters SaveStateToParameters(GenerationParameters parameters);\n\n    public GenerationParameters SaveStateToParameters()\n    {\n        return SaveStateToParameters(new GenerationParameters());\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/IPersistentViewProvider.cs",
    "content": "﻿using Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic interface IPersistentViewProvider\n{\n    Control? AttachedPersistentView { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/IRemovableListItem.cs",
    "content": "﻿using System;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic interface IRemovableListItem\n{\n    public event EventHandler ParentListRemoveRequested;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/ITemplateKey.cs",
    "content": "namespace StabilityMatrix.Avalonia.Models;\n\n/// <summary>\n/// Implements a template key for <see cref=\"DataTemplateSelector\"/>\n/// </summary>\npublic interface ITemplateKey<out T>\n{\n    T TemplateKey { get; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/IconData.cs",
    "content": "﻿using Avalonia.Media;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic record IconData\n{\n    public string? FAIcon { get; init; }\n    \n    public int? FontSize { get; init; }\n    \n    public SolidColorBrush? Foreground { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/ImageCacheProviders.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic static class ImageCacheProviders\n{\n    private static readonly Lazy<IImageCache> OutputsPageImageCacheLazy =\n        new(\n            () =>\n                new MemoryImageCache\n                {\n                    CacheDuration = TimeSpan.FromMinutes(10),\n                    MaxMemoryCacheCount = 50,\n                    RetryCount = 2\n                },\n            LazyThreadSafetyMode.ExecutionAndPublication\n        );\n\n    public static IImageCache OutputsPageImageCache => OutputsPageImageCacheLazy.Value;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/ImageSource.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Net.Http;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\nusing AsyncImageLoader;\nusing Avalonia.Media.Imaging;\nusing Blake3;\nusing Microsoft.Extensions.DependencyInjection;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Webp;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic record ImageSource : IDisposable, ITemplateKey<ImageSourceTemplateType>\n{\n    private Hash? contentHashBlake3;\n\n    /// <summary>\n    /// Local file path\n    /// </summary>\n    public FilePath? LocalFile { get; init; }\n\n    /// <summary>\n    /// Remote URL\n    /// </summary>\n    public Uri? RemoteUrl { get; init; }\n\n    /// <summary>\n    /// Bitmap\n    /// </summary>\n    [JsonIgnore]\n    public Bitmap? Bitmap { get; set; }\n\n    /// <summary>\n    /// Optional label for the image\n    /// </summary>\n    public string? Label { get; set; }\n\n    [JsonConstructor]\n    public ImageSource() { }\n\n    public ImageSource(FilePath localFile)\n    {\n        LocalFile = localFile;\n    }\n\n    public ImageSource(Uri remoteUrl)\n    {\n        RemoteUrl = remoteUrl;\n    }\n\n    public ImageSource(Bitmap bitmap)\n    {\n        Bitmap = bitmap;\n    }\n\n    [JsonIgnore]\n    public Uri? Uri => LocalFile?.FullPath != null ? new Uri(LocalFile.FullPath) : RemoteUrl;\n\n    /// <inheritdoc />\n    public ImageSourceTemplateType TemplateKey { get; private set; }\n\n    private async Task<bool> TryRefreshTemplateKeyAsync()\n    {\n        if ((LocalFile?.Extension ?? Path.GetExtension(RemoteUrl?.ToString())) is not { } extension)\n        {\n            return false;\n        }\n\n        if (extension.Equals(\".webp\", StringComparison.OrdinalIgnoreCase))\n        {\n            if (LocalFile is not null && LocalFile.Exists)\n            {\n                await using var stream = LocalFile.Info.OpenRead();\n                using var reader = new WebpReader(stream);\n\n                try\n                {\n                    TemplateKey = reader.GetIsAnimatedFlag()\n                        ? ImageSourceTemplateType.WebpAnimation\n                        : ImageSourceTemplateType.Image;\n                }\n                catch (InvalidDataException)\n                {\n                    return false;\n                }\n\n                return true;\n            }\n\n            if (RemoteUrl is not null)\n            {\n                var httpClientFactory = App.Services.GetRequiredService<IHttpClientFactory>();\n                using var client = httpClientFactory.CreateClient();\n\n                try\n                {\n                    await using var stream = await client.GetStreamAsync(RemoteUrl);\n                    using var reader = new WebpReader(stream);\n\n                    TemplateKey = reader.GetIsAnimatedFlag()\n                        ? ImageSourceTemplateType.WebpAnimation\n                        : ImageSourceTemplateType.Image;\n                }\n                catch (Exception)\n                {\n                    return false;\n                }\n\n                return true;\n            }\n\n            return false;\n        }\n\n        if (extension.Equals(\".gif\", StringComparison.OrdinalIgnoreCase))\n        {\n            TemplateKey = ImageSourceTemplateType.WebpAnimation;\n            return true;\n        }\n\n        TemplateKey = ImageSourceTemplateType.Image;\n\n        return true;\n    }\n\n    public async Task<ImageSourceTemplateType> GetOrRefreshTemplateKeyAsync()\n    {\n        if (TemplateKey is ImageSourceTemplateType.Default)\n        {\n            await TryRefreshTemplateKeyAsync();\n        }\n\n        return TemplateKey;\n    }\n\n    [JsonIgnore]\n    public Task<ImageSourceTemplateType> TemplateKeyAsync => GetOrRefreshTemplateKeyAsync();\n\n    [JsonIgnore]\n    public Task<Bitmap?> BitmapAsync => GetBitmapAsync();\n\n    /// <summary>\n    /// Get the bitmap\n    /// </summary>\n    public async Task<Bitmap?> GetBitmapAsync()\n    {\n        if (Bitmap?.Format != null)\n            return Bitmap;\n\n        var loader = ImageLoader.AsyncImageLoader;\n\n        // Use local file path if available, otherwise remote URL\n        var path = LocalFile?.FullPath ?? RemoteUrl?.ToString();\n\n        if (path is null)\n            return null;\n\n        // Load the image\n        Bitmap = await loader.ProvideImageAsync(path).ConfigureAwait(false);\n        return Bitmap;\n    }\n\n    public async Task<Hash> GetBlake3HashAsync()\n    {\n        // Use cached value if available\n        if (contentHashBlake3 is not null)\n        {\n            return contentHashBlake3.Value;\n        }\n\n        if (LocalFile is not null)\n        {\n            var data = await LocalFile.ReadAllBytesAsync();\n            contentHashBlake3 = await FileHash.GetBlake3ParallelAsync(data);\n        }\n        else\n        {\n            if (await GetBitmapAsync() is not { } bitmap)\n            {\n                throw new InvalidOperationException(\"GetBitmapAsync returned null\");\n            }\n\n            contentHashBlake3 = await FileHash.GetBlake3ParallelAsync(bitmap.ToByteArray());\n        }\n\n        return contentHashBlake3.Value;\n    }\n\n    /// <summary>\n    /// Return a file name with Guid from Blake3 hash\n    /// </summary>\n    public async Task<string> GetHashGuidFileNameAsync()\n    {\n        var hash = await GetBlake3HashAsync();\n        var guid = hash.ToGuid().ToString();\n\n        if (LocalFile?.Extension is { } extension)\n        {\n            guid += extension;\n        }\n        else\n        {\n            // Default to PNG if no extension\n            guid += \".png\";\n        }\n\n        return guid;\n    }\n\n    /// <summary>\n    /// Return a file name with Guid from Blake3 hash\n    /// This will throw if the Blake3 hash has not been calculated yet\n    /// </summary>\n    public string GetHashGuidFileNameCached()\n    {\n        // Calculate hash if not available\n        if (contentHashBlake3 is null)\n        {\n            // Local file\n            if (LocalFile is not null)\n            {\n                // File must exist\n                if (!LocalFile.Exists)\n                {\n                    throw new FileNotFoundException(\"Image file does not exist\", LocalFile);\n                }\n\n                // Fail in debug since hash should have been pre-calculated\n                Debug.Fail(\"Hash has not been calculated when GetHashGuidFileNameCached() was called\");\n\n                var data = LocalFile.ReadAllBytes();\n                contentHashBlake3 = FileHash.GetBlake3Parallel(data);\n            }\n            // Bitmap\n            else if (Bitmap is not null)\n            {\n                var data = Bitmap.ToByteArray();\n                contentHashBlake3 = FileHash.GetBlake3Parallel(data);\n            }\n            else\n            {\n                throw new InvalidOperationException(\"ImageSource is not a local file or bitmap\");\n            }\n        }\n\n        var guid = contentHashBlake3.Value.ToGuid().ToString();\n\n        if (LocalFile?.Extension is { } extension)\n        {\n            guid += extension;\n        }\n        else\n        {\n            // Default to PNG if no extension\n            guid += \".png\";\n        }\n\n        return guid;\n    }\n\n    public string GetHashGuidFileNameCached(string pathPrefix)\n    {\n        return Path.Combine(pathPrefix, GetHashGuidFileNameCached());\n    }\n\n    /// <summary>\n    /// Clears the cached bitmap\n    /// </summary>\n    protected virtual void Dispose(bool disposing)\n    {\n        if (!disposing)\n            return;\n\n        Bitmap = null;\n    }\n\n    /// <inheritdoc />\n    public void Dispose()\n    {\n        Dispose(true);\n        GC.SuppressFinalize(this);\n    }\n\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        return LocalFile?.FullPath ?? RemoteUrl?.ToString() ?? \"\";\n    }\n\n    /// <summary>\n    /// Implicit conversion to string for async image loader.\n    /// Resolves with the local file path if available, otherwise the remote URL.\n    /// Otherwise returns null.\n    /// </summary>\n    public static implicit operator string(ImageSource imageSource) => imageSource.ToString();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/ImageSourceTemplateType.cs",
    "content": "namespace StabilityMatrix.Avalonia.Models;\n\npublic enum ImageSourceTemplateType\n{\n    Default,\n    Image,\n    WebpAnimation\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/EditableModule.cs",
    "content": "﻿using System;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Core.Models.Base;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic record EditableModule : StringValue\n{\n    public static readonly EditableModule FreeU =\n        new(\n            \"FreeU\",\n            builder =>\n                builder.Get<StackExpanderViewModel>(vm =>\n                {\n                    vm.Title = \"FreeU\";\n                    vm.AddCards(builder.Get<FreeUCardViewModel>());\n                })\n        );\n\n    public static readonly EditableModule HiresFix =\n        new(\n            \"HiresFix\",\n            builder =>\n                builder.Get<StackExpanderViewModel>(vm =>\n                {\n                    vm.Title = \"HiresFix\";\n                    vm.AddCards(\n                        builder.Get<UpscalerCardViewModel>(),\n                        builder.Get<SamplerCardViewModel>(vmSampler =>\n                        {\n                            vmSampler.IsDenoiseStrengthEnabled = true;\n                        })\n                    );\n                })\n        );\n\n    public static readonly EditableModule Upscaler =\n        new(\n            \"Upscaler\",\n            builder =>\n                builder.Get<StackExpanderViewModel>(vm =>\n                {\n                    vm.Title = \"Upscaler\";\n                    vm.AddCards(builder.Get<UpscalerCardViewModel>());\n                })\n        );\n\n    public Func<IServiceManager<ViewModelBase>, ViewModelBase> Build { get; }\n\n    private EditableModule(string value, Func<IServiceManager<ViewModelBase>, ViewModelBase> build)\n        : base(value)\n    {\n        Build = build;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/FileNameFormat.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Linq;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic record FileNameFormat\n{\n    public string Template { get; }\n\n    public string Prefix { get; set; } = \"\";\n\n    public string Postfix { get; set; } = \"\";\n\n    public IReadOnlyList<FileNameFormatPart> Parts { get; }\n\n    private FileNameFormat(string template, IReadOnlyList<FileNameFormatPart> parts)\n    {\n        Template = template;\n        Parts = parts;\n    }\n\n    public FileNameFormat WithBatchPostFix(int current, int total)\n    {\n        return this with { Postfix = Postfix + $\" ({current}-{total})\" };\n    }\n\n    public FileNameFormat WithGridPrefix()\n    {\n        return this with { Prefix = Prefix + \"Grid_\" };\n    }\n\n    public string GetFileName()\n    {\n        return Prefix\n            + string.Join(\n                \"\",\n                Parts.Select(part =>\n                    part.Match(\n                        constant => constant,\n                        substitution =>\n                        {\n                            // Filter invalid path chars\n                            var result = substitution.Invoke();\n                            return result is null\n                                ? null\n                                : Path.GetInvalidFileNameChars()\n                                    .Aggregate(result, (current, c) => current.Replace(c, '_'));\n                        }\n                    )\n                )\n            )\n            + Postfix;\n    }\n\n    public static FileNameFormat Parse(string template, FileNameFormatProvider provider)\n    {\n        var parts = provider.GetParts(template).ToImmutableArray();\n        return new FileNameFormat(template, parts);\n    }\n\n    public static bool TryParse(\n        string template,\n        FileNameFormatProvider provider,\n        [NotNullWhen(true)] out FileNameFormat? format\n    )\n    {\n        try\n        {\n            format = Parse(template, provider);\n            return true;\n        }\n        catch (ArgumentException)\n        {\n            format = null;\n            return false;\n        }\n    }\n\n    public const string DefaultTemplate = \"{date}_{time}-{model_name}-{seed}\";\n    public const string DefaultModelBrowserTemplate = \"{file_name}\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/FileNameFormatPart.cs",
    "content": "﻿using System;\nusing OneOf;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\n[GenerateOneOf]\npublic partial class FileNameFormatPart : OneOfBase<string, Func<string?>> { }\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/FileNameFormatProvider.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.Diagnostics.Contracts;\nusing System.IO;\nusing System.Linq;\nusing System.Text.RegularExpressions;\nusing Avalonia.Data;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Inference;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic partial class FileNameFormatProvider\n{\n    public GenerationParameters? GenerationParameters { get; init; }\n\n    public InferenceProjectType? ProjectType { get; init; }\n\n    public string? ProjectName { get; init; }\n\n    public CivitModel? CivitModel { get; init; }\n    public CivitModelVersion? CivitModelVersion { get; init; }\n    public CivitFile? CivitFile { get; init; }\n\n    private Dictionary<string, Func<string?>>? _substitutions;\n\n    public Dictionary<string, Func<string?>> Substitutions =>\n        _substitutions ??= new Dictionary<string, Func<string?>>\n        {\n            { \"seed\", () => GenerationParameters?.Seed.ToString() },\n            { \"prompt\", () => GenerationParameters?.PositivePrompt },\n            { \"negative_prompt\", () => GenerationParameters?.NegativePrompt },\n            {\n                \"model_name\",\n                () => Path.GetFileNameWithoutExtension(GenerationParameters?.ModelName) ?? CivitModel?.Name\n            },\n            { \"model_hash\", () => GenerationParameters?.ModelHash },\n            { \"sampler\", () => GenerationParameters?.Sampler },\n            { \"cfgscale\", () => GenerationParameters?.CfgScale.ToString() },\n            { \"steps\", () => GenerationParameters?.Steps.ToString() },\n            { \"width\", () => GenerationParameters?.Width.ToString() },\n            { \"height\", () => GenerationParameters?.Height.ToString() },\n            { \"project_type\", () => ProjectType?.GetStringValue() },\n            { \"project_name\", () => ProjectName },\n            { \"date\", () => DateTime.Now.ToString(\"yyyy-MM-dd\") },\n            { \"time\", () => DateTime.Now.ToString(\"HH-mm-ss\") },\n            { \"author\", () => CivitModel?.Creator?.Username },\n            { \"base_model\", () => CivitModelVersion?.BaseModel },\n            { \"file_name\", () => Path.GetFileNameWithoutExtension(CivitFile?.Name) },\n            { \"file_id\", () => CivitFile?.Id.ToString() },\n            { \"model_id\", () => CivitModel?.Id.ToString() },\n            { \"model_version_id\", () => CivitModelVersion?.Id.ToString() },\n            { \"model_version_name\", () => CivitModelVersion?.Name },\n            { \"model_type\", () => CivitModel?.Type.ToString() },\n        };\n\n    /// <summary>\n    /// Validate a format string\n    /// </summary>\n    /// <param name=\"format\">Format string</param>\n    /// <exception cref=\"DataValidationException\">Thrown if the format string contains an unknown variable</exception>\n    [Pure]\n    public ValidationResult Validate(string format)\n    {\n        var regex = BracketRegex();\n        var matches = regex.Matches(format);\n        var variables = matches.Select(m => m.Groups[1].Value);\n\n        foreach (var variableText in variables)\n        {\n            try\n            {\n                var (variable, _) = ExtractVariableAndSlice(variableText);\n\n                if (!Substitutions.ContainsKey(variable))\n                {\n                    return new ValidationResult($\"Unknown variable '{variable}'\");\n                }\n            }\n            catch (Exception e)\n            {\n                return new ValidationResult($\"Invalid variable '{variableText}': {e.Message}\");\n            }\n        }\n\n        return ValidationResult.Success!;\n    }\n\n    public IEnumerable<FileNameFormatPart> GetParts(string template)\n    {\n        var regex = BracketRegex();\n        var matches = regex.Matches(template);\n\n        var parts = new List<FileNameFormatPart>();\n\n        // Loop through all parts of the string, including matches and non-matches\n        var currentIndex = 0;\n\n        foreach (var result in matches.Cast<Match>())\n        {\n            // If the match is not at the start of the string, add a constant part\n            if (result.Index != currentIndex)\n            {\n                var constant = template[currentIndex..result.Index];\n                parts.Add(constant);\n\n                currentIndex += constant.Length;\n            }\n\n            // Now we're at start of the current match, add the variable part\n            var (variable, slice) = ExtractVariableAndSlice(result.Groups[1].Value);\n            var substitution = Substitutions[variable];\n\n            // Slice string if necessary\n            if (slice is not null)\n            {\n                parts.Add(\n                    (FileNameFormatPart)(\n                        () =>\n                        {\n                            var value = substitution();\n                            if (value is null)\n                                return null;\n\n                            if (slice.End is null)\n                            {\n                                value = value[(slice.Start ?? 0)..];\n                            }\n                            else\n                            {\n                                var length = Math.Min(value.Length, slice.End.Value) - (slice.Start ?? 0);\n                                value = value.Substring(slice.Start ?? 0, length);\n                            }\n\n                            return value;\n                        }\n                    )\n                );\n            }\n            else\n            {\n                parts.Add(substitution);\n            }\n\n            currentIndex += result.Length;\n        }\n\n        // Add remaining as constant\n        if (currentIndex != template.Length)\n        {\n            var constant = template[currentIndex..];\n            parts.Add(constant);\n        }\n\n        return parts;\n    }\n\n    /// <summary>\n    /// Return a sample provider for UI preview\n    /// </summary>\n    public static FileNameFormatProvider GetSample()\n    {\n        return new FileNameFormatProvider\n        {\n            GenerationParameters = GenerationParameters.GetSample(),\n            ProjectType = InferenceProjectType.TextToImage,\n            ProjectName = \"Sample Project\",\n        };\n    }\n\n    public static FileNameFormatProvider GetSampleForModelBrowser()\n    {\n        return new FileNameFormatProvider\n        {\n            CivitModel = new CivitModel\n            {\n                Id = 1234,\n                Name = \"Sample Model\",\n                Creator = new CivitCreator { Username = \"SampleUser\" },\n                Type = CivitModelType.Checkpoint,\n            },\n            CivitModelVersion = new CivitModelVersion\n            {\n                Id = 5678,\n                Name = \"v1.0\",\n                BaseModel = \"Illustrious\",\n            },\n            CivitFile = new CivitFile\n            {\n                Id = 910,\n                Name = \"sample_file.ckpt\",\n                Type = CivitFileType.Model,\n                Metadata = new CivitFileMetadata { Size = \"pruned\" },\n            },\n        };\n    }\n\n    /// <summary>\n    /// Extract variable and index from a combined string\n    /// </summary>\n    private static (string Variable, Slice? Slice) ExtractVariableAndSlice(string combined)\n    {\n        if (IndexRegex().Matches(combined).FirstOrDefault() is not { Success: true } match)\n        {\n            return (combined, null);\n        }\n\n        // Variable is everything before the match\n        var variable = combined[..match.Groups[0].Index];\n\n        var start = match.Groups[\"start\"].Value;\n        var end = match.Groups[\"end\"].Value;\n        var step = match.Groups[\"step\"].Value;\n\n        var slice = new Slice(\n            string.IsNullOrEmpty(start) ? null : int.Parse(start),\n            string.IsNullOrEmpty(end) ? null : int.Parse(end),\n            string.IsNullOrEmpty(step) ? null : int.Parse(step)\n        );\n\n        return (variable, slice);\n    }\n\n    /// <summary>\n    /// Regex for matching contents within a curly brace.\n    /// </summary>\n    [GeneratedRegex(@\"\\{([a-z_:\\d\\[\\]]+)\\}\")]\n    private static partial Regex BracketRegex();\n\n    /// <summary>\n    /// Regex for matching a Python-like array index.\n    /// </summary>\n    [GeneratedRegex(@\"\\[(?:(?<start>-?\\d+)?)\\:(?:(?<end>-?\\d+)?)?(?:\\:(?<step>-?\\d+))?\\]\")]\n    private static partial Regex IndexRegex();\n\n    private record Slice(int? Start, int? End, int? Step);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/FileNameFormatVar.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic record FileNameFormatVar\n{\n    public required string Variable { get; init; }\n\n    public string? Example { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/GenerateFlags.cs",
    "content": "﻿using System;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\n[Flags]\npublic enum GenerateFlags\n{\n    None = 0,\n    HiresFixEnable = 1 << 1,\n    HiresFixDisable = 1 << 2,\n    UseCurrentSeed = 1 << 3,\n    UseRandomSeed = 1 << 4,\n    HiresFixAndUseCurrentSeed = HiresFixEnable | UseCurrentSeed,\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/GenerateOverrides.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic class GenerateOverrides\n{\n    public bool? IsHiresFixEnabled { get; set; }\n    public bool? UseCurrentSeed { get; set; }\n\n    public static GenerateOverrides FromFlags(GenerateFlags flags)\n    {\n        var overrides = new GenerateOverrides\n        {\n            IsHiresFixEnabled = flags.HasFlag(GenerateFlags.HiresFixEnable)\n                ? true\n                : flags.HasFlag(GenerateFlags.HiresFixDisable)\n                    ? false\n                    : null,\n            UseCurrentSeed = flags.HasFlag(GenerateFlags.UseCurrentSeed)\n                ? true\n                : flags.HasFlag(GenerateFlags.UseRandomSeed)\n                    ? false\n                    : null\n        };\n\n        return overrides;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/IComfyStep.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic interface IComfyStep\n{\n    void ApplyStep(ModuleApplyStepEventArgs e);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/IInputImageProvider.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic interface IInputImageProvider\n{\n    IEnumerable<ImageSource> GetInputImages();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/IValidatableModule.cs",
    "content": "﻿using System.Threading.Tasks;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic interface IValidatableModule\n{\n    public Task<bool> Validate();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/InferenceTextToImageModel.cs",
    "content": "﻿using System.Text.Json.Nodes;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic class InferenceTextToImageModel\n{\n    public string? SelectedModelName { get; init; }\n    public JsonObject? SeedCardState { get; init; }\n    public JsonObject? PromptCardState { get; init; }\n    public JsonObject? StackCardState { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/LatentType.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic enum LatentType\n{\n    Default,\n    Sd3,\n    Hunyuan,\n    Plasma\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/LoadViewStateEventArgs.cs",
    "content": "﻿using System;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\n/// <summary>\n/// Event for view models requesting to load their view state from a provided state\n/// </summary>\npublic class LoadViewStateEventArgs : EventArgs\n{\n    public ViewState? State { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/ModuleApplyStepEventArgs.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.IO.Hashing;\nusing System.Linq;\nusing System.Text;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\nusing StabilityMatrix.Core.Models.Inference;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\n/// <summary>\n/// Event args for applying a <see cref=\"IComfyStep\"/>.\n/// </summary>\npublic class ModuleApplyStepEventArgs : EventArgs\n{\n    public required ComfyNodeBuilder Builder { get; init; }\n\n    public NodeDictionary Nodes => Builder.Nodes;\n\n    public ModuleApplyStepTemporaryArgs Temp { get; set; } = new();\n\n    /// <summary>\n    /// Generation overrides (like hires fix generate, current seed generate, etc.)\n    /// </summary>\n    public IReadOnlyDictionary<Type, bool> IsEnabledOverrides { get; init; } = new Dictionary<Type, bool>();\n\n    public List<(string SourcePath, string DestinationRelativePath)> FilesToTransfer { get; init; } = [];\n\n    public List<Action<ModuleApplyStepEventArgs>> PreOutputActions { get; init; } = [];\n\n    public void InvokeAllPreOutputActions()\n    {\n        foreach (var action in PreOutputActions)\n        {\n            action(this);\n        }\n    }\n\n    /// <summary>\n    /// Creates a new <see cref=\"ModuleApplyStepEventArgs\"/> with the given <see cref=\"ComfyNodeBuilder\"/>.\n    /// </summary>\n    /// <returns></returns>\n    public ModuleApplyStepTemporaryArgs CreateTempFromBuilder()\n    {\n        return new ModuleApplyStepTemporaryArgs\n        {\n            Primary = Builder.Connections.Primary,\n            PrimaryVAE = Builder.Connections.PrimaryVAE,\n            Models = new Dictionary<string, ModelConnections>(\n                Builder.Connections.Models.ToDictionary(\n                    pair => pair.Key,\n                    pair => new ModelConnections(pair.Value)\n                )\n            )\n        };\n    }\n\n    public void AddFileTransfer(string sourcePath, string destinationRelativePath)\n    {\n        FilesToTransfer.Add((sourcePath, destinationRelativePath));\n    }\n\n    /// <summary>\n    /// Adds a file transfer to `models/configs`\n    /// </summary>\n    /// <returns>The destination relative path</returns>\n    public string AddFileTransferToConfigs(string sourcePath)\n    {\n        // To avoid conflicts, we'll add the file name's crc32 before the extension\n        var sourceNameWithoutExtension = Path.GetFileNameWithoutExtension(sourcePath);\n        var sourceExtension = Path.GetExtension(sourcePath);\n        var sourceNameCrc = Crc32.Hash(Encoding.UTF8.GetBytes(sourceNameWithoutExtension));\n        var sourceNameCrcShort = BitConverter\n            .ToString(sourceNameCrc)\n            .ToLowerInvariant()\n            .Replace(\"-\", string.Empty)[..4];\n\n        var destFileName = $\"{sourceNameWithoutExtension}_{sourceNameCrcShort}{sourceExtension}\";\n\n        var destPath = Path.Combine(\"models\", \"configs\", destFileName);\n\n        FilesToTransfer.Add((sourcePath, destPath));\n\n        return destPath;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/NoiseType.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic enum NoiseType\n{\n    Plasma,\n    Random,\n    Greyscale,\n    Pink,\n    Brown\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/Prompt.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Globalization;\nusing System.IO;\nusing System.Linq;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.Tokens;\nusing StabilityMatrix.Core.Services;\nusing TextMateSharp.Grammars;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic record Prompt\n{\n    public required string RawText { get; init; }\n\n    public required ITokenizeLineResult TokenizeResult { get; init; }\n\n    public required ITokenizerProvider Tokenizer { get; init; }\n\n    [MemberNotNullWhen(true, nameof(ExtraNetworks), nameof(ProcessedText))]\n    public bool IsProcessed { get; private set; }\n\n    /// <summary>\n    /// Extra networks specified in prompt.\n    /// </summary>\n    public IReadOnlyList<PromptExtraNetwork>? ExtraNetworks { get; private set; }\n\n    /// <summary>\n    /// Processed text suitable for sending to inference backend.\n    /// This excludes extra network (i.e. LORA) tokens.\n    /// </summary>\n    public string? ProcessedText { get; private set; }\n\n    [MemberNotNull(nameof(ExtraNetworks), nameof(ProcessedText))]\n    public void Process(bool processWildcards = true)\n    {\n        if (IsProcessed)\n            return;\n\n        var (promptExtraNetworks, processedText) = GetExtraNetworks(processWildcards: processWildcards);\n        ExtraNetworks = promptExtraNetworks;\n        ProcessedText = processedText;\n    }\n\n    /// <summary>\n    /// Verifies that extra network files exists locally.\n    /// </summary>\n    /// <exception cref=\"PromptValidationError\">Thrown if a filename does not exist</exception>\n    public void ValidateExtraNetworks(IModelIndexService indexService)\n    {\n        GetExtraNetworks(indexService, false);\n    }\n\n    /// <summary>\n    /// Get ExtraNetworks as local model files and weights.\n    /// </summary>\n    public IEnumerable<(\n        LocalModelFile ModelFile,\n        double? ModelWeight,\n        double? ClipWeight\n    )> GetExtraNetworksAsLocalModels(IModelIndexService indexService)\n    {\n        if (ExtraNetworks is null)\n        {\n            throw new InvalidOperationException(\n                \"Prompt must be processed before calling GetExtraNetworksAsLocalModels\"\n            );\n        }\n\n        foreach (var network in ExtraNetworks)\n        {\n            var sharedFolderType = network.Type.ConvertTo<SharedFolderType>();\n\n            if (!indexService.ModelIndex.TryGetValue(sharedFolderType, out var modelList))\n            {\n                throw new ApplicationException($\"Model {network.Name} does not exist in index\");\n            }\n\n            var localModel = modelList.FirstOrDefault(m => m.FileNameWithoutExtension == network.Name);\n            if (localModel == null)\n            {\n                throw new ApplicationException($\"Model {network.Name} does not exist in index\");\n            }\n\n            yield return (localModel, network.ModelWeight, network.ClipWeight);\n        }\n    }\n\n    private int GetSafeEndIndex(int index)\n    {\n        return Math.Min(index, RawText.Length);\n    }\n\n    private (List<PromptExtraNetwork> promptExtraNetworks, string processedText) GetExtraNetworks(\n        IModelIndexService? indexService = null,\n        bool processWildcards = true\n    )\n    {\n        // Parse tokens \"meta.structure.network.prompt\"\n        // \"<\": \"punctuation.definition.network.begin.prompt\"\n        // (type): \"meta.embedded.network.type.prompt\"\n        // \":\": \"punctuation.separator.variable.prompt\"\n        // (content): \"meta.embedded.network.model.prompt\"\n        // \">\": \"punctuation.definition.network.end.prompt\"\n        using var tokens = TokenizeResult.Tokens.Cast<IToken>().GetEnumerator();\n\n        // Maintain both token and text stacks for validation\n        var outputTokens = new Stack<IToken>();\n        var outputText = new Stack<string>();\n        var wildcardStack = new Stack<StringBuilder>();\n\n        // Store extra networks\n        var promptExtraNetworks = new List<PromptExtraNetwork>();\n\n        while (tokens.MoveNext())\n        {\n            var currentToken = tokens.Current;\n\n            // For any invalid syntax, throw\n            if (currentToken.Scopes.Any(s => s.Contains(\"invalid.illegal\")))\n            {\n                // Generic\n                throw new PromptSyntaxError(\n                    \"Invalid Token\",\n                    currentToken.StartIndex,\n                    GetSafeEndIndex(currentToken.EndIndex)\n                );\n            }\n\n            // Comments - ignore\n            if (currentToken.Scopes.Any(s => s.Contains(\"comment.line\")))\n            {\n                continue;\n            }\n\n            // Handle wildcard start\n            if (\n                processWildcards\n                && currentToken.Scopes.Contains(\"punctuation.definition.wildcard.begin.prompt\")\n            )\n            {\n                wildcardStack.Push(new StringBuilder());\n                continue;\n            }\n\n            // Handle wildcard end\n            if (\n                processWildcards && currentToken.Scopes.Contains(\"punctuation.definition.wildcard.end.prompt\")\n            )\n            {\n                if (wildcardStack.Count == 0)\n                    continue;\n\n                var wildcardContent = wildcardStack.Pop();\n                var options = wildcardContent\n                    .ToString()\n                    .Split('|', StringSplitOptions.RemoveEmptyEntries)\n                    .Select(o => o.Trim())\n                    .ToList();\n\n                if (options.Count == 0)\n                {\n                    outputTokens.Push(currentToken);\n                    outputText.Push(string.Empty);\n                    continue;\n                }\n\n                // Process wildcard selection\n                var selectedIndex = RandomNumberGenerator.GetInt32(options.Count);\n                var selectedOption = options[selectedIndex];\n\n                // Process selected option\n                var tempPrompt = FromRawText(selectedOption, Tokenizer);\n                tempPrompt.Process();\n\n                // Collect networks from selected option\n                if (tempPrompt.ExtraNetworks is { Count: > 0 } networks)\n                {\n                    promptExtraNetworks.AddRange(networks);\n                }\n\n                outputTokens.Push(currentToken);\n                outputText.Push(tempPrompt.ProcessedText ?? string.Empty);\n                continue;\n            }\n\n            // Handle wildcard content\n            if (processWildcards && wildcardStack.Count > 0)\n            {\n                var currentWildcard = wildcardStack.Peek();\n                currentWildcard.Append(\n                    RawText[currentToken.StartIndex..GetSafeEndIndex(currentToken.EndIndex)]\n                );\n                continue;\n            }\n\n            // Find start of network token, until then just add to output\n            if (!currentToken.Scopes.Contains(\"punctuation.definition.network.begin.prompt\"))\n            {\n                // Normal tags - Push to output\n                outputTokens.Push(currentToken);\n                outputText.Push(RawText[currentToken.StartIndex..GetSafeEndIndex(currentToken.EndIndex)]);\n                continue;\n            }\n\n            // Expect next token to be network type\n            if (!tokens.MoveNext())\n            {\n                throw PromptSyntaxError.UnexpectedEndOfText(\n                    currentToken.StartIndex,\n                    GetSafeEndIndex(currentToken.EndIndex)\n                );\n            }\n            currentToken = tokens.Current;\n\n            if (!currentToken.Scopes.Contains(\"meta.embedded.network.type.prompt\"))\n            {\n                throw PromptSyntaxError.Network_ExpectedType(\n                    currentToken.StartIndex,\n                    GetSafeEndIndex(currentToken.EndIndex)\n                );\n            }\n\n            var networkType = RawText[currentToken.StartIndex..GetSafeEndIndex(currentToken.EndIndex)];\n\n            // Match network type\n            var parsedNetworkType = networkType switch\n            {\n                \"lora\" => PromptExtraNetworkType.Lora,\n                \"lyco\" => PromptExtraNetworkType.LyCORIS,\n                \"embedding\" => PromptExtraNetworkType.Embedding,\n                _\n                    => throw PromptValidationError.Network_UnknownType(\n                        currentToken.StartIndex,\n                        GetSafeEndIndex(currentToken.EndIndex)\n                    )\n            };\n\n            // Skip colon token\n            if (!tokens.MoveNext())\n            {\n                throw PromptSyntaxError.UnexpectedEndOfText(\n                    currentToken.StartIndex,\n                    GetSafeEndIndex(currentToken.EndIndex)\n                );\n            }\n            currentToken = tokens.Current;\n\n            // Ensure next token is colon\n            if (!currentToken.Scopes.Contains(\"punctuation.separator.variable.prompt\"))\n            {\n                throw PromptSyntaxError.Network_ExpectedSeparator(\n                    currentToken.StartIndex,\n                    GetSafeEndIndex(currentToken.EndIndex)\n                );\n            }\n\n            // Get model name\n            if (!tokens.MoveNext())\n            {\n                throw PromptSyntaxError.UnexpectedEndOfText(\n                    currentToken.StartIndex,\n                    GetSafeEndIndex(currentToken.EndIndex)\n                );\n            }\n            currentToken = tokens.Current;\n\n            if (!currentToken.Scopes.Contains(\"meta.embedded.network.model.prompt\"))\n            {\n                throw PromptSyntaxError.Network_ExpectedName(\n                    currentToken.StartIndex,\n                    GetSafeEndIndex(currentToken.EndIndex)\n                );\n            }\n\n            var modelName = RawText[currentToken.StartIndex..GetSafeEndIndex(currentToken.EndIndex)];\n\n            // If index service provided, validate model name\n            if (indexService != null)\n            {\n                var localModelList = indexService.ModelIndex.GetOrAdd(\n                    parsedNetworkType.ConvertTo<SharedFolderType>()\n                );\n                var localModel = localModelList.FirstOrDefault(\n                    m => Path.GetFileNameWithoutExtension(m.FileName) == modelName\n                );\n                if (localModel == null)\n                {\n                    throw PromptValidationError.Network_UnknownModel(\n                        modelName,\n                        parsedNetworkType,\n                        currentToken.StartIndex,\n                        GetSafeEndIndex(currentToken.EndIndex)\n                    );\n                }\n            }\n\n            // Skip another colon token\n            if (!tokens.MoveNext())\n            {\n                throw PromptSyntaxError.UnexpectedEndOfText(\n                    currentToken.StartIndex,\n                    GetSafeEndIndex(currentToken.EndIndex)\n                );\n            }\n            currentToken = tokens.Current;\n\n            double? weight = null;\n            // If its a ending token instead, we can end here, otherwise keep parsing for weight\n            if (!currentToken.Scopes.Contains(\"punctuation.definition.network.end.prompt\"))\n            {\n                // Ensure next token is colon\n                if (!currentToken.Scopes.Contains(\"punctuation.separator.variable.prompt\"))\n                {\n                    throw PromptSyntaxError.Network_ExpectedSeparator(\n                        currentToken.StartIndex,\n                        GetSafeEndIndex(currentToken.EndIndex)\n                    );\n                }\n\n                // Get model weight\n                if (!tokens.MoveNext())\n                {\n                    throw PromptSyntaxError.UnexpectedEndOfText(\n                        currentToken.StartIndex,\n                        GetSafeEndIndex(currentToken.EndIndex)\n                    );\n                }\n                currentToken = tokens.Current;\n\n                if (!currentToken.Scopes.Contains(\"constant.numeric\"))\n                {\n                    throw PromptSyntaxError.Network_ExpectedWeight(\n                        currentToken.StartIndex,\n                        GetSafeEndIndex(currentToken.EndIndex)\n                    );\n                }\n\n                var modelWeight = RawText[currentToken.StartIndex..GetSafeEndIndex(currentToken.EndIndex)];\n\n                // Convert to double\n                if (!double.TryParse(modelWeight, CultureInfo.InvariantCulture, out var weightValue))\n                {\n                    throw PromptValidationError.Network_InvalidWeight(\n                        currentToken.StartIndex,\n                        GetSafeEndIndex(currentToken.EndIndex)\n                    );\n                }\n                weight = weightValue;\n\n                // Expect end\n                if (!tokens.MoveNext())\n                {\n                    throw PromptSyntaxError.UnexpectedEndOfText(\n                        currentToken.StartIndex,\n                        GetSafeEndIndex(currentToken.EndIndex)\n                    );\n                }\n\n                currentToken = tokens.Current;\n\n                if (!currentToken.Scopes.Contains(\"punctuation.definition.network.end.prompt\"))\n                {\n                    throw PromptSyntaxError.UnexpectedEndOfText(\n                        currentToken.StartIndex,\n                        GetSafeEndIndex(currentToken.EndIndex)\n                    );\n                }\n            }\n\n            // Modified embedding handling with stack validation\n            if (parsedNetworkType is PromptExtraNetworkType.Embedding)\n            {\n                outputTokens.Push(currentToken);\n                outputText.Push(\n                    weight is null ? $\"embedding:{modelName}\" : $\"(embedding:{modelName}:{weight:F2})\"\n                );\n            }\n            else\n            {\n                // Original colon cleanup logic\n                if (\n                    outputTokens.TryPeek(out var lastToken)\n                    && lastToken.Scopes.Contains(\"punctuation.separator.variable.prompt\")\n                )\n                {\n                    outputTokens.Pop();\n                    outputText.Pop();\n                }\n\n                promptExtraNetworks.Add(\n                    new PromptExtraNetwork\n                    {\n                        Type = parsedNetworkType,\n                        Name = modelName,\n                        ModelWeight = weight\n                    }\n                );\n            }\n        }\n\n        // Build final text maintaining original order\n        var processedText = string.Concat(outputText.Reverse());\n        return (promptExtraNetworks, processedText);\n    }\n\n    public string GetDebugText()\n    {\n        var builder = new StringBuilder();\n\n        foreach (var token in TokenizeResult.Tokens)\n        {\n            // Get token text\n            var text = RawText[token.StartIndex..Math.Min(token.EndIndex, RawText.Length - 1)];\n\n            // Format scope\n            var scopeStr = string.Join(\n                \", \",\n                token\n                    .Scopes.Where(s => s != \"source.prompt\")\n                    .Select(\n                        s =>\n                            s.EndsWith(\".prompt\")\n                                ? s.Remove(s.LastIndexOf(\".prompt\", StringComparison.Ordinal))\n                                : s\n                    )\n            );\n\n            builder.AppendLine($\"{text.ToRepr()} ({token.StartIndex}, {token.EndIndex})\");\n            builder.AppendLine($\"  └─ {scopeStr}\");\n        }\n\n        return builder.ToString();\n    }\n\n    public static Prompt FromRawText(string text, ITokenizerProvider tokenizer)\n    {\n        using var _ = CodeTimer.StartDebug();\n\n        var result = tokenizer.TokenizeLine(text);\n\n        return new Prompt\n        {\n            RawText = text,\n            TokenizeResult = result,\n            Tokenizer = tokenizer\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/PromptAmplifierMode.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic enum PromptAmplifierMode\n{\n    Focused,\n    Balanced,\n    Imaginative\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/PromptCardModel.cs",
    "content": "﻿using System.Text.Json.Nodes;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic class PromptCardModel\n{\n    public string? Prompt { get; init; }\n    public string? NegativePrompt { get; init; }\n    public JsonObject? ModulesCardState { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/SamplerCardModel.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic class SamplerCardModel\n{\n    public int Steps { get; init; }\n\n    public bool IsDenoiseStrengthEnabled { get; init; } = false;\n    public double DenoiseStrength { get; init; }\n\n    public bool IsCfgScaleEnabled { get; init; } = true;\n    public double CfgScale { get; init; }\n\n    public bool IsDimensionsEnabled { get; init; }\n    public int Width { get; init; }\n    public int Height { get; init; }\n\n    public bool IsSamplerSelectionEnabled { get; init; } = true;\n    public string? SelectedSampler { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/SaveViewStateEventArgs.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\n/// <summary>\n/// Event for view models requesting to get their view state for saving\n/// </summary>\npublic class SaveViewStateEventArgs : EventArgs\n{\n    public Task<ViewState>? StateTask { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/SeedCardModel.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\n[JsonSerializable(typeof(SeedCardModel))]\npublic record SeedCardModel\n{\n    [JsonNumberHandling(\n        JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString\n    )]\n    public long Seed { get; init; }\n    public bool IsRandomizeEnabled { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/StackCardModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Text.Json.Nodes;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic class StackCardModel\n{\n    public List<JsonObject>? Cards { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/StackExpanderModel.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic class StackExpanderModel : StackCardModel\n{\n    public string? Title { get; set; }\n    public bool IsEnabled { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/UpscalerCardModel.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Api.Comfy;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\npublic class UpscalerCardModel\n{\n    public double Scale { get; init; } = 1;\n    public ComfyUpscaler? SelectedUpscaler { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/VideoOutputMethod.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Avalonia.Models.Inference;\n\n[JsonConverter(typeof(JsonStringEnumConverter<VideoOutputMethod>))]\npublic enum VideoOutputMethod\n{\n    Fastest,\n    Default,\n    Slowest,\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/Inference/ViewState.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models.Inference;\n\n/// <summary>\n/// Model for view states of inference tabs\n/// </summary>\npublic class ViewState\n{\n    public string? DockLayout { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/InferenceProjectDocument.cs",
    "content": "﻿using System;\nusing System.Text.Json;\nusing System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Core.Models.Inference;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\n/// <summary>\n/// This is the project file for inference tabs\n/// </summary>\npublic class InferenceProjectDocument : ICloneable\n{\n    [JsonIgnore]\n    private static readonly JsonSerializerOptions SerializerOptions =\n        new() { IgnoreReadOnlyProperties = true, WriteIndented = true, };\n\n    public int Version { get; set; } = 2;\n\n    [JsonConverter(typeof(JsonStringEnumConverter<InferenceProjectType>))]\n    public InferenceProjectType ProjectType { get; set; }\n\n    public JsonObject? State { get; set; }\n\n    public static InferenceProjectDocument FromLoadable(IJsonLoadableState loadableModel)\n    {\n        return new InferenceProjectDocument\n        {\n            ProjectType = loadableModel switch\n            {\n                InferenceImageToImageViewModel => InferenceProjectType.ImageToImage,\n                InferenceTextToImageViewModel => InferenceProjectType.TextToImage,\n                InferenceImageUpscaleViewModel => InferenceProjectType.Upscale,\n                InferenceImageToVideoViewModel => InferenceProjectType.ImageToVideo,\n                InferenceFluxTextToImageViewModel => InferenceProjectType.FluxTextToImage,\n                InferenceWanImageToVideoViewModel => InferenceProjectType.WanImageToVideo,\n                InferenceWanTextToVideoViewModel => InferenceProjectType.WanTextToVideo\n            },\n            State = loadableModel.SaveStateToJsonObject()\n        };\n    }\n\n    public void VerifyVersion()\n    {\n        if (Version < 2)\n        {\n            throw new NotSupportedException(\n                $\"Project was created in an earlier pre-release version of Stability Matrix and is no longer supported. \"\n                    + $\"Please create a new project.\"\n            );\n        }\n    }\n\n    public SeedCardModel? GetSeedModel()\n    {\n        if (State is null || !State.TryGetPropertyValue(\"Seed\", out var seedCard))\n        {\n            return null;\n        }\n\n        return seedCard.Deserialize<SeedCardModel>();\n    }\n\n    /// <summary>\n    /// Returns a new <see cref=\"InferenceProjectDocument\"/> with the State modified.\n    /// </summary>\n    /// <param name=\"stateModifier\">Action that changes the state</param>\n    public InferenceProjectDocument WithState(Action<JsonObject?> stateModifier)\n    {\n        var document = (InferenceProjectDocument)Clone();\n        stateModifier(document.State);\n        return document;\n    }\n\n    public bool TryUpdateModel<T>(string key, Func<T, T> modifier)\n    {\n        if (State is not { } state)\n            return false;\n\n        if (!state.TryGetPropertyValue(key, out var modelNode))\n        {\n            return false;\n        }\n\n        if (modelNode.Deserialize<T>() is not { } model)\n        {\n            return false;\n        }\n\n        modelNode = JsonSerializer.SerializeToNode(modifier(model));\n\n        state[key] = modelNode;\n\n        return true;\n    }\n\n    public bool TryUpdateModel(string key, Func<JsonNode, JsonNode> modifier)\n    {\n        if (State is not { } state)\n            return false;\n\n        if (!state.TryGetPropertyValue(key, out var modelNode) || modelNode is null)\n        {\n            return false;\n        }\n\n        state[key] = modifier(modelNode);\n\n        return true;\n    }\n\n    public InferenceProjectDocument WithBatchSize(int batchSize, int batchCount)\n    {\n        if (State is null)\n            throw new InvalidOperationException(\"State is null\");\n\n        var document = (InferenceProjectDocument)Clone();\n\n        var batchSizeCard =\n            document.State![\"BatchSize\"] ?? throw new InvalidOperationException(\"BatchSize card is null\");\n\n        batchSizeCard[\"BatchSize\"] = batchSize;\n        batchSizeCard[\"BatchCount\"] = batchCount;\n\n        return document;\n    }\n\n    /// <inheritdoc />\n    public object Clone()\n    {\n        var newObj = (InferenceProjectDocument)MemberwiseClone();\n        // Clone State also since its mutable\n        newObj.State = State == null ? null : JsonSerializer.SerializeToNode(State).Deserialize<JsonObject>();\n        return newObj;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/ObservableDictionary.cs",
    "content": "﻿using System.Collections;\nusing System.Collections.Generic;\nusing System.Collections.Specialized;\nusing System.ComponentModel;\nusing System.Diagnostics.CodeAnalysis;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>,\n    INotifyCollectionChanged, INotifyPropertyChanged where TKey : notnull\n{\n    private readonly IDictionary<TKey, TValue> dictionary;\n\n    public event NotifyCollectionChangedEventHandler? CollectionChanged;\n    public event PropertyChangedEventHandler? PropertyChanged;\n    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => dictionary.GetEnumerator();\n\n    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();\n\n    public ObservableDictionary()\n    {\n        dictionary = new Dictionary<TKey, TValue>();\n    }\n\n    public ObservableDictionary(Dictionary<TKey, TValue> dictionary)\n    {\n        this.dictionary = dictionary;\n    }\n\n    public void Add(KeyValuePair<TKey, TValue> item)\n    {\n        dictionary.Add(item);\n        CollectionChanged?.Invoke(this,\n            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Keys)));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Values)));\n    }\n\n    public void Clear()\n    {\n        dictionary.Clear();\n        CollectionChanged?.Invoke(this,\n            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Keys)));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Values)));\n    }\n\n    public bool Contains(KeyValuePair<TKey, TValue> item) => dictionary.Contains(item);\n\n    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)\n    {\n        dictionary.CopyTo(array, arrayIndex);\n    }\n\n    public bool Remove(KeyValuePair<TKey, TValue> item)\n    {\n        var success = dictionary.Remove(item);\n        if (!success) return false;\n\n        CollectionChanged?.Invoke(this,\n            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Keys)));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Values)));\n\n        return success;\n    }\n\n    public int Count => dictionary.Count;\n    public bool IsReadOnly => dictionary.IsReadOnly;\n\n    public void Add(TKey key, TValue value)\n    {\n        dictionary.Add(key, value);\n        CollectionChanged?.Invoke(this,\n            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,\n                new KeyValuePair<TKey, TValue>(key, value)));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Keys)));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Values)));\n    }\n\n    public bool ContainsKey(TKey key) => dictionary.ContainsKey(key);\n\n    public bool Remove(TKey key)\n    {\n        var success = dictionary.TryGetValue(key, out var value) && dictionary.Remove(key);\n        if (!success) return false;\n\n        CollectionChanged?.Invoke(this,\n            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,\n                new KeyValuePair<TKey, TValue>(key, value!)));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Keys)));\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Values)));\n\n        return success;\n    }\n\n    public bool TryGetValue([NotNull] TKey key, [MaybeNullWhen(false)] out TValue value) \n        => dictionary.TryGetValue(key, out value);\n\n    public TValue this[TKey key]\n    {\n        get => dictionary[key];\n        set\n        {\n            var exists = dictionary.ContainsKey(key);\n            var action = exists\n                ? NotifyCollectionChangedAction.Replace\n                : NotifyCollectionChangedAction.Add;\n            dictionary[key] = value;\n            CollectionChanged?.Invoke(this,\n                new NotifyCollectionChangedEventArgs(action, dictionary[key]));\n            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));\n            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Keys)));\n            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Values)));\n        }\n    }\n\n    public ICollection<TKey> Keys => dictionary.Keys;\n    public ICollection<TValue> Values => dictionary.Values;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/OpenArtCustomNode.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic class OpenArtCustomNode\n{\n    public required string Title { get; set; }\n    public List<string> Children { get; set; } = [];\n    public bool IsInstalled { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Linq;\nusing System.Text.Json.Serialization;\nusing Avalonia.Platform.Storage;\nusing StabilityMatrix.Core.Models.Api.OpenArt;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic class OpenArtMetadata\n{\n    [JsonPropertyName(\"sm_workflow_data\")]\n    public OpenArtSearchResult? Workflow { get; set; }\n\n    [JsonIgnore]\n    public string? FirstThumbnail => Workflow?.Thumbnails?.Select(x => x.Url).FirstOrDefault()?.ToString();\n\n    [JsonIgnore]\n    public List<IStorageFile>? FilePath { get; set; }\n\n    [JsonIgnore]\n    [MemberNotNullWhen(true, nameof(Workflow))]\n    public bool HasMetadata => Workflow?.Creator != null;\n\n    [JsonIgnore]\n    internal int Index { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/PackageManagerNavigationOptions.cs",
    "content": "using StabilityMatrix.Core.Models.Packages;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic record PackageManagerNavigationOptions\n{\n    public bool OpenInstallerDialog { get; init; }\n\n    public BasePackage? InstallerSelectedPackage { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/PackageSteps/UnpackSiteCustomizeStep.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Avalonia.Models.PackageSteps;\n\npublic class UnpackSiteCustomizeStep(DirectoryPath venvPath) : IPackageStep\n{\n    public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        var sitePackages = venvPath.JoinDir(PyVenvRunner.RelativeSitePackagesPath);\n        var file = sitePackages.JoinFile(\"sitecustomize.py\");\n        file.Directory?.Create();\n        await Assets.PyScriptSiteCustomize.ExtractTo(file);\n    }\n\n    public string ProgressTitle => \"Unpacking prerequisites...\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/PaintCanvasTool.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models;\n\npublic enum PaintCanvasTool\n{\n    None,\n    PaintBrush,\n    Eraser\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/PythonPackageSpecifiersItem.cs",
    "content": "﻿using StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\n/// <summary>\n/// Row item of <see cref=\"PythonPackageSpecifiersViewModel\"/>.\n/// </summary>\npublic class PythonPackageSpecifiersItem\n{\n    public string? Name { get; set; }\n    public string? Constraint { get; set; }\n    public string? Version { get; set; }\n    public PipPackageSpecifierOverrideAction Action { get; set; }\n\n    public static PythonPackageSpecifiersItem FromSpecifier(PipPackageSpecifierOverride specifier)\n    {\n        return new PythonPackageSpecifiersItem\n        {\n            Name = specifier.Name,\n            Constraint = specifier.Constraint,\n            Version = specifier.Version,\n            Action = specifier.Action\n        };\n    }\n\n    public PipPackageSpecifierOverride ToSpecifier()\n    {\n        return new PipPackageSpecifierOverride\n        {\n            Name = Name,\n            Constraint = Constraint,\n            Version = Version,\n            Action = Action\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/SelectableItem.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Windows.Input;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData.Binding;\nusing JetBrains.Annotations;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\n[PublicAPI]\npublic class SelectableItem<T>(T item) : AbstractNotifyPropertyChanged, IEquatable<SelectableItem<T>>\n{\n    public T Item { get; } = item;\n\n    private bool _isSelected;\n\n    public bool IsSelected\n    {\n        get => _isSelected;\n        set => SetAndRaise(ref _isSelected, value);\n    }\n\n    public ICommand ToggleSelectedCommand => new RelayCommand(() => IsSelected = !IsSelected); \n    \n    /// <inheritdoc />\n    public bool Equals(SelectableItem<T>? other)\n    {\n        if (ReferenceEquals(null, other))\n            return false;\n        if (ReferenceEquals(this, other))\n            return true;\n        return EqualityComparer<T>.Default.Equals(Item, other.Item);\n    }\n\n    /// <inheritdoc />\n    public override bool Equals(object? obj)\n    {\n        if (ReferenceEquals(null, obj))\n            return false;\n        if (ReferenceEquals(this, obj))\n            return true;\n        if (obj.GetType() != GetType())\n            return false;\n        return Equals((SelectableItem<T>)obj);\n    }\n\n    /// <inheritdoc />\n    public override int GetHashCode()\n    {\n        return HashCode.Combine(GetType().GetHashCode(), Item?.GetHashCode());\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/SharedState.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\n/// <summary>\n/// Singleton DI service for observable shared UI state.\n/// </summary>\n[RegisterSingleton<SharedState>]\npublic partial class SharedState : ObservableObject\n{\n    /// <summary>\n    /// Whether debug mode enabled from settings page version tap.\n    /// </summary>\n    [ObservableProperty]\n    private bool isDebugMode;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TagCompletion/CompletionProvider.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Text.RegularExpressions;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing AutoComplete.Builders;\nusing AutoComplete.Clients.IndexSearchers;\nusing AutoComplete.DataStructure;\nusing AutoComplete.Domain;\nusing Avalonia.Controls.Notifications;\nusing Injectio.Attributes;\nusing Nito.AsyncEx;\nusing NLog;\nusing StabilityMatrix.Avalonia.Controls.CodeCompletion;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Models.Tokens;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.Models.TagCompletion;\n\n[RegisterSingleton<ICompletionProvider, CompletionProvider>]\npublic partial class CompletionProvider : ICompletionProvider\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    [GeneratedRegex(@\"([\\[\\]()<>])\")]\n    private static partial Regex BracketsRegex();\n\n    private readonly ISettingsManager settingsManager;\n    private readonly INotificationService notificationService;\n    private readonly IModelIndexService modelIndexService;\n    private readonly IDownloadService downloadService;\n\n    private readonly AsyncLock loadLock = new();\n    private readonly Dictionary<string, TagCsvEntry> entries = new();\n\n    private InMemoryIndexSearcher? searcher;\n\n    /// <inheritdoc />\n    public CompletionType AvailableCompletionTypes\n    {\n        get\n        {\n            var types = CompletionType.ExtraNetwork | CompletionType.ExtraNetworkType;\n            if (searcher is not null)\n            {\n                types |= CompletionType.Tag;\n            }\n            return types;\n        }\n    }\n\n    public Func<ICompletionData, string>? PrepareInsertionText { get; }\n\n    public CompletionProvider(\n        ISettingsManager settingsManager,\n        INotificationService notificationService,\n        IModelIndexService modelIndexService,\n        IDownloadService downloadService\n    )\n    {\n        this.settingsManager = settingsManager;\n        this.notificationService = notificationService;\n        this.modelIndexService = modelIndexService;\n        this.downloadService = downloadService;\n\n        PrepareInsertionText = PrepareInsertionText_Process;\n\n        // Attach to load from set file on initial settings load\n        settingsManager.Loaded += (_, _) => UpdateTagCompletionCsv();\n\n        // Also load when TagCompletionCsv property changes\n        settingsManager.SettingsPropertyChanged += (_, args) =>\n        {\n            if (args.PropertyName == nameof(Settings.TagCompletionCsv))\n            {\n                UpdateTagCompletionCsv();\n            }\n        };\n\n        // If library already loaded, start a background load\n        if (settingsManager.IsLibraryDirSet)\n        {\n            UpdateTagCompletionCsv();\n        }\n\n        return;\n\n        void UpdateTagCompletionCsv()\n        {\n            var csvPath = settingsManager.Settings.TagCompletionCsv;\n            if (csvPath is null)\n                return;\n\n            var fullPath = settingsManager.TagsDirectory.JoinFile(csvPath);\n            BackgroundLoadFromFile(fullPath);\n        }\n    }\n\n    private string PrepareInsertionText_Process(ICompletionData data)\n    {\n        var text = data.Text;\n\n        // For tags and if enabled, replace underscores with spaces\n        if (data is TagCompletionData && settingsManager.Settings.IsCompletionRemoveUnderscoresEnabled)\n        {\n            // Remove underscores\n            text = text.Replace(\"_\", \" \");\n        }\n\n        // (Only for non model types)\n        // For bracket type character, escape it\n        if (data is not ModelCompletionData)\n        {\n            text = BracketsRegex().Replace(text, @\"\\$1\");\n        }\n\n        return text;\n    }\n\n    /// <inheritdoc />\n    public void BackgroundLoadFromFile(FilePath path, bool recreate = false)\n    {\n        LoadFromFile(path, recreate)\n            .SafeFireAndForget(\n                onException: exception =>\n                {\n                    const string title = \"Failed to load tag completion file\";\n                    if (Debugger.IsAttached)\n                    {\n                        Debug.Fail(title);\n                    }\n                    Logger.Warn(exception, title);\n                    notificationService.Show(\n                        title + $\" {path.Name}\",\n                        exception.Message,\n                        NotificationType.Error\n                    );\n                },\n                true\n            );\n    }\n\n    /// <inheritdoc />\n    public async Task Setup()\n    {\n        var tagsDir = settingsManager.TagsDirectory;\n        tagsDir.Create();\n\n        // If tagsDir is empty and no selected, download defaults\n        if (\n            !tagsDir.Info.EnumerateFiles().Any()\n            && (\n                settingsManager.Settings.TagCompletionCsv is null\n                || !tagsDir.JoinFile(settingsManager.Settings.TagCompletionCsv).Exists\n            )\n        )\n        {\n            foreach (var remoteCsv in Assets.DefaultCompletionTags)\n            {\n                var fileName = remoteCsv.Url.Segments.Last();\n                Logger.Info(\n                    \"Downloading default tag source {Name} [{Hash}]\",\n                    fileName,\n                    remoteCsv.HashSha256[..7]\n                );\n                await downloadService.DownloadToFileAsync(\n                    remoteCsv.Url.ToString(),\n                    tagsDir.JoinFile(fileName)\n                );\n            }\n\n            var defaultFile = tagsDir.JoinFile(\"danbooru_e621_merged.csv\");\n            if (!defaultFile.Exists)\n            {\n                Logger.Warn(\"Failed to download default tag source\");\n                return;\n            }\n\n            // Set default file as selected\n            settingsManager.Settings.TagCompletionCsv = defaultFile.Name;\n            Logger.Debug(\"Tag completion source set to {Name}\", defaultFile.Name);\n\n            // Load default file\n            BackgroundLoadFromFile(defaultFile);\n        }\n        else\n        {\n            var newDefaultFile = tagsDir.JoinFile(\"danbooru_e621_merged.csv\");\n            if (newDefaultFile.Exists)\n            {\n                return;\n            }\n\n            var newRemoteCsv = Assets.DefaultCompletionTags[^1];\n            var fileName = newRemoteCsv.Url.Segments.Last();\n            Logger.Info(\n                \"Downloading new default tag source {Name} [{Hash}]\",\n                fileName,\n                newRemoteCsv.HashSha256[..7]\n            );\n            await downloadService.DownloadToFileAsync(\n                newRemoteCsv.Url.ToString(),\n                tagsDir.JoinFile(fileName)\n            );\n\n            notificationService.Show(\n                \"New autocomplete tag source downloaded\",\n                \"You can activate this in Settings -> Inference -> Auto Completion\",\n                expiration: TimeSpan.FromSeconds(8)\n            );\n        }\n    }\n\n    /// <inheritdoc />\n    public async Task LoadFromFile(FilePath path, bool recreate = false)\n    {\n        using var _ = await loadLock.LockAsync();\n\n        // Get Blake3 hash of file\n        var hash = await FileHash.GetBlake3Async(path);\n\n        Logger.Trace(\"Loading tags from {Path} with Blake3 hash {Hash}\", path, hash);\n\n        // Check for AppData/StabilityMatrix/Temp/Tags/<hash>/*.bin\n        var tempTagsDir = GlobalConfig.HomeDir.JoinDir(\"Temp\", \"Tags\");\n        var hashDir = tempTagsDir.JoinDir(hash);\n        hashDir.Create();\n\n        var headerFile = hashDir.JoinFile(\"header.bin\");\n        var indexFile = hashDir.JoinFile(\"index.bin\");\n\n        entries.Clear();\n\n        var timer = Stopwatch.StartNew();\n\n        // If directory or any file is missing, rebuild the index\n        if (recreate || !(hashDir.Exists && headerFile.Exists && indexFile.Exists))\n        {\n            Logger.Debug(\"Creating new index for {Path}\", hashDir);\n\n            await using var headerStream = headerFile.Info.OpenWrite();\n            await using var indexStream = indexFile.Info.OpenWrite();\n\n            var builder = new IndexBuilder(headerStream, indexStream);\n\n            // Parse csv\n            await using var csvStream = path.Info.OpenRead();\n            var parser = new TagCsvParser(csvStream);\n\n            await foreach (var entry in parser.ParseAsync())\n            {\n                if (string.IsNullOrWhiteSpace(entry.Name))\n                    continue;\n\n                // Add to index\n                builder.Add(entry.Name);\n                // Add to local dictionary\n                entries.Add(entry.Name, entry);\n            }\n\n            await Task.Run(builder.Build);\n        }\n        else\n        {\n            // Otherwise just load the dictionary\n            Logger.Debug(\"Loading existing index for {Path}\", hashDir);\n\n            await using var csvStream = path.Info.OpenRead();\n            var parser = new TagCsvParser(csvStream);\n\n            await foreach (var entry in parser.ParseAsync())\n            {\n                if (string.IsNullOrWhiteSpace(entry.Name))\n                    continue;\n\n                // Add to local dictionary\n                entries.Add(entry.Name, entry);\n            }\n        }\n\n        searcher = new InMemoryIndexSearcher(headerFile, indexFile);\n        searcher.Init();\n\n        var elapsed = timer.Elapsed;\n\n        Logger.Info(\n            \"Loaded {Count} tags for {Path} in {Time:F2}s\",\n            entries.Count,\n            path.Name,\n            elapsed.TotalSeconds\n        );\n    }\n\n    /// <inheritdoc />\n    public IEnumerable<ICompletionData> GetCompletions(\n        TextCompletionRequest completionRequest,\n        int itemsCount,\n        bool suggest\n    )\n    {\n        if (completionRequest.Type == CompletionType.Tag)\n        {\n            return GetCompletionTags(completionRequest.Text, itemsCount, suggest);\n        }\n\n        if (completionRequest.Type == CompletionType.ExtraNetwork)\n        {\n            return GetCompletionNetworks(\n                completionRequest.ExtraNetworkTypes,\n                completionRequest.Text,\n                itemsCount\n            );\n        }\n\n        if (completionRequest.Type == CompletionType.ExtraNetworkType)\n        {\n            return GetCompletionNetworkTypes(completionRequest.Text);\n        }\n\n        throw new InvalidOperationException();\n    }\n\n    private IEnumerable<ICompletionData> GetCompletionNetworks(\n        PromptExtraNetworkType networkType,\n        string searchTerm,\n        int itemsCount\n    )\n    {\n        var folderTypes = Enum.GetValues(typeof(PromptExtraNetworkType))\n            .Cast<PromptExtraNetworkType>()\n            .Where(f => networkType.HasFlag(f))\n            .Select(network => network.ConvertTo<SharedFolderType>())\n            // Convert back to bit flags\n            .Aggregate((a, b) => a | b);\n\n        var models = modelIndexService.FindByModelType(folderTypes);\n\n        var matches = models\n            .Where(model => model.FileName.StartsWith(searchTerm, StringComparison.OrdinalIgnoreCase))\n            .Select(model => ModelCompletionData.FromLocalModel(model, networkType))\n            .Take(itemsCount);\n\n        return matches;\n    }\n\n    private IEnumerable<ICompletionData> GetCompletionNetworkTypes(string searchTerm)\n    {\n        var availableTypes = new[]\n        {\n            (PromptExtraNetworkType.Lora, \"lora\"),\n            (PromptExtraNetworkType.LyCORIS, \"lyco\"),\n            (PromptExtraNetworkType.Embedding, \"embedding\")\n        };\n\n        return availableTypes\n            .Where(\n                type => type.Item1.GetStringValue().StartsWith(searchTerm, StringComparison.OrdinalIgnoreCase)\n            )\n            .Select(type => new ModelTypeCompletionData(type.Item2, type.Item1));\n    }\n\n    private IEnumerable<ICompletionData> GetCompletionTags(string searchTerm, int itemsCount, bool suggest)\n    {\n        if (searcher is null)\n        {\n            throw new InvalidOperationException(\"Index is not loaded\");\n        }\n\n        var timer = Stopwatch.StartNew();\n\n        var searchOptions = new SearchOptions\n        {\n            Term = searchTerm,\n            MaxItemCount = itemsCount,\n            SuggestWhenFoundStartsWith = suggest\n        };\n\n        var result = searcher.Search(searchOptions);\n\n        // No results\n        if (result.ResultType == TrieNodeSearchResultType.NotFound)\n        {\n            Logger.Trace(\"No results for {Term}\", searchTerm);\n            return Array.Empty<ICompletionData>();\n        }\n\n        // Is null for some reason?\n        if (result.Items is null)\n        {\n            Logger.Warn(\"Got null results for {Term}\", searchTerm);\n            return Array.Empty<ICompletionData>();\n        }\n\n        Logger.Trace(\"Got {Count} results for {Term}\", result.Items.Length, searchTerm);\n\n        // Get entry for each result\n        var completions = new List<ICompletionData>();\n        foreach (var item in result.Items)\n        {\n            if (entries.TryGetValue(item, out var entry))\n            {\n                var entryType = TagTypeExtensions.FromE621(entry.Type.GetValueOrDefault(-1));\n                completions.Add(new TagCompletionData(entry.Name!, entryType));\n            }\n        }\n\n        timer.Stop();\n        Logger.Trace(\"Completions for {Term} took {Time:F2}ms\", searchTerm, timer.Elapsed.TotalMilliseconds);\n\n        return completions;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TagCompletion/CompletionType.cs",
    "content": "﻿using System;\n\nnamespace StabilityMatrix.Avalonia.Models.TagCompletion;\n\n/// <summary>\n/// Type of completion requested.\n/// </summary>\n[Flags]\npublic enum CompletionType\n{\n    None = 0,\n    Tag = 1 << 1,\n    ExtraNetwork = 1 << 2,\n    ExtraNetworkType = 1 << 3\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TagCompletion/EditorCompletionRequest.cs",
    "content": "﻿using AvaloniaEdit.Document;\n\nnamespace StabilityMatrix.Avalonia.Models.TagCompletion;\n\npublic record EditorCompletionRequest : TextCompletionRequest\n{\n    public required ISegment Segment { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TagCompletion/ICompletionProvider.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Avalonia.Controls.CodeCompletion;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Avalonia.Models.TagCompletion;\n\npublic interface ICompletionProvider\n{\n    /// <summary>\n    /// Currently available completion types.\n    /// </summary>\n    CompletionType AvailableCompletionTypes { get; }\n\n    /// <summary>\n    /// Optional function to transform the text to be inserted\n    /// </summary>\n    Func<ICompletionData, string>? PrepareInsertionText => null;\n\n    /// <summary>\n    /// Downloads default tags and selects one if required.\n    /// </summary>\n    Task Setup();\n\n    /// <summary>\n    /// Load the completion provider from a file.\n    /// </summary>\n    Task LoadFromFile(FilePath path, bool recreate = false);\n\n    /// <summary>\n    /// Load the completion provider from a file in the background.\n    /// </summary>\n    void BackgroundLoadFromFile(FilePath path, bool recreate = false);\n\n    /// <summary>\n    /// Returns a list of completion items for the given text.\n    /// </summary>\n    public IEnumerable<ICompletionData> GetCompletions(\n        TextCompletionRequest completionRequest,\n        int itemsCount,\n        bool suggest\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TagCompletion/ITokenizerProvider.cs",
    "content": "﻿using TextMateSharp.Grammars;\n\nnamespace StabilityMatrix.Avalonia.Models.TagCompletion;\n\npublic interface ITokenizerProvider\n{\n    /// <summary>\n    /// Returns a <see cref=\"ITokenizeLineResult\"/> for the given line.\n    /// </summary>\n    ITokenizeLineResult TokenizeLine(string lineText);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TagCompletion/ModelCompletionData.cs",
    "content": "﻿using System.IO;\nusing StabilityMatrix.Avalonia.Controls.CodeCompletion;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Tokens;\n\nnamespace StabilityMatrix.Avalonia.Models.TagCompletion;\n\npublic class ModelCompletionData : CompletionData\n{\n    protected PromptExtraNetworkType NetworkType { get; }\n\n    /// <inheritdoc />\n    public ModelCompletionData(string text, PromptExtraNetworkType networkType)\n        : base(text)\n    {\n        NetworkType = networkType;\n        Icon = CompletionIcons.Model;\n        Description = networkType.GetStringValue();\n    }\n\n    public static ModelCompletionData FromLocalModel(\n        LocalModelFile localModel,\n        PromptExtraNetworkType networkType\n    )\n    {\n        var displayName = Path.GetFileNameWithoutExtension(localModel.FileName);\n        return new ModelCompletionData(displayName, networkType)\n        {\n            ImageTitle = localModel.ConnectedModelInfo?.ModelName,\n            ImageSubtitle = localModel.ConnectedModelInfo?.VersionName,\n            ImageSource = localModel.PreviewImageFullPathGlobal is { } img\n                ? new ImageSource(new FilePath(img))\n                : null\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TagCompletion/ModelTypeCompletionData.cs",
    "content": "﻿using StabilityMatrix.Avalonia.Controls.CodeCompletion;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Tokens;\n\nnamespace StabilityMatrix.Avalonia.Models.TagCompletion;\n\npublic class ModelTypeCompletionData : CompletionData\n{\n    protected PromptExtraNetworkType NetworkType { get; }\n\n    /// <inheritdoc />\n    public ModelTypeCompletionData(string text, PromptExtraNetworkType networkType)\n        : base(text)\n    {\n        NetworkType = networkType;\n        Icon = CompletionIcons.ModelType;\n        Description = $\"{networkType.GetStringValue()} Network\";\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TagCompletion/TagCompletionData.cs",
    "content": "﻿using StabilityMatrix.Avalonia.Controls.CodeCompletion;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.Models.TagCompletion;\n\npublic class TagCompletionData : CompletionData\n{\n    protected TagType TagType { get; }\n    \n    /// <inheritdoc />\n    public TagCompletionData(string text, TagType tagType) : base(text)\n    {\n        TagType = tagType;\n        Icon = CompletionIcons.GetIconForTagType(tagType) ?? CompletionIcons.Invalid;\n        Description = tagType.GetStringValue();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TagCompletion/TagCsvEntry.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models.TagCompletion;\n\npublic record TagCsvEntry\n{\n    public string? Name { get; init; }\n    \n    public int? Type { get; init; }\n    \n    public int? Count { get; init; }\n    \n    public string? Aliases { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TagCompletion/TagType.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Avalonia.Models.TagCompletion;\n\n[JsonConverter(typeof(DefaultUnknownEnumConverter<TagType>))]\npublic enum TagType\n{\n    Unknown,\n    Invalid,\n    General,\n    Artist,\n    Copyright,\n    Character,\n    Species,\n    Meta,\n    Lore\n}\n\npublic static class TagTypeExtensions\n{\n    public static TagType FromE621(int tag)\n    {\n        return tag switch\n        {\n            -1 => TagType.Invalid,\n            0 => TagType.General,\n            1 => TagType.Artist,\n            3 => TagType.Copyright,\n            4 => TagType.Character,\n            5 => TagType.Species,\n            6 => TagType.Invalid,\n            7 => TagType.Meta,\n            8 => TagType.Lore,\n            _ => TagType.Unknown\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TagCompletion/TextCompletionRequest.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Tokens;\n\nnamespace StabilityMatrix.Avalonia.Models.TagCompletion;\n\npublic record TextCompletionRequest\n{\n    public required CompletionType Type { get; init; }\n    public required string Text { get; init; }\n    public PromptExtraNetworkType ExtraNetworkTypes { get; init; } = new();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TagCompletion/TokenizerProvider.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Extensions;\nusing TextMateSharp.Grammars;\nusing TextMateSharp.Registry;\n\nnamespace StabilityMatrix.Avalonia.Models.TagCompletion;\n\n[RegisterSingleton<ITokenizerProvider, TokenizerProvider>]\npublic class TokenizerProvider : ITokenizerProvider\n{\n    private readonly Registry registry = new(new RegistryOptions(ThemeName.DarkPlus));\n    private IGrammar? grammar;\n\n    /// <inheritdoc />\n    public ITokenizeLineResult TokenizeLine(string lineText)\n    {\n        if (grammar is null)\n        {\n            SetPromptGrammar();\n        }\n        return grammar.TokenizeLine(lineText);\n    }\n\n    [MemberNotNull(nameof(grammar))]\n    public void SetPromptGrammar()\n    {\n        using var stream = Assets.ImagePromptLanguageJson.Open();\n        grammar = registry.LoadGrammarFromStream(stream);\n    }\n\n    public void SetGrammar(string scopeName)\n    {\n        grammar = registry.LoadGrammar(scopeName);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TextEditorPreset.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models;\n\npublic enum TextEditorPreset\n{\n    None,\n    Prompt,\n    Console\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerDirectory.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Avalonia.Models.TreeFileExplorer;\n\npublic class TreeFileExplorerDirectory(IPathObject path, TreeFileExplorerOptions options)\n    : TreeFileExplorerItem(path, options)\n{\n    public IEnumerable<TreeFileExplorerItem> Children =>\n        GetChildren(Path, Options)\n            .OrderByDescending(item => item.Path is DirectoryPath)\n            .ThenBy(item => item.Path.Name);\n\n    private static IEnumerable<TreeFileExplorerItem> GetChildren(\n        IPathObject pathObject,\n        TreeFileExplorerOptions options\n    )\n    {\n        return pathObject switch\n        {\n            FilePath => Enumerable.Empty<TreeFileExplorerItem>(),\n            DirectoryPath directoryPath => GetChildren(directoryPath, options),\n            _ => throw new NotSupportedException()\n        };\n    }\n\n    private static IEnumerable<TreeFileExplorerItem> GetChildren(\n        DirectoryPath directoryPath,\n        TreeFileExplorerOptions options\n    )\n    {\n        if (options.HasFlag(TreeFileExplorerOptions.IndexFiles))\n        {\n            foreach (var file in directoryPath.EnumerateFiles())\n            {\n                yield return new TreeFileExplorerFile(file, options);\n            }\n        }\n\n        if (options.HasFlag(TreeFileExplorerOptions.IndexFolders))\n        {\n            foreach (var directory in directoryPath.EnumerateDirectories())\n            {\n                yield return new TreeFileExplorerDirectory(directory, options);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerFile.cs",
    "content": "﻿using StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Avalonia.Models.TreeFileExplorer;\n\npublic class TreeFileExplorerFile(IPathObject path, TreeFileExplorerOptions options)\n    : TreeFileExplorerItem(path, options);\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerItem.cs",
    "content": "﻿using StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Avalonia.Models.TreeFileExplorer;\n\npublic class TreeFileExplorerItem(IPathObject path, TreeFileExplorerOptions options)\n{\n    public IPathObject Path { get; } = path;\n\n    public TreeFileExplorerOptions Options { get; } = options;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerOptions.cs",
    "content": "﻿using System;\n\nnamespace StabilityMatrix.Avalonia.Models.TreeFileExplorer;\n\n[Flags]\npublic enum TreeFileExplorerOptions\n{\n    None = 0,\n\n    IndexFiles = 1 << 5,\n    IndexFolders = 1 << 6,\n\n    CanSelectFiles = 1 << 10,\n    CanSelectFolders = 1 << 11,\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TreeFileExplorer/TreeFileExplorerType.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Models.TreeFileExplorer;\n\npublic enum TreeFileExplorerType\n{\n    None,\n    File,\n    Directory\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TreeViewDirectory.cs",
    "content": "﻿using System.Collections.ObjectModel;\nusing CommunityToolkit.Mvvm.ComponentModel;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic partial class TreeViewDirectory : ObservableObject\n{\n    public ObservableCollection<TreeViewDirectory> SubDirectories { get; set; } = new();\n    public required string Name { get; set; }\n    public required string Path { get; set; }\n\n    [ObservableProperty]\n    public partial bool IsExpanded { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/TypedNavigationEventArgs.cs",
    "content": "﻿using System;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic class TypedNavigationEventArgs : EventArgs\n{\n    public required Type ViewModelType { get; init; }\n\n    public object? ViewModel { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/UpdateChannelCard.cs",
    "content": "﻿using System;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Semver;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Update;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\npublic partial class UpdateChannelCard : ObservableObject\n{\n    public UpdateChannel UpdateChannel { get; init; }\n\n    public string DisplayName => UpdateChannel.GetStringValue();\n\n    public string? Description { get; init; }\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(LatestVersionString))]\n    [NotifyPropertyChangedFor(nameof(IsLatestVersionUpdateable))]\n    private SemVersion? latestVersion;\n\n    public string? LatestVersionString => LatestVersion is null ? null : $\"Latest: v{LatestVersion}\";\n\n    [ObservableProperty]\n    private bool isSelectable = true;\n\n    /// <summary>\n    /// Whether the <see cref=\"LatestVersion\"/> is available for update.\n    /// </summary>\n    public bool IsLatestVersionUpdateable\n    {\n        get\n        {\n            if (LatestVersion is null)\n            {\n                return false;\n            }\n\n            switch (LatestVersion.ComparePrecedenceTo(Compat.AppVersion))\n            {\n                case > 0:\n                    // Newer version available\n                    return true;\n                case 0:\n                {\n                    // Same version available, check if we both have commit hash metadata\n                    var updateHash = LatestVersion.Metadata;\n                    var appHash = Compat.AppVersion.Metadata;\n\n                    // Always assume update if (We don't have hash && Update has hash)\n                    if (string.IsNullOrEmpty(appHash) && !string.IsNullOrEmpty(updateHash))\n                    {\n                        return true;\n                    }\n\n                    // Trim both to the lower length, to a minimum of 7 characters\n                    var minLength = Math.Min(7, Math.Min(updateHash.Length, appHash.Length));\n                    updateHash = updateHash[..minLength];\n                    appHash = appHash[..minLength];\n\n                    // If different, we can update\n                    if (updateHash != appHash)\n                    {\n                        return true;\n                    }\n\n                    break;\n                }\n            }\n\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Models/ViewModelState.cs",
    "content": "﻿using System;\n\nnamespace StabilityMatrix.Avalonia.Models;\n\n/// <summary>\n///\n/// </summary>\n[Flags]\npublic enum ViewModelState : uint\n{\n    /// <summary>\n    /// View Model has been initially loaded\n    /// </summary>\n    InitialLoaded = 1 << 0,\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Program.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.IO;\nusing System.Linq;\nusing System.Reflection;\nusing System.Runtime.InteropServices;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing AsyncImageLoader;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.ApplicationLifetimes;\nusing Avalonia.Threading;\nusing CommandLine;\nusing NLog;\nusing Polly.Contrib.WaitAndRetry;\nusing Projektanker.Icons.Avalonia;\nusing Projektanker.Icons.Avalonia.FontAwesome;\nusing Semver;\nusing Sentry;\nusing StabilityMatrix.Avalonia.Controls.VendorLabs;\nusing StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Updater;\n\nnamespace StabilityMatrix.Avalonia;\n\npublic static class Program\n{\n    private static Logger? _logger;\n    private static Logger Logger => _logger ??= LogManager.GetCurrentClassLogger();\n\n    public static AppArgs Args { get; private set; } = new();\n\n    public static bool IsDebugBuild { get; private set; }\n\n    public static Stopwatch StartupTimer { get; } = new();\n\n    public static UriHandler UriHandler { get; } = new(\"stabilitymatrix\", \"StabilityMatrix\");\n\n    public static Uri MessagePipeUri { get; } = new(\"stabilitymatrix://app\");\n\n    // Initialization code. Don't use any Avalonia, third-party APIs or any\n    // SynchronizationContext-reliant code before AppMain is called: things aren't initialized\n    // yet and stuff might break.\n    [STAThread]\n    public static void Main(string[] args)\n    {\n        StartupTimer.Start();\n\n        SetDebugBuild();\n\n        var parseResult = Parser\n            .Default.ParseArguments<AppArgs>(args)\n            .WithNotParsed(errors =>\n            {\n                foreach (var error in errors)\n                {\n                    Console.Error.WriteLine(error.ToString());\n                }\n            });\n\n        if (\n            parseResult.Errors.Any(\n                x => x.Tag is ErrorType.HelpRequestedError or ErrorType.VersionRequestedError\n            )\n        )\n        {\n            Environment.Exit(0);\n            return;\n        }\n\n        Args = parseResult.Value ?? new AppArgs();\n\n        if (Args.HomeDirectoryOverride is { } homeDir)\n        {\n            Compat.SetAppDataHome(homeDir);\n            GlobalConfig.HomeDir = homeDir;\n        }\n\n        // Launched for custom URI scheme, handle and\n        // on macOS we use activation events so ignore this\n        if (!Compat.IsMacOS && Args.Uri is { } uriArg)\n        {\n            if (Uri.TryCreate(uriArg, UriKind.Absolute, out var uri))\n            {\n                HandleUriScheme(uri);\n            }\n            else\n            {\n                Console.Error.WriteLine($\"Invalid URI argument: {uriArg}\");\n                Environment.Exit(1);\n            }\n        }\n\n        if (Args.WaitForExitPid is { } waitExitPid)\n        {\n            WaitForPidExit(waitExitPid, TimeSpan.FromSeconds(30));\n        }\n\n        HandleUpdateReplacement();\n        HandleUpdateCleanup();\n\n        var infoVersion = Assembly\n            .GetExecutingAssembly()\n            .GetCustomAttribute<AssemblyInformationalVersionAttribute>()\n            ?.InformationalVersion;\n        Compat.AppVersion = SemVersion.Parse(infoVersion ?? \"0.0.0\", SemVersionStyles.Strict);\n\n        // Configure exception dialog for unhandled exceptions\n        if (!Debugger.IsAttached || Args.DebugExceptionDialog)\n        {\n            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;\n        }\n\n        TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;\n\n        // Configure Sentry\n        if (!Args.NoSentry && (!Debugger.IsAttached || Args.DebugSentry))\n        {\n            ConfigureSentry();\n        }\n\n        BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);\n    }\n\n    [DoesNotReturn]\n    private static void HandleUriScheme(Uri uri)\n    {\n        Console.Error.WriteLine($\"Handling URI: {uri}\");\n\n        if (!string.Equals(uri.Scheme, UriHandler.Scheme, StringComparison.OrdinalIgnoreCase))\n        {\n            Console.Error.WriteLine($\"Unknown URI scheme: {uri.Scheme}\");\n            Environment.Exit(1);\n        }\n\n        try\n        {\n            UriHandler.SendAndExit(uri);\n            Environment.Exit(0);\n        }\n        catch (Exception e)\n        {\n            Console.Error.WriteLine($\"Uri handler encountered an error: {e.Message}\");\n            Environment.Exit(1);\n        }\n    }\n\n    /// <summary>\n    /// Called in <see cref=\"BuildAvaloniaApp\"/> and UI tests to setup static configurations\n    /// </summary>\n    internal static void SetupAvaloniaApp()\n    {\n        IconProvider.Current.Register<FontAwesomeIconProvider>();\n\n        // Use our custom image loader for custom local load error handling\n        ImageLoader.AsyncImageLoader.Dispose();\n        ImageLoader.AsyncImageLoader = new FallbackRamCachedWebImageLoader();\n\n        // Setup BetterAsyncImage cache provider\n        BetterAsyncImageCacheProvider.DefaultCache = new ImageCache(\n            new CacheOptions\n            {\n                BaseCachePath = Path.Combine(Path.GetTempPath(), \"StabilityMatrix\", \"Cache\"),\n                CacheDuration = TimeSpan.FromDays(1),\n                MaxMemoryCacheCount = 100\n            }\n        );\n    }\n\n    // Avalonia configuration, don't remove; also used by visual designer.\n    public static AppBuilder BuildAvaloniaApp()\n    {\n        SetupAvaloniaApp();\n\n        var app = AppBuilder.Configure<App>().UsePlatformDetect().WithInterFont().LogToTrace();\n\n        if (Compat.IsLinux)\n        {\n            app = app.With(new X11PlatformOptions { OverlayPopups = true });\n        }\n        else if (Compat.IsMacOS)\n        {\n            app = app.With(new AvaloniaNativePlatformOptions { OverlayPopups = true });\n        }\n\n        if (Args.UseOpenGlRendering)\n        {\n            app = app.With(\n                new Win32PlatformOptions\n                {\n                    RenderingMode = [Win32RenderingMode.Wgl, Win32RenderingMode.Software]\n                }\n            );\n        }\n\n        if (Args.UseVulkanRendering)\n        {\n            app = app.With(new X11PlatformOptions { RenderingMode = [X11RenderingMode.Vulkan] })\n                .With(new Win32PlatformOptions { RenderingMode = [Win32RenderingMode.Vulkan] });\n        }\n\n        if (Args.DisableGpuRendering)\n        {\n            app = app.With(new Win32PlatformOptions { RenderingMode = new[] { Win32RenderingMode.Software } })\n                .With(new X11PlatformOptions { RenderingMode = new[] { X11RenderingMode.Software } })\n                .With(\n                    new AvaloniaNativePlatformOptions\n                    {\n                        RenderingMode = new[] { AvaloniaNativeRenderingMode.Software }\n                    }\n                );\n        }\n\n        return app;\n    }\n\n    private static void HandleUpdateReplacement()\n    {\n        // Check if we're in the named update folder or the legacy update folder for 1.2.0 -> 2.0.0\n        if (Compat.AppCurrentDir.Name is not (UpdateHelper.UpdateFolderName or \"Update\"))\n            return;\n\n        if (Compat.AppCurrentDir.Parent is not { } parentDir)\n            return;\n\n        // Copy our current file to the parent directory, overwriting the old app file\n\n        var isCopied = false;\n\n        foreach (\n            var delay in Backoff.DecorrelatedJitterBackoffV2(\n                TimeSpan.FromMilliseconds(300),\n                retryCount: 6,\n                fastFirst: true\n            )\n        )\n        {\n            try\n            {\n                if (Compat.IsMacOS)\n                {\n                    var currentApp = Compat.AppBundleCurrentPath!;\n                    var targetApp = parentDir.JoinDir(Compat.GetAppName());\n\n                    // Since macOS has issues with signature caching, delete the target app first\n                    if (targetApp.Exists)\n                    {\n                        targetApp.Delete(true);\n                    }\n\n                    currentApp.CopyTo(targetApp);\n                }\n                else\n                {\n                    var currentExe = Compat.AppCurrentPath;\n                    var targetExe = parentDir.JoinFile(Compat.GetExecutableName());\n\n                    currentExe.CopyTo(targetExe, true);\n                }\n\n                isCopied = true;\n                break;\n            }\n            catch (Exception)\n            {\n                Thread.Sleep(delay);\n            }\n        }\n\n        if (!isCopied)\n        {\n            Logger.Error(\"Failed to copy current executable to parent directory\");\n            Environment.Exit(1);\n        }\n\n        var targetAppOrBundle = Path.Combine(parentDir, Compat.GetAppName());\n\n        // Ensure permissions are set for unix\n        if (Compat.IsUnix)\n        {\n            File.SetUnixFileMode(\n                targetAppOrBundle, // 0755\n                UnixFileMode.UserRead\n                    | UnixFileMode.UserWrite\n                    | UnixFileMode.UserExecute\n                    | UnixFileMode.GroupRead\n                    | UnixFileMode.GroupExecute\n                    | UnixFileMode.OtherRead\n                    | UnixFileMode.OtherExecute\n            );\n        }\n\n        // Start the new app while passing our own PID to wait for exit\n        ProcessRunner.StartApp(\n            targetAppOrBundle,\n            new[] { \"--wait-for-exit-pid\", $\"{Environment.ProcessId}\" }\n        );\n\n        // Shutdown the current app\n        Environment.Exit(0);\n    }\n\n    private static void HandleUpdateCleanup()\n    {\n        // Delete update folder if it exists in current directory\n        if (UpdateHelper.UpdateFolder is { Exists: true } updateDir)\n        {\n            try\n            {\n                updateDir.Delete(true);\n            }\n            catch (Exception e)\n            {\n                Logger.Error(e, \"Failed to delete update folder\");\n            }\n        }\n    }\n\n    /// <summary>\n    /// Wait for an external process to exit,\n    /// ignores if process is not found, already exited, or throws an exception\n    /// </summary>\n    /// <param name=\"pid\">External process PID</param>\n    /// <param name=\"timeout\">Timeout to wait for process to exit</param>\n    private static void WaitForPidExit(int pid, TimeSpan timeout)\n    {\n        try\n        {\n            var process = Process.GetProcessById(pid);\n            process.WaitForExitAsync(new CancellationTokenSource(timeout).Token).GetAwaiter().GetResult();\n        }\n        catch (OperationCanceledException)\n        {\n            Logger.Warn(\"Timed out ({Timeout:g}) waiting for process {Pid} to exit\", timeout, pid);\n        }\n        catch (SystemException e)\n        {\n            Logger.Warn(e, \"Failed to wait for process {Pid} to exit\", pid);\n        }\n        catch (Exception e)\n        {\n            Logger.Error(e, \"Unexpected error during WaitForPidExit\");\n            throw;\n        }\n    }\n\n    private static void ConfigureSentry()\n    {\n        SentrySdk.Init(o =>\n        {\n            o.Dsn =\n                \"https://eac7a5ea065d44cf9a8565e0f1817da2@o4505314753380352.ingest.sentry.io/4505314756067328\";\n            o.StackTraceMode = StackTraceMode.Enhanced;\n            o.TracesSampleRate = 1.0;\n            o.IsGlobalModeEnabled = true;\n            // Enables Sentry's \"Release Health\" feature.\n            o.AutoSessionTracking = true;\n            // 1.0 to capture 100% of transactions for performance monitoring.\n            o.TracesSampleRate = 1.0;\n#if DEBUG\n            o.Environment = \"Development\";\n#endif\n            // Filters\n            o.SetBeforeSend(\n                (sentryEvent, _) =>\n                {\n                    // Ignore websocket errors from ComfyClient\n                    if (sentryEvent.Logger == \"Websocket.Client.WebsocketClient\")\n                    {\n                        return null;\n                    }\n\n                    return sentryEvent;\n                }\n            );\n        });\n    }\n\n    private static void TaskScheduler_UnobservedTaskException(\n        object? sender,\n        UnobservedTaskExceptionEventArgs e\n    )\n    {\n        if (e.Exception is Exception ex)\n        {\n            Logger.Error(ex, \"Unobserved Task Exception\");\n        }\n    }\n\n    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)\n    {\n        if (e.ExceptionObject is not Exception ex)\n            return;\n\n        SentryId? sentryId = null;\n\n        // Exception automatically logged by Sentry if enabled\n        if (SentrySdk.IsEnabled)\n        {\n            ex.SetSentryMechanism(\"AppDomain.UnhandledException\", handled: false);\n            sentryId = SentrySdk.CaptureException(ex);\n            SentrySdk.FlushAsync().SafeFireAndForget();\n            Logger.Warn(ex, \"Unhandled {Type}: {Message}\", ex.GetType().Name, ex.Message);\n        }\n        else\n        {\n            Logger.Fatal(ex, \"Unhandled {Type}: {Message}\", ex.GetType().Name, ex.Message);\n        }\n\n        if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)\n        {\n            var dialog = new ExceptionDialog\n            {\n                DataContext = new ExceptionViewModel { Exception = ex, SentryId = sentryId }\n            };\n\n            // We can only show dialog if main window exists, and is visible\n            if (lifetime.MainWindow is { PlatformImpl: not null, IsVisible: true } mainWindow)\n            {\n                // Configure for dialog mode\n                dialog.ShowAsDialog = true;\n                dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;\n\n                // Show synchronously without blocking UI thread\n                // https://github.com/AvaloniaUI/Avalonia/issues/4810#issuecomment-704259221\n                var cts = new CancellationTokenSource();\n\n                dialog\n                    .ShowDialog(mainWindow)\n                    .ContinueWith(\n                        _ =>\n                        {\n                            cts.Cancel();\n                            ExitWithException(ex);\n                        },\n                        TaskScheduler.FromCurrentSynchronizationContext()\n                    );\n\n                Dispatcher.UIThread.MainLoop(cts.Token);\n            }\n            else\n            {\n                // No parent window available\n                var cts = new CancellationTokenSource();\n                // Exit on token cancellation\n                cts.Token.Register(() => ExitWithException(ex));\n\n                dialog.ShowWithCts(cts);\n\n                Dispatcher.UIThread.MainLoop(cts.Token);\n            }\n        }\n    }\n\n    [DoesNotReturn]\n    public static void ExitWithException(Exception exception)\n    {\n        if (SentrySdk.IsEnabled)\n        {\n            SentrySdk.Flush();\n        }\n        App.Shutdown(1);\n        Dispatcher.UIThread.InvokeShutdown();\n        Environment.Exit(Marshal.GetHRForException(exception));\n    }\n\n    [Conditional(\"DEBUG\")]\n    private static void SetDebugBuild()\n    {\n        IsDebugBuild = true;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/AccountsService.cs",
    "content": "﻿using System;\nusing System.Net;\nusing System.Security.Claims;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.IdentityModel.JsonWebTokens;\nusing Microsoft.IdentityModel.Tokens;\nusing OpenIddict.Abstractions;\nusing OpenIddict.Client;\nusing OpenIddict.Client.SystemNetHttp;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Api.LykosAuthApi;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Api.CivitTRPC;\nusing StabilityMatrix.Core.Models.Api.Lykos;\nusing StabilityMatrix.Core.Services;\nusing static OpenIddict.Client.OpenIddictClientModels;\nusing ApiException = Refit.ApiException;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\n[RegisterSingleton<IAccountsService, AccountsService>]\npublic class AccountsService : IAccountsService\n{\n    private readonly ILogger<AccountsService> logger;\n    private readonly ISecretsManager secretsManager;\n    private readonly ILykosAuthApiV1 lykosAuthApi;\n    private readonly ILykosAuthApiV2 lykosAuthApiV2;\n    private readonly ICivitTRPCApi civitTRPCApi;\n    private readonly IHuggingFaceApi huggingFaceApi; // Added\n    private readonly OpenIddictClientService openIdClient;\n\n    /// <inheritdoc />\n    public event EventHandler<LykosAccountStatusUpdateEventArgs>? LykosAccountStatusUpdate;\n\n    /// <inheritdoc />\n    public event EventHandler<CivitAccountStatusUpdateEventArgs>? CivitAccountStatusUpdate;\n\n    /// <inheritdoc />\n    public event EventHandler<HuggingFaceAccountStatusUpdateEventArgs>? HuggingFaceAccountStatusUpdate;\n\n    public LykosAccountStatusUpdateEventArgs? LykosStatus { get; private set; }\n\n    public CivitAccountStatusUpdateEventArgs? CivitStatus { get; private set; }\n\n    public HuggingFaceAccountStatusUpdateEventArgs? HuggingFaceStatus { get; private set; }\n\n    public AccountsService(\n        ILogger<AccountsService> logger,\n        ISecretsManager secretsManager,\n        ILykosAuthApiV1 lykosAuthApi,\n        ILykosAuthApiV2 lykosAuthApiV2,\n        ICivitTRPCApi civitTRPCApi,\n        IHuggingFaceApi huggingFaceApi, // Added\n        OpenIddictClientService openIdClient\n    )\n    {\n        this.logger = logger;\n        this.secretsManager = secretsManager;\n        this.lykosAuthApi = lykosAuthApi;\n        this.lykosAuthApiV2 = lykosAuthApiV2;\n        this.civitTRPCApi = civitTRPCApi;\n        this.huggingFaceApi = huggingFaceApi; // Added\n        this.openIdClient = openIdClient;\n\n        // Update our own status when the Lykos account status changes\n        LykosAccountStatusUpdate += (_, args) => LykosStatus = args;\n        CivitAccountStatusUpdate += (_, args) => CivitStatus = args; // Assuming this was intended\n        HuggingFaceAccountStatusUpdate += (_, args) => HuggingFaceStatus = args;\n    }\n\n    public async Task<bool> HasStoredLykosAccountAsync()\n    {\n        if (LykosStatus?.IsConnected == true)\n        {\n            return true;\n        }\n\n        var secrets = await secretsManager.SafeLoadAsync();\n\n        return !string.IsNullOrEmpty(secrets.LykosAccountV2?.RefreshToken);\n    }\n\n    public async Task LykosLoginAsync(string email, string password)\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n\n        var tokens = await lykosAuthApi.PostLogin(new PostLoginRequest(email, password));\n\n        secrets = secrets with { LykosAccount = tokens };\n\n        await secretsManager.SaveAsync(secrets);\n\n        await RefreshLykosAsync(secrets);\n    }\n\n    public async Task LykosLoginViaGoogleOAuthAsync(string code, string state, string codeVerifier)\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n\n        var tokens = await lykosAuthApi.GetOAuthGoogleCallback(code, state, codeVerifier);\n\n        secrets = secrets with { LykosAccount = tokens };\n\n        await secretsManager.SaveAsync(secrets);\n\n        await RefreshLykosAsync(secrets);\n    }\n\n    public async Task LykosSignupAsync(string email, string password, string username)\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n\n        var tokens = await lykosAuthApi.PostAccount(\n            new PostAccountRequest(email, password, password, username)\n        );\n\n        secrets = secrets with { LykosAccount = tokens };\n\n        await secretsManager.SaveAsync(secrets);\n\n        await RefreshLykosAsync(secrets);\n    }\n\n    public async Task LykosLogoutAsync()\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n        await secretsManager.SaveAsync(secrets with { LykosAccount = null });\n\n        OnLykosAccountStatusUpdate(LykosAccountStatusUpdateEventArgs.Disconnected);\n    }\n\n    public async Task LykosAccountV2LoginAsync(LykosAccountV2Tokens tokens)\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n        secrets = secrets with { LykosAccountV2 = tokens };\n        await secretsManager.SaveAsync(secrets);\n\n        await RefreshLykosAsync(secrets);\n    }\n\n    public async Task LykosAccountV2LogoutAsync()\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n        await secretsManager.SaveAsync(secrets with { LykosAccountV2 = null });\n\n        OnLykosAccountStatusUpdate(LykosAccountStatusUpdateEventArgs.Disconnected);\n    }\n\n    /// <inheritdoc />\n    public async Task LykosPatreonOAuthLogoutAsync()\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n        if (secrets.LykosAccountV2 is null)\n        {\n            throw new InvalidOperationException(\n                \"Lykos account must be connected in to manage OAuth connections\"\n            );\n        }\n\n        await lykosAuthApiV2.ApiV2OauthPatreon();\n\n        await RefreshLykosAsync(secrets);\n    }\n\n    public async Task CivitLoginAsync(string apiToken)\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n\n        // Get id first using the api token\n        var userAccount = await civitTRPCApi.GetUserAccount(bearerToken: apiToken);\n        var id =\n            userAccount.InnerJson?.Id\n            ?? throw new InvalidOperationException(\"GetUserAccount did not contain an id\");\n\n        // Then get the username using the id\n        var account = await civitTRPCApi.GetUserById(new CivitGetUserByIdRequest { Id = id }, apiToken);\n        var username = account.Result.Data.Json.Username;\n\n        secrets = secrets with { CivitApi = new CivitApiTokens(apiToken, username) };\n\n        await secretsManager.SaveAsync(secrets);\n\n        await RefreshCivitAsync(secrets);\n    }\n\n    /// <inheritdoc />\n    public async Task CivitLogoutAsync()\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n        await secretsManager.SaveAsync(secrets with { CivitApi = null });\n\n        OnCivitAccountStatusUpdate(CivitAccountStatusUpdateEventArgs.Disconnected);\n    }\n\n    public async Task RefreshAsync()\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n\n        await RefreshLykosAsync(secrets);\n        await RefreshCivitAsync(secrets);\n        await RefreshHuggingFaceAsync(secrets);\n    }\n\n    public async Task RefreshLykosAsync()\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n\n        await RefreshLykosAsync(secrets);\n    }\n\n    public async Task RefreshHuggingFaceAsync()\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n        await RefreshHuggingFaceAsync(secrets);\n    }\n\n    private async Task RefreshLykosAsync(Secrets secrets)\n    {\n        if (\n            secrets.LykosAccountV2 is not null\n            && !string.IsNullOrWhiteSpace(secrets.LykosAccountV2?.RefreshToken)\n            && !string.IsNullOrWhiteSpace(secrets.LykosAccountV2?.AccessToken)\n        )\n        {\n            try\n            {\n                var user = await lykosAuthApiV2.ApiV2AccountsMe();\n\n                ClaimsPrincipal? principal = null;\n\n                if (secrets.LykosAccountV2.IdentityToken is not null)\n                {\n                    principal = secrets.LykosAccountV2.GetIdentityTokenPrincipal();\n                }\n\n                OnLykosAccountStatusUpdate(\n                    new LykosAccountStatusUpdateEventArgs\n                    {\n                        IsConnected = true,\n                        Principal = principal,\n                        User = user,\n                    }\n                );\n\n                return;\n            }\n            catch (OperationCanceledException)\n            {\n                logger.LogWarning(\"Timed out while fetching Lykos Auth user info\");\n            }\n            catch (InvalidOperationException e)\n            {\n                logger.LogWarning(e, \"Failed to get authentication token\");\n            }\n            catch (ApiException e)\n            {\n                if (e.StatusCode is HttpStatusCode.Unauthorized) { }\n                else\n                {\n                    logger.LogWarning(e, \"Failed to get user info from Lykos\");\n                }\n            }\n            catch (Exception e)\n            {\n                logger.LogError(e, \"Unknown error while refreshing Lykos account status\");\n            }\n        }\n\n        OnLykosAccountStatusUpdate(LykosAccountStatusUpdateEventArgs.Disconnected);\n    }\n\n    private async Task RefreshHuggingFaceAsync(Secrets secrets)\n    {\n        if (!string.IsNullOrWhiteSpace(secrets.HuggingFaceToken))\n        {\n            try\n            {\n                var response = await huggingFaceApi.GetCurrentUserAsync($\"Bearer {secrets.HuggingFaceToken}\");\n                if (response.IsSuccessStatusCode && response.Content != null)\n                {\n                    // Token is valid, user info fetched\n                    logger.LogInformation(\n                        \"Hugging Face token is valid. User: {Username}\",\n                        response.Content.Name\n                    );\n                    OnHuggingFaceAccountStatusUpdate(\n                        new HuggingFaceAccountStatusUpdateEventArgs(true, response.Content.Name)\n                    );\n                }\n                else\n                {\n                    // Token is likely invalid or other API error\n                    logger.LogWarning(\n                        \"Hugging Face token validation failed. Status: {StatusCode}, Error: {Error}, Content: {Content}\",\n                        response.StatusCode,\n                        response.Error?.ToString(),\n                        await response.Error?.GetContentAsAsync<string>() ?? \"N/A\"\n                    );\n                    OnHuggingFaceAccountStatusUpdate(\n                        new HuggingFaceAccountStatusUpdateEventArgs(\n                            false,\n                            null,\n                            $\"Token validation failed: {response.StatusCode}\"\n                        )\n                    );\n                }\n            }\n            catch (ApiException apiEx)\n            {\n                // Handle Refit's ApiException (network issues, non-success status codes not caught by IsSuccessStatusCode if IApiResponse isn't used directly)\n                logger.LogError(\n                    apiEx,\n                    \"Hugging Face API request failed during token validation. Content: {Content}\",\n                    await apiEx.GetContentAsAsync<string>() ?? \"N/A\"\n                );\n                OnHuggingFaceAccountStatusUpdate(\n                    new HuggingFaceAccountStatusUpdateEventArgs(\n                        false,\n                        null,\n                        \"API request failed during token validation.\"\n                    )\n                );\n            }\n            catch (Exception ex)\n            {\n                // Handle other unexpected errors\n                logger.LogError(ex, \"An unexpected error occurred during Hugging Face token validation.\");\n                OnHuggingFaceAccountStatusUpdate(\n                    new HuggingFaceAccountStatusUpdateEventArgs(false, null, \"An unexpected error occurred.\")\n                );\n            }\n        }\n        else\n        {\n            OnHuggingFaceAccountStatusUpdate(HuggingFaceAccountStatusUpdateEventArgs.Disconnected);\n        }\n    }\n\n    private async Task RefreshCivitAsync(Secrets secrets)\n    {\n        if (secrets.CivitApi is not null)\n        {\n            try\n            {\n                var user = await civitTRPCApi.GetUserProfile(\n                    new CivitUserProfileRequest { Username = secrets.CivitApi.Username },\n                    secrets.CivitApi.ApiToken\n                );\n\n                OnCivitAccountStatusUpdate(\n                    new CivitAccountStatusUpdateEventArgs { IsConnected = true, UserProfile = user }\n                );\n\n                return;\n            }\n            catch (OperationCanceledException)\n            {\n                logger.LogWarning(\"Timed out while fetching Civit Auth user info\");\n            }\n            catch (ApiException e)\n            {\n                if (e.StatusCode is HttpStatusCode.Unauthorized) { }\n                else\n                {\n                    logger.LogWarning(e, \"Failed to get user info from Civit\");\n                }\n            }\n        }\n\n        OnCivitAccountStatusUpdate(CivitAccountStatusUpdateEventArgs.Disconnected);\n    }\n\n    private void OnLykosAccountStatusUpdate(LykosAccountStatusUpdateEventArgs e)\n    {\n        if (!e.IsConnected && LykosStatus?.IsConnected == true)\n        {\n            logger.LogInformation(\"Lykos account disconnected\");\n        }\n        else if (e.IsConnected && LykosStatus?.IsConnected == false)\n        {\n            logger.LogInformation(\n                \"Lykos account connected: {Id} ({Username})\",\n                e.User?.Id,\n                e.Principal?.Identity?.Name\n            );\n        }\n\n        LykosAccountStatusUpdate?.Invoke(this, e);\n    }\n\n    private void OnCivitAccountStatusUpdate(CivitAccountStatusUpdateEventArgs e)\n    {\n        if (!e.IsConnected && CivitStatus?.IsConnected == true)\n        {\n            logger.LogInformation(\"Civit account disconnected\");\n        }\n        else if (e.IsConnected && CivitStatus?.IsConnected == false)\n        {\n            logger.LogInformation(\n                \"Civit account connected: {Id} ({Username})\",\n                e.UserProfile?.UserId,\n                e.UserProfile?.Username\n            );\n        }\n\n        CivitAccountStatusUpdate?.Invoke(this, e);\n    }\n\n    private void OnHuggingFaceAccountStatusUpdate(HuggingFaceAccountStatusUpdateEventArgs e)\n    {\n        if (!e.IsConnected && HuggingFaceStatus?.IsConnected == true)\n        {\n            logger.LogInformation(\"Hugging Face account disconnected\");\n        }\n        else if (e.IsConnected && HuggingFaceStatus?.IsConnected == false)\n        {\n            // Assuming Username might be null for now as we are not fetching it.\n            logger.LogInformation(\n                \"Hugging Face account connected\"\n                    + (string.IsNullOrWhiteSpace(e.Username) ? \"\" : $\" (User: {e.Username})\")\n            );\n        }\n        else if (!e.IsConnected && !string.IsNullOrWhiteSpace(e.ErrorMessage))\n        {\n            logger.LogWarning(\"Hugging Face connection/validation failed: {ErrorMessage}\", e.ErrorMessage);\n        }\n        HuggingFaceAccountStatusUpdate?.Invoke(this, e);\n    }\n\n    public async Task HuggingFaceLoginAsync(string token)\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n        secrets = secrets with { HuggingFaceToken = token };\n        await secretsManager.SaveAsync(secrets);\n        await RefreshHuggingFaceAsync(secrets);\n    }\n\n    public async Task HuggingFaceLogoutAsync()\n    {\n        var secrets = await secretsManager.SafeLoadAsync();\n        await secretsManager.SaveAsync(secrets with { HuggingFaceToken = null });\n        OnHuggingFaceAccountStatusUpdate(HuggingFaceAccountStatusUpdateEventArgs.Disconnected);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/CivitBaseModelTypeService.cs",
    "content": "﻿using System.Text.Json.Nodes;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\n[RegisterSingleton<ICivitBaseModelTypeService, CivitBaseModelTypeService>]\npublic class CivitBaseModelTypeService(\n    ILogger<CivitBaseModelTypeService> logger,\n    ICivitApi civitApi,\n    ILiteDbContext dbContext\n) : ICivitBaseModelTypeService\n{\n    private const string CacheId = \"BaseModelTypes\";\n    private static readonly TimeSpan CacheExpiration = TimeSpan.FromHours(24);\n\n    /// <summary>\n    /// Gets the list of base model types, using cache if available and not expired\n    /// </summary>\n    public async Task<List<string>> GetBaseModelTypes(bool forceRefresh = false, bool includeAllOption = true)\n    {\n        List<string> civitBaseModels = [];\n\n        if (!forceRefresh)\n        {\n            var cached = await dbContext.GetCivitBaseModelTypeCacheEntry(CacheId);\n            if (cached != null && DateTimeOffset.UtcNow.Subtract(cached.CreatedAt) < CacheExpiration)\n            {\n                civitBaseModels = cached.ModelTypes;\n            }\n        }\n\n        try\n        {\n            if (civitBaseModels.Count <= 0)\n            {\n                var baseModelsResponse = await civitApi.GetBaseModelList();\n                var jsonContent = await baseModelsResponse.Content.ReadAsStringAsync();\n                var baseModels = JsonNode.Parse(jsonContent);\n\n                var innerJson = baseModels?[\"error\"]?[\"message\"]?.GetValue<string>();\n                var jArray = JsonNode.Parse(innerJson).AsArray();\n                var baseModelValues = jArray[0]?[\"errors\"]?[0]?[0]?[\"values\"]?.AsArray();\n\n                civitBaseModels = baseModelValues?.GetValues<string>().ToList() ?? [];\n\n                // Cache the results\n                var cacheEntry = new CivitBaseModelTypeCacheEntry\n                {\n                    Id = CacheId,\n                    ModelTypes = civitBaseModels,\n                    CreatedAt = DateTimeOffset.UtcNow,\n                };\n\n                await dbContext.UpsertCivitBaseModelTypeCacheEntry(cacheEntry);\n            }\n\n            if (includeAllOption)\n            {\n                civitBaseModels.Insert(0, CivitBaseModelType.All.ToString());\n            }\n\n            // Filter and sort results\n            var filteredResults = civitBaseModels\n                .Where(s => !s.Equals(\"odor\", StringComparison.OrdinalIgnoreCase))\n                .OrderBy(s => s)\n                .ToList();\n\n            return filteredResults;\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Failed to get base model list\");\n\n            // Return cached results if available, even if expired\n            var expiredCache = await dbContext.GetCivitBaseModelTypeCacheEntry(CacheId);\n            return expiredCache?.ModelTypes\n                ?? Enum.GetValues<CivitBaseModelType>().Select(b => b.GetStringValue()).ToList();\n        }\n    }\n\n    /// <summary>\n    /// Clears the cached base model types\n    /// </summary>\n    public void ClearCache()\n    {\n        dbContext.CivitBaseModelTypeCache.DeleteAllAsync();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/ConnectedServiceManager.cs",
    "content": "﻿using System.ComponentModel;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing OpenIddict.Client;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Models.Api.Lykos;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\n[Localizable(false)]\n[RegisterSingleton<IConnectedServiceManager>]\npublic class ConnectedServiceManager(\n    IAccountsService accountsService,\n    ISettingsManager settingsManager,\n    ServiceManager<ViewModelBase> vmFactory,\n    ILogger<ConnectedServiceManager> logger\n) : IConnectedServiceManager\n{\n    /// <summary>\n    /// Attempt to enable CivitUseDiscoveryApi, prompting for login as needed.\n    /// </summary>\n    public async Task<bool> PromptEnableCivitUseDiscoveryApi()\n    {\n        // Ensure logged in\n        if (!await PromptLoginIfRequired() || accountsService.LykosStatus is not { User: { } user } status)\n        {\n            return false;\n        }\n\n        // Check if we can enable\n        var canEnable = user.Permissions.Contains(\"read:discovery\");\n        if (!canEnable)\n        {\n            logger.LogDebug(\"User {Id} does not have permissions: {Permissions}\", user.Id, user.Permissions);\n\n            // Sponsor prompt\n            var sponsorVm = vmFactory.Get<SponsorshipPromptViewModel>();\n            sponsorVm.Initialize(status, \"Accelerated Model Discovery\", \"Insider\");\n            await sponsorVm.ShowDialogAsync();\n\n            return false;\n        }\n\n        logger.LogInformation(\"Enabling CivitUseDiscoveryApi\");\n\n        // Save settings\n        settingsManager.Transaction(s => s.CivitUseDiscoveryApi, true);\n        return true;\n    }\n\n    /// <summary>\n    /// Prompts the user to log in to their Lykos account if they are not already logged in.\n    /// </summary>\n    /// <returns>True if the user is logged in after this function, false otherwise.</returns>\n    public async Task<bool> PromptLoginIfRequired()\n    {\n        Exception? refreshException = null;\n\n        // Check already logged in\n        if (accountsService.LykosStatus?.IsConnected == true)\n        {\n            return true;\n        }\n\n        var hasStoredAccount = await accountsService.HasStoredLykosAccountAsync();\n\n        if (hasStoredAccount)\n        {\n            // Try refresh\n            try\n            {\n                await accountsService.RefreshLykosAsync();\n            }\n            catch (Exception e)\n            {\n                logger.LogWarning(e, \"Error refreshing Lykos account\");\n                refreshException = e;\n            }\n\n            // Check again\n            if (accountsService.LykosStatus?.IsConnected == true)\n            {\n                return true;\n            }\n        }\n\n        // If we have a stored account but refresh failed\n        var isSessionExpired = hasStoredAccount && refreshException is not null;\n\n        var dialog = DialogHelper.CreateTaskDialog(\n            isSessionExpired ? Resources.Text_Login_ExpiredTitle : Resources.Text_Login_ConnectTitle,\n            isSessionExpired\n                ? Resources.Text_Login_ExpiredDescription\n                : Resources.Text_Login_ConnectDescription\n        );\n\n        dialog.Buttons =\n        [\n            new TaskDialogButton(Resources.Action_Login, TaskDialogStandardResult.OK) { IsDefault = true },\n            new TaskDialogButton(Resources.Action_Close, TaskDialogStandardResult.Close),\n        ];\n\n        if (await dialog.ShowAsync(true) is not TaskDialogStandardResult.OK)\n            return false;\n\n        var vm = vmFactory.Get<OAuthDeviceAuthViewModel>();\n        vm.ChallengeRequest = new OpenIddictClientModels.DeviceChallengeRequest\n        {\n            ProviderName = OpenIdClientConstants.LykosAccount.ProviderName\n        };\n        await vm.ShowDialogAsync();\n\n        if (vm.AuthenticationResult is not { } result)\n            return false;\n\n        await accountsService.LykosAccountV2LoginAsync(\n            new LykosAccountV2Tokens(result.AccessToken, result.RefreshToken, result.IdentityToken)\n        );\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/DiscordRichPresenceService.cs",
    "content": "﻿using System;\nusing DiscordRPC;\nusing DiscordRPC.Logging;\nusing DiscordRPC.Message;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\npublic class DiscordRichPresenceService : IDiscordRichPresenceService\n{\n    private const string ApplicationId = \"1134669805237059615\";\n\n    private readonly ILogger<DiscordRichPresenceService> logger;\n    private readonly ISettingsManager settingsManager;\n    private readonly DiscordRpcClient client;\n    private readonly string appDetails;\n    private bool isDisposed;\n\n    private RichPresence DefaultPresence =>\n        new()\n        {\n            Details = appDetails,\n            Assets = new DiscordRPC.Assets\n            {\n                LargeImageKey = \"stabilitymatrix-logo-1\",\n                LargeImageText = $\"Stability Matrix {appDetails}\",\n            },\n            Buttons = new[]\n            {\n                new Button { Label = \"GitHub\", Url = \"https://github.com/LykosAI/StabilityMatrix\", }\n            }\n        };\n\n    public DiscordRichPresenceService(\n        ILogger<DiscordRichPresenceService> logger,\n        ISettingsManager settingsManager\n    )\n    {\n        this.logger = logger;\n        this.settingsManager = settingsManager;\n\n        appDetails = $\"v{Compat.AppVersion.WithoutMetadata()}\";\n\n        client = new DiscordRpcClient(ApplicationId);\n        client.Logger = new NullLogger();\n        client.OnReady += OnReady;\n        client.OnError += OnError;\n        client.OnClose += OnClose;\n        client.OnPresenceUpdate += OnPresenceUpdate;\n\n        settingsManager.SettingsPropertyChanged += (sender, args) =>\n        {\n            if (args.PropertyName == nameof(settingsManager.Settings.IsDiscordRichPresenceEnabled))\n            {\n                UpdateState();\n            }\n        };\n\n        EventManager.Instance.RunningPackageStatusChanged += OnRunningPackageStatusChanged;\n    }\n\n    private void OnReady(object sender, ReadyMessage args)\n    {\n        logger.LogInformation(\"Received Ready from user {User}\", args.User.Username);\n    }\n\n    private void OnError(object sender, ErrorMessage args)\n    {\n        logger.LogWarning(\"Received Error: {Message}\", args.Message);\n    }\n\n    private void OnClose(object sender, CloseMessage args)\n    {\n        logger.LogInformation(\"Received Close: {Reason}\", args.Reason);\n    }\n\n    private void OnPresenceUpdate(object sender, PresenceMessage args)\n    {\n        logger.LogDebug(\"Received Update: {Presence}\", args.Presence.ToString());\n    }\n\n    private void OnRunningPackageStatusChanged(object? sender, RunningPackageStatusChangedEventArgs args)\n    {\n        if (!client.IsInitialized || !settingsManager.Settings.IsDiscordRichPresenceEnabled)\n            return;\n\n        if (args.CurrentPackagePair is null)\n        {\n            client.SetPresence(DefaultPresence);\n        }\n        else\n        {\n            var presence = DefaultPresence;\n\n            var packageTitle = args.CurrentPackagePair.BasePackage switch\n            {\n                FluxGym => \"FluxGym\",\n                Fooocus => \"Fooocus\",\n                Reforge => \"SD WebUI reForge\",\n                Sdfx => \"SDFX\",\n                StableSwarm => \"SwarmUI\",\n                SDWebForge or ForgeAmdGpu => \"SD WebUI Forge\",\n                KohyaSs => \"KohyaSS\",\n                OneTrainer => \"OneTrainer\",\n                Cogstudio => \"Cogstudio\",\n                ComfyUI or ComfyZluda => \"ComfyUI\",\n                InvokeAI => \"InvokeAI\",\n                VoltaML => \"VoltaML\",\n                VladAutomatic => \"SD.Next Web UI\",\n                A3WebUI => \"Automatic1111 Web UI\",\n                _ => \"Stable Diffusion\"\n            };\n\n            presence.State = $\"Running {packageTitle}\";\n\n            presence.Assets.SmallImageText = presence.State;\n            presence.Assets.SmallImageKey = args.CurrentPackagePair.BasePackage switch\n            {\n                ComfyUI => \"fa_diagram_project\",\n                VoltaML => \"ic_package_voltaml\",\n                InvokeAI => \"ic_package_invokeai\",\n                _ => \"ic_fluent_box_512_filled\"\n            };\n\n            presence.WithTimestamps(\n                new Timestamps\n                {\n                    StartUnixMilliseconds = (ulong?)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()\n                }\n            );\n\n            client.SetPresence(presence);\n        }\n    }\n\n    public void UpdateState()\n    {\n        // Set initial rich presence\n        if (settingsManager.Settings.IsDiscordRichPresenceEnabled)\n        {\n            lock (client)\n            {\n                if (!client.IsInitialized)\n                {\n                    client.Initialize();\n                    client.SetPresence(DefaultPresence);\n                }\n            }\n        }\n        else\n        {\n            lock (client)\n            {\n                if (client.IsInitialized)\n                {\n                    client.ClearPresence();\n                    client.Deinitialize();\n                }\n            }\n        }\n    }\n\n    public void Dispose()\n    {\n        if (!isDisposed)\n        {\n            if (client.IsInitialized)\n            {\n                client.ClearPresence();\n            }\n            client.Dispose();\n            EventManager.Instance.RunningPackageStatusChanged -= OnRunningPackageStatusChanged;\n        }\n\n        isDisposed = true;\n        GC.SuppressFinalize(this);\n    }\n\n    ~DiscordRichPresenceService()\n    {\n        Dispose();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/IAccountsService.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Api.Lykos;\n// Ensure this using is present if HuggingFaceAccountStatusUpdateEventArgs is in StabilityMatrix.Core.Models.Api\n// using StabilityMatrix.Core.Models.Api; \n\nnamespace StabilityMatrix.Avalonia.Services;\n\npublic interface IAccountsService\n{\n    event EventHandler<LykosAccountStatusUpdateEventArgs>? LykosAccountStatusUpdate;\n    event EventHandler<CivitAccountStatusUpdateEventArgs>? CivitAccountStatusUpdate;\n    event EventHandler<HuggingFaceAccountStatusUpdateEventArgs>? HuggingFaceAccountStatusUpdate;\n\n    LykosAccountStatusUpdateEventArgs? LykosStatus { get; }\n    CivitAccountStatusUpdateEventArgs? CivitStatus { get; } // Assuming this was missed in the provided file content but is standard\n    HuggingFaceAccountStatusUpdateEventArgs? HuggingFaceStatus { get; }\n\n    /// <summary>\n    /// Returns whether SecretsManager has a stored Lykos V2 account.\n    /// Does not mean <see cref=\"LykosStatus\"/> is populated or refresh/access tokens are valid.\n    /// </summary>\n    Task<bool> HasStoredLykosAccountAsync();\n\n    [Obsolete]\n    Task LykosSignupAsync(string email, string password, string username);\n\n    [Obsolete]\n    Task LykosLoginAsync(string email, string password);\n\n    [Obsolete]\n    Task LykosLoginViaGoogleOAuthAsync(string code, string state, string codeVerifier);\n\n    [Obsolete]\n    Task LykosLogoutAsync();\n\n    Task LykosAccountV2LoginAsync(LykosAccountV2Tokens tokens);\n\n    Task LykosAccountV2LogoutAsync();\n\n    Task LykosPatreonOAuthLogoutAsync();\n\n    Task CivitLoginAsync(string apiToken);\n\n    Task CivitLogoutAsync();\n\n    Task RefreshAsync();\n\n    Task RefreshLykosAsync();\n\n    Task HuggingFaceLoginAsync(string token);\n    Task HuggingFaceLogoutAsync();\n    Task RefreshHuggingFaceAsync();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/ICivitBaseModelTypeService.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Services;\n\npublic interface ICivitBaseModelTypeService\n{\n    Task<List<string>> GetBaseModelTypes(bool forceRefresh = false, bool includeAllOption = true);\n    void ClearCache();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/IConnectedServiceManager.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Services;\n\npublic interface IConnectedServiceManager\n{\n    /// <summary>\n    /// Attempt to enable CivitUseDiscoveryApi, prompting for login as needed.\n    /// </summary>\n    Task<bool> PromptEnableCivitUseDiscoveryApi();\n\n    /// <summary>\n    /// Prompts the user to log in to their Lykos account if they are not already logged in.\n    /// </summary>\n    /// <returns>True if the user is logged in after this function, false otherwise.</returns>\n    Task<bool> PromptLoginIfRequired();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/IDiscordRichPresenceService.cs",
    "content": "﻿using System;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\npublic interface IDiscordRichPresenceService : IDisposable\n{\n    public void UpdateState();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/IInferenceClientManager.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing DynamicData;\nusing DynamicData.Binding;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Core.Inference;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\npublic interface IInferenceClientManager : IDisposable, INotifyPropertyChanged, INotifyPropertyChanging\n{\n    ComfyClient? Client { get; set; }\n\n    /// <summary>\n    /// Whether the client is connected\n    /// </summary>\n    [MemberNotNullWhen(true, nameof(Client))]\n    bool IsConnected { get; }\n\n    /// <summary>\n    /// Whether the client is connecting\n    /// </summary>\n    bool IsConnecting { get; }\n\n    /// <summary>\n    /// Whether the user can initiate a connection\n    /// </summary>\n    bool CanUserConnect { get; }\n\n    /// <summary>\n    /// Whether the user can initiate a disconnection\n    /// </summary>\n    bool CanUserDisconnect { get; }\n\n    IObservableCollection<HybridModelFile> Models { get; }\n    IObservableCollection<HybridModelFile> VaeModels { get; }\n    IObservableCollection<HybridModelFile> ControlNetModels { get; }\n    IObservableCollection<HybridModelFile> LoraModels { get; }\n    IObservableCollection<HybridModelFile> PromptExpansionModels { get; }\n    IObservableCollection<ComfySampler> Samplers { get; }\n    IObservableCollection<ComfyUpscaler> Upscalers { get; }\n    IObservableCollection<ComfyScheduler> Schedulers { get; }\n    IObservableCollection<ComfyAuxPreprocessor> Preprocessors { get; }\n    IObservableCollection<HybridModelFile> UltralyticsModels { get; }\n    IObservableCollection<HybridModelFile> SamModels { get; }\n    IObservableCollection<HybridModelFile> UnetModels { get; }\n    IObservableCollection<HybridModelFile> ClipModels { get; }\n    IObservableCollection<HybridModelFile> ClipVisionModels { get; }\n    IObservable<IChangeSet<HybridModelFile, string>> LoraModelsChangeSet { get; }\n\n    Task CopyImageToInputAsync(FilePath imageFile, CancellationToken cancellationToken = default);\n\n    Task UploadInputImageAsync(ImageSource image, CancellationToken cancellationToken = default);\n\n    Task WriteImageToInputAsync(ImageSource imageSource, CancellationToken cancellationToken = default);\n\n    Task ConnectAsync(CancellationToken cancellationToken = default);\n\n    Task ConnectAsync(PackagePair packagePair, CancellationToken cancellationToken = default);\n\n    Task CloseAsync();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/IModelDownloadLinkHandler.cs",
    "content": "﻿using System.Threading.Tasks;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\npublic interface IModelDownloadLinkHandler\n{\n    Task StartListening();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/IModelImportService.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Api.OpenModelsDb;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\npublic interface IModelImportService\n{\n    /// <summary>\n    /// Saves the preview image to the same directory as the model file\n    /// </summary>\n    /// <param name=\"modelVersion\"></param>\n    /// <param name=\"modelFilePath\"></param>\n    /// <returns>The file path of the saved preview image</returns>\n    Task<FilePath?> SavePreviewImage(CivitModelVersion modelVersion, FilePath modelFilePath);\n\n    Task DoImport(\n        CivitModel model,\n        DirectoryPath downloadFolder,\n        CivitModelVersion? selectedVersion = null,\n        CivitFile? selectedFile = null,\n        string? fileNameOverride = null,\n        SamplerCardViewModel? inferenceDefaults = null,\n        IProgress<ProgressReport>? progress = null,\n        Func<Task>? onImportComplete = null,\n        Func<Task>? onImportCanceled = null,\n        Func<Task>? onImportFailed = null\n    );\n\n    /// <summary>\n    /// Imports a model from OpenModelsDb\n    /// </summary>\n    Task DoOpenModelDbImport(\n        OpenModelDbKeyedModel model,\n        OpenModelDbResource resource,\n        DirectoryPath downloadFolder,\n        Action<TrackedDownload>? configureDownload = null\n    );\n\n    /// <summary>\n    /// Imports a model from a custom source\n    /// </summary>\n    /// <param name=\"modelUri\">Url to download the model</param>\n    /// <param name=\"modelFileName\">Model file name with extension</param>\n    /// <param name=\"downloadFolder\">Local directory to save model file</param>\n    /// <param name=\"previewImageUri\">Url to download the preview image</param>\n    /// <param name=\"previewImageFileExtension\">Preview image file name with extension</param>\n    /// <param name=\"connectedModelInfo\">Connected model info</param>\n    /// <param name=\"configureDownload\">Optional configuration for the download</param>\n    Task DoCustomImport(\n        Uri modelUri,\n        string modelFileName,\n        DirectoryPath downloadFolder,\n        Uri? previewImageUri = null,\n        string? previewImageFileExtension = null,\n        ConnectedModelInfo? connectedModelInfo = null,\n        Action<TrackedDownload>? configureDownload = null\n    ) =>\n        DoCustomImport(\n            [modelUri],\n            modelFileName,\n            downloadFolder,\n            previewImageUri,\n            previewImageFileExtension,\n            connectedModelInfo,\n            configureDownload\n        );\n\n    /// <summary>\n    /// Imports a model from a custom source\n    /// </summary>\n    /// <param name=\"modelUris\">Urls to download the model. If multiple provided each will be attempted in order.</param>\n    /// <param name=\"modelFileName\">Model file name with extension</param>\n    /// <param name=\"downloadFolder\">Local directory to save model file</param>\n    /// <param name=\"previewImageUri\">Url to download the preview image</param>\n    /// <param name=\"previewImageFileExtension\">Preview image file name with extension</param>\n    /// <param name=\"connectedModelInfo\">Connected model info</param>\n    /// <param name=\"configureDownload\">Optional configuration for the download</param>\n    Task DoCustomImport(\n        IEnumerable<Uri> modelUris,\n        string modelFileName,\n        DirectoryPath downloadFolder,\n        Uri? previewImageUri = null,\n        string? previewImageFileExtension = null,\n        ConnectedModelInfo? connectedModelInfo = null,\n        Action<TrackedDownload>? configureDownload = null\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/INavigationService.cs",
    "content": "﻿using System;\nusing System.Diagnostics.CodeAnalysis;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Media.Animation;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\npublic interface INavigationService<[SuppressMessage(\"ReSharper\", \"UnusedTypeParameter\")] T>\n{\n    event EventHandler<TypedNavigationEventArgs>? TypedNavigation;\n\n    /// <summary>\n    /// Set the frame to use for navigation.\n    /// </summary>\n    void SetFrame(Frame frame);\n\n    /// <summary>\n    /// Navigate to the view of the given view model type.\n    /// </summary>\n    void NavigateTo<TViewModel>(NavigationTransitionInfo? transitionInfo = null, object? param = null)\n        where TViewModel : ViewModelBase;\n\n    /// <summary>\n    /// Navigate to the view of the given view model type.\n    /// </summary>\n    void NavigateTo(\n        Type viewModelType,\n        NavigationTransitionInfo? transitionInfo = null,\n        object? param = null\n    );\n\n    /// <summary>\n    /// Navigate to the view of the given view model.\n    /// </summary>\n    void NavigateTo(ViewModelBase viewModel, NavigationTransitionInfo? transitionInfo = null);\n\n    bool GoBack();\n\n    bool CanGoBack { get; }\n    object? CurrentPageDataContext { get; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/INotificationService.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing Avalonia;\nusing Avalonia.Controls.Notifications;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Settings;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\npublic interface INotificationService\n{\n    public void Initialize(\n        Visual? visual,\n        NotificationPosition position = NotificationPosition.BottomRight,\n        int maxItems = 3\n    );\n\n    public void Show(INotification notification);\n\n    /// <summary>\n    /// Attempt to run the given task, showing a generic error notification if it fails.\n    /// </summary>\n    /// <param name=\"task\">The task to run.</param>\n    /// <param name=\"title\">The title to show in the notification.</param>\n    /// <param name=\"message\">The message to show, default to exception.Message</param>\n    /// <param name=\"appearance\">The appearance of the notification.</param>\n    Task<TaskResult<T>> TryAsync<T>(\n        Task<T> task,\n        string title = \"Error\",\n        string? message = null,\n        NotificationType appearance = NotificationType.Error\n    );\n\n    /// <summary>\n    /// Attempt to run the given void task, showing a generic error notification if it fails.\n    /// Return a TaskResult with true if the task succeeded, false if it failed.\n    /// </summary>\n    /// <param name=\"task\">The task to run.</param>\n    /// <param name=\"title\">The title to show in the notification.</param>\n    /// <param name=\"message\">The message to show, default to exception.Message</param>\n    /// <param name=\"appearance\">The appearance of the notification.</param>\n    Task<TaskResult<bool>> TryAsync(\n        Task task,\n        string title = \"Error\",\n        string? message = null,\n        NotificationType appearance = NotificationType.Error\n    );\n\n    /// <summary>\n    /// Show a keyed customizable persistent notification with the given parameters.\n    /// </summary>\n    Task ShowPersistentAsync(NotificationKey key, DesktopNotifications.Notification notification);\n\n    /// <summary>\n    /// Show a keyed customizable notification with the given parameters.\n    /// </summary>\n    Task ShowAsync(\n        NotificationKey key,\n        DesktopNotifications.Notification notification,\n        TimeSpan? expiration = null\n    );\n\n    /// <summary>\n    /// Show a notification with the given parameters.\n    /// </summary>\n    void Show(\n        string title,\n        string message,\n        NotificationType appearance = NotificationType.Information,\n        TimeSpan? expiration = null\n    );\n\n    /// <summary>\n    /// Show a notification that will not auto-dismiss.\n    /// </summary>\n    /// <param name=\"title\"></param>\n    /// <param name=\"message\"></param>\n    /// <param name=\"appearance\"></param>\n    void ShowPersistent(\n        string title,\n        string message,\n        NotificationType appearance = NotificationType.Information\n    );\n\n    /// <summary>\n    /// Show a notification for a <see cref=\"AppException\"/> that will not auto-dismiss.\n    /// </summary>\n    void ShowPersistent(\n        AppException exception,\n        NotificationType appearance = NotificationType.Error,\n        LogLevel logLevel = LogLevel.Warning\n    );\n\n    /// <summary>\n    /// Get the native notification manager.\n    /// </summary>\n    Task<DesktopNotifications.INotificationManager?> GetNativeNotificationManagerAsync();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/IServiceManager.cs",
    "content": "﻿using System.ComponentModel;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\n[Localizable(false)]\npublic interface IServiceManager<T>\n{\n    /// <summary>\n    /// Register a new dialog view model (singleton instance)\n    /// </summary>\n    IServiceManager<T> Register<TService>(TService instance)\n        where TService : T;\n\n    /// <summary>\n    /// Register a new dialog view model provider action (called on each dialog creation)\n    /// </summary>\n    IServiceManager<T> Register<TService>(Func<TService> provider)\n        where TService : T;\n\n    void Register(Type type, Func<T> providerFunc);\n\n    /// <summary>\n    /// Register a new dialog view model instance using a service provider\n    /// Equal to Register[TService](serviceProvider.GetRequiredService[TService])\n    /// </summary>\n    IServiceManager<T> RegisterProvider<TService>(IServiceProvider provider)\n        where TService : notnull, T;\n\n    /// <summary>\n    /// Register a new service provider action with Scoped lifetime.\n    /// The factory is called once per scope.\n    /// </summary>\n    IServiceManager<T> RegisterScoped<TService>(Func<IServiceProvider, TService> provider)\n        where TService : T;\n\n    /// <summary>\n    /// Creates a new service scope.\n    /// </summary>\n    /// <returns>An IServiceManagerScope representing the created scope.</returns>\n    IServiceManagerScope<T> CreateScope();\n\n    /// <summary>\n    /// Get a view model instance from runtime type\n    /// </summary>\n    T Get(Type serviceType);\n\n    /// <summary>\n    /// Get a view model instance\n    /// </summary>\n    TService Get<TService>()\n        where TService : T;\n\n    /// <summary>\n    /// Get a view model instance with an initializer parameter\n    /// </summary>\n    public TService Get<TService>(Func<TService, TService> initializer)\n        where TService : T\n    {\n        var instance = Get<TService>();\n        return initializer(instance);\n    }\n\n    /// <summary>\n    /// Get a view model instance with an initializer for a mutable instance\n    /// </summary>\n    public TService Get<TService>(Action<TService> initializer)\n        where TService : T\n    {\n        var instance = Get<TService>();\n        initializer(instance);\n        return instance;\n    }\n\n    /// <summary>\n    /// Register a new service provider action with Scoped lifetime.\n    /// The factory is called once per scope.\n    /// </summary>\n    IServiceManager<T> RegisterScoped(Type type, Func<IServiceProvider, T> provider);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/IServiceManagerScope.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Services;\n\npublic interface IServiceManagerScope<T> : IDisposable\n{\n    /// <summary>\n    /// Provides access to an instance of IServiceManager for managing and retrieving services\n    /// of a specified type T within the associated scope.\n    /// </summary>\n    IServiceManager<T> ServiceManager { get; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/InferenceClientManager.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing System.IO;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing DynamicData;\nusing DynamicData.Binding;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing SkiaSharp;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Inference;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\n/// <summary>\n/// Manager for the current inference client\n/// Has observable shared properties for shared info like model names\n/// </summary>\n[RegisterSingleton<IInferenceClientManager, InferenceClientManager>]\npublic partial class InferenceClientManager : ObservableObject, IInferenceClientManager\n{\n    private readonly ILogger<InferenceClientManager> logger;\n    private readonly IApiFactory apiFactory;\n    private readonly IModelIndexService modelIndexService;\n    private readonly ISettingsManager settingsManager;\n    private readonly ICompletionProvider completionProvider;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsConnected), nameof(CanUserConnect))]\n    private ComfyClient? client;\n\n    [MemberNotNullWhen(true, nameof(Client))]\n    public virtual bool IsConnected => Client is not null;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(CanUserConnect))]\n    private bool isConnecting;\n\n    /// <inheritdoc />\n    public bool CanUserConnect => !IsConnected && !IsConnecting;\n\n    /// <inheritdoc />\n    public bool CanUserDisconnect => IsConnected && !IsConnecting;\n\n    private readonly SourceCache<HybridModelFile, string> modelsSource = new(p => p.GetId());\n\n    public IObservableCollection<HybridModelFile> Models { get; } =\n        new ObservableCollectionExtended<HybridModelFile>();\n\n    private readonly SourceCache<HybridModelFile, string> vaeModelsSource = new(p => p.GetId());\n\n    private readonly SourceCache<HybridModelFile, string> vaeModelsDefaults = new(p => p.GetId());\n\n    public IObservableCollection<HybridModelFile> VaeModels { get; } =\n        new ObservableCollectionExtended<HybridModelFile>();\n\n    private readonly SourceCache<HybridModelFile, string> controlNetModelsSource = new(p => p.GetId());\n\n    private readonly SourceCache<HybridModelFile, string> downloadableControlNetModelsSource = new(p =>\n        p.GetId()\n    );\n\n    public IObservableCollection<HybridModelFile> ControlNetModels { get; } =\n        new ObservableCollectionExtended<HybridModelFile>();\n\n    private readonly SourceCache<HybridModelFile, string> loraModelsSource = new(p => p.GetId());\n\n    public IObservable<IChangeSet<HybridModelFile, string>> LoraModelsChangeSet { get; }\n\n    public IObservableCollection<HybridModelFile> LoraModels { get; } =\n        new ObservableCollectionExtended<HybridModelFile>();\n\n    private readonly SourceCache<HybridModelFile, string> promptExpansionModelsSource = new(p => p.GetId());\n\n    private readonly SourceCache<HybridModelFile, string> downloadablePromptExpansionModelsSource = new(p =>\n        p.GetId()\n    );\n\n    public IObservableCollection<HybridModelFile> PromptExpansionModels { get; } =\n        new ObservableCollectionExtended<HybridModelFile>();\n\n    private readonly SourceCache<ComfySampler, string> samplersSource = new(p => p.Name);\n\n    public IObservableCollection<ComfySampler> Samplers { get; } =\n        new ObservableCollectionExtended<ComfySampler>();\n\n    private readonly SourceCache<ComfyUpscaler, string> modelUpscalersSource = new(p => p.Name);\n\n    private readonly SourceCache<ComfyUpscaler, string> latentUpscalersSource = new(p => p.Name);\n\n    private readonly SourceCache<ComfyUpscaler, string> downloadableUpscalersSource = new(p => p.Name);\n\n    public IObservableCollection<ComfyUpscaler> Upscalers { get; } =\n        new ObservableCollectionExtended<ComfyUpscaler>();\n\n    private readonly SourceCache<ComfyScheduler, string> schedulersSource = new(p => p.Name);\n\n    public IObservableCollection<ComfyScheduler> Schedulers { get; } =\n        new ObservableCollectionExtended<ComfyScheduler>();\n\n    public IObservableCollection<ComfyAuxPreprocessor> Preprocessors { get; } =\n        new ObservableCollectionExtended<ComfyAuxPreprocessor>();\n\n    private readonly SourceCache<ComfyAuxPreprocessor, string> preprocessorsSource = new(p => p.Value);\n\n    public IObservableCollection<HybridModelFile> UltralyticsModels { get; } =\n        new ObservableCollectionExtended<HybridModelFile>();\n\n    private readonly SourceCache<HybridModelFile, string> ultralyticsModelsSource = new(p => p.GetId());\n\n    private readonly SourceCache<HybridModelFile, string> downloadableUltralyticsModelsSource = new(p =>\n        p.GetId()\n    );\n\n    public IObservableCollection<HybridModelFile> SamModels { get; } =\n        new ObservableCollectionExtended<HybridModelFile>();\n\n    private readonly SourceCache<HybridModelFile, string> samModelsSource = new(p => p.GetId());\n\n    private readonly SourceCache<HybridModelFile, string> downloadableSamModelsSource = new(p => p.GetId());\n\n    private readonly SourceCache<HybridModelFile, string> unetModelsSource = new(p => p.GetId());\n\n    public IObservableCollection<HybridModelFile> UnetModels { get; } =\n        new ObservableCollectionExtended<HybridModelFile>();\n\n    private readonly SourceCache<HybridModelFile, string> clipModelsSource = new(p => p.GetId());\n    private readonly SourceCache<HybridModelFile, string> downloadableClipModelsSource = new(p => p.GetId());\n\n    public IObservableCollection<HybridModelFile> ClipModels { get; } =\n        new ObservableCollectionExtended<HybridModelFile>();\n\n    private readonly SourceCache<HybridModelFile, string> clipVisionModelsSource = new(p => p.GetId());\n    private readonly SourceCache<HybridModelFile, string> downloadableClipVisionModelsSource = new(p =>\n        p.GetId()\n    );\n\n    public IObservableCollection<HybridModelFile> ClipVisionModels { get; } =\n        new ObservableCollectionExtended<HybridModelFile>();\n\n    public InferenceClientManager(\n        ILogger<InferenceClientManager> logger,\n        IApiFactory apiFactory,\n        IModelIndexService modelIndexService,\n        ISettingsManager settingsManager,\n        ICompletionProvider completionProvider\n    )\n    {\n        this.logger = logger;\n        this.apiFactory = apiFactory;\n        this.modelIndexService = modelIndexService;\n        this.settingsManager = settingsManager;\n        this.completionProvider = completionProvider;\n\n        modelsSource\n            .Connect()\n            .DeferUntilLoaded()\n            .SortAndBind(Models, SortExpressionComparer<HybridModelFile>.Ascending(f => f.ShortDisplayName))\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        controlNetModelsSource\n            .Connect()\n            .Or(downloadableControlNetModelsSource.Connect())\n            .Sort(\n                SortExpressionComparer<HybridModelFile>\n                    .Ascending(f => f.Type)\n                    .ThenByAscending(f => f.ShortDisplayName)\n            )\n            .DeferUntilLoaded()\n            .Bind(ControlNetModels)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        LoraModelsChangeSet = loraModelsSource\n            .Connect()\n            .DeferUntilLoaded()\n            // Adding .RefCount() if multiple consumers might subscribe to this\n            // LoraModelsChangeSet property. It keeps the upstream connection active as long\n            // as there's at least one subscriber. This is usually a good idea when exposing streams.\n            .RefCount();\n\n        LoraModelsChangeSet\n            .SortAndBind(\n                LoraModels,\n                SortExpressionComparer<HybridModelFile>.Ascending(f => f.Type).ThenByAscending(f => f.SortKey)\n            )\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        promptExpansionModelsSource\n            .Connect()\n            .Or(downloadablePromptExpansionModelsSource.Connect())\n            .Sort(\n                SortExpressionComparer<HybridModelFile>\n                    .Ascending(f => f.Type)\n                    .ThenByAscending(f => f.ShortDisplayName)\n            )\n            .DeferUntilLoaded()\n            .Bind(PromptExpansionModels)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        ultralyticsModelsSource\n            .Connect()\n            .Or(downloadableUltralyticsModelsSource.Connect())\n            .Sort(\n                SortExpressionComparer<HybridModelFile>\n                    .Ascending(f => f.Type)\n                    .ThenByAscending(f => f.ShortDisplayName)\n            )\n            .DeferUntilLoaded()\n            .Bind(UltralyticsModels)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        samModelsSource\n            .Connect()\n            .Or(downloadableSamModelsSource.Connect())\n            .Sort(\n                SortExpressionComparer<HybridModelFile>\n                    .Ascending(f => f.Type)\n                    .ThenByAscending(f => f.ShortDisplayName)\n            )\n            .DeferUntilLoaded()\n            .Bind(SamModels)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        unetModelsSource\n            .Connect()\n            .DeferUntilLoaded()\n            .SortAndBind(\n                UnetModels,\n                SortExpressionComparer<HybridModelFile>.Ascending(f => f.ShortDisplayName)\n            )\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        clipModelsSource\n            .Connect()\n            .Or(downloadableClipModelsSource.Connect())\n            .SortBy(\n                f => f.ShortDisplayName,\n                SortDirection.Ascending,\n                SortOptimisations.ComparesImmutableValuesOnly\n            )\n            .DeferUntilLoaded()\n            .Bind(ClipModels)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        clipVisionModelsSource\n            .Connect()\n            .Or(downloadableClipVisionModelsSource.Connect())\n            .SortBy(\n                f => f.ShortDisplayName,\n                SortDirection.Ascending,\n                SortOptimisations.ComparesImmutableValuesOnly\n            )\n            .DeferUntilLoaded()\n            .Bind(ClipVisionModels)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        vaeModelsDefaults.AddOrUpdate(HybridModelFile.Default);\n\n        vaeModelsDefaults\n            .Connect()\n            .Or(vaeModelsSource.Connect())\n            .Bind(VaeModels)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        samplersSource\n            .Connect()\n            .DeferUntilLoaded()\n            .Bind(Samplers)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        latentUpscalersSource\n            .Connect()\n            .Or(modelUpscalersSource.Connect())\n            .Or(downloadableUpscalersSource.Connect())\n            .Sort(SortExpressionComparer<ComfyUpscaler>.Ascending(f => f.Type).ThenByAscending(f => f.Name))\n            .Bind(Upscalers)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        schedulersSource\n            .Connect()\n            .DeferUntilLoaded()\n            .Bind(Schedulers)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        preprocessorsSource\n            .Connect()\n            .DeferUntilLoaded()\n            .Bind(Preprocessors)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        settingsManager.RegisterOnLibraryDirSet(_ =>\n        {\n            Dispatcher.UIThread.Post(ResetSharedProperties, DispatcherPriority.Background);\n        });\n\n        EventManager.Instance.ModelIndexChanged += (_, _) =>\n        {\n            logger.LogDebug(\"Model index changed, reloading shared properties for Inference\");\n\n            if (!settingsManager.IsLibraryDirSet)\n                return;\n\n            // Dispatch to UI thread to prevent race conditions with Avalonia's selection model.\n            // The ModelIndexChanged event may be raised from a background thread, and modifying\n            // observable collections from a non-UI thread can cause ArgumentOutOfRangeException\n            // when the selection model tries to enumerate selected items.\n            Dispatcher.UIThread.Post(() =>\n            {\n                ResetSharedProperties();\n\n                if (IsConnected)\n                {\n                    LoadSharedPropertiesAsync()\n                        .SafeFireAndForget(onException: ex =>\n                            logger.LogError(ex, \"Error loading shared properties\")\n                        );\n                }\n            });\n        };\n    }\n\n    [MemberNotNull(nameof(Client))]\n    private void EnsureConnected()\n    {\n        if (!IsConnected)\n            throw new InvalidOperationException(\"Client is not connected\");\n    }\n\n    protected virtual async Task LoadSharedPropertiesAsync()\n    {\n        EnsureConnected();\n\n        // Get model names\n        if (await Client.GetModelNamesAsync() is { } modelNames)\n        {\n            modelsSource.EditDiff(\n                modelNames.Select(HybridModelFile.FromRemote),\n                HybridModelFile.RemoteLocalComparer\n            );\n        }\n\n        // Get control net model names\n        if (\n            await Client.GetNodeOptionNamesAsync(\"ControlNetLoader\", \"control_net_name\") is\n            { } controlNetModelNames\n        )\n        {\n            controlNetModelsSource.EditDiff(\n                controlNetModelNames.Select(HybridModelFile.FromRemote),\n                HybridModelFile.RemoteLocalComparer\n            );\n        }\n\n        // Get Lora model names\n        if (await Client.GetNodeOptionNamesAsync(\"LoraLoader\", \"lora_name\") is { } loraModelNames)\n        {\n            loraModelsSource.EditDiff(\n                loraModelNames.Select(HybridModelFile.FromRemote),\n                HybridModelFile.RemoteLocalComparer\n            );\n        }\n\n        // Get Ultralytics model names\n        if (\n            await Client.GetOptionalNodeOptionNamesAsync(\"UltralyticsDetectorProvider\", \"model_name\") is\n            { } ultralyticsModelNames\n        )\n        {\n            IEnumerable<HybridModelFile> models =\n            [\n                HybridModelFile.None,\n                .. ultralyticsModelNames.Select(HybridModelFile.FromRemote),\n            ];\n            ultralyticsModelsSource.EditDiff(models, HybridModelFile.RemoteLocalComparer);\n        }\n\n        // Get SAM model names\n        if (await Client.GetOptionalNodeOptionNamesAsync(\"SAMLoader\", \"model_name\") is { } samModelNames)\n        {\n            IEnumerable<HybridModelFile> models =\n            [\n                HybridModelFile.None,\n                .. samModelNames.Select(HybridModelFile.FromRemote),\n            ];\n            samModelsSource.EditDiff(models, HybridModelFile.RemoteLocalComparer);\n        }\n\n        // Prompt Expansion indexing is local only\n\n        // Fetch sampler names from KSampler node\n        if (await Client.GetSamplerNamesAsync() is { } samplerNames)\n        {\n            samplersSource.EditDiff(\n                samplerNames.Select(name => new ComfySampler(name)),\n                ComfySampler.Comparer\n            );\n        }\n\n        // Upscalers is latent and esrgan combined\n\n        // Add latent upscale methods from LatentUpscale node\n        if (\n            await Client.GetNodeOptionNamesAsync(\"LatentUpscale\", \"upscale_method\") is { } latentUpscalerNames\n        )\n        {\n            latentUpscalersSource.EditDiff(\n                latentUpscalerNames.Select(s => new ComfyUpscaler(s, ComfyUpscalerType.Latent)),\n                ComfyUpscaler.Comparer\n            );\n\n            logger.LogTrace(\"Loaded latent upscale methods: {@Upscalers}\", latentUpscalerNames);\n        }\n\n        // Add Model upscale methods\n        if (\n            await Client.GetNodeOptionNamesAsync(\"UpscaleModelLoader\", \"model_name\") is { } modelUpscalerNames\n        )\n        {\n            modelUpscalersSource.EditDiff(\n                modelUpscalerNames.Select(s => new ComfyUpscaler(s, ComfyUpscalerType.ESRGAN)),\n                ComfyUpscaler.Comparer\n            );\n            logger.LogTrace(\"Loaded model upscale methods: {@Upscalers}\", modelUpscalerNames);\n        }\n\n        // Add scheduler names from Scheduler node\n        if (await Client.GetNodeOptionNamesAsync(\"KSampler\", \"scheduler\") is { } schedulerNames)\n        {\n            schedulersSource.Edit(updater =>\n            {\n                updater.AddOrUpdate(\n                    schedulerNames\n                        .Where(n => !schedulersSource.Keys.Contains(n))\n                        .Select(s => new ComfyScheduler(s))\n                );\n            });\n            logger.LogTrace(\"Loaded scheduler methods: {@Schedulers}\", schedulerNames);\n        }\n\n        // Add preprocessor names from Inference_Core_AIO_Preprocessor node (might not exist if no extension)\n        if (\n            await Client.GetOptionalNodeOptionNamesAsync(\"Inference_Core_AIO_Preprocessor\", \"preprocessor\") is\n            { } preprocessorNames\n        )\n        {\n            preprocessorsSource.EditDiff(preprocessorNames.Select(n => new ComfyAuxPreprocessor(n)));\n        }\n\n        // Get Unet model names from UNETLoader node\n        if (await Client.GetNodeOptionNamesAsync(\"UNETLoader\", \"unet_name\") is { } unetModelNames)\n        {\n            var unetModels = unetModelNames.Select(HybridModelFile.FromRemote);\n\n            if (\n                await Client.GetRequiredNodeOptionNamesFromOptionalNodeAsync(\"UnetLoaderGGUF\", \"unet_name\") is\n                { } ggufModelNames\n            )\n            {\n                unetModels = unetModels.Concat(ggufModelNames.Select(HybridModelFile.FromRemote));\n            }\n\n            unetModelsSource.AddOrUpdate(unetModels, HybridModelFile.RemoteLocalComparer);\n        }\n\n        // Get CLIP model names from DualCLIPLoader node\n        if (await Client.GetNodeOptionNamesAsync(\"DualCLIPLoader\", \"clip_name1\") is { } clipModelNames)\n        {\n            IEnumerable<HybridModelFile> models =\n            [\n                HybridModelFile.None,\n                .. clipModelNames.Select(HybridModelFile.FromRemote),\n            ];\n\n            if (\n                await Client.GetRequiredNodeOptionNamesFromOptionalNodeAsync(\n                    \"DualCLIPLoaderGGUF\",\n                    \"clip_name1\"\n                ) is\n                { } ggufClipModelNames\n            )\n            {\n                models = models.Concat(ggufClipModelNames.Select(HybridModelFile.FromRemote));\n            }\n\n            clipModelsSource.EditDiff(models, HybridModelFile.RemoteLocalComparer);\n        }\n\n        // Get CLIP Vision model names from CLIPVisionLoader node\n        if (await Client.GetNodeOptionNamesAsync(\"CLIPVisionLoader\", \"clip_name\") is { } clipVisionModelNames)\n        {\n            IEnumerable<HybridModelFile> models =\n            [\n                HybridModelFile.None,\n                .. clipVisionModelNames.Select(HybridModelFile.FromRemote),\n            ];\n            clipVisionModelsSource.EditDiff(models, HybridModelFile.RemoteLocalComparer);\n        }\n    }\n\n    /// <summary>\n    /// Clears shared properties and sets them to local defaults\n    /// </summary>\n    protected void ResetSharedProperties()\n    {\n        // Load local models\n        modelsSource.EditDiff(\n            modelIndexService\n                .FindByModelType(SharedFolderType.StableDiffusion)\n                .Select(HybridModelFile.FromLocal),\n            HybridModelFile.Comparer\n        );\n\n        // Load local control net models\n        controlNetModelsSource.EditDiff(\n            modelIndexService.FindByModelType(SharedFolderType.ControlNet).Select(HybridModelFile.FromLocal),\n            HybridModelFile.Comparer\n        );\n\n        // Downloadable ControlNet models\n        var downloadableControlNets = RemoteModels.ControlNetModels.Where(u =>\n            !controlNetModelsSource.Lookup(u.GetId()).HasValue\n        );\n        downloadableControlNetModelsSource.EditDiff(downloadableControlNets, HybridModelFile.Comparer);\n\n        // Load local Lora / LyCORIS models\n        loraModelsSource.EditDiff(\n            modelIndexService\n                .FindByModelType(SharedFolderType.Lora | SharedFolderType.LyCORIS)\n                .Select(HybridModelFile.FromLocal),\n            HybridModelFile.Comparer\n        );\n\n        // Load local prompt expansion models\n        promptExpansionModelsSource.EditDiff(\n            modelIndexService\n                .FindByModelType(SharedFolderType.PromptExpansion)\n                .Select(HybridModelFile.FromLocal),\n            HybridModelFile.Comparer\n        );\n\n        // Downloadable PromptExpansion models\n        downloadablePromptExpansionModelsSource.EditDiff(\n            RemoteModels.PromptExpansionModels.Where(u =>\n                !promptExpansionModelsSource.Lookup(u.GetId()).HasValue\n            ),\n            HybridModelFile.Comparer\n        );\n\n        // Load local VAE models\n        vaeModelsSource.EditDiff(\n            modelIndexService.FindByModelType(SharedFolderType.VAE).Select(HybridModelFile.FromLocal),\n            HybridModelFile.Comparer\n        );\n\n        // Load Ultralytics models\n        IEnumerable<HybridModelFile> ultralyticsModels =\n        [\n            HybridModelFile.None,\n            .. modelIndexService\n                .FindByModelType(SharedFolderType.Ultralytics)\n                .Select(HybridModelFile.FromLocal),\n        ];\n        ultralyticsModelsSource.EditDiff(ultralyticsModels, HybridModelFile.Comparer);\n\n        var downloadableUltralyticsModels = RemoteModels.UltralyticsModelFiles.Where(u =>\n            !ultralyticsModelsSource.Lookup(u.GetId()).HasValue\n        );\n        downloadableUltralyticsModelsSource.EditDiff(downloadableUltralyticsModels, HybridModelFile.Comparer);\n\n        // Load SAM models\n        IEnumerable<HybridModelFile> samModels =\n        [\n            HybridModelFile.None,\n            .. modelIndexService.FindByModelType(SharedFolderType.Sams).Select(HybridModelFile.FromLocal),\n        ];\n        samModelsSource.EditDiff(samModels, HybridModelFile.Comparer);\n\n        var downloadableSamModels = RemoteModels.SamModelFiles.Where(u =>\n            !samModelsSource.Lookup(u.GetId()).HasValue\n        );\n        downloadableSamModelsSource.EditDiff(downloadableSamModels, HybridModelFile.Comparer);\n\n        unetModelsSource.EditDiff(\n            modelIndexService\n                .FindByModelType(SharedFolderType.DiffusionModels)\n                .Select(HybridModelFile.FromLocal),\n            HybridModelFile.Comparer\n        );\n\n        clipModelsSource.EditDiff(\n            modelIndexService\n                .FindByModelType(SharedFolderType.TextEncoders)\n                .Select(HybridModelFile.FromLocal),\n            HybridModelFile.Comparer\n        );\n\n        var downloadableClipModels = RemoteModels.ClipModelFiles.Where(u =>\n            !clipModelsSource.Lookup(u.GetId()).HasValue\n        );\n        downloadableClipModelsSource.EditDiff(downloadableClipModels, HybridModelFile.Comparer);\n\n        clipVisionModelsSource.EditDiff(\n            modelIndexService.FindByModelType(SharedFolderType.ClipVision).Select(HybridModelFile.FromLocal),\n            HybridModelFile.Comparer\n        );\n\n        var downloadableClipVisionModels = RemoteModels.ClipVisionModelFiles.Where(u =>\n            !clipVisionModelsSource.Lookup(u.GetId()).HasValue\n        );\n        downloadableClipVisionModelsSource.EditDiff(downloadableClipVisionModels, HybridModelFile.Comparer);\n\n        samplersSource.EditDiff(ComfySampler.Defaults, ComfySampler.Comparer);\n\n        latentUpscalersSource.EditDiff(ComfyUpscaler.Defaults, ComfyUpscaler.Comparer);\n\n        schedulersSource.EditDiff(ComfyScheduler.Defaults, ComfyScheduler.Comparer);\n\n        // Load Upscalers\n        modelUpscalersSource.EditDiff(\n            modelIndexService\n                .FindByModelType(\n                    SharedFolderType.ESRGAN | SharedFolderType.RealESRGAN | SharedFolderType.SwinIR\n                )\n                .Select(m => new ComfyUpscaler(m.FileName, ComfyUpscalerType.ESRGAN)),\n            ComfyUpscaler.Comparer\n        );\n\n        // Remote upscalers\n        var remoteUpscalers = ComfyUpscaler.DefaultDownloadableModels.Where(u =>\n            !modelUpscalersSource.Lookup(u.Name).HasValue\n        );\n        downloadableUpscalersSource.EditDiff(remoteUpscalers, ComfyUpscaler.Comparer);\n\n        // Default Preprocessors\n        preprocessorsSource.EditDiff(ComfyAuxPreprocessor.Defaults);\n    }\n\n    /// <inheritdoc />\n    public async Task UploadInputImageAsync(ImageSource image, CancellationToken cancellationToken = default)\n    {\n        EnsureConnected();\n\n        var uploadName = await image.GetHashGuidFileNameAsync();\n\n        if (image.LocalFile is { } localFile)\n        {\n            logger.LogDebug(\"Uploading image {FileName} as {UploadName}\", localFile.Name, uploadName);\n\n            // For pngs, strip metadata since Pillow can't handle some valid files?\n            if (localFile.Extension.Equals(\".png\", StringComparison.OrdinalIgnoreCase))\n            {\n                var bytes = PngDataHelper.RemoveMetadata(\n                    await localFile.ReadAllBytesAsync(cancellationToken)\n                );\n                using var stream = new MemoryStream(bytes);\n\n                await Client.UploadImageAsync(stream, uploadName, cancellationToken);\n            }\n            else\n            {\n                await using var stream = localFile.Info.OpenRead();\n\n                await Client.UploadImageAsync(stream, uploadName, cancellationToken);\n            }\n        }\n        else\n        {\n            logger.LogDebug(\"Uploading bitmap as {UploadName}\", uploadName);\n\n            if (await image.GetBitmapAsync() is not { } bitmap)\n            {\n                throw new InvalidOperationException(\"Failed to get bitmap from image\");\n            }\n\n            await using var ms = new MemoryStream();\n            bitmap.Save(ms);\n            ms.Position = 0;\n\n            await Client.UploadImageAsync(ms, uploadName, cancellationToken);\n        }\n    }\n\n    /// <inheritdoc />\n    public async Task CopyImageToInputAsync(FilePath imageFile, CancellationToken cancellationToken = default)\n    {\n        if (!IsConnected)\n            return;\n\n        if (Client.InputImagesDir is not { } inputImagesDir)\n        {\n            throw new InvalidOperationException(\"InputImagesDir is null\");\n        }\n\n        var inferenceInputs = inputImagesDir.JoinDir(\"Inference\");\n        inferenceInputs.Create();\n\n        var destination = inferenceInputs.JoinFile(imageFile.Name);\n\n        // Read to SKImage then write to file, to prevent errors from metadata\n        await Task.Run(\n            () =>\n            {\n                using var imageStream = imageFile.Info.OpenRead();\n                using var image = SKImage.FromEncodedData(imageStream);\n                using var destinationStream = destination.Info.OpenWrite();\n                image.Encode(SKEncodedImageFormat.Png, 100).SaveTo(destinationStream);\n            },\n            cancellationToken\n        );\n    }\n\n    /// <inheritdoc />\n    public async Task WriteImageToInputAsync(\n        ImageSource imageSource,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (!IsConnected)\n            return;\n\n        if (Client.InputImagesDir is not { } inputImagesDir)\n        {\n            throw new InvalidOperationException(\"InputImagesDir is null\");\n        }\n\n        var inferenceInputs = inputImagesDir.JoinDir(\"Inference\");\n        inferenceInputs.Create();\n    }\n\n    [MemberNotNull(nameof(Client))]\n    private async Task ConnectAsyncImpl(Uri uri, CancellationToken cancellationToken = default)\n    {\n        if (IsConnected)\n            return;\n\n        IsConnecting = true;\n        try\n        {\n            logger.LogDebug(\"Connecting to {@Uri}...\", uri);\n\n            var tempClient = new ComfyClient(apiFactory, uri);\n\n            await tempClient.ConnectAsync(cancellationToken);\n            logger.LogDebug(\"Connected to {@Uri}\", uri);\n\n            Client = tempClient;\n\n            await LoadSharedPropertiesAsync();\n        }\n        catch (Exception)\n        {\n            Client = null;\n            throw;\n        }\n        finally\n        {\n            IsConnecting = false;\n        }\n    }\n\n    private async Task MigrateLinksIfNeeded(PackagePair packagePair)\n    {\n        if (packagePair.InstalledPackage.FullPath is not { } packagePath)\n        {\n            throw new ArgumentException(\"Package path is null\", nameof(packagePair));\n        }\n\n        var inferenceDir = settingsManager.ImagesInferenceDirectory;\n        inferenceDir.Create();\n\n        // For locally installed packages only\n        // Delete ./output/Inference\n\n        var legacyInferenceLinkDir = new DirectoryPath(packagePair.InstalledPackage.FullPath).JoinDir(\n            \"output\",\n            \"Inference\"\n        );\n\n        if (legacyInferenceLinkDir.Exists)\n        {\n            logger.LogInformation(\"Deleting legacy inference link at {LegacyDir}\", legacyInferenceLinkDir);\n\n            if (legacyInferenceLinkDir.IsSymbolicLink)\n            {\n                await legacyInferenceLinkDir.DeleteAsync(false);\n            }\n            else\n            {\n                logger.LogWarning(\n                    \"Legacy inference link at {LegacyDir} is not a symbolic link, skipping\",\n                    legacyInferenceLinkDir\n                );\n            }\n        }\n    }\n\n    /// <inheritdoc />\n    public virtual Task ConnectAsync(CancellationToken cancellationToken = default)\n    {\n        return ConnectAsyncImpl(new Uri(\"http://127.0.0.1:8188\"), cancellationToken);\n    }\n\n    /// <inheritdoc />\n    public virtual async Task ConnectAsync(\n        PackagePair packagePair,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (IsConnected)\n            return;\n\n        if (packagePair.BasePackage is not ComfyUI comfyPackage)\n        {\n            throw new ArgumentException(\"Base package is not ComfyUI\", nameof(packagePair));\n        }\n\n        // Setup completion provider\n        completionProvider\n            .Setup()\n            .SafeFireAndForget(ex =>\n            {\n                logger.LogError(ex, \"Error setting up completion provider\");\n            });\n\n        await MigrateLinksIfNeeded(packagePair);\n\n        // Get user defined host and port\n        var host = packagePair.InstalledPackage.GetLaunchArgsHost();\n        if (string.IsNullOrWhiteSpace(host))\n        {\n            host = \"127.0.0.1\";\n        }\n        host = host.Replace(\"localhost\", \"127.0.0.1\");\n\n        var port = packagePair.InstalledPackage.GetLaunchArgsPort();\n        if (string.IsNullOrWhiteSpace(port))\n        {\n            port = \"8188\";\n        }\n\n        var uri = new UriBuilder(\"http\", host, int.Parse(port)).Uri;\n\n        await ConnectAsyncImpl(uri, cancellationToken);\n\n        Client.LocalServerPackage = packagePair;\n        Client.LocalServerPath = packagePair.InstalledPackage.FullPath!;\n    }\n\n    public async Task CloseAsync()\n    {\n        if (!IsConnected)\n            return;\n\n        await Client.CloseAsync();\n        Client = null;\n        ResetSharedProperties();\n    }\n\n    public void Dispose()\n    {\n        Client?.Dispose();\n        Client = null;\n        GC.SuppressFinalize(this);\n    }\n\n    ~InferenceClientManager()\n    {\n        Dispose();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/ModelDownloadLinkHandler.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Web;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Threading;\nusing Injectio.Attributes;\nusing MessagePipe;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\n[RegisterSingleton<IModelDownloadLinkHandler, ModelDownloadLinkHandler>]\npublic class ModelDownloadLinkHandler(\n    IDistributedSubscriber<string, Uri> uriHandlerSubscriber,\n    ILogger<ModelDownloadLinkHandler> logger,\n    ICivitApi civitApi,\n    INotificationService notificationService,\n    ISettingsManager settingsManager,\n    IModelImportService modelImportService\n) : IAsyncDisposable, IModelDownloadLinkHandler\n{\n    private IAsyncDisposable? uriHandlerSubscription;\n    private const string DownloadCivitModel = \"downloadCivitModel\";\n\n    public async Task StartListening()\n    {\n        uriHandlerSubscription = await uriHandlerSubscriber.SubscribeAsync(\n            UriHandler.IpcKeySend,\n            UriReceivedHandler\n        );\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (uriHandlerSubscription is not null)\n        {\n            await uriHandlerSubscription.DisposeAsync();\n            uriHandlerSubscription = null;\n        }\n    }\n\n    private void UriReceivedHandler(Uri receivedUri)\n    {\n        if (!receivedUri.Host.Equals(DownloadCivitModel, StringComparison.OrdinalIgnoreCase))\n            return;\n\n        logger.LogDebug(\"ModelDownloadLinkHandler Received URI: {Uri}\", receivedUri.PathAndQuery);\n\n        var queryDict = HttpUtility.ParseQueryString(receivedUri.Query);\n        var modelIdStr = queryDict[\"modelId\"];\n        var modelVersionIdStr = queryDict[\"modelVersionId\"];\n        var type = queryDict[\"type\"];\n        var format = queryDict[\"format\"];\n        var size = queryDict[\"size\"];\n        var fp = queryDict[\"fp\"];\n\n        if (\n            string.IsNullOrWhiteSpace(modelIdStr)\n            || string.IsNullOrWhiteSpace(type)\n            || string.IsNullOrWhiteSpace(format)\n            || !int.TryParse(modelIdStr, out var modelId)\n            || !Enum.TryParse<CivitFileType>(type, out var civitFileType)\n            || !Enum.TryParse<CivitModelFormat>(format, out var civitFormat)\n        )\n        {\n            logger.LogError(\"ModelDownloadLinkHandler: Invalid query parameters\");\n\n            Dispatcher.UIThread.Post(\n                () =>\n                    notificationService.Show(\n                        new Notification(\n                            \"Invalid Download Link\",\n                            \"The download link is invalid\",\n                            NotificationType.Error\n                        )\n                    )\n            );\n            return;\n        }\n\n        Dispatcher.UIThread.Post(\n            () =>\n                notificationService.Show(\n                    \"Link Received\",\n                    \"Successfully received download link\",\n                    NotificationType.Warning\n                )\n        );\n\n        var modelTask = civitApi.GetModelById(modelId);\n        modelTask.Wait();\n        var model = modelTask.Result;\n\n        var useModelVersion = !string.IsNullOrWhiteSpace(modelVersionIdStr);\n        var modelVersionId = useModelVersion ? int.Parse(modelVersionIdStr) : 0;\n\n        var modelVersion = useModelVersion\n            ? model.ModelVersions?.FirstOrDefault(x => x.Id == modelVersionId)\n            : model.ModelVersions?.FirstOrDefault();\n\n        if (modelVersion is null)\n        {\n            logger.LogError(\"ModelDownloadLinkHandler: Model version not found\");\n            Dispatcher.UIThread.Post(\n                () =>\n                    notificationService.Show(\n                        new Notification(\n                            \"Model has no versions available\",\n                            \"This model has no versions available for download\",\n                            NotificationType.Error\n                        )\n                    )\n            );\n            return;\n        }\n\n        var possibleFiles = modelVersion.Files?.Where(\n            x => x.Type == civitFileType && x.Metadata.Format == civitFormat\n        );\n\n        if (!string.IsNullOrWhiteSpace(fp))\n        {\n            possibleFiles = possibleFiles?.Where(\n                x => x.Metadata.Fp != null && x.Metadata.Fp.Equals(fp, StringComparison.OrdinalIgnoreCase)\n            );\n        }\n\n        if (!string.IsNullOrWhiteSpace(size))\n        {\n            possibleFiles = possibleFiles?.Where(x => x.Metadata.Size != null && x.Metadata.Size == size);\n        }\n\n        possibleFiles = possibleFiles?.ToList();\n\n        if (possibleFiles is null)\n        {\n            Dispatcher.UIThread.Post(\n                () =>\n                    notificationService.Show(\n                        new Notification(\n                            \"Model has no files available\",\n                            \"This model has no files available for download\",\n                            NotificationType.Error\n                        )\n                    )\n            );\n            logger.LogError(\"ModelDownloadLinkHandler: Model file not found\");\n            return;\n        }\n\n        var selectedFile = possibleFiles.FirstOrDefault() ?? modelVersion.Files?.FirstOrDefault();\n\n        var rootModelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory);\n        var downloadDirectory = rootModelsDirectory.JoinDir(\n            selectedFile?.Type == CivitFileType.VAE\n                ? SharedFolderType.VAE.GetStringValue()\n                : model.Type.ConvertTo<SharedFolderType>().GetStringValue()\n        );\n\n        var importTask = modelImportService.DoImport(\n            model,\n            downloadDirectory,\n            selectedVersion: modelVersion,\n            selectedFile: selectedFile\n        );\n        importTask.Wait();\n\n        Dispatcher.UIThread.Post(\n            () => notificationService.Show(\"Download Started\", $\"Downloading {selectedFile.Name}\")\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/ModelImportService.cs",
    "content": "﻿using AsyncAwaitBestPractices;\nusing Avalonia.Controls.Notifications;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Api.OpenModelsDb;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\nusing Dispatcher = Avalonia.Threading.Dispatcher;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\n[RegisterSingleton<IModelImportService, ModelImportService>]\npublic class ModelImportService(\n    IDownloadService downloadService,\n    INotificationService notificationService,\n    ITrackedDownloadService trackedDownloadService\n) : IModelImportService\n{\n    public static async Task<FilePath> SaveCmInfo(\n        CivitModel model,\n        CivitModelVersion modelVersion,\n        CivitFile modelFile,\n        DirectoryPath downloadDirectory,\n        string? fileNameOverride = null,\n        SamplerCardViewModel? samplerCardVm = null\n    )\n    {\n        var modelFileName = fileNameOverride ?? Path.GetFileNameWithoutExtension(modelFile.Name);\n        InferenceDefaults? inferenceDefaults = null;\n        if (samplerCardVm != null)\n        {\n            inferenceDefaults = new InferenceDefaults\n            {\n                Sampler = samplerCardVm.SelectedSampler,\n                Scheduler = samplerCardVm.SelectedScheduler,\n                CfgScale = samplerCardVm.CfgScale,\n                Steps = samplerCardVm.Steps,\n                Width = samplerCardVm.Width,\n                Height = samplerCardVm.Height,\n            };\n        }\n\n        var modelInfo = new ConnectedModelInfo(\n            model,\n            modelVersion,\n            modelFile,\n            DateTime.UtcNow,\n            inferenceDefaults\n        );\n\n        await modelInfo.SaveJsonToDirectory(downloadDirectory, modelFileName);\n\n        var jsonName = $\"{modelFileName}.cm-info.json\";\n        return downloadDirectory.JoinFile(jsonName);\n    }\n\n    /// <summary>\n    /// Saves the preview image to the same directory as the model file\n    /// </summary>\n    /// <param name=\"modelVersion\"></param>\n    /// <param name=\"modelFilePath\"></param>\n    /// <returns>The file path of the saved preview image</returns>\n    public async Task<FilePath?> SavePreviewImage(CivitModelVersion modelVersion, FilePath modelFilePath)\n    {\n        // Skip if model has no images\n        if (modelVersion.Images == null || modelVersion.Images.Count == 0)\n        {\n            return null;\n        }\n\n        var image = modelVersion.Images.FirstOrDefault(x => x.Type == \"image\");\n        if (image is null)\n            return null;\n\n        var imageExtension = Path.GetExtension(image.Url).TrimStart('.');\n        if (imageExtension is \"jpg\" or \"jpeg\" or \"png\")\n        {\n            var imageDownloadPath = modelFilePath.Directory!.JoinFile(\n                $\"{modelFilePath.NameWithoutExtension}.preview.{imageExtension}\"\n            );\n\n            var imageTask = downloadService.DownloadToFileAsync(image.Url, imageDownloadPath);\n            await notificationService.TryAsync(imageTask, \"Could not download preview image\");\n\n            return imageDownloadPath;\n        }\n\n        return null;\n    }\n\n    public async Task DoImport(\n        CivitModel model,\n        DirectoryPath downloadFolder,\n        CivitModelVersion? selectedVersion = null,\n        CivitFile? selectedFile = null,\n        string? fileNameOverride = null,\n        SamplerCardViewModel? inferenceDefaults = null,\n        IProgress<ProgressReport>? progress = null,\n        Func<Task>? onImportComplete = null,\n        Func<Task>? onImportCanceled = null,\n        Func<Task>? onImportFailed = null\n    )\n    {\n        // Get latest version\n        var modelVersion = selectedVersion ?? model.ModelVersions?.FirstOrDefault();\n        if (modelVersion is null)\n        {\n            notificationService.Show(\n                new Notification(\n                    \"Model has no versions available\",\n                    \"This model has no versions available for download\",\n                    NotificationType.Warning\n                )\n            );\n            return;\n        }\n\n        // Get latest version file\n        var modelFile =\n            selectedFile ?? modelVersion.Files?.FirstOrDefault(x => x.Type == CivitFileType.Model);\n        if (modelFile is null)\n        {\n            notificationService.Show(\n                new Notification(\n                    \"Model has no files available\",\n                    \"This model has no files available for download\",\n                    NotificationType.Warning\n                )\n            );\n            return;\n        }\n\n        if (fileNameOverride != null && (fileNameOverride.Contains(\"\\\\\") || fileNameOverride.Contains(\"/\")))\n        {\n            // figure out the folder path to add to downloadFolder\n            var lastIndex = fileNameOverride.LastIndexOfAny(['\\\\', '/']);\n            if (lastIndex >= 0)\n            {\n                // Extract folder path and file name\n                var folderPath = fileNameOverride.Substring(0, lastIndex);\n                fileNameOverride = fileNameOverride.Substring(lastIndex + 1);\n\n                // Join with download folder\n                downloadFolder = downloadFolder.JoinDir(folderPath);\n            }\n        }\n\n        // Folders might be missing if user didn't install any packages yet\n        downloadFolder.Create();\n\n        var originalFileName =\n            fileNameOverride == null\n                ? modelFile.Name\n                : $@\"{fileNameOverride}{Path.GetExtension(modelFile.Name)}\";\n\n        // Fix invalid chars in FileName\n        originalFileName = Path.GetInvalidFileNameChars()\n            .Aggregate(originalFileName, (current, c) => current.Replace(c, '_'));\n\n        // Generate unique file name if it already exists\n        var uniqueFileName = GenerateUniqueFileName(downloadFolder.ToString(), originalFileName);\n        if (!uniqueFileName.Equals(originalFileName, StringComparison.Ordinal))\n        {\n            Dispatcher.UIThread.Post(() =>\n            {\n                notificationService.Show(\n                    new Notification(\n                        \"File renamed\",\n                        $\"A file with the name \\\"{originalFileName}\\\" already exists. The model will be saved as \\\"{uniqueFileName}\\\".\"\n                    )\n                );\n            });\n        }\n\n        var downloadPath = downloadFolder.JoinFile(uniqueFileName);\n\n        // Download model info and preview first\n        var cmInfoPath = await SaveCmInfo(\n            model,\n            modelVersion,\n            modelFile,\n            downloadFolder,\n            Path.GetFileNameWithoutExtension(uniqueFileName),\n            inferenceDefaults\n        );\n        var previewImagePath = await SavePreviewImage(modelVersion, downloadPath);\n\n        // Create tracked download\n        var download = trackedDownloadService.NewDownload(modelFile.DownloadUrl, downloadPath);\n\n        // Add hash info\n        download.ExpectedHashSha256 = modelFile.Hashes.SHA256;\n\n        // Add files to cleanup list\n        download.ExtraCleanupFileNames.Add(cmInfoPath);\n        if (previewImagePath is not null)\n        {\n            download.ExtraCleanupFileNames.Add(previewImagePath);\n        }\n\n        // Attach for progress updates\n        download.ProgressUpdate += (s, e) =>\n        {\n            progress?.Report(e);\n        };\n\n        download.ProgressStateChanged += (s, e) =>\n        {\n            if (e == ProgressState.Success)\n            {\n                onImportComplete?.Invoke().SafeFireAndForget();\n            }\n            else if (e == ProgressState.Cancelled)\n            {\n                onImportCanceled?.Invoke().SafeFireAndForget();\n            }\n            else if (e == ProgressState.Failed)\n            {\n                onImportFailed?.Invoke().SafeFireAndForget();\n            }\n        };\n\n        // Add hash context action\n        download.ContextAction = CivitPostDownloadContextAction.FromCivitFile(modelFile);\n\n        await trackedDownloadService.TryStartDownload(download);\n    }\n\n    public Task DoOpenModelDbImport(\n        OpenModelDbKeyedModel model,\n        OpenModelDbResource resource,\n        DirectoryPath downloadFolder,\n        Action<TrackedDownload>? configureDownload = null\n    )\n    {\n        // todo: maybe can get actual filename from url?\n        ArgumentException.ThrowIfNullOrEmpty(model.Id, nameof(model));\n        ArgumentException.ThrowIfNullOrEmpty(resource.Type, nameof(resource));\n        var modelFileName = $\"{model.Id}.{resource.Type}\";\n\n        var modelUris = resource.Urls?.Select(u => new Uri(u, UriKind.Absolute)).ToArray();\n        if (modelUris is null || modelUris.Length == 0)\n        {\n            notificationService.Show(\n                new Notification(\n                    \"Model has no download links\",\n                    \"This model has no download links available\",\n                    NotificationType.Warning\n                )\n            );\n            return Task.CompletedTask;\n        }\n\n        return DoCustomImport(\n            modelUris,\n            modelFileName,\n            downloadFolder,\n            model.Images?.SelectImageAbsoluteUris().FirstOrDefault(),\n            configureDownload: configureDownload,\n            connectedModelInfo: new ConnectedModelInfo(model, resource, DateTimeOffset.Now)\n        );\n    }\n\n    public async Task DoCustomImport(\n        IEnumerable<Uri> modelUris,\n        string modelFileName,\n        DirectoryPath downloadFolder,\n        Uri? previewImageUri = null,\n        string? previewImageFileExtension = null,\n        ConnectedModelInfo? connectedModelInfo = null,\n        Action<TrackedDownload>? configureDownload = null\n    )\n    {\n        // Folders might be missing if user didn't install any packages yet\n        downloadFolder.Create();\n\n        // Fix invalid chars in FileName\n        var modelBaseFileName = Path.GetFileNameWithoutExtension(modelFileName);\n        modelBaseFileName = Path.GetInvalidFileNameChars()\n            .Aggregate(modelBaseFileName, (current, c) => current.Replace(c, '_'));\n        var modelFileExtension = Path.GetExtension(modelFileName);\n\n        var downloadPath = downloadFolder.JoinFile(modelBaseFileName + modelFileExtension);\n\n        // Save model info and preview image first if available\n        var cleanupFilePaths = new List<string>();\n        if (connectedModelInfo is not null)\n        {\n            await connectedModelInfo.SaveJsonToDirectory(downloadFolder, modelBaseFileName);\n            cleanupFilePaths.Add(\n                downloadFolder.JoinFile(modelBaseFileName + ConnectedModelInfo.FileExtension)\n            );\n        }\n        if (previewImageUri is not null)\n        {\n            if (previewImageFileExtension is null)\n            {\n                previewImageFileExtension = Path.GetExtension(previewImageUri.LocalPath);\n                if (string.IsNullOrEmpty(previewImageFileExtension))\n                {\n                    throw new InvalidOperationException(\n                        \"Unable to get preview image file extension from from Uri, and no file extension provided\"\n                    );\n                }\n            }\n\n            var previewImageDownloadPath = downloadFolder.JoinFile(\n                modelBaseFileName + \".preview\" + previewImageFileExtension\n            );\n\n            await notificationService.TryAsync(\n                downloadService.DownloadToFileAsync(previewImageUri.ToString(), previewImageDownloadPath),\n                \"Could not download preview image\"\n            );\n\n            cleanupFilePaths.Add(previewImageDownloadPath);\n        }\n\n        // Create tracked download\n        // todo: support multiple uris\n        var modelUri = modelUris.First();\n        var download = trackedDownloadService.NewDownload(modelUri, downloadPath);\n\n        // Add hash info\n        // download.ExpectedHashSha256 = modelFile.Hashes.SHA256;\n\n        // Add files to cleanup list\n        download.ExtraCleanupFileNames.AddRange(cleanupFilePaths);\n\n        // Configure\n        configureDownload?.Invoke(download);\n\n        // Add hash context action\n        // download.ContextAction = CivitPostDownloadContextAction.FromCivitFile(modelFile);\n\n        await trackedDownloadService.TryStartDownload(download);\n    }\n\n    private string GenerateUniqueFileName(string folder, string fileName)\n    {\n        var fullPath = Path.Combine(folder, fileName);\n        if (!File.Exists(fullPath))\n            return fileName;\n\n        var name = Path.GetFileNameWithoutExtension(fileName);\n        var extension = Path.GetExtension(fileName);\n        var count = 1;\n        string newFileName;\n\n        do\n        {\n            newFileName = $\"{name} ({count}){extension}\";\n            fullPath = Path.Combine(folder, newFileName);\n            count++;\n        } while (File.Exists(fullPath));\n\n        return newFileName;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/NavigationService.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing Avalonia.Controls;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Media.Animation;\nusing FluentAvalonia.UI.Navigation;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\n[RegisterSingleton<INavigationService<MainWindowViewModel>, NavigationService<MainWindowViewModel>>]\n[RegisterSingleton<INavigationService<SettingsViewModel>, NavigationService<SettingsViewModel>>]\n[RegisterSingleton<INavigationService<PackageManagerViewModel>, NavigationService<PackageManagerViewModel>>]\npublic class NavigationService<T> : INavigationService<T>\n{\n    private Frame? _frame;\n\n    public event EventHandler<TypedNavigationEventArgs>? TypedNavigation;\n\n    /// <inheritdoc />\n    public void SetFrame(Frame frame)\n    {\n        _frame = frame;\n    }\n\n    /// <inheritdoc />\n    public void NavigateTo<TViewModel>(NavigationTransitionInfo? transitionInfo = null, object? param = null)\n        where TViewModel : ViewModelBase\n    {\n        if (_frame is null)\n        {\n            throw new InvalidOperationException(\"SetFrame was not called before NavigateTo.\");\n        }\n\n        if (App.Services.GetService(typeof(ISettingsManager)) is ISettingsManager settingsManager)\n        {\n            // Handle animation scale\n            switch (transitionInfo)\n            {\n                // If the transition info is null or animation scale is 0, suppress the transition\n                case null:\n                case BaseTransitionInfo when settingsManager.Settings.AnimationScale == 0f:\n                    transitionInfo = new SuppressNavigationTransitionInfo();\n                    break;\n                case BaseTransitionInfo baseTransitionInfo:\n                    baseTransitionInfo.Duration *= settingsManager.Settings.AnimationScale;\n                    break;\n            }\n        }\n\n        _frame.NavigateToType(\n            typeof(TViewModel),\n            param,\n            new FrameNavigationOptions\n            {\n                IsNavigationStackEnabled = true,\n                TransitionInfoOverride = transitionInfo ?? new SuppressNavigationTransitionInfo(),\n            }\n        );\n\n        TypedNavigation?.Invoke(this, new TypedNavigationEventArgs { ViewModelType = typeof(TViewModel) });\n    }\n\n    /// <inheritdoc />\n    public void NavigateTo(\n        Type viewModelType,\n        NavigationTransitionInfo? transitionInfo = null,\n        object? param = null\n    )\n    {\n        if (!viewModelType.IsAssignableTo(typeof(ViewModelBase)))\n        {\n            // ReSharper disable once LocalizableElement\n            throw new ArgumentException(\"Type must be a ViewModelBase.\", nameof(viewModelType));\n        }\n\n        if (_frame is null)\n        {\n            throw new InvalidOperationException(\"SetFrame was not called before NavigateTo.\");\n        }\n\n        if (App.Services.GetService(typeof(ISettingsManager)) is ISettingsManager settingsManager)\n        {\n            // Handle animation scale\n            switch (transitionInfo)\n            {\n                // If the transition info is null or animation scale is 0, suppress the transition\n                case null:\n                case BaseTransitionInfo when settingsManager.Settings.AnimationScale == 0f:\n                    transitionInfo = new SuppressNavigationTransitionInfo();\n                    break;\n                case BaseTransitionInfo baseTransitionInfo:\n                    baseTransitionInfo.Duration *= settingsManager.Settings.AnimationScale;\n                    break;\n            }\n        }\n\n        _frame.NavigateToType(\n            viewModelType,\n            param,\n            new FrameNavigationOptions\n            {\n                IsNavigationStackEnabled = true,\n                TransitionInfoOverride = transitionInfo ?? new SuppressNavigationTransitionInfo(),\n            }\n        );\n\n        TypedNavigation?.Invoke(this, new TypedNavigationEventArgs { ViewModelType = viewModelType });\n    }\n\n    /// <inheritdoc />\n    public void NavigateTo(ViewModelBase viewModel, NavigationTransitionInfo? transitionInfo = null)\n    {\n        if (_frame is null)\n        {\n            throw new InvalidOperationException(\"SetFrame was not called before NavigateTo.\");\n        }\n\n        if (App.Services.GetService(typeof(ISettingsManager)) is ISettingsManager settingsManager)\n        {\n            // Handle animation scale\n            switch (transitionInfo)\n            {\n                // If the transition info is null or animation scale is 0, suppress the transition\n                case null:\n                case BaseTransitionInfo when settingsManager.Settings.AnimationScale == 0f:\n                    transitionInfo = new SuppressNavigationTransitionInfo();\n                    break;\n                case BaseTransitionInfo baseTransitionInfo:\n                    baseTransitionInfo.Duration *= settingsManager.Settings.AnimationScale;\n                    break;\n            }\n        }\n\n        _frame.NavigateFromObject(\n            viewModel,\n            new FrameNavigationOptions\n            {\n                IsNavigationStackEnabled = true,\n                TransitionInfoOverride = transitionInfo ?? new SuppressNavigationTransitionInfo(),\n            }\n        );\n\n        TypedNavigation?.Invoke(\n            this,\n            new TypedNavigationEventArgs { ViewModelType = viewModel.GetType(), ViewModel = viewModel }\n        );\n    }\n\n    public bool GoBack()\n    {\n        if (_frame?.Content is IHandleNavigation navigationHandler)\n        {\n            var wentBack = navigationHandler.GoBack();\n            if (wentBack)\n            {\n                return true;\n            }\n        }\n\n        if (_frame is not { CanGoBack: true })\n            return false;\n\n        TypedNavigation?.Invoke(\n            this,\n            new TypedNavigationEventArgs\n            {\n                ViewModelType = _frame.BackStack.Last().SourcePageType,\n                ViewModel = _frame.BackStack.Last().Context,\n            }\n        );\n\n        _frame.GoBack(BetterSlideNavigationTransition.PageSlideFromLeft);\n        return true;\n    }\n\n    public bool CanGoBack => _frame?.CanGoBack ?? false;\n\n    public object? CurrentPageDataContext => (_frame?.Content as Control)?.DataContext;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/NotificationService.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Threading;\nusing DesktopNotifications.FreeDesktop;\nusing DesktopNotifications.Windows;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing Nito.AsyncEx;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Services;\nusing INotificationManager = DesktopNotifications.INotificationManager;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\n[RegisterSingleton<INotificationService, NotificationService>]\npublic class NotificationService(ILogger<NotificationService> logger, ISettingsManager settingsManager)\n    : INotificationService,\n        IDisposable\n{\n    private WindowNotificationManager? notificationManager;\n\n    private readonly AsyncLock nativeNotificationManagerLock = new();\n    private volatile INotificationManager? nativeNotificationManager;\n    private volatile bool isNativeNotificationManagerInitialized;\n\n    public void Initialize(\n        Visual? visual,\n        NotificationPosition position = NotificationPosition.BottomRight,\n        int maxItems = 4\n    )\n    {\n        if (notificationManager is not null)\n            return;\n        notificationManager = new WindowNotificationManager(TopLevel.GetTopLevel(visual))\n        {\n            Position = position,\n            MaxItems = maxItems\n        };\n    }\n\n    public void Show(INotification notification)\n    {\n        notificationManager?.Show(notification);\n    }\n\n    /// <inheritdoc />\n    public Task ShowPersistentAsync(NotificationKey key, DesktopNotifications.Notification notification)\n    {\n        return ShowAsyncCore(key, notification, null, true);\n    }\n\n    /// <inheritdoc />\n    public Task ShowAsync(\n        NotificationKey key,\n        DesktopNotifications.Notification notification,\n        TimeSpan? expiration = null\n    )\n    {\n        // Use default expiration if not specified\n        expiration ??= TimeSpan.FromSeconds(5);\n\n        return ShowAsyncCore(key, notification, expiration, false);\n    }\n\n    private async Task ShowAsyncCore(\n        NotificationKey key,\n        DesktopNotifications.Notification notification,\n        TimeSpan? expiration,\n        bool isPersistent\n    )\n    {\n        // If settings has option preference, use that, otherwise default\n        if (!settingsManager.Settings.NotificationOptions.TryGetValue(key, out var option))\n        {\n            option = key.DefaultOption;\n        }\n\n        switch (option)\n        {\n            case NotificationOption.None:\n                break;\n            case NotificationOption.NativePush:\n            {\n                // If native option is not supported, fallback to toast\n                if (await GetNativeNotificationManagerAsync() is not { } nativeManager)\n                {\n                    // Show app toast\n                    if (isPersistent)\n                    {\n                        Dispatcher.UIThread.Invoke(\n                            () =>\n                                ShowPersistent(\n                                    notification.Title ?? \"\",\n                                    notification.Body ?? \"\",\n                                    key.Level.ToNotificationType()\n                                )\n                        );\n                    }\n                    else\n                    {\n                        Dispatcher.UIThread.Invoke(\n                            () =>\n                                Show(\n                                    notification.Title ?? \"\",\n                                    notification.Body ?? \"\",\n                                    key.Level.ToNotificationType(),\n                                    expiration\n                                )\n                        );\n                    }\n                    return;\n                }\n\n                // Show native notification\n                await nativeManager.ShowNotification(\n                    notification,\n                    expiration is null ? null : DateTimeOffset.Now.Add(expiration.Value)\n                );\n\n                break;\n            }\n            case NotificationOption.AppToast:\n                // Show app toast\n                if (isPersistent)\n                {\n                    Dispatcher.UIThread.Invoke(\n                        () =>\n                            ShowPersistent(\n                                notification.Title ?? \"\",\n                                notification.Body ?? \"\",\n                                key.Level.ToNotificationType()\n                            )\n                    );\n                }\n                else\n                {\n                    Dispatcher.UIThread.Invoke(\n                        () =>\n                            Show(\n                                notification.Title ?? \"\",\n                                notification.Body ?? \"\",\n                                key.Level.ToNotificationType(),\n                                expiration\n                            )\n                    );\n                }\n\n                break;\n            default:\n                logger.LogError(\"Unknown notification option {Option}\", option);\n                break;\n        }\n    }\n\n    public void Show(\n        string title,\n        string message,\n        NotificationType appearance = NotificationType.Information,\n        TimeSpan? expiration = null\n    )\n    {\n        Show(new Notification(title, message, appearance, expiration));\n    }\n\n    public void ShowPersistent(\n        string title,\n        string message,\n        NotificationType appearance = NotificationType.Information\n    )\n    {\n        Show(new Notification(title, message, appearance, TimeSpan.Zero));\n    }\n\n    /// <inheritdoc />\n    public void ShowPersistent(\n        AppException exception,\n        NotificationType appearance = NotificationType.Warning,\n        LogLevel logLevel = LogLevel.Warning\n    )\n    {\n        // Log exception\n        logger.Log(logLevel, exception, \"{Message}\", exception.Message);\n\n        Show(new Notification(exception.Message, exception.Details, appearance, TimeSpan.Zero));\n    }\n\n    /// <inheritdoc />\n    public async Task<TaskResult<T>> TryAsync<T>(\n        Task<T> task,\n        string title = \"Error\",\n        string? message = null,\n        NotificationType appearance = NotificationType.Error\n    )\n    {\n        try\n        {\n            return new TaskResult<T>(await task);\n        }\n        catch (Exception e)\n        {\n            Show(new Notification(title, message ?? e.Message, appearance));\n            return TaskResult<T>.FromException(e);\n        }\n    }\n\n    /// <inheritdoc />\n    public async Task<TaskResult<bool>> TryAsync(\n        Task task,\n        string title = \"Error\",\n        string? message = null,\n        NotificationType appearance = NotificationType.Error\n    )\n    {\n        try\n        {\n            await task;\n            return new TaskResult<bool>(true);\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"{Exception}\", e);\n            Show(new Notification(title, message ?? e.Message, appearance));\n            return new TaskResult<bool>(false, e);\n        }\n    }\n\n    public void Dispose()\n    {\n        nativeNotificationManager?.Dispose();\n\n        GC.SuppressFinalize(this);\n    }\n\n    public async Task<INotificationManager?> GetNativeNotificationManagerAsync()\n    {\n        if (isNativeNotificationManagerInitialized)\n            return nativeNotificationManager;\n\n        using var _ = await nativeNotificationManagerLock.LockAsync();\n\n        if (isNativeNotificationManagerInitialized)\n            return nativeNotificationManager;\n\n        try\n        {\n            if (Compat.IsWindows)\n            {\n                var context = WindowsApplicationContext.FromCurrentProcess(\"Stability Matrix\");\n                nativeNotificationManager = new WindowsNotificationManager(context);\n\n                await nativeNotificationManager.Initialize();\n            }\n            else if (Compat.IsLinux)\n            {\n                var context = FreeDesktopApplicationContext.FromCurrentProcess();\n                nativeNotificationManager = new FreeDesktopNotificationManager(context);\n\n                await nativeNotificationManager.Initialize();\n            }\n            else\n            {\n                logger.LogInformation(\"Native notifications are not supported on this platform\");\n            }\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Failed to initialize native notification manager\");\n        }\n\n        isNativeNotificationManagerInitialized = true;\n\n        return nativeNotificationManager;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/RunningPackageService.cs",
    "content": "﻿using System.Collections.Immutable;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing KeyedSemaphores;\nusing Microsoft.Extensions.Logging;\nusing Nito.Disposables.Internals;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\n[RegisterSingleton<RunningPackageService>]\npublic partial class RunningPackageService(\n    ILogger<RunningPackageService> logger,\n    IPackageFactory packageFactory,\n    INotificationService notificationService,\n    ISettingsManager settingsManager,\n    IPyRunner pyRunner\n) : ObservableObject, IDisposable\n{\n    /// <summary>\n    /// Locks for starting or stopping packages.\n    /// </summary>\n    private readonly KeyedSemaphoresDictionary<Guid> packageLocks = new();\n\n    // 🤔 what if we put the ConsoleViewModel inside the BasePackage? 🤔\n    [ObservableProperty]\n    private ObservableDictionary<Guid, RunningPackageViewModel> runningPackages = [];\n\n    public async Task<PackagePair?> StartPackage(\n        InstalledPackage installedPackage,\n        string? command = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        // Get lock\n        using var @lock = await packageLocks.LockAsync(installedPackage.Id, cancellationToken);\n\n        // Ignore if already running after lock\n        if (RunningPackages.ContainsKey(installedPackage.Id))\n        {\n            logger.LogWarning(\"Skipping StartPackage, already running: {Id}\", installedPackage.Id);\n            return null;\n        }\n\n        var activeInstallName = installedPackage.PackageName;\n        var basePackage = string.IsNullOrWhiteSpace(activeInstallName)\n            ? null\n            : packageFactory.GetNewBasePackage(installedPackage);\n\n        if (basePackage == null)\n        {\n            logger.LogWarning(\n                \"During launch, package name '{PackageName}' did not match a definition\",\n                activeInstallName\n            );\n\n            notificationService.Show(\n                new Notification(\n                    \"Package name invalid\",\n                    \"Install package name did not match a definition. Please reinstall and let us know about this issue.\",\n                    NotificationType.Error\n                )\n            );\n            return null;\n        }\n\n        // Show warning if critical vulnerabilities are found\n        if (basePackage.HasCriticalVulnerabilities)\n        {\n            var vulns = basePackage\n                .KnownVulnerabilities.Where(v => v.Severity == VulnerabilitySeverity.Critical)\n                .Select(v =>\n                    $\"**{v.Id}**: {v.Title}\\n  - Severity: {v.Severity}\\n  - Description: {v.Description}\"\n                )\n                .ToList();\n\n            var message =\n                $\"# ⚠️ Critical Security Vulnerabilities\\n\\nThis package has critical security vulnerabilities that may put your system at risk:\\n\\n{string.Join(\"\\n\\n\", vulns)}\";\n            message +=\n                \"\\n\\nFor more information, please visit the [GitHub Security Advisory page](https://github.com/LykosAI/StabilityMatrix/security/advisories).\";\n\n            var dialog = DialogHelper.CreateMarkdownDialog(message, \"Security Warning\");\n\n            dialog.IsPrimaryButtonEnabled = false;\n            dialog.PrimaryButtonText = \"Continue Anyway (3)\";\n            dialog.CloseButtonText = Resources.Action_Cancel;\n            dialog.DefaultButton = ContentDialogButton.Close;\n\n            // Start a timer to enable the button after 3 seconds\n            var countdown = 3;\n            var timer = new System.Timers.Timer(1000);\n            timer.Elapsed += (_, _) =>\n            {\n                Dispatcher.UIThread.Post(() =>\n                {\n                    countdown--;\n                    if (countdown <= 0)\n                    {\n                        dialog.IsPrimaryButtonEnabled = true;\n                        dialog.PrimaryButtonText = \"Continue Anyway\";\n                        timer.Stop();\n                        timer.Dispose();\n                    }\n                    else\n                    {\n                        dialog.PrimaryButtonText = $\"Continue Anyway ({countdown})\";\n                    }\n                });\n            };\n            timer.Start();\n\n            var result = await dialog.ShowAsync();\n            if (result != ContentDialogResult.Primary)\n            {\n                return null;\n            }\n        }\n        // Show warning if any vulnerabilities are found\n        else if (basePackage.HasVulnerabilities)\n        {\n            var vulns = basePackage\n                .KnownVulnerabilities.Select(v =>\n                    $\"**{v.Id}**: {v.Title}\\n  - Severity: {v.Severity}\\n  - Description: {v.Description}\"\n                )\n                .ToList();\n\n            var message =\n                $\"# ⚠️ Security Notice\\n\\nThis package has known vulnerabilities:\\n\\n{string.Join(\"\\n\\n\", vulns)}\";\n\n            message +=\n                \"\\n\\nFor more information, please visit the [GitHub Security Advisory page](https://github.com/LykosAI/StabilityMatrix/security/advisories).\";\n\n            var dialog = DialogHelper.CreateMarkdownDialog(message, \"Security Notice\");\n\n            dialog.IsPrimaryButtonEnabled = false;\n            dialog.PrimaryButtonText = \"Continue Anyway (3)\";\n            dialog.CloseButtonText = Resources.Action_Cancel;\n            dialog.DefaultButton = ContentDialogButton.Close;\n\n            // Start a timer to enable the button after 3 seconds\n            var countdown = 3;\n            var timer = new System.Timers.Timer(1000);\n            timer.Elapsed += (_, _) =>\n            {\n                Dispatcher.UIThread.Post(() =>\n                {\n                    countdown--;\n                    if (countdown <= 0)\n                    {\n                        dialog.IsPrimaryButtonEnabled = true;\n                        dialog.PrimaryButtonText = \"Continue Anyway\";\n                        timer.Stop();\n                        timer.Dispose();\n                    }\n                    else\n                    {\n                        dialog.PrimaryButtonText = $\"Continue Anyway ({countdown})\";\n                    }\n                });\n            };\n            timer.Start();\n\n            var result = await dialog.ShowAsync();\n            if (result != ContentDialogResult.Primary)\n            {\n                return null;\n            }\n        }\n\n        // If this is the first launch (LaunchArgs is null),\n        // load and save a launch options dialog vm\n        // so that dynamic initial values are saved.\n        if (installedPackage.LaunchArgs == null)\n        {\n            var definitions = basePackage.LaunchOptions;\n            // Create config cards and save them\n            var cards = LaunchOptionCard\n                .FromDefinitions(definitions, Array.Empty<LaunchOption>())\n                .ToImmutableArray();\n\n            var args = cards.SelectMany(c => c.Options).ToList();\n\n            logger.LogDebug(\n                \"Setting initial launch args: {Args}\",\n                string.Join(\", \", args.Select(o => o.ToArgString()?.ToRepr()))\n            );\n\n            settingsManager.SaveLaunchArgs(installedPackage.Id, args);\n        }\n\n        if (basePackage is not StableSwarm)\n        {\n            await pyRunner.Initialize();\n        }\n\n        // Get path from package\n        var packagePath = new DirectoryPath(settingsManager.LibraryDir, installedPackage.LibraryPath!);\n\n        if (basePackage is not StableSwarm)\n        {\n            // Unpack sitecustomize.py to venv\n            await UnpackSiteCustomize(packagePath.JoinDir(\"venv\"));\n        }\n\n        // Clear console and start update processing\n        var console = new ConsoleViewModel();\n        console.StartUpdates();\n\n        // Update shared folder links (in case library paths changed)\n        await basePackage.UpdateModelFolders(\n            packagePath,\n            installedPackage.PreferredSharedFolderMethod ?? basePackage.RecommendedSharedFolderMethod\n        );\n\n        if (installedPackage.UseSharedOutputFolder)\n        {\n            await basePackage.SetupOutputFolderLinks(installedPackage.FullPath!);\n        }\n\n        // Load user launch args from settings\n        var launchArgStrings = (installedPackage.LaunchArgs ?? [])\n            .Select(option => option.ToArgString())\n            .WhereNotNull()\n            .ToArray();\n\n        var launchProcessArgs = ProcessArgs.FromQuoted(launchArgStrings);\n        var runPackageOptions = new RunPackageOptions { Command = command, Arguments = launchProcessArgs };\n\n        // Join with extras, if any\n        await basePackage.RunPackage(\n            packagePath,\n            installedPackage,\n            runPackageOptions,\n            console.Post,\n            cancellationToken\n        );\n\n        var runningPackage = new PackagePair(installedPackage, basePackage);\n\n        var viewModel = new RunningPackageViewModel(\n            settingsManager,\n            notificationService,\n            this,\n            runningPackage,\n            runPackageOptions,\n            console\n        );\n        RunningPackages.Add(runningPackage.InstalledPackage.Id, viewModel);\n\n        return runningPackage;\n    }\n\n    public async Task StopPackage(Guid id, CancellationToken cancellationToken = default)\n    {\n        // Get lock\n        using var @lock = await packageLocks.LockAsync(id, cancellationToken);\n\n        // Ignore if not running after lock\n        if (!RunningPackages.TryGetValue(id, out var vm))\n        {\n            logger.LogWarning(\"Skipping StopPackage, not running: {Id}\", id);\n            return;\n        }\n\n        var runningPackage = vm.RunningPackage;\n        await runningPackage.BasePackage.WaitForShutdown();\n\n        await vm.DisposeAsync();\n\n        RunningPackages.Remove(id);\n    }\n\n    public RunningPackageViewModel? GetRunningPackageViewModel(Guid id) =>\n        RunningPackages.TryGetValue(id, out var vm) ? vm : null;\n\n    private static async Task UnpackSiteCustomize(DirectoryPath venvPath)\n    {\n        var sitePackages = venvPath.JoinDir(PyVenvRunner.RelativeSitePackagesPath);\n        var file = sitePackages.JoinFile(\"sitecustomize.py\");\n        file.Directory?.Create();\n        await Assets.PyScriptSiteCustomize.ExtractTo(file, true);\n    }\n\n    public void Dispose()\n    {\n        var exceptions = new List<Exception>();\n\n        foreach (var (_, vm) in RunningPackages)\n        {\n            try\n            {\n                vm.Dispose();\n            }\n            catch (Exception e)\n            {\n                exceptions.Add(e);\n            }\n        }\n\n        if (exceptions.Count != 0)\n        {\n            throw new AggregateException(exceptions);\n        }\n\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/ScopedServiceManager.cs",
    "content": "﻿using Microsoft.Extensions.DependencyInjection;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\ninternal class ScopedServiceManager<T> : IServiceManager<T>\n{\n    private readonly ServiceManager<T> parentManager;\n    private readonly IServiceProvider scopedServiceProvider;\n\n    internal ScopedServiceManager(ServiceManager<T> parentManager, IServiceProvider scopedServiceProvider)\n    {\n        this.parentManager = parentManager;\n        this.scopedServiceProvider = scopedServiceProvider;\n    }\n\n    // Delegate Register methods to the parent manager\n\n    public IServiceManager<T> Register<TService>(TService instance)\n        where TService : T\n    {\n        return parentManager.Register(instance);\n    }\n\n    public IServiceManager<T> Register<TService>(Func<TService> provider)\n        where TService : T\n    {\n        return parentManager.Register(provider);\n    }\n\n    public void Register(Type type, Func<T> providerFunc)\n    {\n        parentManager.Register(type, providerFunc);\n    }\n\n    public IServiceManager<T> RegisterProvider<TService>(IServiceProvider provider)\n        where TService : notnull, T\n    {\n        return parentManager.RegisterProvider<TService>(provider);\n    }\n\n    public IServiceManager<T> RegisterScoped<TService>(Func<IServiceProvider, TService> provider)\n        where TService : T\n    {\n        return parentManager.RegisterScoped(provider);\n    }\n\n    public IServiceManager<T> RegisterScoped(Type type, Func<IServiceProvider, T> provider)\n    {\n        return parentManager.RegisterScoped(type, provider);\n    }\n\n    public IServiceManagerScope<T> CreateScope()\n    {\n        return parentManager.CreateScope();\n    }\n\n    public TService Get<TService>()\n        where TService : T\n    {\n        return (TService)Get(typeof(TService))!;\n    }\n\n    public T Get(Type serviceType)\n    {\n        if (!typeof(T).IsAssignableFrom(serviceType)) // Ensure type compatibility\n        {\n            throw new ArgumentException($\"Service type {serviceType} is not assignable to {typeof(T)}\");\n        }\n\n        // Check if it's a known *scoped* service type from the parent\n        if (parentManager.TryGetScopedProvider(serviceType, out var scopedProvider))\n        {\n            // Create the scoped instance using the factory from the parent\n            var newScopedInstance = scopedProvider(scopedServiceProvider);\n            if (newScopedInstance == null)\n                throw new InvalidOperationException($\"Scoped provider for {serviceType} returned null.\");\n\n            return newScopedInstance;\n        }\n\n        // 3. If not scoped, delegate to the parent manager to resolve Singleton or Transient\n        //    (Parent's Get will throw if the type isn't registered there either)\n        // return parentManager.Get(serviceType);\n\n        // We don't use parent manager for scoped contexts anymore,\n        // since we'll lose the scope through transients,\n        // then we have to make Inference Cards scoped as well,\n        // which cases samplers to be shared with Civit page and other issues.\n        // 3. Just use the scoped service provider, since we might need to keep the scope through transients as well\n        return (T)scopedServiceProvider.GetRequiredService(serviceType);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/ServiceManager.cs",
    "content": "﻿using System.Collections.Concurrent;\nusing System.ComponentModel;\nusing System.Diagnostics.CodeAnalysis;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\n[Localizable(false)]\npublic class ServiceManager<T>(IServiceProvider scopedServiceProvider) : IServiceManager<T>\n{\n    // Holds providers\n    private readonly Dictionary<Type, Func<T>> providers = new();\n\n    // Holds singleton instances\n    private readonly Dictionary<Type, T> instances = new();\n\n    // Holds scoped providers (factories)\n    private readonly ConcurrentDictionary<Type, Func<IServiceProvider, T>> scopedProviders = new();\n\n    /// <summary>\n    /// Register a new dialog view model (singleton instance)\n    /// </summary>\n    public IServiceManager<T> Register<TService>(TService instance)\n        where TService : T\n    {\n        if (instance is null)\n            throw new ArgumentNullException(nameof(instance));\n\n        lock (instances)\n        {\n            if (instances.ContainsKey(typeof(TService)) || providers.ContainsKey(typeof(TService)))\n            {\n                throw new ArgumentException(\n                    $\"Service of type {typeof(TService)} is already registered for {typeof(T)}\"\n                );\n            }\n\n            instances[instance.GetType()] = instance;\n        }\n\n        return this;\n    }\n\n    /// <summary>\n    /// Register a new dialog view model provider action (called on each dialog creation)\n    /// </summary>\n    public IServiceManager<T> Register<TService>(Func<TService> provider)\n        where TService : T\n    {\n        lock (providers)\n        {\n            if (instances.ContainsKey(typeof(TService)) || providers.ContainsKey(typeof(TService)))\n            {\n                throw new ArgumentException(\n                    $\"Service of type {typeof(TService)} is already registered for {typeof(T)}\"\n                );\n            }\n\n            // Return type is wrong during build with method group syntax\n            // ReSharper disable once RedundantCast\n            providers[typeof(TService)] = () => (TService)provider();\n        }\n\n        return this;\n    }\n\n    public void Register(Type type, Func<T> providerFunc)\n    {\n        lock (providers)\n        {\n            if (instances.ContainsKey(type) || providers.ContainsKey(type))\n            {\n                throw new ArgumentException($\"Service of type {type} is already registered for {typeof(T)}\");\n            }\n\n            providers[type] = providerFunc;\n        }\n    }\n\n    /// <summary>\n    /// Register a new service provider action with Scoped lifetime.\n    /// The factory is called once per scope.\n    /// </summary>\n    public IServiceManager<T> RegisterScoped<TService>(Func<IServiceProvider, TService> provider)\n        where TService : T\n    {\n        var type = typeof(TService);\n\n        lock (providers)\n        {\n            if (instances.ContainsKey(type) || providers.ContainsKey(type))\n                throw new ArgumentException(\n                    $\"Service of type {type} is already registered with a different lifetime.\"\n                );\n\n            if (!scopedProviders.TryAdd(type, sp => provider(sp))) // Store as base type T\n                throw new ArgumentException($\"Service of type {type} is already registered as Scoped.\");\n        }\n\n        return this;\n    }\n\n    /// <summary>\n    /// Register a new service provider action with Scoped lifetime.\n    /// The factory is called once per scope.\n    /// </summary>\n    public IServiceManager<T> RegisterScoped(Type type, Func<IServiceProvider, T> provider)\n    {\n        lock (providers)\n        {\n            if (instances.ContainsKey(type) || providers.ContainsKey(type))\n                throw new ArgumentException(\n                    $\"Service of type {type} is already registered with a different lifetime.\"\n                );\n\n            if (!scopedProviders.TryAdd(type, provider)) // Store as base type T\n                throw new ArgumentException($\"Service of type {type} is already registered as Scoped.\");\n        }\n\n        return this;\n    }\n\n    /// <summary>\n    /// Register a new dialog view model instance using a service provider\n    /// Equal to Register[TService](serviceProvider.GetRequiredService[TService])\n    /// </summary>\n    public IServiceManager<T> RegisterProvider<TService>(IServiceProvider provider)\n        where TService : notnull, T\n    {\n        lock (providers)\n        {\n            if (instances.ContainsKey(typeof(TService)) || providers.ContainsKey(typeof(TService)))\n            {\n                throw new ArgumentException(\n                    $\"Service of type {typeof(TService)} is already registered for {typeof(T)}\"\n                );\n            }\n\n            // Return type is wrong during build with method group syntax\n            // ReSharper disable once RedundantCast\n            providers[typeof(TService)] = () => (TService)provider.GetRequiredService<TService>();\n        }\n\n        return this;\n    }\n\n    /// <summary>\n    /// Creates a new service scope.\n    /// </summary>\n    /// <returns>An IServiceManagerScope representing the created scope.</returns>\n    public IServiceManagerScope<T> CreateScope()\n    {\n        var scope = scopedServiceProvider.CreateScope();\n        return new ServiceManagerScope<T>(scope, new ScopedServiceManager<T>(this, scope.ServiceProvider));\n    }\n\n    // Internal method for ScopedServiceManager to access providers\n    internal bool TryGetScopedProvider(\n        Type serviceType,\n        [MaybeNullWhen(false)] out Func<IServiceProvider, T> provider\n    )\n    {\n        return scopedProviders.TryGetValue(serviceType, out provider);\n    }\n\n    /// <summary>\n    /// Get a view model instance from runtime type\n    /// </summary>\n    [SuppressMessage(\"ReSharper\", \"InconsistentlySynchronizedField\")]\n    public T Get(Type serviceType)\n    {\n        if (!serviceType.IsAssignableTo(typeof(T)))\n        {\n            throw new ArgumentException($\"Service type {serviceType} is not assignable to {typeof(T)}\");\n        }\n\n        if (instances.TryGetValue(serviceType, out var instance))\n        {\n            if (instance is null)\n            {\n                throw new ArgumentException($\"Service of type {serviceType} was registered as null\");\n            }\n            return (T)instance;\n        }\n\n        if (providers.TryGetValue(serviceType, out var provider))\n        {\n            if (provider is null)\n            {\n                throw new ArgumentException($\"Service of type {serviceType} was registered as null\");\n            }\n            var result = provider();\n            if (result is null)\n            {\n                throw new ArgumentException($\"Service provider for type {serviceType} returned null\");\n            }\n            return (T)result;\n        }\n\n        throw new ArgumentException($\"Service of type {serviceType} is not registered for {typeof(T)}\");\n    }\n\n    /// <summary>\n    /// Get a view model instance\n    /// </summary>\n    [SuppressMessage(\"ReSharper\", \"InconsistentlySynchronizedField\")]\n    public TService Get<TService>()\n        where TService : T\n    {\n        if (instances.TryGetValue(typeof(TService), out var instance))\n        {\n            if (instance is null)\n            {\n                throw new ArgumentException($\"Service of type {typeof(TService)} was registered as null\");\n            }\n            return (TService)instance;\n        }\n\n        if (providers.TryGetValue(typeof(TService), out var provider))\n        {\n            if (provider is null)\n            {\n                throw new ArgumentException($\"Service of type {typeof(TService)} was registered as null\");\n            }\n            var result = provider();\n            if (result is null)\n            {\n                throw new ArgumentException($\"Service provider for type {typeof(TService)} returned null\");\n            }\n            return (TService)result;\n        }\n\n        throw new ArgumentException($\"Service of type {typeof(TService)} is not registered for {typeof(T)}\");\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/ServiceManagerScope.cs",
    "content": "﻿using JetBrains.Annotations;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\ninternal class ServiceManagerScope<T>(\n    [HandlesResourceDisposal] IServiceScope scope,\n    ScopedServiceManager<T> serviceManager\n) : IServiceManagerScope<T>\n{\n    public IServiceManager<T> ServiceManager { get; } = serviceManager;\n\n    public void Dispose()\n    {\n        scope.Dispose();\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Services/TabContext.cs",
    "content": "using CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.Services;\n\n/// <summary>\n/// Holds shared state scoped to a single Inference tab.\n/// </summary>\n[RegisterScoped<TabContext>]\n[ManagedService]\npublic partial class TabContext : ObservableObject\n{\n    [ObservableProperty]\n    private HybridModelFile? _selectedModel;\n\n    public event EventHandler<TabStateChangedEventArgs>? StateChanged;\n\n    partial void OnSelectedModelChanged(HybridModelFile? value)\n    {\n        OnStateChanged(nameof(SelectedModel));\n    }\n\n    protected virtual void OnStateChanged(string propertyName)\n    {\n        StateChanged?.Invoke(this, new TabStateChangedEventArgs(propertyName));\n    }\n\n    public class TabStateChangedEventArgs(string propertyName) : EventArgs\n    {\n        public string PropertyName { get; } = propertyName;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n    <Import Project=\"..\\ConditionalSymbols.props\" />\n    <Import Project=\"..\\Runtimes.Default.props\" />\n    \n    <PropertyGroup>\n        <OutputType>WinExe</OutputType>\n        <BuiltInComInteropSupport>true</BuiltInComInteropSupport>\n        <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>\n        <ApplicationIcon>./Assets/Icon.ico</ApplicationIcon>\n        <Version>2.16.0-dev.999</Version>\n        <InformationalVersion>$(Version)</InformationalVersion>\n        <EnableWindowsTargeting>true</EnableWindowsTargeting>\n        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n    </PropertyGroup>\n    \n    <PropertyGroup Condition=\" '$(RuntimeIdentifier)' == 'win-x64' \">\n        <ApplicationManifest>app.manifest</ApplicationManifest>\n    </PropertyGroup>\n    \n    <PropertyGroup Condition=\" '$(RuntimeIdentifier)' == 'osx-arm64' \">\n        <CFBundleName>Stability Matrix</CFBundleName>\n        <CFBundleIdentifier>ai.lykos.stabilitymatrix</CFBundleIdentifier>\n        <CFBundleIconFile>Assets/AppIcon.icns</CFBundleIconFile>\n        <CFBundlePackageType>APPL</CFBundlePackageType>\n    </PropertyGroup>\n\n    <Target Name=\"EditInfoPlist\" AfterTargets=\"BundleApp\" Condition=\" '$(RuntimeIdentifier)' == 'osx-arm64' \">\n        <XmlPeek \n                XmlInputPath=\"$(PublishDir)/Stability Matrix.app/Contents/Info.plist\"\n                Query=\"//plist/dict\">\n            <Output TaskParameter=\"Result\" PropertyName=\"PlistDictOriginal\"/>\n        </XmlPeek>\n        <PropertyGroup>\n            <!-- Trim the dict tags -->\n            <PlistDictOriginalInner>\n                $(PlistDictOriginal.Substring(6,\n                    $([MSBuild]::Subtract(\n                        $(PlistDictOriginal.Length),\n                        13\n                    ))\n                ))\n            </PlistDictOriginalInner>\n            <PlistDictAppend>\n                <![CDATA[\n                    <key>CFBundleURLTypes</key>\n                    <array>\n                        <dict>\n                            <key>CFBundleTypeRole</key>\n                            <string>Viewer</string>\n                            <key>CFBundleURLName</key>\n                            <string>ai.lykos.stabilitymatrix</string>\n                            <key>CFBundleURLIconFile</key>\n                            <string>AppIcon.icns</string>\n                            <key>CFBundleURLSchemes</key>\n                            <array>\n                                <string>stabilitymatrix</string>\n                            </array>\n                        </dict>\n                    </array>\n                ]]>\n            </PlistDictAppend>\n        </PropertyGroup>\n        <XmlPoke\n                XmlInputPath=\"$(PublishDir)/Stability Matrix.app/Contents/Info.plist\"\n                Query=\"//plist/dict\" \n                Value=\"$(PlistDictOriginalInner)$(PlistDictAppend)\"/>\n    </Target>\n    \n    <ItemGroup>\n        <InternalsVisibleTo Include=\"StabilityMatrix.Tests\" />\n        <InternalsVisibleTo Include=\"StabilityMatrix.UITests\" />\n    </ItemGroup>\n    \n    <ItemGroup>\n        <PackageReference Include=\"AsyncImageLoader.Avalonia\" />\n        <PackageReference Include=\"AutoComplete.Net\" />\n        <PackageReference Include=\"Avalonia.AvaloniaEdit\" />\n        <PackageReference Include=\"Avalonia.Controls.DataGrid\" />\n        <PackageReference Include=\"Avalonia.Controls.ItemsRepeater\" />\n        <PackageReference Include=\"Avalonia.Controls.PanAndZoom\" />\n        <PackageReference Include=\"Avalonia\" />\n        <PackageReference Include=\"Avalonia.Desktop\" />\n        <PackageReference Include=\"Avalonia.Fonts.Inter\" />\n        <PackageReference Include=\"Avalonia.Svg\" />\n        <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->\n        <PackageReference Condition=\"'$(Configuration)' == 'Debug'\" Include=\"Avalonia.Diagnostics\" />\n        <PackageReference Include=\"Avalonia.HtmlRenderer\" />\n        <PackageReference Include=\"Avalonia.Labs.Controls\" />\n        <PackageReference Include=\"Avalonia.Xaml.Behaviors\" />\n        <PackageReference Include=\"AvaloniaEdit.TextMate\" />\n        <PackageReference Include=\"bodong.Avalonia.PropertyGrid\" />\n        <PackageReference Include=\"bodong.PropertyModels\" />\n        <PackageReference Include=\"CommandLineParser\" />\n        <PackageReference Include=\"CommunityToolkit.Mvvm\" />\n        <PackageReference Include=\"DesktopNotifications\" />\n        <PackageReference Include=\"DesktopNotifications.Avalonia\" />\n        <PackageReference Include=\"DiscordRichPresence\" />\n        <PackageReference Include=\"Dock.Avalonia\" />\n        <PackageReference Include=\"Dock.Model.Avalonia\" />\n        <PackageReference Include=\"Dock.Serializer\" />\n        <PackageReference Include=\"DynamicData\" />\n        <PackageReference Include=\"DotNet.Bundle\" />\n        <PackageReference Include=\"Exceptionless.DateTimeExtensions\" />\n        <PackageReference Include=\"FluentAvalonia.BreadcrumbBar\" />\n        <PackageReference Include=\"FluentAvaloniaUI\" />\n        <PackageReference Include=\"FluentIcons.Avalonia\" />\n        <PackageReference Include=\"FluentIcons.Avalonia.Fluent\" />\n        <PackageReference Include=\"FuzzySharp\" />\n        <PackageReference Include=\"JetBrains.Annotations\" />\n        <PackageReference Include=\"KeyedSemaphores\" />\n        <PackageReference Include=\"Markdig\" />\n        <PackageReference Include=\"Markdown.Avalonia\" />\n        <PackageReference Include=\"MessagePipe\" />\n        <PackageReference Include=\"MessagePipe.Interprocess\" />\n        <PackageReference Include=\"MetadataExtractor\" />\n        <PackageReference Include=\"Microsoft.Extensions.Configuration\" />\n        <PackageReference Include=\"Microsoft.Extensions.Configuration.EnvironmentVariables\" />\n        <PackageReference Include=\"Microsoft.Extensions.Configuration.FileExtensions\" />\n        <PackageReference Include=\"Microsoft.Extensions.Configuration.Json\" />\n        <PackageReference Include=\"Microsoft.Extensions.DependencyInjection\" />\n        <PackageReference Include=\"Microsoft.Extensions.Http.Polly\" />\n        <PackageReference Include=\"Microsoft.Extensions.Options.ConfigurationExtensions\" />\n        <PackageReference Include=\"Nito.AsyncEx\" />\n        <PackageReference Include=\"NLog\" />\n        <PackageReference Include=\"NLog.Extensions.Logging\" />\n        <PackageReference Include=\"NSubstitute\" />\n        <PackageReference Include=\"OneOf\" />\n        <PackageReference Include=\"OpenIddict.Client\" />\n        <PackageReference Include=\"OpenIddict.Client.SystemNetHttp\" />\n        <PackageReference Include=\"Polly\" />\n        <PackageReference Include=\"Polly.Contrib.WaitAndRetry\" />\n        <PackageReference Include=\"Polly.Extensions.Http\" />\n        <PackageReference Include=\"Projektanker.Icons.Avalonia.FontAwesome\" />\n        <PackageReference Include=\"RockLib.Reflection.Optimized\" />\n        <PackageReference Include=\"Semi.Avalonia\"/>\n        <PackageReference Include=\"Sentry\" />\n        <PackageReference Include=\"Sentry.NLog\" />\n        <PackageReference Include=\"SpacedGrid-Avalonia\" />\n        <PackageReference Include=\"Sylvan.Common\" />\n        <PackageReference Include=\"Sylvan.Data\" />\n        <PackageReference Include=\"Sylvan.Data.Csv\" />\n        <PackageReference Include=\"System.Drawing.Common\" />\n        <PackageReference Include=\"System.IO.Hashing\" />\n        <PackageReference Include=\"TextMateSharp.Grammars\" />\n        <PackageReference Include=\"URISchemeTools\" />\n        <PackageReference Include=\"SkiaSharp.NativeAssets.Linux\" Condition=\"'$(RuntimeIdentifier)' == 'linux-x64'\" />\n    </ItemGroup>\n\n\n    <ItemGroup>\n        <AvaloniaResource Include=\"Assets\\Icon.ico\" />\n        <AvaloniaResource Include=\"Assets\\Icon.png\" />\n        <AvaloniaResource Include=\"Assets\\hf-packages.json\" />\n        <AvaloniaResource Include=\"Assets\\santahat.png\" />\n        <AvaloniaResource Include=\"Assets\\markdown.css\" />\n        <AvaloniaResource Include=\"Assets\\openai-white-logomark.png\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <AvaloniaResource Include=\"Assets\\ImagePrompt.tmLanguage.json\" />\n        <AvaloniaResource Include=\"Assets\\ThemeMatrixDark.json\" />\n    </ItemGroup>\n    \n    <ItemGroup>\n        <ProjectReference Include=\"..\\Avalonia.Gif\\Avalonia.Gif.csproj\" />\n        <ProjectReference Include=\"..\\StabilityMatrix.Core\\StabilityMatrix.Core.csproj\" />\n        <ProjectReference Include=\"..\\analyzers\\StabilityMatrix.Analyzers\\StabilityMatrix.Analyzers.csproj\"\n                          PrivateAssets=\"all\"\n                          ReferenceOutputAssembly=\"false\"\n                          OutputItemType=\"Analyzer\" />\n        <ProjectReference Include=\"..\\analyzers\\StabilityMatrix.Analyzers.CodeFixes\\StabilityMatrix.Analyzers.CodeFixes.csproj\"\n                          PrivateAssets=\"all\"\n                          ReferenceOutputAssembly=\"false\"\n                          OutputItemType=\"Analyzer\" />\n    </ItemGroup>\n\n    <ItemGroup Condition=\"$(SM_IncludeLogWindow) == 'true'\">\n        <ProjectReference Include=\"..\\StabilityMatrix.Avalonia.Diagnostics\\StabilityMatrix.Avalonia.Diagnostics.csproj\" />\n    </ItemGroup>\n    \n    <ItemGroup>\n        <AvaloniaResource Include=\"Assets\\noimage.png\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <AvaloniaResource Include=\"Assets\\brands-*.png\" />\n        <AvaloniaResource Include=\"Assets\\brands-*.svg\" />\n        <AvaloniaResource Include=\"Assets\\guide-*.webp\"/>\n    </ItemGroup>\n\n    <ItemGroup>\n        <AvaloniaResource Include=\"Assets\\licenses.json\" />\n    </ItemGroup>\n    \n    <ItemGroup>\n        <AvaloniaResource Include=\"Assets\\sitecustomize.py\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <AvaloniaResource Include=\"Assets\\Fonts\\**\" />\n    </ItemGroup>\n    \n    <ItemGroup>\n      <!-- Only for win-64 -->\n      <AvaloniaResource Include=\"Assets\\win-x64\\**\" Condition=\"'$(RuntimeIdentifier)' == 'win-x64'\" />\n      <!-- Only for linux-64 -->\n      <AvaloniaResource Include=\"Assets\\linux-x64\\**\" Condition=\"'$(RuntimeIdentifier)' == 'linux-x64'\" />\n      <!-- Only for osx-arm64 -->\n      <AvaloniaResource Include=\"Assets\\macos-arm64\\**\" Condition=\"'$(RuntimeIdentifier)' == 'osx-arm64'\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <None Update=\"Assets\\AppIcon.icns\" Condition=\"'$(RuntimeIdentifier)' == 'osx-arm64'\">\n            <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n        </None>\n    </ItemGroup>\n    \n    <ItemGroup>\n      <EmbeddedResource Update=\"Languages\\Resources.resx\">\n        <Generator>PublicResXFileCodeGenerator</Generator>\n        <LastGenOutput>Resources.Designer.cs</LastGenOutput>\n      </EmbeddedResource>\n    </ItemGroup>\n    \n    <ItemGroup>\n      <Compile Update=\"Languages\\Resources.Designer.cs\">\n        <DesignTime>True</DesignTime>\n        <AutoGen>True</AutoGen>\n        <DependentUpon>Resources.resx</DependentUpon>\n      </Compile>\n      <Compile Update=\"Views\\Inference\\InferenceImageToVideoView.axaml.cs\">\n        <DependentUpon>InferenceImageToVideoView.axaml</DependentUpon>\n        <SubType>Code</SubType>\n      </Compile>\n      <Compile Update=\"Controls\\VideoGenerationSettingsCard.axaml.cs\">\n        <DependentUpon>VideoGenerationSettingsCard.axaml</DependentUpon>\n        <SubType>Code</SubType>\n      </Compile>\n      <Compile Update=\"Controls\\VideoOutputSettingsCard.axaml.cs\">\n        <DependentUpon>VideoOutputSettingsCard.axaml</DependentUpon>\n        <SubType>Code</SubType>\n      </Compile>\n      <Compile Update=\"Views\\PackageManagerPage.axaml.cs\">\n        <DependentUpon>NewPackageManagerPage.axaml</DependentUpon>\n        <SubType>Code</SubType>\n      </Compile>\n      <Compile Update=\"Views\\PackageManager\\PackageInstallDetailView.axaml.cs\">\n        <DependentUpon>PackageInstallDetailView.axaml</DependentUpon>\n        <SubType>Code</SubType>\n      </Compile>\n      <Compile Update=\"Views\\PackageManager\\PackageInstallBrowserView.axaml.cs\">\n        <DependentUpon>NewInstallerDialog.axaml</DependentUpon>\n        <SubType>Code</SubType>\n      </Compile>\n      <Compile Update=\"Models\\OpenArtMetadata.cs\">\n        <Generator>MSBuild:GenerateCodeFromAttributes</Generator>\n      </Compile>\n      <Compile Update=\"Views\\PackageManager\\MainPackageManagerView.axaml.cs\">\n        <DependentUpon>MainPackageManagerView.axaml</DependentUpon>\n        <SubType>Code</SubType>\n      </Compile>\n      <Compile Update=\"Views\\Inference\\InferenceWanTextToVideoView.axaml.cs\">\n        <DependentUpon>InferenceWanTextToVideoView.axaml</DependentUpon>\n        <SubType>Code</SubType>\n      </Compile>\n      <Compile Update=\"Controls\\Inference\\NrsCard.axaml.cs\">\n        <DependentUpon>NrsCard.axaml</DependentUpon>\n        <SubType>Code</SubType>\n      </Compile>\n    </ItemGroup>\n\n    <!-- set HUSKY to 0 to disable, or opt-in during CI by setting HUSKY to 1 -->\n    <Target Name=\"husky\" BeforeTargets=\"Restore;CollectPackageReferences\" Condition=\"'$(HUSKY)' == 1 Or ('$(HUSKY)' != 0 And '$(CI)' != 'true')\">\n        <Exec Command=\"dotnet tool restore\"  StandardOutputImportance=\"Low\" StandardErrorImportance=\"High\"/>\n        <Exec Command=\"dotnet husky install\" StandardOutputImportance=\"Low\" StandardErrorImportance=\"High\" WorkingDirectory=\"../\" />\n    </Target>\n</Project>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj.DotSettings",
    "content": "﻿<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namespace:System;assembly=mscorlib\" xmlns:wpf=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">\n\t<s:String x:Key=\"/Default/CodeEditing/Localization/Localizable/@EntryValue\">Yes</s:String>\n\t<s:String x:Key=\"/Default/CodeEditing/Localization/LocalizableInspector/@EntryValue\">Pessimistic</s:String>\n\t<s:String x:Key=\"/Default/CodeInspection/Daemon/ConfigureAwaitAnalysisMode/@EntryValue\">UI</s:String>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cinference/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cpropertygrid/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cvendorlabs_005Casyncimage/@EntryIndexedValue\">True</s:Boolean>\n    <s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cpainting/@EntryIndexedValue\">True</s:Boolean>\n</wpf:ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/BorderStyles.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n    <Design.PreviewWith>\n        <StackPanel Margin=\"16\" Spacing=\"4\" Height=\"400\" Width=\"300\">\n            <Border Padding=\"16\">\n                <TextBlock Text=\"Default Test\"/>\n            </Border>\n            <Border Padding=\"16\" Classes=\"theme-dark\">\n                <TextBlock Text=\"theme-dark Test\"/>\n            </Border>\n        </StackPanel>\n    </Design.PreviewWith>\n\n    <!-- Add Styles Here -->\n    <Style Selector=\"Border.theme-dark\">\n        <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkAccentBackgroundBrush}\"/>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\">\n    <Design.PreviewWith>\n        <Border Padding=\"20\">\n            <StackPanel>\n                <Button\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"success\"\n                    Content=\"Success Button\" />\n                <Button\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"accent\"\n                    Content=\"FA Accent Button\" />\n                <Button\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"systemaccent\"\n                    Content=\"System Accent Button\" />\n                <Button\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"danger\"\n                    Content=\"Danger Button\" />\n                <Button\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"info\"\n                    Content=\"Info Button\" />\n                <Button\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"transparent-info\"\n                    Content=\"Semi-Transparent Info Button\" />\n                <Button\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"transparent\"\n                    Content=\"Transparent Button\" />\n                <Button\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"transparent-full\"\n                    Content=\"Transparent Button\" />\n                <Button\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Content=\"Disabled Button\"\n                    IsEnabled=\"False\" />\n            </StackPanel>\n        </Border>\n    </Design.PreviewWith>\n\n    <!--  Success  -->\n    <Style Selector=\"Button.success\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeGreenColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeGreenColor}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkGreenColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkGreenColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Borderless Success  -->\n    <Style Selector=\"Button.borderless-success\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeGreenColor}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkGreenColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Danger  -->\n    <Style Selector=\"Button.danger\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeRedColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeRedColor}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Danger  -->\n    <Style Selector=\"Button.borderless-danger\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeRedColor}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Info  -->\n    <Style Selector=\"Button.info\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeLightBlueColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeLightBlueColor}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeBlueColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <Style Selector=\"Button.borderless-info\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeLightBlueColor}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Accent Button  -->\n    <Style Selector=\"Button.accent\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackground}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrush}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackgroundPointerOver}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrushPointerOver}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrushPressed}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrushDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  SystemAccent  -->\n    <Style Selector=\"Button.systemaccent\">\n        <Style Selector=\"^ /template/ Border#Root\" />\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColor}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark1}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColorDark1}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Transparent  -->\n    <Style Selector=\"Button.transparent\">\n        <Style Selector=\"^ /template/ Border#Root\" />\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrush}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Semi-Transparent Info  -->\n    <Style Selector=\"Button.transparent-info\">\n        <Style Selector=\"^ /template/ Border#Root\" />\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeLightBlueColorTransparent}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeLightBlueColorTransparent}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueColorTransparent}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeBlueColorTransparent}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Transparent red  -->\n    <Style Selector=\"Button.transparent-red\">\n        <Style Selector=\"^ /template/ Border#Root\" />\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrush}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeCoralRedColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeCoralRedColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkCoralRedColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkCoralRedColor}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Full Transparent  -->\n    <Style Selector=\"Button.transparent-full\">\n        <Style Selector=\"^ /template/ Border#Root\" />\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/Card.axaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\">\n    <Design.PreviewWith>\n        <Border Padding=\"20\">\n            <StackPanel>\n                <controls:Card\n                    Name=\"ModelCard\"\n                    Width=\"330\"\n                    MaxHeight=\"450\"\n                    Margin=\"8\">\n                    <TextBlock Text=\"Normal Card\" />\n                </controls:Card>\n                <controls:Card\n                    Width=\"330\"\n                    MaxHeight=\"450\"\n                    Margin=\"8\"\n                    Classes=\"success\">\n                    <TextBlock Text=\"Success Card\" />\n                </controls:Card>\n                <controls:Card\n                    Width=\"330\"\n                    MaxHeight=\"450\"\n                    Margin=\"8\"\n                    Classes=\"info\">\n                    <TextBlock Text=\"Info Card\" />\n                </controls:Card>\n                <controls:Card\n                    Width=\"330\"\n                    MaxHeight=\"450\"\n                    Margin=\"8\"\n                    Classes=\"disabled\">\n                    <TextBlock Text=\"Disabled Card (by classes)\" />\n                </controls:Card>\n                <controls:Card\n                    Width=\"330\"\n                    MaxHeight=\"450\"\n                    Margin=\"8\"\n                    IsCardVisualsEnabled=\"False\">\n                    <TextBlock Text=\"Disabled Card (by property)\" />\n                </controls:Card>\n            </StackPanel>\n        </Border>\n    </Design.PreviewWith>\n\n    <ControlTheme x:Key=\"{x:Type controls:Card}\" TargetType=\"controls:Card\">\n        <Setter Property=\"Background\" Value=\"{DynamicResource ComboBoxBackground}\" />\n        <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ComboBoxBorderBrush}\" />\n        <Setter Property=\"Foreground\" Value=\"{DynamicResource ComboBoxForeground}\" />\n        <Setter Property=\"BorderThickness\" Value=\"1\" />\n        <Setter Property=\"Padding\" Value=\"14,16,14,16\" />\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"VerticalAlignment\" Value=\"Center\" />\n        <Setter Property=\"HorizontalContentAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"VerticalContentAlignment\" Value=\"Center\" />\n        <Setter Property=\"FontSize\" Value=\"{DynamicResource ControlContentThemeFontSize}\" />\n        <Setter Property=\"FontWeight\" Value=\"Light\" />\n        <Setter Property=\"Border.CornerRadius\" Value=\"{DynamicResource ControlCornerRadius}\" />\n        <Setter Property=\"Focusable\" Value=\"False\" />\n        <Setter Property=\"KeyboardNavigation.IsTabStop\" Value=\"False\" />\n        <Setter Property=\"Template\">\n            <Setter.Value>\n                <ControlTemplate>\n                    <Grid>\n                        <Border\n                            x:Name=\"ContentBorder\"\n                            Padding=\"{TemplateBinding Padding}\"\n                            HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\"\n                            VerticalAlignment=\"{TemplateBinding VerticalAlignment}\"\n                            Background=\"{TemplateBinding Background}\"\n                            BorderBrush=\"{TemplateBinding BorderBrush}\"\n                            BorderThickness=\"{TemplateBinding BorderThickness}\"\n                            CornerRadius=\"{TemplateBinding Border.CornerRadius}\">\n                            <ContentPresenter\n                                x:Name=\"ContentPresenter\"\n                                HorizontalContentAlignment=\"{TemplateBinding HorizontalContentAlignment}\"\n                                VerticalContentAlignment=\"{TemplateBinding VerticalContentAlignment}\"\n                                Content=\"{TemplateBinding Content}\" />\n                        </Border>\n                    </Grid>\n                </ControlTemplate>\n            </Setter.Value>\n        </Setter>\n\n        <Styles>\n\n            <!--  Success  -->\n            <Style Selector=\"controls|Card.success\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeGreenColorTransparent}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeGreenColorTransparent}\" />\n                <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                    <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                </Style>\n            </Style>\n\n            <!--  Early Access  -->\n            <Style Selector=\"controls|Card.early-access\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDeepOrangeColorTransparent}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDeepOrangeColorTransparent}\" />\n                <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                    <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                </Style>\n            </Style>\n\n            <!--  Info  -->\n            <Style Selector=\"controls|Card.info\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n                <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                    <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                </Style>\n            </Style>\n\n            <!--  Transparent  -->\n            <Style Selector=\"controls|Card.transparent\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeControlTransparentBrush}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeControlTransparentBrush}\" />\n                <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                    <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                </Style>\n            </Style>\n\n            <!--  Disabled  -->\n            <Style Selector=\"controls|Card.disabled\">\n                <Setter Property=\"Background\" Value=\"Transparent\" />\n                <Setter Property=\"BorderBrush\" Value=\"Transparent\" />\n                <Setter Property=\"BorderThickness\" Value=\"0\" />\n                <Setter Property=\"Padding\" Value=\"0\" />\n                <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                    <Setter Property=\"Foreground\" Value=\"Transparent\" />\n                </Style>\n            </Style>\n\n        </Styles>\n    </ControlTheme>\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/CommandBarButtonStyles.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\">\n    <Design.PreviewWith>\n        <Border Padding=\"20\">\n            <ui:CommandBar\n                Margin=\"8\"\n                VerticalAlignment=\"Center\"\n                VerticalContentAlignment=\"Center\"\n                HorizontalAlignment=\"Left\"\n                HorizontalContentAlignment=\"Left\"\n                DefaultLabelPosition=\"Right\">\n                <ui:CommandBar.PrimaryCommands>\n                    <ui:CommandBarButton Classes=\"success\" Label=\"Success Button\" Margin=\"8\"\n                                         HorizontalAlignment=\"Center\" />\n                    <ui:CommandBarButton Classes=\"accent\" Label=\"FA Accent Button\" Margin=\"8\"\n                                         HorizontalAlignment=\"Center\" />\n                    <ui:CommandBarButton Classes=\"systemaccent\" Label=\"System Accent Button\" Margin=\"8\"\n                                         HorizontalAlignment=\"Center\" />\n                    <ui:CommandBarButton Classes=\"danger\" Label=\"Danger Button\" Margin=\"8\" HorizontalAlignment=\"Center\" />\n                    <ui:CommandBarButton Classes=\"info\" Label=\"Info Button\" Margin=\"8\" HorizontalAlignment=\"Center\" />\n                    <ui:CommandBarButton Classes=\"transparent-info\" Label=\"Semi-Transparent Info Button\" Margin=\"8\"\n                                         HorizontalAlignment=\"Center\" />\n                    <ui:CommandBarButton Classes=\"transparent\" Label=\"Transparent Button\" Margin=\"8\"\n                                         HorizontalAlignment=\"Center\" />\n                    <ui:CommandBarButton Classes=\"transparent-full\" Label=\"Transparent Button\" Margin=\"8\"\n                                         HorizontalAlignment=\"Center\" />\n                    <ui:CommandBarButton Label=\"Disabled Button\" Margin=\"8\" IsEnabled=\"False\"\n                                         HorizontalAlignment=\"Center\" />\n                </ui:CommandBar.PrimaryCommands>\n            </ui:CommandBar>\n        </Border>\n    </Design.PreviewWith>\n\n    <!-- Success -->\n     <Style Selector=\"ui|CommandBarButton.success\">\n        <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeGreenColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeGreenColor}\" />\n        </Style>\n        <Style Selector=\"^ /template/ TextBlock#TextLabel\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkGreenColor}\"/>\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkGreenColor}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"Green\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n    \n    <!-- Danger -->\n    <Style Selector=\"ui|CommandBarButton.danger\">\n        <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeRedColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeRedColor}\" />\n        </Style>\n        <Style Selector=\"^\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkRedColor}\"/>\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n    \n    <!-- Info -->\n    <Style Selector=\"ui|CommandBarButton.info\">\n        <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeLightBlueColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeLightBlueColor}\" />\n        </Style>\n        <Style Selector=\"^\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueColor}\"/>\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeBlueColor}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n    \n    <!--Accent Button-->\n    <Style Selector=\"ui|CommandBarButton.accent\">\n        <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackground}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrush}\" />\n        </Style>\n        <Style Selector=\"^\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackgroundPointerOver}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrushPointerOver}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrushPressed}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n    \n    <!-- SystemAccent -->\n    <Style Selector=\"ui|CommandBarButton.systemaccent\">\n        <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColor}\" />\n        </Style>\n        <Style Selector=\"^\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark1}\"/>\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColorDark1}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n    \n    <!-- Transparent -->\n    <Style Selector=\"ui|CommandBarButton.transparent\">\n        <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrush}\" />\n        </Style>\n        <Style Selector=\"^\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\"/>\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n    \n    <!-- Semi-Transparent Info -->\n    <Style Selector=\"ui|CommandBarButton.transparent-info\">\n        <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeLightBlueColorTransparent}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeLightBlueColorTransparent}\" />\n        </Style>\n        <Style Selector=\"^\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueColorTransparent}\"/>\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeBlueColorTransparent}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n    \n    <!-- Transparent red -->\n    <Style Selector=\"ui|CommandBarButton.transparent-red\">\n        <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrush}\" />\n        </Style>\n        <Style Selector=\"^\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeCoralRedColor}\"/>\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeCoralRedColor}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkCoralRedColor}\"/>\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkCoralRedColor}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n    \n    <!-- Full Transparent -->\n    <Style Selector=\"ui|CommandBarButton.transparent-full\">\n        <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n        </Style>\n        <Style Selector=\"^\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\"/>\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Border#AppBarButtonInnerBorder\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n            </Style>\n            <Style Selector=\"^\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ContextMenuStyles.axaml",
    "content": "﻿<ResourceDictionary \n    xmlns=\"https://github.com/avaloniaui\" \n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n    \n    <Design.PreviewWith>\n        <Border\n            Width=\"400\"\n            Height=\"200\"\n            Margin=\"16\"\n            Padding=\"48\"\n            Background=\"{DynamicResource SystemAccentColor}\">\n            <Border.ContextMenu>\n                <ContextMenu>\n                    <MenuItem Header=\"Standard _Menu Item\" />\n                    <MenuItem Header=\"Disabled\" IsEnabled=\"False\" />\n                    <Separator />\n                    <MenuItem Header=\"Menu with _Submenu\">\n                        <MenuItem Header=\"Submenu _1\" />\n                        <MenuItem Header=\"Submenu _2\" />\n                    </MenuItem>\n                    <MenuItem Header=\"Menu Item with _Icon\" />\n                    <MenuItem Header=\"Menu Item with _Checkbox\">\n                        <MenuItem.Icon>\n                            <CheckBox\n                                BorderThickness=\"0\"\n                                IsChecked=\"True\"\n                                IsHitTestVisible=\"False\" />\n                        </MenuItem.Icon>\n                    </MenuItem>\n                </ContextMenu>\n            </Border.ContextMenu>\n            <TextBlock Text=\"Defined in XAML\" />\n        </Border>\n    </Design.PreviewWith>\n\n    <!--  Added missing resource  -->\n    <Thickness x:Key=\"MenuFlyoutScrollerMargin\">0,4,0,4</Thickness>\n\n    <ControlTheme x:Key=\"{x:Type ContextMenu}\" TargetType=\"ContextMenu\">\n        <Setter Property=\"Background\" Value=\"{DynamicResource MenuFlyoutPresenterBackground}\" />\n        <Setter Property=\"BorderBrush\" Value=\"{DynamicResource MenuFlyoutPresenterBorderBrush}\" />\n        <Setter Property=\"BorderThickness\" Value=\"{DynamicResource MenuFlyoutPresenterBorderThemeThickness}\" />\n        <Setter Property=\"CornerRadius\" Value=\"{DynamicResource OverlayCornerRadius}\" />\n        <Setter Property=\"MaxWidth\" Value=\"{DynamicResource FlyoutThemeMaxWidth}\" />\n        <Setter Property=\"MinHeight\" Value=\"{DynamicResource MenuFlyoutThemeMinHeight}\" />\n        <Setter Property=\"Padding\" Value=\"{DynamicResource MenuFlyoutPresenterThemePadding}\" />\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"TextBlock.FontSize\" Value=\"{DynamicResource ControlContentThemeFontSize}\" />\n        <Setter Property=\"TextBlock.FontWeight\" Value=\"Normal\" />\n        <Setter Property=\"WindowManagerAddShadowHint\" Value=\"False\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Border\n                    MinHeight=\"{TemplateBinding MinHeight}\"\n                    MaxWidth=\"{TemplateBinding MaxWidth}\"\n                    Padding=\"{TemplateBinding Padding}\"\n                    HorizontalAlignment=\"{TemplateBinding HorizontalAlignment}\"\n                    Background=\"{TemplateBinding Background}\"\n                    BorderBrush=\"{TemplateBinding BorderBrush}\"\n                    BorderThickness=\"{TemplateBinding BorderThickness}\"\n                    CornerRadius=\"{TemplateBinding CornerRadius}\">\n                    <ScrollViewer>\n                        <ItemsPresenter\n                            Name=\"PART_ItemsPresenter\"\n                            Margin=\"{DynamicResource MenuFlyoutScrollerMargin}\"\n                            Grid.IsSharedSizeScope=\"True\"\n                            ItemsPanel=\"{TemplateBinding ItemsPanel}\"\n                            KeyboardNavigation.TabNavigation=\"Continue\" />\n                    </ScrollViewer>\n                </Border>\n            </ControlTemplate>\n        </Setter>\n    </ControlTheme>\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:labs=\"clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls\"\n    xmlns:mocks=\"using:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\">\n\n    <Design.PreviewWith>\n        <Panel Width=\"550\" Height=\"600\">\n            <StackPanel\n                Width=\"250\"\n                Margin=\"64,24\"\n                HorizontalAlignment=\"Left\"\n                Spacing=\"4\">\n                <controls:BetterComboBox\n                    HorizontalAlignment=\"Stretch\"\n                    ItemsSource=\"{x:Static mocks:DesignData.SampleHybridModels}\"\n                    SelectedIndex=\"0\" />\n\n                <controls:BetterComboBox\n                    HorizontalAlignment=\"Stretch\"\n                    ItemsSource=\"{x:Static mocks:DesignData.SampleHybridModels}\"\n                    SelectedIndex=\"0\"\n                    Theme=\"{DynamicResource BetterComboBoxHybridModelTheme}\" />\n            </StackPanel>\n        </Panel>\n    </Design.PreviewWith>\n\n    <converters:FileUriConverter x:Key=\"FileUriConverter\" />\n\n    <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n    <ControlTheme\n        x:Key=\"BetterComboBoxItemHybridModelTheme\"\n        BasedOn=\"{StaticResource {x:Type ComboBoxItem}}\"\n        TargetType=\"ComboBoxItem\">\n        <Setter Property=\"ToolTip.Placement\" Value=\"RightEdgeAlignedTop\" />\n        <Setter Property=\"ToolTip.Tip\">\n            <Template>\n                <sg:SpacedGrid\n                    x:DataType=\"models:HybridModelFile\"\n                    ColumnDefinitions=\"Auto,*\"\n                    ColumnSpacing=\"6\"\n                    RowSpacing=\"0\">\n                    <!--  Image  -->\n                    <vendorLabs:BetterAsyncImage\n                        Width=\"64\"\n                        Height=\"96\"\n                        CornerRadius=\"6\"\n                        IsVisible=\"{Binding Local.PreviewImageFullPathGlobal, Converter={x:Static StringConverters.IsNotNullOrEmpty}, FallbackValue=''}\"\n                        RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n                        RenderOptions.EdgeMode=\"Antialias\"\n                        Source=\"{Binding Local.PreviewImageFullPathGlobal, FallbackValue='', Converter={StaticResource FileUriConverter}}\"\n                        Stretch=\"UniformToFill\" />\n                    <StackPanel\n                        Grid.Column=\"1\"\n                        MaxWidth=\"300\"\n                        VerticalAlignment=\"Stretch\">\n                        <!--  Title  -->\n                        <TextBlock\n                            Margin=\"0,0,0,4\"\n                            HorizontalAlignment=\"Left\"\n                            FontSize=\"14\"\n                            FontWeight=\"Medium\"\n                            Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n                            IsVisible=\"{Binding Local.ConnectedModelInfo, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}\"\n                            Text=\"{Binding Local.ConnectedModelInfo.ModelName, FallbackValue=''}\"\n                            TextWrapping=\"WrapWithOverflow\" />\n                        <!--  Version  -->\n                        <TextBlock\n                            Margin=\"0,0,0,8\"\n                            HorizontalAlignment=\"Left\"\n                            FontSize=\"13\"\n                            Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                            IsVisible=\"{Binding Local.ConnectedModelInfo, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}\"\n                            Text=\"{Binding Local.ConnectedModelInfo.VersionName, FallbackValue=''}\"\n                            TextWrapping=\"WrapWithOverflow\" />\n                        <!--  Path  -->\n                        <TextBlock\n                            HorizontalAlignment=\"Left\"\n                            FontSize=\"13\"\n                            Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                            Text=\"{Binding FileName}\"\n                            TextWrapping=\"Wrap\" />\n                    </StackPanel>\n                </sg:SpacedGrid>\n            </Template>\n        </Setter>\n    </ControlTheme>\n\n    <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n    <ControlTheme\n        x:Key=\"{x:Type controls:BetterComboBox}\"\n        BasedOn=\"{StaticResource {x:Type ComboBox}}\"\n        TargetType=\"controls:BetterComboBox\" />\n\n    <ControlTheme\n        x:Key=\"BetterComboBoxHybridModelTheme\"\n        BasedOn=\"{StaticResource {x:Type controls:BetterComboBox}}\"\n        TargetType=\"controls:BetterComboBox\">\n\n        <ControlTheme.Resources>\n            <controls:HybridModelTemplateSelector x:Key=\"HybridModelTemplateSelector\">\n                <DataTemplate x:Key=\"{x:Static models:HybridModelType.Downloadable}\" DataType=\"models:HybridModelFile\">\n                    <Grid ColumnDefinitions=\"*,Auto\">\n                        <TextBlock Foreground=\"{DynamicResource ThemeGreyColor}\" Text=\"{Binding ShortDisplayName}\" />\n                        <Button\n                            Grid.Column=\"1\"\n                            Margin=\"8,0,0,0\"\n                            Padding=\"0\"\n                            Classes=\"transparent-full\">\n                            <fluentIcons:SymbolIcon\n                                VerticalAlignment=\"Center\"\n                                FontSize=\"18\"\n                                Foreground=\"{DynamicResource ThemeGreyColor}\"\n                                IconVariant=\"Filled\"\n                                Symbol=\"CloudArrowDown\" />\n                        </Button>\n                    </Grid>\n                </DataTemplate>\n\n                <DataTemplate x:Key=\"{x:Static models:HybridModelType.Local}\" DataType=\"models:HybridModelFile\">\n                    <sg:SpacedGrid\n                        HorizontalAlignment=\"Stretch\"\n                        ColumnDefinitions=\"Auto,*\"\n                        ColumnSpacing=\"8\"\n                        TextBlock.TextTrimming=\"CharacterEllipsis\"\n                        TextBlock.TextWrapping=\"NoWrap\">\n                        <vendorLabs:BetterAsyncImage\n                            Grid.RowSpan=\"2\"\n                            Width=\"42\"\n                            Height=\"42\"\n                            CornerRadius=\"20\"\n                            RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n                            RenderOptions.EdgeMode=\"Antialias\"\n                            Source=\"{Binding Local.PreviewImageFullPathGlobal, Converter={StaticResource FileUriConverter}}\"\n                            Stretch=\"UniformToFill\" />\n\n                        <!--  Text  -->\n                        <sg:SpacedGrid\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            ColumnDefinitions=\"Auto,*\"\n                            RowDefinitions=\"Auto,Auto,Auto\"\n                            RowSpacing=\"1\">\n                            <TextBlock\n                                Grid.ColumnSpan=\"2\"\n                                Text=\"{Binding Local.DisplayModelName}\"\n                                TextTrimming=\"CharacterEllipsis\" />\n                            <TextBlock\n                                Grid.Row=\"1\"\n                                Grid.Column=\"0\"\n                                FontSize=\"12\"\n                                FontWeight=\"Regular\"\n                                Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                Text=\"{Binding Local.DisplayModelVersion}\"\n                                TextTrimming=\"CharacterEllipsis\" />\n                            <TextBlock\n                                Grid.Row=\"2\"\n                                Grid.Column=\"0\"\n                                FontSize=\"11\"\n                                FontWeight=\"Normal\"\n                                Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                Text=\"{Binding Local.DisplayModelFileName}\" />\n\n                            <!--  Badges  -->\n                            <StackPanel\n                                Grid.Row=\"1\"\n                                Grid.RowSpan=\"2\"\n                                Grid.Column=\"1\"\n                                HorizontalAlignment=\"Right\"\n                                VerticalAlignment=\"Bottom\"\n                                Orientation=\"Horizontal\">\n                                <fluentIcons:SymbolIcon\n                                    Margin=\"4\"\n                                    Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                    IsVisible=\"{Binding Local.ConfigFullPath, Converter={x:Static StringConverters.IsNotNullOrEmpty}, FallbackValue=False}\"\n                                    Symbol=\"BeakerSettings\"\n                                    ToolTip.Tip=\"{Binding Local.DisplayConfigFileName}\" />\n                                <Label\n                                    MinHeight=\"0\"\n                                    Margin=\"0,0,0,0\"\n                                    Padding=\"6,0,6,0\"\n                                    Classes=\"Circle LightBlue\"\n                                    Content=\"{Binding Local.ConnectedModelInfo.BaseModel}\"\n                                    FontSize=\"10\"\n                                    FontWeight=\"SemiBold\"\n                                    Theme=\"{StaticResource TagLabel}\" />\n                            </StackPanel>\n                        </sg:SpacedGrid>\n                    </sg:SpacedGrid>\n                </DataTemplate>\n\n                <DataTemplate x:Key=\"{x:Static models:HybridModelType.None}\" DataType=\"models:HybridModelFile\">\n                    <StackPanel>\n                        <TextBlock Text=\"{Binding ShortDisplayName}\" TextTrimming=\"CharacterEllipsis\" />\n                    </StackPanel>\n                </DataTemplate>\n            </controls:HybridModelTemplateSelector>\n\n            <controls:HybridModelTemplateSelector x:Key=\"HybridModelSelectionBoxTemplateSelector\">\n                <DataTemplate x:Key=\"{x:Static models:HybridModelType.Downloadable}\" DataType=\"models:HybridModelFile\">\n                    <Grid ColumnDefinitions=\"*,Auto\">\n                        <TextBlock Foreground=\"{DynamicResource ThemeGreyColor}\" Text=\"{Binding ShortDisplayName}\" />\n                        <Button\n                            Grid.Column=\"1\"\n                            Margin=\"8,0,0,0\"\n                            Padding=\"0\"\n                            Classes=\"transparent-full\">\n                            <fluentIcons:SymbolIcon\n                                VerticalAlignment=\"Center\"\n                                FontSize=\"18\"\n                                Foreground=\"{DynamicResource ThemeGreyColor}\"\n                                IconVariant=\"Filled\"\n                                Symbol=\"CloudArrowDown\" />\n                        </Button>\n                    </Grid>\n                </DataTemplate>\n\n                <DataTemplate x:Key=\"{x:Static models:HybridModelType.Local}\" DataType=\"models:HybridModelFile\">\n                    <sg:SpacedGrid\n                        HorizontalAlignment=\"Stretch\"\n                        ColumnDefinitions=\"Auto,*\"\n                        ColumnSpacing=\"8\"\n                        TextBlock.TextTrimming=\"CharacterEllipsis\"\n                        TextBlock.TextWrapping=\"NoWrap\">\n                        <vendorLabs:BetterAsyncImage\n                            Grid.RowSpan=\"2\"\n                            Width=\"36\"\n                            Height=\"36\"\n                            CornerRadius=\"36\"\n                            RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n                            Source=\"{Binding Local.PreviewImageFullPathGlobal, Converter={StaticResource FileUriConverter}}\"\n                            Stretch=\"UniformToFill\" />\n\n                        <!--<Label\n                            Grid.RowSpan=\"2\"\n                            Theme=\"{StaticResource TagLabel}\"\n                            Classes=\"Circle Solid White\"\n                            VerticalAlignment=\"Bottom\"\n                            FontSize=\"10\"\n                            FontWeight=\"SemiBold\"\n                            Padding=\"4,1\"\n                            Content=\"{Binding Local.ConnectedModelInfo.BaseModel}\"/>-->\n\n                        <!--  Text  -->\n                        <sg:SpacedGrid\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            ColumnDefinitions=\"*,Auto\"\n                            RowDefinitions=\"Auto,Auto\"\n                            RowSpacing=\"1\">\n\n                            <TextBlock\n                                Grid.Row=\"0\"\n                                Grid.Column=\"0\"\n                                Grid.ColumnSpan=\"2\"\n                                Text=\"{Binding Local.DisplayModelName}\"\n                                TextTrimming=\"CharacterEllipsis\" />\n                            <TextBlock\n                                Grid.Row=\"1\"\n                                Grid.Column=\"0\"\n                                Grid.ColumnSpan=\"1\"\n                                Margin=\"0,2,0,0\"\n                                FontSize=\"12\"\n                                FontWeight=\"Regular\"\n                                Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                Text=\"{Binding Local.DisplayModelVersion}\"\n                                TextTrimming=\"CharacterEllipsis\" />\n\n                            <!--  Badges  -->\n                            <StackPanel\n                                Grid.Row=\"1\"\n                                Grid.Column=\"1\"\n                                HorizontalAlignment=\"Right\"\n                                VerticalAlignment=\"Bottom\"\n                                Orientation=\"Horizontal\">\n                                <fluentIcons:SymbolIcon\n                                    Margin=\"4\"\n                                    FontSize=\"12\"\n                                    Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                    IsVisible=\"{Binding Local.ConfigFullPath, Converter={x:Static StringConverters.IsNotNullOrEmpty}, FallbackValue=False}\"\n                                    Symbol=\"BeakerSettings\"\n                                    ToolTip.Tip=\"{Binding Local.DisplayConfigFileName}\" />\n                                <Label\n                                    MinHeight=\"0\"\n                                    Margin=\"0,0,0,0\"\n                                    Padding=\"6,0,6,0\"\n                                    Classes=\"Circle LightBlue\"\n                                    Content=\"{Binding Local.ConnectedModelInfo.BaseModel}\"\n                                    FontSize=\"10\"\n                                    FontWeight=\"SemiBold\"\n                                    Theme=\"{StaticResource TagLabel}\" />\n                            </StackPanel>\n                        </sg:SpacedGrid>\n                    </sg:SpacedGrid>\n                </DataTemplate>\n\n                <DataTemplate x:Key=\"{x:Static models:HybridModelType.None}\" DataType=\"models:HybridModelFile\">\n                    <StackPanel>\n                        <TextBlock Text=\"{Binding ShortDisplayName}\" TextTrimming=\"CharacterEllipsis\" />\n                    </StackPanel>\n                </DataTemplate>\n            </controls:HybridModelTemplateSelector>\n        </ControlTheme.Resources>\n\n        <Setter Property=\"TextBlock.TextWrapping\" Value=\"NoWrap\" />\n        <Setter Property=\"SelectionBoxItemTemplate\" Value=\"{StaticResource HybridModelSelectionBoxTemplateSelector}\" />\n        <Setter Property=\"ItemTemplate\" Value=\"{StaticResource HybridModelTemplateSelector}\" />\n        <Setter Property=\"ItemContainerTheme\" Value=\"{StaticResource BetterComboBoxItemHybridModelTheme}\" />\n\n        <Style Selector=\"^ /template/ Popup#PART_Popup\">\n            <Setter Property=\"Width\" Value=\"400\" />\n            <Setter Property=\"Placement\" Value=\"Bottom\" />\n            <Setter Property=\"ScrollViewer.HorizontalScrollBarVisibility\" Value=\"Disabled\" />\n            <Setter Property=\"Effect\">\n                <DropShadowEffect\n                    BlurRadius=\"32\"\n                    Opacity=\"0.6\"\n                    Color=\"#FF000000\" />\n            </Setter>\n        </Style>\n\n    </ControlTheme>\n\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ControlThemes/ButtonStyles.Accelerator.axaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:fa=\"https://github.com/projektanker/icons.avalonia\">\n\n    <Design.PreviewWith>\n        <Border\n            MinWidth=\"300\"\n            MinHeight=\"200\"\n            Padding=\"20\">\n            <StackPanel HorizontalAlignment=\"Center\" Spacing=\"8\">\n                <Button Classes=\"accelerator-indicator\" Theme=\"{DynamicResource AcceleratorButtonStyle}\">\n                    <fa:Icon FontSize=\"14\" Value=\"fa-solid fa-bolt-lightning\" />\n                </Button>\n                <Button Classes=\"accelerator-indicator active\" Theme=\"{DynamicResource AcceleratorButtonStyle}\">\n                    <fa:Icon FontSize=\"14\" Value=\"fa-solid fa-bolt-lightning\" />\n                </Button>\n                <Button Classes=\"accelerator-indicator active searching\" Theme=\"{DynamicResource AcceleratorButtonStyle}\">\n                    <fa:Icon FontSize=\"14\" Value=\"fa-solid fa-bolt-lightning\" />\n                </Button>\n                <Button Classes=\"accelerator-indicator active flash\" Theme=\"{DynamicResource AcceleratorButtonStyle}\">\n                    <fa:Icon FontSize=\"14\" Value=\"fa-solid fa-bolt-lightning\" />\n                </Button>\n            </StackPanel>\n        </Border>\n    </Design.PreviewWith>\n\n    <SolidColorBrush x:Key=\"AccentFillColorDefaultBrush\">#ff822d</SolidColorBrush>\n    <Color x:Key=\"AccentFillColorDefault\">#ff822d</Color>\n\n    <!--  ControlTheme for the standard Avalonia Button  -->\n    <ControlTheme x:Key=\"AcceleratorButtonStyle\" TargetType=\"Button\">\n        <!--  Define the full template structure  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate TargetType=\"Button\">\n                <!--  Main Border for background, border, etc.  -->\n                <Border\n                    Name=\"RootBorder\"\n                    Background=\"{TemplateBinding Background}\"\n                    BorderBrush=\"{TemplateBinding BorderBrush}\"\n                    BorderThickness=\"{TemplateBinding BorderThickness}\"\n                    ClipToBounds=\"True\"\n                    CornerRadius=\"{TemplateBinding CornerRadius}\">\n\n                    <Panel>\n                        <!--  Glow Element: Added Border inside RootBorder  -->\n                        <Border\n                            Name=\"GlowElement\"\n                            HorizontalAlignment=\"Stretch\"\n                            VerticalAlignment=\"Stretch\"\n                            Background=\"{DynamicResource AccentFillColorDefaultBrush}\"\n                            CornerRadius=\"{TemplateBinding CornerRadius}\"\n                            IsHitTestVisible=\"False\"\n                            Opacity=\"0\">\n                            <!--  Transitions for smooth opacity changes  -->\n                            <Border.Transitions>\n                                <Transitions>\n                                    <DoubleTransition\n                                        Easing=\"SineEaseOut\"\n                                        Property=\"Opacity\"\n                                        Duration=\"0.3\" />\n                                </Transitions>\n                            </Border.Transitions>\n                        </Border>\n\n                        <!--  Content Presenter: Sits on top of the GlowElement  -->\n                        <ContentPresenter\n                            Name=\"PART_ContentPresenter\"\n                            Padding=\"{TemplateBinding Padding}\"\n                            HorizontalAlignment=\"{TemplateBinding HorizontalContentAlignment}\"\n                            VerticalAlignment=\"{TemplateBinding VerticalContentAlignment}\"\n                            Content=\"{TemplateBinding Content}\"\n                            ContentTemplate=\"{TemplateBinding ContentTemplate}\"\n                            RecognizesAccessKey=\"True\"\n                            TextBlock.Foreground=\"{TemplateBinding Foreground}\" />\n                    </Panel>\n\n                </Border>\n            </ControlTemplate>\n        </Setter>\n\n        <!--  Base Styling  -->\n        <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n        <!--<Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackground}\" />-->\n        <!--<Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />-->\n        <!--<Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrush}\" />-->\n        <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n        <Setter Property=\"BorderThickness\" Value=\"{DynamicResource ButtonBorderThemeThickness}\" />\n        <Setter Property=\"CornerRadius\" Value=\"{DynamicResource ControlCornerRadius}\" />\n        <Setter Property=\"Padding\" Value=\"10,4\" />\n        <Setter Property=\"HorizontalAlignment\" Value=\"Left\" />\n        <Setter Property=\"VerticalAlignment\" Value=\"Center\" />\n        <Setter Property=\"HorizontalContentAlignment\" Value=\"Center\" />\n        <Setter Property=\"VerticalContentAlignment\" Value=\"Center\" />\n\n        <Style Selector=\"^.accelerator-indicator\">\n            <Style Selector=\"^:pointerover\">\n                <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                    <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                    <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                    <!--<Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />-->\n                </Style>\n            </Style>\n\n            <Style Selector=\"^:pressed\">\n                <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                    <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                    <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                    <!--<Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />-->\n                </Style>\n            </Style>\n        </Style>\n\n\n        <!--  Add Base Transitions  -->\n        <Setter Property=\"Transitions\">\n            <Transitions>\n                <BrushTransition Property=\"Background\" Duration=\"0.15\" />\n                <BrushTransition Property=\"Foreground\" Duration=\"0.15\" />\n                <BrushTransition Property=\"BorderBrush\" Duration=\"0.15\" />\n            </Transitions>\n        </Setter>\n\n        <!--  *** Styles for Accelerator States (using classes) ***  -->\n\n        <!--  State: Not Eligible (Disabled)  -->\n        <Style Selector=\"^.accelerator-indicator:disabled\">\n            <Setter Property=\"Opacity\" Value=\"{DynamicResource ThemeDisabledOpacity}\" />\n            <!--  Ensure Glow is off  -->\n            <Style Selector=\"^ /template/ Border#GlowElement\">\n                <Setter Property=\"Opacity\" Value=\"0\" />\n            </Style>\n        </Style>\n\n        <!--  State: Eligible, Proxy OFF (Default Enabled Look)  -->\n        <Style Selector=\"^.accelerator-indicator:not(.active):not(:disabled)\">\n            <!--  Use default button foreground  -->\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            <!--  Ensure Glow is off  -->\n            <Style Selector=\"^ /template/ Border#GlowElement\">\n                <Setter Property=\"Opacity\" Value=\"0\" />\n            </Style>\n        </Style>\n\n        <!--  State: Eligible, Proxy ON (Active Idle Look)  -->\n        <Style Selector=\"^.accelerator-indicator.active:not(.searching):not(:disabled)\">\n            <!--  Set Foreground to Accent color  -->\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentFillColorDefaultBrush}\" />\n            <!--  Ensure Glow is off when idle  -->\n            <Style Selector=\"^ /template/ Border#GlowElement\">\n                <Setter Property=\"Opacity\" Value=\"0\" />\n            </Style>\n        </Style>\n\n        <!--  Animation: Search Active Glow (Proxy ON)  -->\n        <Style Selector=\"^.accelerator-indicator.active.searching:not(:disabled)\">\n            <!--  Ensure Foreground is Accent  -->\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentFillColorDefaultBrush}\" />\n            <!--  Animate the GlowElement  -->\n            <Style Selector=\"^ /template/ Border#GlowElement\">\n                <Style.Animations>\n                    <Animation\n                        IterationCount=\"INFINITE\"\n                        PlaybackDirection=\"Alternate\"\n                        Duration=\"1.5\">\n                        <KeyFrame Cue=\"0%\">\n                            <Setter Property=\"Opacity\" Value=\"0.15\" />\n                        </KeyFrame>\n                        <KeyFrame Cue=\"100%\">\n                            <Setter Property=\"Opacity\" Value=\"0.35\" />\n                        </KeyFrame>\n                    </Animation>\n                </Style.Animations>\n            </Style>\n        </Style>\n\n        <!--  Animation: Search Start Flash (Proxy ON)  -->\n        <Style Selector=\"^.accelerator-indicator.active.flash:not(:disabled)\">\n            <!--  Animate the main button Foreground  -->\n            <Style.Animations>\n                <Animation FillMode=\"None\" Duration=\"0.3\">\n                    <KeyFrame Cue=\"0%\">\n                        <!--  Flash to default  -->\n                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                    </KeyFrame>\n                    <KeyFrame Cue=\"50%\">\n                        <!--  Back to accent  -->\n                        <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentFillColorDefaultBrush}\" />\n                    </KeyFrame>\n                    <KeyFrame Cue=\"100%\">\n                        <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentFillColorDefaultBrush}\" />\n                    </KeyFrame>\n                </Animation>\n            </Style.Animations>\n        </Style>\n\n        <!--  Standard Hover/Pressed states (optional, adjust as needed)  -->\n        <Style Selector=\"^:pointerover /template/ Border#RootBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPointerOver}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushPointerOver}\" />\n        </Style>\n        <Style Selector=\"^:pressed /template/ Border#RootBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushPressed}\" />\n            <Setter Property=\"RenderTransform\" Value=\"scale(0.98)\" />\n            <Setter Property=\"RenderTransformOrigin\" Value=\"50%,50%\" />\n        </Style>\n        <Style Selector=\"^:focus-visible /template/ Border#RootBorder\">\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushFocused}\" />\n            <Setter Property=\"BoxShadow\" Value=\"{DynamicResource FocusBoxShadow}\" />\n        </Style>\n\n    </ControlTheme>\n\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ControlThemes/HyperlinkIconButtonStyles.axaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\">\n\n    <Design.PreviewWith>\n        <StackPanel Margin=\"8\" Spacing=\"6\">\n            <controls:HyperlinkIconButton Content=\"Link\" />\n            <controls:HyperlinkIconButton Content=\"Disabled\" IsEnabled=\"False\" />\n            <controls:HyperlinkIconButton Content=\"File\" Icon=\"Open\" />\n        </StackPanel>\n    </Design.PreviewWith>\n\n    <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n    <ControlTheme x:Key=\"{x:Type controls:HyperlinkIconButton}\" TargetType=\"controls:HyperlinkIconButton\">\n\n        <Setter Property=\"Background\" Value=\"{DynamicResource HyperlinkButtonBackground}\" />\n        <Setter Property=\"Foreground\" Value=\"{DynamicResource HyperlinkButtonForeground}\" />\n        <Setter Property=\"BorderBrush\" Value=\"{DynamicResource HyperlinkButtonBorderBrush}\" />\n        <Setter Property=\"BorderThickness\" Value=\"{DynamicResource HyperlinkButtonBorderThemeThickness}\" />\n        <Setter Property=\"Padding\" Value=\"{DynamicResource ButtonPadding}\" />\n        <Setter Property=\"HorizontalAlignment\" Value=\"Left\" />\n        <Setter Property=\"VerticalAlignment\" Value=\"Center\" />\n        <Setter Property=\"FontFamily\" Value=\"{DynamicResource ContentControlThemeFontFamily}\" />\n        <Setter Property=\"FontSize\" Value=\"{DynamicResource ControlContentThemeFontSize}\" />\n        <Setter Property=\"Cursor\" Value=\"Hand\" />\n        <Setter Property=\"CornerRadius\" Value=\"{DynamicResource ControlCornerRadius}\" />\n\n        <Setter Property=\"Icon\" Value=\"Link\" />\n\n        <Setter Property=\"ContentTemplate\">\n            <DataTemplate DataType=\"system:Object\">\n                <StackPanel Orientation=\"Horizontal\">\n                    <fluentIcons:SymbolIcon\n                        Margin=\"0,1,4,0\"\n                        VerticalAlignment=\"Center\"\n                        FontSize=\"15\"\n                        Foreground=\"{DynamicResource HyperlinkButtonForeground}\"\n                        Symbol=\"{Binding $parent[controls:HyperlinkIconButton].Icon}\" />\n                    <ContentPresenter Content=\"\" Foreground=\"{DynamicResource HyperlinkButtonForeground}\" />\n                    <TextBlock Foreground=\"{DynamicResource HyperlinkButtonForeground}\" Text=\"{Binding}\" />\n                </StackPanel>\n            </DataTemplate>\n        </Setter>\n\n        <!--  Override template to not set Underline TextDecoration  -->\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <ContentPresenter\n                    Name=\"ContentPresenter\"\n                    Padding=\"{TemplateBinding Padding}\"\n                    HorizontalContentAlignment=\"{TemplateBinding HorizontalContentAlignment}\"\n                    VerticalContentAlignment=\"{TemplateBinding VerticalContentAlignment}\"\n                    Background=\"{TemplateBinding Background}\"\n                    BorderBrush=\"{TemplateBinding BorderBrush}\"\n                    BorderThickness=\"{TemplateBinding BorderThickness}\"\n                    Content=\"{TemplateBinding Content}\"\n                    ContentTemplate=\"{TemplateBinding ContentTemplate}\"\n                    CornerRadius=\"{TemplateBinding CornerRadius}\"\n                    Foreground=\"{TemplateBinding Foreground}\" />\n            </ControlTemplate>\n        </Setter>\n\n        <Style Selector=\"^:pointerover /template/ ContentPresenter#ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource HyperlinkButtonForegroundPointerOver}\" />\n            <Setter Property=\"Background\" Value=\"{DynamicResource HyperlinkButtonBackgroundPointerOver}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource HyperlinkButtonBorderBrushPointerOver}\" />\n        </Style>\n        <Style Selector=\"^:pressed /template/ ContentPresenter#ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource HyperlinkButtonForegroundPressed}\" />\n            <Setter Property=\"Background\" Value=\"{DynamicResource HyperlinkButtonBackgroundPressed}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource HyperlinkButtonBorderBrushPressed}\" />\n        </Style>\n        <Style Selector=\"^:disabled /template/ ContentPresenter#ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource HyperlinkButtonForegroundDisabled}\" />\n            <Setter Property=\"Background\" Value=\"{DynamicResource HyperlinkButtonBackgroundDisabled}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource HyperlinkButtonBorderBrushDisabled}\" />\n        </Style>\n\n    </ControlTheme>\n\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.Dark.axaml",
    "content": "﻿<!--  https://github.com/irihitech/Semi.Avalonia/blob/main/src/Semi.Avalonia/Themes/Dark/Label.axaml  -->\n<ResourceDictionary xmlns=\"https://github.com/avaloniaui\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n    <!--<x:Double x:Key=\"LabelTagBackgroundOpacity\">0.15</x:Double>-->\n    <x:Double x:Key=\"LabelTagBackgroundOpacity\">0.3</x:Double>\n\n    <!--  Typography related resources are combined with TextBlock  -->\n    <SolidColorBrush x:Key=\"LabelTagLightRedForeground\" Color=\"#FEE0D5\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightRedBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#FC725A\" />\n    <SolidColorBrush x:Key=\"LabelTagLightPinkForeground\" Color=\"#FBD3DC\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightPinkBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#EF5686\" />\n    <SolidColorBrush x:Key=\"LabelTagLightPurpleForeground\" Color=\"#EFCEF0\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightPurpleBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#B553C2\" />\n    <SolidColorBrush x:Key=\"LabelTagLightVioletForeground\" Color=\"#DDD4F4\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightVioletBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#8865D4\" />\n    <SolidColorBrush x:Key=\"LabelTagLightIndigoForeground\" Color=\"#D1D8F1\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightIndigoBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#5F71C5\" />\n    <SolidColorBrush x:Key=\"LabelTagLightBlueForeground\" Color=\"#D4ECFF\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightBlueBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#54A9FF\" />\n    <SolidColorBrush x:Key=\"LabelTagLightLightBlueForeground\" Color=\"#CEEEFC\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightLightBlueBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#40B4F3\" />\n    <SolidColorBrush x:Key=\"LabelTagLightCyanForeground\" Color=\"#C6EFF1\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightCyanBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#38BBC6\" />\n    <SolidColorBrush x:Key=\"LabelTagLightTealForeground\" Color=\"#C4F0E8\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightTealBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#33C2B0\" />\n    <SolidColorBrush x:Key=\"LabelTagLightGreenForeground\" Color=\"#D0F0D1\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightGreenBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#5DC264\" />\n    <SolidColorBrush x:Key=\"LabelTagLightLightGreenForeground\" Color=\"#E4F1D1\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightLightGreenBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#97C65F\" />\n    <SolidColorBrush x:Key=\"LabelTagLightLimeForeground\" Color=\"#E5F6C9\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightLimeBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#AEDC3A\" />\n    <SolidColorBrush x:Key=\"LabelTagLightYellowForeground\" Color=\"#FEFBD0\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightYellowBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#FDDE43\" />\n    <SolidColorBrush x:Key=\"LabelTagLightAmberForeground\" Color=\"#FCF6D2\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightAmberBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#F5CA50\" />\n    <SolidColorBrush x:Key=\"LabelTagLightOrangeForeground\" Color=\"#FFEFD0\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightOrangeBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#FFAE43\" />\n    <SolidColorBrush x:Key=\"LabelTagLightGreyForeground\" Color=\"#E6E8EA\" />\n    <SolidColorBrush\n        x:Key=\"LabelTagLightGreyBackground\"\n        Opacity=\"{DynamicResource LabelTagBackgroundOpacity}\"\n        Color=\"#888D92\" />\n    <SolidColorBrush x:Key=\"LabelTagLightWhiteBackground\" Color=\"#4F5159\" />\n    <SolidColorBrush x:Key=\"LabelTagLightWhiteBorderBrush\" Color=\"#41464C\" />\n    <SolidColorBrush x:Key=\"LabelTagLightWhiteForeground\" Color=\"#F9F9F9\" />\n\n    <SolidColorBrush x:Key=\"LabelTagGhostRedBorderBrush\" Color=\"#FB4932\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostRedForeground\" Color=\"#FC725A\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostPinkBorderBrush\" Color=\"#EB2F71\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostPinkForeground\" Color=\"#EF5686\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostPurpleBorderBrush\" Color=\"#A033B3\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostPurpleForeground\" Color=\"#B553C2\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostVioletBorderBrush\" Color=\"#7246C9\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostVioletForeground\" Color=\"#8865D4\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostIndigoBorderBrush\" Color=\"#4053B7\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostIndigoForeground\" Color=\"#5F71C5\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostBlueBorderBrush\" Color=\"#2990FF\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostBlueForeground\" Color=\"#54A9FF\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostLightBlueBorderBrush\" Color=\"#139FF0\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostLightBlueForeground\" Color=\"#40B4F3\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostCyanBorderBrush\" Color=\"#13A8B8\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostCyanForeground\" Color=\"#38BBC6\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostTealBorderBrush\" Color=\"#0EB3A1\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostTealForeground\" Color=\"#33C2B0\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostGreenBorderBrush\" Color=\"#3EB349\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostGreenForeground\" Color=\"#5DC264\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostLightGreenBorderBrush\" Color=\"#7FB840\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostLightGreenForeground\" Color=\"#97C65F\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostLimeBorderBrush\" Color=\"#A2D311\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostLimeForeground\" Color=\"#AEDC3A\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostYellowBorderBrush\" Color=\"#FCCE14\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostYellowForeground\" Color=\"#FDDE43\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostAmberBorderBrush\" Color=\"#F2B726\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostAmberForeground\" Color=\"#F5CA50\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostOrangeBorderBrush\" Color=\"#FF9214\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostOrangeForeground\" Color=\"#FFAE43\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostGreyBorderBrush\" Color=\"#6B7075\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostGreyForeground\" Color=\"#888D92\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostWhiteBackground\" Color=\"#4F5159\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostWhiteBorderBrush\" Color=\"#41464C\" />\n    <SolidColorBrush x:Key=\"LabelTagGhostWhiteForeground\" Color=\"#F9F9F9\" />\n\n    <SolidColorBrush x:Key=\"LabelTagSolidForeground\" Color=\"White\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidRedBackground\" Color=\"#FC725A\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidPinkBackground\" Color=\"#EF5686\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidPurpleBackground\" Color=\"#B553C2\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidVioletBackground\" Color=\"#8865D4\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidIndigoBackground\" Color=\"#5F71C5\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidBlueBackground\" Color=\"#54A9FF\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidLightBlueBackground\" Color=\"#40B4F3\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidCyanBackground\" Color=\"#38BBC6\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidTealBackground\" Color=\"#33C2B0\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidGreenBackground\" Color=\"#5DC264\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidLightGreenBackground\" Color=\"#97C65F\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidLimeBackground\" Color=\"#AEDC3A\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidYellowBackground\" Color=\"#FDDE43\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidAmberBackground\" Color=\"#F5CA50\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidOrangeBackground\" Color=\"#FFAE43\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidGreyBackground\" Color=\"#888D92\" />\n\n    <SolidColorBrush x:Key=\"LabelTagSolidWhiteBackground\" Color=\"#4F5159\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidWhiteBorderBrush\" Color=\"#41464C\" />\n    <SolidColorBrush x:Key=\"LabelTagSolidWhiteForeground\" Color=\"#F9F9F9\" />\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.axaml",
    "content": "﻿<!--  https://github.com/irihitech/Semi.Avalonia/blob/main/src/Semi.Avalonia/Controls/Label.axaml  -->\n<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    x:CompileBindings=\"True\">\n    <Design.PreviewWith>\n        <StackPanel\n            Width=\"500\"\n            Margin=\"20\"\n            HorizontalAlignment=\"Left\"\n            Spacing=\"20\">\n            <StackPanel.Styles>\n                <Style Selector=\"Label\">\n                    <Setter Property=\"Margin\" Value=\"4\" />\n                </Style>\n            </StackPanel.Styles>\n            <WrapPanel>\n                <Label Theme=\"{StaticResource TagLabel}\">Label</Label>\n                <Label Classes=\"Large\" Theme=\"{StaticResource TagLabel}\">Large Label</Label>\n                <Label Classes=\"Circle\" Theme=\"{StaticResource TagLabel}\">Circle Label</Label>\n                <Label Classes=\"Large Circle\" Theme=\"{StaticResource TagLabel}\">Large Circle Label</Label>\n            </WrapPanel>\n            <WrapPanel>\n                <Label Classes=\"Red\" Theme=\"{StaticResource TagLabel}\">Red</Label>\n                <Label Classes=\"Pink\" Theme=\"{StaticResource TagLabel}\">Pink</Label>\n                <Label Classes=\"Purple\" Theme=\"{StaticResource TagLabel}\">Purple</Label>\n                <Label Classes=\"Violet\" Theme=\"{StaticResource TagLabel}\">Violet</Label>\n                <Label Classes=\"Indigo\" Theme=\"{StaticResource TagLabel}\">Indigo</Label>\n                <Label Classes=\"Blue\" Theme=\"{StaticResource TagLabel}\">Blue</Label>\n                <Label Classes=\"LightBlue\" Theme=\"{StaticResource TagLabel}\">LightBlue</Label>\n                <Label Classes=\"Cyan\" Theme=\"{StaticResource TagLabel}\">Cyan</Label>\n                <Label Classes=\"Teal\" Theme=\"{StaticResource TagLabel}\">Teal</Label>\n                <Label Classes=\"Green\" Theme=\"{StaticResource TagLabel}\">Green</Label>\n                <Label Classes=\"LightGreen\" Theme=\"{StaticResource TagLabel}\">LightGreen</Label>\n                <Label Classes=\"Lime\" Theme=\"{StaticResource TagLabel}\">Lime</Label>\n                <Label Classes=\"Yellow\" Theme=\"{StaticResource TagLabel}\">Yellow</Label>\n                <Label Classes=\"Amber\" Theme=\"{StaticResource TagLabel}\">Amber</Label>\n                <Label Classes=\"Orange\" Theme=\"{StaticResource TagLabel}\">Orange</Label>\n                <Label Classes=\"Grey\" Theme=\"{StaticResource TagLabel}\">Grey</Label>\n                <Label Classes=\"White\" Theme=\"{StaticResource TagLabel}\">White</Label>\n            </WrapPanel>\n            <WrapPanel>\n                <Label Classes=\"Ghost Red\" Theme=\"{StaticResource TagLabel}\">Red</Label>\n                <Label Classes=\"Ghost Pink\" Theme=\"{StaticResource TagLabel}\">Pink</Label>\n                <Label Classes=\"Ghost Purple\" Theme=\"{StaticResource TagLabel}\">Purple</Label>\n                <Label Classes=\"Ghost Violet\" Theme=\"{StaticResource TagLabel}\">Violet</Label>\n                <Label Classes=\"Ghost Indigo\" Theme=\"{StaticResource TagLabel}\">Indigo</Label>\n                <Label Classes=\"Ghost Blue\" Theme=\"{StaticResource TagLabel}\">Blue</Label>\n                <Label Classes=\"Ghost LightBlue\" Theme=\"{StaticResource TagLabel}\">LightBlue</Label>\n                <Label Classes=\"Ghost Cyan\" Theme=\"{StaticResource TagLabel}\">Cyan</Label>\n                <Label Classes=\"Ghost Teal\" Theme=\"{StaticResource TagLabel}\">Teal</Label>\n                <Label Classes=\"Ghost Green\" Theme=\"{StaticResource TagLabel}\">Green</Label>\n                <Label Classes=\"Ghost LightGreen\" Theme=\"{StaticResource TagLabel}\">LightGreen</Label>\n                <Label Classes=\"Ghost Lime\" Theme=\"{StaticResource TagLabel}\">Lime</Label>\n                <Label Classes=\"Ghost Yellow\" Theme=\"{StaticResource TagLabel}\">Yellow</Label>\n                <Label Classes=\"Ghost Amber\" Theme=\"{StaticResource TagLabel}\">Amber</Label>\n                <Label Classes=\"Ghost Orange\" Theme=\"{StaticResource TagLabel}\">Orange</Label>\n                <Label Classes=\"Ghost Grey\" Theme=\"{StaticResource TagLabel}\">Grey</Label>\n                <Label Classes=\"Ghost White\" Theme=\"{StaticResource TagLabel}\">White</Label>\n            </WrapPanel>\n            <WrapPanel>\n                <Label Classes=\"Solid Red\" Theme=\"{StaticResource TagLabel}\">Red</Label>\n                <Label Classes=\"Solid Pink\" Theme=\"{StaticResource TagLabel}\">Pink</Label>\n                <Label Classes=\"Solid Purple\" Theme=\"{StaticResource TagLabel}\">Purple</Label>\n                <Label Classes=\"Solid Violet\" Theme=\"{StaticResource TagLabel}\">Violet</Label>\n                <Label Classes=\"Solid Indigo\" Theme=\"{StaticResource TagLabel}\">Indigo</Label>\n                <Label Classes=\"Solid Blue\" Theme=\"{StaticResource TagLabel}\">Blue</Label>\n                <Label Classes=\"Solid LightBlue\" Theme=\"{StaticResource TagLabel}\">LightBlue</Label>\n                <Label Classes=\"Solid Cyan\" Theme=\"{StaticResource TagLabel}\">Cyan</Label>\n                <Label Classes=\"Solid Teal\" Theme=\"{StaticResource TagLabel}\">Teal</Label>\n                <Label Classes=\"Solid Green\" Theme=\"{StaticResource TagLabel}\">Green</Label>\n                <Label Classes=\"Solid LightGreen\" Theme=\"{StaticResource TagLabel}\">LightGreen</Label>\n                <Label Classes=\"Solid Lime\" Theme=\"{StaticResource TagLabel}\">Lime</Label>\n                <Label Classes=\"Solid Yellow\" Theme=\"{StaticResource TagLabel}\">Yellow</Label>\n                <Label Classes=\"Solid Amber\" Theme=\"{StaticResource TagLabel}\">Amber</Label>\n                <Label Classes=\"Solid Orange\" Theme=\"{StaticResource TagLabel}\">Orange</Label>\n                <Label Classes=\"Solid Grey\" Theme=\"{StaticResource TagLabel}\">Grey</Label>\n                <Label Classes=\"Solid White\" Theme=\"{StaticResource TagLabel}\">White</Label>\n            </WrapPanel>\n\n        </StackPanel>\n    </Design.PreviewWith>\n\n    <!--  https://github.com/irihitech/Semi.Avalonia/blob/main/src/Semi.Avalonia/Themes/Shared/Label.axaml  -->\n    <Thickness x:Key=\"LabelTagBorderThickness\">1</Thickness>\n    <Thickness x:Key=\"LabelTagSmallPadding\">8 2</Thickness>\n    <Thickness x:Key=\"LabelTagLargePadding\">8 4</Thickness>\n    <x:Double x:Key=\"LabelTagSmallHeight\">20</x:Double>\n    <x:Double x:Key=\"LabelTagLargeHeight\">24</x:Double>\n    <x:Double x:Key=\"LabelTagFontSize\">12</x:Double>\n    <CornerRadius x:Key=\"LabelTagSquareCornerRadius\">3</CornerRadius>\n    <CornerRadius x:Key=\"LabelTagCircleCornerRadius\">16</CornerRadius>\n\n    <!--<ControlTheme x:Key=\"{x:Type Label}\" TargetType=\"Label\">\n        <Setter Property=\"Foreground\" Value=\"{DynamicResource TextBlockDefaultForeground}\" />\n        <Setter Property=\"FontSize\" Value=\"{DynamicResource TextBlockFontSize}\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate TargetType=\"Label\">\n                <ContentPresenter\n                    Name=\"PART_ContentPresenter\"\n                    Padding=\"{TemplateBinding Padding}\"\n                    HorizontalContentAlignment=\"{TemplateBinding HorizontalContentAlignment}\"\n                    VerticalContentAlignment=\"{TemplateBinding VerticalContentAlignment}\"\n                    Background=\"{TemplateBinding Background}\"\n                    BorderBrush=\"{TemplateBinding BorderBrush}\"\n                    BorderThickness=\"{TemplateBinding BorderThickness}\"\n                    Content=\"{TemplateBinding Content}\"\n                    ContentTemplate=\"{TemplateBinding ContentTemplate}\"\n                    CornerRadius=\"{TemplateBinding CornerRadius}\"\n                    FontSize=\"{TemplateBinding FontSize}\"\n                    RecognizesAccessKey=\"True\" />\n            </ControlTemplate>\n        </Setter>\n        <Style Selector=\"^.Secondary\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource TextBlockSecondaryForeground}\" />\n        </Style>\n        <Style Selector=\"^.Tertiary\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource TextBlockTertiaryForeground}\" />\n        </Style>\n        <Style Selector=\"^.Quaternary\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource TextBlockQuaternaryForeground}\" />\n        </Style>\n        <Style Selector=\"^.Success\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource TextBlockSuccessForeground}\" />\n        </Style>\n        <Style Selector=\"^.Warning\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource TextBlockWarningForeground}\" />\n        </Style>\n        <Style Selector=\"^.Danger\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource TextBlockDangerForeground}\" />\n        </Style>\n        <Style Selector=\"^.Mark\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource TextBlockMarkBackground}\" />\n        </Style>\n        <Style Selector=\"^.Code\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource TextBlockCodeBackground}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource TextBlockCodeForeground}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource TextBlockCodeBorderBrush}\" />\n            <Setter Property=\"BorderThickness\" Value=\"1\" />\n            <Setter Property=\"Padding\" Value=\"2\" />\n            <Setter Property=\"CornerRadius\" Value=\"{DynamicResource TextBlockCodeCornerRadius}\" />\n            <Setter Property=\"FontFamily\" Value=\"{DynamicResource CodeFontFamily}\" />\n            <Setter Property=\"FontSize\" Value=\"{DynamicResource TextBlockCodeFontSize}\" />\n        </Style>\n        <Style Selector=\"^:disabled\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource TextBlockDisabledForeground}\" />\n        </Style>\n    </ControlTheme>\n    <ControlTheme\n        x:Key=\"TitleLabel\"\n        BasedOn=\"{StaticResource {x:Type Label}}\"\n        TargetType=\"Label\">\n        <Setter Property=\"FontWeight\" Value=\"{DynamicResource TextBlockTitleFontWeight}\" />\n        <Style Selector=\"^.H1\">\n            <Setter Property=\"FontSize\" Value=\"{DynamicResource TextBlockTitleH1FontSize}\" />\n        </Style>\n        <Style Selector=\"^.H2\">\n            <Setter Property=\"FontSize\" Value=\"{DynamicResource TextBlockTitleH2FontSize}\" />\n        </Style>\n        <Style Selector=\"^.H3\">\n            <Setter Property=\"FontSize\" Value=\"{DynamicResource TextBlockTitleH3FontSize}\" />\n        </Style>\n        <Style Selector=\"^.H4\">\n            <Setter Property=\"FontSize\" Value=\"{DynamicResource TextBlockTitleH4FontSize}\" />\n        </Style>\n        <Style Selector=\"^.H5\">\n            <Setter Property=\"FontSize\" Value=\"{DynamicResource TextBlockTitleH5FontSize}\" />\n        </Style>\n        <Style Selector=\"^.H6\">\n            <Setter Property=\"FontSize\" Value=\"{DynamicResource TextBlockTitleH6FontSize}\" />\n        </Style>\n    </ControlTheme>-->\n\n    <!--  Light, Ghost, Solid.  -->\n    <!--  Shape: Square,Circle. Default is Square  -->\n    <!--  Size: Small, Large. Default is Small  -->\n    <!--  Color: Red, Pink, Purple, Violet, Indigo, Blue, LightBlue, Cyan, Teal, Green, LightGreen, Lime, Yellow, Amber, Orange, Grey, White. Default is Grey  -->\n    <ControlTheme x:Key=\"TagLabel\" TargetType=\"Label\">\n        <Setter Property=\"BorderThickness\" Value=\"{DynamicResource LabelTagBorderThickness}\" />\n        <Setter Property=\"MinHeight\" Value=\"{DynamicResource LabelTagSmallHeight}\" />\n        <Setter Property=\"CornerRadius\" Value=\"{DynamicResource LabelTagSquareCornerRadius}\" />\n        <Setter Property=\"HorizontalAlignment\" Value=\"Left\" />\n        <Setter Property=\"UseLayoutRounding\" Value=\"False\" />\n        <Setter Property=\"VerticalAlignment\" Value=\"Center\" />\n        <Setter Property=\"Padding\" Value=\"{DynamicResource LabelTagSmallPadding}\" />\n        <Setter Property=\"FontSize\" Value=\"{DynamicResource LabelTagFontSize}\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate TargetType=\"Label\">\n                <ContentPresenter\n                    Name=\"PART_ContentPresenter\"\n                    MinWidth=\"{TemplateBinding MinWidth}\"\n                    MinHeight=\"{TemplateBinding MinHeight}\"\n                    Padding=\"{TemplateBinding Padding}\"\n                    HorizontalContentAlignment=\"{TemplateBinding HorizontalContentAlignment}\"\n                    VerticalContentAlignment=\"{TemplateBinding VerticalContentAlignment}\"\n                    Background=\"{TemplateBinding Background}\"\n                    BorderBrush=\"{TemplateBinding BorderBrush}\"\n                    BorderThickness=\"{TemplateBinding BorderThickness}\"\n                    Content=\"{TemplateBinding Content}\"\n                    ContentTemplate=\"{TemplateBinding ContentTemplate}\"\n                    CornerRadius=\"{TemplateBinding CornerRadius}\"\n                    FontSize=\"{TemplateBinding FontSize}\"\n                    RecognizesAccessKey=\"True\"\n                    UseLayoutRounding=\"True\" />\n            </ControlTemplate>\n        </Setter>\n        <Style Selector=\"^.Large\">\n            <Setter Property=\"Padding\" Value=\"{DynamicResource LabelTagLargePadding}\" />\n            <Setter Property=\"MinHeight\" Value=\"{DynamicResource LabelTagLargeHeight}\" />\n        </Style>\n        <Style Selector=\"^.Circle\">\n            <Setter Property=\"CornerRadius\" Value=\"{DynamicResource LabelTagCircleCornerRadius}\" />\n        </Style>\n        <Style Selector=\"^\">\n            <Setter Property=\"BorderBrush\" Value=\"Transparent\" />\n            <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightGreyBackground}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightGreyForeground}\" />\n            <Style Selector=\"^.Red\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightRedBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightRedForeground}\" />\n            </Style>\n            <Style Selector=\"^.Pink\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightPinkBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightPinkForeground}\" />\n            </Style>\n            <Style Selector=\"^.Purple\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightPurpleBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightPurpleForeground}\" />\n            </Style>\n            <Style Selector=\"^.Violet\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightVioletBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightVioletForeground}\" />\n            </Style>\n            <Style Selector=\"^.Indigo\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightIndigoBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightIndigoForeground}\" />\n            </Style>\n            <Style Selector=\"^.Blue\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightBlueBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightBlueForeground}\" />\n            </Style>\n            <Style Selector=\"^.LightBlue\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightLightBlueBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightLightBlueForeground}\" />\n            </Style>\n            <Style Selector=\"^.Cyan\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightCyanBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightCyanForeground}\" />\n            </Style>\n            <Style Selector=\"^.Teal\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightTealBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightTealForeground}\" />\n            </Style>\n            <Style Selector=\"^.Green\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightGreenBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightGreenForeground}\" />\n            </Style>\n            <Style Selector=\"^.LightGreen\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightLightGreenBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightLightGreenForeground}\" />\n            </Style>\n            <Style Selector=\"^.Lime\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightLimeBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightLimeForeground}\" />\n            </Style>\n            <Style Selector=\"^.Yellow\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightYellowBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightYellowForeground}\" />\n            </Style>\n            <Style Selector=\"^.Amber\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightAmberBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightAmberForeground}\" />\n            </Style>\n            <Style Selector=\"^.Orange\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightOrangeBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightOrangeForeground}\" />\n            </Style>\n            <Style Selector=\"^.Grey\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightGreyBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightGreyForeground}\" />\n            </Style>\n            <Style Selector=\"^.White\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagLightWhiteBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightWhiteForeground}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagLightWhiteBorderBrush}\" />\n            </Style>\n        </Style>\n        <Style Selector=\"^.Ghost\">\n            <Setter Property=\"Background\" Value=\"Transparent\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagLightGreyBackground}\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagLightGreyForeground}\" />\n            <Style Selector=\"^.Red\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostRedBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostRedForeground}\" />\n            </Style>\n            <Style Selector=\"^.Pink\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostPinkBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostPinkForeground}\" />\n            </Style>\n            <Style Selector=\"^.Purple\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostPurpleBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostPurpleForeground}\" />\n            </Style>\n            <Style Selector=\"^.Violet\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostVioletBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostVioletForeground}\" />\n            </Style>\n            <Style Selector=\"^.Indigo\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostIndigoBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostIndigoForeground}\" />\n            </Style>\n            <Style Selector=\"^.Blue\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostBlueBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostBlueForeground}\" />\n            </Style>\n            <Style Selector=\"^.LightBlue\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostLightBlueBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostLightBlueForeground}\" />\n            </Style>\n            <Style Selector=\"^.Cyan\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostCyanBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostCyanForeground}\" />\n            </Style>\n            <Style Selector=\"^.Teal\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostTealBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostTealForeground}\" />\n            </Style>\n            <Style Selector=\"^.Green\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostGreenBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostGreenForeground}\" />\n            </Style>\n            <Style Selector=\"^.LightGreen\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostLightGreenBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostLightGreenForeground}\" />\n            </Style>\n            <Style Selector=\"^.Lime\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostLimeBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostLimeForeground}\" />\n            </Style>\n            <Style Selector=\"^.Yellow\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostYellowBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostYellowForeground}\" />\n            </Style>\n            <Style Selector=\"^.Amber\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostAmberBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostAmberForeground}\" />\n            </Style>\n            <Style Selector=\"^.Orange\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostOrangeBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostOrangeForeground}\" />\n            </Style>\n            <Style Selector=\"^.Grey\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostGreyBorderBrush}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostGreyForeground}\" />\n            </Style>\n            <Style Selector=\"^.White\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagGhostWhiteBackground}\" />\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostWhiteForeground}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagGhostWhiteBorderBrush}\" />\n            </Style>\n        </Style>\n        <Style Selector=\"^.Solid\">\n            <Setter Property=\"BorderBrush\" Value=\"Transparent\" />\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagSolidForeground}\" />\n            <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidGreyBackground}\" />\n            <Style Selector=\"^.Red\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidRedBackground}\" />\n            </Style>\n            <Style Selector=\"^.Pink\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidPinkBackground}\" />\n            </Style>\n            <Style Selector=\"^.Purple\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidPurpleBackground}\" />\n            </Style>\n            <Style Selector=\"^.Violet\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidVioletBackground}\" />\n            </Style>\n            <Style Selector=\"^.Indigo\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidIndigoBackground}\" />\n            </Style>\n            <Style Selector=\"^.Blue\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidBlueBackground}\" />\n            </Style>\n            <Style Selector=\"^.LightBlue\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidLightBlueBackground}\" />\n            </Style>\n            <Style Selector=\"^.Cyan\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidCyanBackground}\" />\n            </Style>\n            <Style Selector=\"^.Teal\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidTealBackground}\" />\n            </Style>\n            <Style Selector=\"^.Green\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidGreenBackground}\" />\n            </Style>\n            <Style Selector=\"^.LightGreen\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidLightGreenBackground}\" />\n            </Style>\n            <Style Selector=\"^.Lime\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidLimeBackground}\" />\n            </Style>\n            <Style Selector=\"^.Yellow\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidYellowBackground}\" />\n            </Style>\n            <Style Selector=\"^.Amber\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidAmberBackground}\" />\n            </Style>\n            <Style Selector=\"^.Orange\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidOrangeBackground}\" />\n            </Style>\n            <Style Selector=\"^.Grey\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidGreyBackground}\" />\n            </Style>\n            <Style Selector=\"^.White\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagSolidWhiteForeground}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource LabelTagSolidWhiteBorderBrush}\" />\n                <Setter Property=\"Background\" Value=\"{DynamicResource LabelTagSolidWhiteBackground}\" />\n            </Style>\n        </Style>\n    </ControlTheme>\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ControlThemes/ListBoxStyles.axaml",
    "content": "﻿<ResourceDictionary xmlns=\"https://github.com/avaloniaui\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n\n    <!-- ListBoxItem theme that removes ListBox visuals (i.e. for using with radiobuttons) -->\n    <ControlTheme x:Key=\"ListBoxItemBorderlessTheme\" TargetType=\"ListBoxItem\">\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Panel>\n                    <ContentPresenter\n                        Name=\"PART_ContentPresenter\"\n                        Margin=\"2,0\"\n                        Padding=\"{TemplateBinding Padding}\"\n                        HorizontalContentAlignment=\"{TemplateBinding HorizontalContentAlignment}\"\n                        VerticalContentAlignment=\"{TemplateBinding VerticalContentAlignment}\"\n                        Background=\"{TemplateBinding Background}\"\n                        BorderBrush=\"{TemplateBinding BorderBrush}\"\n                        BorderThickness=\"{TemplateBinding BorderThickness}\"\n                        Content=\"{TemplateBinding Content}\"\n                        ContentTemplate=\"{TemplateBinding ContentTemplate}\"\n                        CornerRadius=\"{TemplateBinding CornerRadius}\" />\n                </Panel>\n            </ControlTemplate>\n        </Setter>\n\n    </ControlTheme>\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ControlThemes/_index.axaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    x:CompileBindings=\"True\">\n    <ResourceDictionary.MergedDictionaries>\n        <ResourceInclude Source=\"avares://StabilityMatrix.Avalonia/Styles/ControlThemes/BetterComboBoxStyles.axaml\" />\n        <ResourceInclude Source=\"avares://StabilityMatrix.Avalonia/Styles/ControlThemes/HyperlinkIconButtonStyles.axaml\" />\n        <ResourceInclude Source=\"avares://StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.axaml\" />\n        <ResourceInclude Source=\"avares://StabilityMatrix.Avalonia/Styles/ControlThemes/LabelStyles.Dark.axaml\" />\n        <ResourceInclude Source=\"avares://StabilityMatrix.Avalonia/Styles/ControlThemes/ListBoxStyles.axaml\" />\n    </ResourceDictionary.MergedDictionaries>\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/DockStyles.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">\n    <Design.PreviewWith>\n        <Border Padding=\"20\">\n            <!-- Add Controls for Previewer Here -->\n        </Border>\n    </Design.PreviewWith>\n    \n    <!--  Default grid splitter style  -->\n    <Style Selector=\"GridSplitter\">\n        <Setter Property=\"Opacity\" Value=\"0.6\" />\n        <Setter Property=\"Background\" Value=\"{DynamicResource ScrollBarTrackStroke}\" />\n        <!--<Setter Property=\"Background\" Value=\"{DynamicResource ScrollBarTrackStroke}\" />-->\n        <Setter Property=\"BorderThickness\" Value=\"6\" />\n        <Setter Property=\"CornerRadius\" Value=\"4\" />\n    </Style>\n        \n    <!-- Remove accent color from active dock bar -->\n    <Style Selector=\"ToolChromeControl\">\n        <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBackgroundBrush}\" />\n    </Style>\n    <Style Selector=\"ToolChromeControl:active /template/ Grid#PART_Grip\">\n        <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBackgroundBrush}\" />\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/FAComboBoxStyles.axaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:mocks=\"using:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\">\n    <Design.PreviewWith>\n        <Panel Width=\"250\" Height=\"600\">\n            <StackPanel Margin=\"8\" Spacing=\"4\">\n                <ui:FAComboBox\n                    HorizontalAlignment=\"Stretch\"\n                    SelectedIndex=\"0\"\n                    Theme=\"{DynamicResource FAComboBoxHybridModelTheme}\"\n                    ItemsSource=\"{x:Static mocks:DesignData.SampleHybridModels}\"/>\n            </StackPanel>\n        </Panel>\n    </Design.PreviewWith>\n\n    <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n    <ControlTheme\n        x:Key=\"FAComboBoxItemHybridModelTheme\"\n        BasedOn=\"{StaticResource {x:Type ui:FAComboBoxItem}}\"\n        TargetType=\"ui:FAComboBoxItem\">\n        <Setter Property=\"ToolTip.Placement\" Value=\"RightEdgeAlignedTop\" />\n        <Setter Property=\"ToolTip.Tip\">\n            <Template>\n                <sg:SpacedGrid\n                    x:DataType=\"models:HybridModelFile\"\n                    ColumnDefinitions=\"Auto,*\"\n                    ColumnSpacing=\"6\"\n                    RowSpacing=\"0\">\n                    <!--  Image  -->\n                    <controls:BetterAdvancedImage\n                        Width=\"64\"\n                        Height=\"96\"\n                        CornerRadius=\"6\"\n                        IsVisible=\"{Binding Local.PreviewImageFullPathGlobal, Converter={x:Static StringConverters.IsNotNullOrEmpty}, FallbackValue=''}\"\n                        RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n                        Source=\"{Binding Local.PreviewImageFullPathGlobal, FallbackValue=''}\"\n                        Stretch=\"UniformToFill\"\n                        StretchDirection=\"Both\" />\n                    <StackPanel Grid.Column=\"1\" MaxWidth=\"300\">\n                        <!--  Title  -->\n                        <TextBlock\n                            Margin=\"0,0,0,4\"\n                            HorizontalAlignment=\"Left\"\n                            FontSize=\"14\"\n                            FontWeight=\"Medium\"\n                            Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n                            IsVisible=\"{Binding Local.ConnectedModelInfo, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}\"\n                            Text=\"{Binding Local.DisplayModelName}\"\n                            TextWrapping=\"WrapWithOverflow\" />\n                        <!--  Version  -->\n                        <TextBlock\n                            Margin=\"0,0,0,8\"\n                            HorizontalAlignment=\"Left\"\n                            FontSize=\"13\"\n                            Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                            IsVisible=\"{Binding Local.ConnectedModelInfo, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}\"\n                            Text=\"{Binding Local.DisplayModelVersion}\"\n                            TextWrapping=\"WrapWithOverflow\" />\n                        <!--  Path  -->\n                        <TextBlock\n                            HorizontalAlignment=\"Left\"\n                            FontSize=\"13\"\n                            Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                            Text=\"{Binding FileName}\"\n                            TextWrapping=\"Wrap\" />\n                    </StackPanel>\n                </sg:SpacedGrid>\n            </Template>\n        </Setter>\n    </ControlTheme>\n\n    <!--  ReSharper disable once Xaml.StaticResourceNotResolved  -->\n    <ControlTheme x:Key=\"FAComboBoxHybridModelTheme\"\n                  TargetType=\"ui:FAComboBox\"\n                  BasedOn=\"{StaticResource {x:Type ui:FAComboBox}}\">\n        <Setter Property=\"ItemTemplate\">\n            <DataTemplate DataType=\"models:HybridModelFile\">\n                <StackPanel>\n                    <TextBlock Text=\"{Binding ShortDisplayName}\" TextTrimming=\"CharacterEllipsis\" />\n                </StackPanel>\n            </DataTemplate>\n        </Setter>\n        <Setter Property=\"ItemContainerTheme\" Value=\"{StaticResource FAComboBoxItemHybridModelTheme}\" />\n    </ControlTheme>\n\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ListBoxStyles.axaml",
    "content": "﻿<ResourceDictionary xmlns=\"https://github.com/avaloniaui\"\n                    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\">\n    \n    <Design.PreviewWith>\n        <Panel Width=\"400\" Height=\"600\">\n            <Panel Width=\"350\" Height=\"200\">\n            </Panel>\n        </Panel>\n    </Design.PreviewWith>\n    \n    <ControlTheme\n        x:Key=\"DraggableListBoxItemTheme\"\n        TargetType=\"ListBoxItem\">\n                <!--\n                    Modified from https://github.com/amwx/FluentAvalonia/blob/main/src/\n                    FluentAvalonia/Styling/ControlThemes/BasicControls/ListBoxStyles.axaml\n                -->\n\n                <Setter Property=\"Padding\" Value=\"0,0,0,0\" />\n\n                <Setter Property=\"FontFamily\" Value=\"{DynamicResource ContentControlThemeFontFamily}\" />\n                <Setter Property=\"FontSize\" Value=\"{DynamicResource ControlContentThemeFontSize}\" />\n                <Setter Property=\"Background\" Value=\"{DynamicResource ListViewItemBackground}\" />\n                <!--<Setter Property=\"CornerRadius\" Value=\"7\" />-->\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ListViewItemForeground}\" />\n\n                <Setter Property=\"HorizontalContentAlignment\" Value=\"Stretch\" />\n                <Setter Property=\"VerticalContentAlignment\" Value=\"Center\" />\n                <Setter Property=\"MinWidth\" Value=\"{DynamicResource ListViewItemMinWidth}\" />\n\n                <Setter Property=\"Template\">\n                    <ControlTemplate>\n                        <Panel>\n                            <ContentPresenter\n                                Name=\"PART_ContentPresenter\"\n                                Padding=\"{TemplateBinding Padding}\"\n                                HorizontalContentAlignment=\"{TemplateBinding HorizontalContentAlignment}\"\n                                VerticalContentAlignment=\"{TemplateBinding VerticalContentAlignment}\"\n                                Background=\"{TemplateBinding Background}\"\n                                BorderBrush=\"{TemplateBinding BorderBrush}\"\n                                BorderThickness=\"{TemplateBinding BorderThickness}\"\n                                Content=\"{TemplateBinding Content}\"\n                                ContentTemplate=\"{TemplateBinding ContentTemplate}\"\n                                CornerRadius=\"{TemplateBinding CornerRadius}\" />\n                        </Panel>\n                    </ControlTemplate>\n                </Setter>\n        \n                <Style Selector=\"^:not(:dragging)\">\n                    <Setter Property=\"Transitions\">\n                        <Setter.Value>\n                            <Transitions>\n                                <TransformOperationsTransition Property=\"RenderTransform\" Duration=\"0:0:0.1\" />\n                            </Transitions>\n                        </Setter.Value>\n                    </Setter>\n                </Style>\n\n                <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                    <Setter Property=\"Background\" Value=\"{DynamicResource ListViewItemBackgroundPressed}\" />\n                </Style>\n                <Style Selector=\"^ /template/ ContentPresenter\">\n                    <Setter Property=\"Foreground\" Value=\"{DynamicResource ListViewItemForegroundPressed}\" />\n                </Style>\n        \n    </ControlTheme>\n    \n    <!-- ReSharper disable once Xaml.StaticResourceNotResolved -->\n    <ControlTheme\n        x:Key=\"DraggableListBoxTheme\"\n        TargetType=\"ListBox\"\n        BasedOn=\"{StaticResource {x:Type ListBox}}\">\n        \n        <Setter Property=\"ItemContainerTheme\" Value=\"{StaticResource DraggableListBoxItemTheme}\"/>\n        \n        <!--<Style Selector=\"^ /template/ ListBoxItem:not(:dragging)\">\n            <Setter Property=\"Transitions\">\n                <Setter.Value>\n                    <Transitions>\n                        <TransformOperationsTransition Property=\"RenderTransform\" Duration=\"0:0:0.1\" />\n                    </Transitions>\n                </Setter.Value>\n            </Setter>\n        </Style>-->\n        \n    </ControlTheme>\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/Markdown/MarkdownStyleFluentAvalonia.axaml",
    "content": "﻿<Styles\n    x:Class=\"StabilityMatrix.Avalonia.Styles.Markdown.MarkdownStyleFluentAvalonia\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avaloniaEdit=\"https://github.com/avaloniaui/avaloniaedit\"\n    xmlns:ctxt=\"clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia\"\n    xmlns:extensions=\"clr-namespace:Markdown.Avalonia.SyntaxHigh.Extensions;assembly=Markdown.Avalonia.SyntaxHigh\"\n    xmlns:md=\"clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia\"\n    xmlns:mdc=\"clr-namespace:Markdown.Avalonia.Controls;assembly=Markdown.Avalonia\"\n    xmlns:mde=\"clr-namespace:Markdown.Avalonia.Extensions;assembly=Markdown.Avalonia\">\n    <Styles.Resources>\n        <SolidColorBrush x:Key=\"MarkdownScrollViewer.SelectionBrush\" Color=\"{DynamicResource SystemAccentColor}\" />\n    </Styles.Resources>\n\n    <!--  Added: default one didn't set appendix styles for fluent avalonia  -->\n    <!--  partly from: https://github.com/whistyun/Markdown.Avalonia/blob/master/Markdown.Avalonia.SyntaxHigh/StyleCollections/AppendixOfFluentAvalonia.axaml  -->\n    <Style Selector=\".CodeBlock avaloniaEdit|TextEditor\">\n        <Setter Property=\"MinHeight\" Value=\"30\" />\n        <Setter Property=\"SyntaxHighlighting\" Value=\"{extensions:SyntaxHighlightWrapper TextFillColorPrimaryBrush}\" />\n        <Setter Property=\"VerticalScrollBarVisibility\" Value=\"Disabled\" />\n        <Setter Property=\"CornerRadius\" Value=\"8\" />\n    </Style>\n\n    <Style Selector=\".CodeBlock Label.LangInfo\">\n        <Setter Property=\"IsVisible\" Value=\"False\" />\n    </Style>\n\n    <Style Selector=\".CodeBlock Button.CopyButton TextBlock\">\n        <Setter Property=\"Text\" Value=\"Copy\" />\n    </Style>\n    <!--  end of added appendix styles  -->\n\n    <Style Selector=\":is(md|MarkdownScrollViewer)\">\n        <Style.Setters>\n            <Setter Property=\"SelectionBrush\" Value=\"{DynamicResource TextControlSelectionHighlightColor}\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer ctxt|CTextBlock\">\n        <Style.Setters>\n            <Setter Property=\"SelectionBrush\" Value=\"{DynamicResource TextControlSelectionHighlightColor}\" />\n            <Setter Property=\"FontSize\" Value=\"14\" />\n            <Setter Property=\"Margin\" Value=\"0,5\" />\n        </Style.Setters>\n    </Style>\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer TextBlock\">\n        <Style.Setters>\n            <Setter Property=\"Margin\" Value=\"0, 7, 0, 14\" />\n            <Setter Property=\"FontSize\" Value=\"14\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer ctxt|CTextBlock.Heading1\">\n        <Style.Setters>\n            <Setter Property=\"Margin\" Value=\"{StaticResource ContentDialogTitleMargin}\" />\n            <Setter Property=\"FontSize\" Value=\"20\" />\n            <Setter Property=\"Foreground\" Value=\"{mde:Alpha TextFillColorPrimaryBrush}\" />\n            <Setter Property=\"FontWeight\" Value=\"SemiBold\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer ctxt|CTextBlock.Heading2\">\n        <Style.Setters>\n            <Setter Property=\"FontSize\" Value=\"{mde:Multiply ControlContentThemeFontSize, 1.6}\" />\n            <Setter Property=\"Foreground\" Value=\"{mde:Alpha TextFillColorPrimaryBrush}\" />\n            <Setter Property=\"FontWeight\" Value=\"Light\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer ctxt|CTextBlock.Heading3\">\n        <Style.Setters>\n            <Setter Property=\"FontSize\" Value=\"{mde:Multiply ControlContentThemeFontSize, 1.4}\" />\n            <Setter Property=\"Foreground\" Value=\"{mde:Alpha TextFillColorPrimaryBrush, 0.7}\" />\n            <Setter Property=\"FontWeight\" Value=\"Light\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer ctxt|CTextBlock.Heading4\">\n        <Style.Setters>\n            <Setter Property=\"Margin\" Value=\"0, 0, 10, 0\" />\n            <Setter Property=\"FontSize\" Value=\"{mde:Multiply ControlContentThemeFontSize, 1.2}\" />\n            <Setter Property=\"Foreground\" Value=\"{mde:Alpha TextFillColorPrimaryBrush, 0.7}\" />\n            <Setter Property=\"FontWeight\" Value=\"Light\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer ctxt|CHyperlink\">\n        <Style.Setters>\n            <Setter Property=\"IsUnderline\" Value=\"true\" />\n            <Setter Property=\"Foreground\" Value=\"{StaticResource SystemAccentColor}\" />\n        </Style.Setters>\n    </Style>\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer ctxt|CHyperlink:pointerover\">\n        <Setter Property=\"Foreground\" Value=\"{mde:Complementary SystemAccentColor}\" />\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer Border.Table\">\n        <Style.Setters>\n            <Setter Property=\"Margin\" Value=\"5\" />\n            <Setter Property=\"BorderThickness\" Value=\"0,0,1,1\" />\n            <Setter Property=\"BorderBrush\" Value=\"{mde:Alpha TextFillColorPrimaryBrush, 0.7}\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer Border.TightTable\">\n        <Style.Setters>\n            <Setter Property=\"HorizontalAlignment\" Value=\"Left\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer Grid.Table &gt; Border\">\n        <Style.Setters>\n            <Setter Property=\"Margin\" Value=\"0\" />\n            <Setter Property=\"BorderThickness\" Value=\"1,1,0,0\" />\n            <Setter Property=\"BorderBrush\" Value=\"{mde:Alpha TextFillColorPrimaryBrush, 0.7}\" />\n            <Setter Property=\"Padding\" Value=\"2\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer Border.TableHeader\">\n        <Style.Setters>\n            <Setter Property=\"Background\" Value=\"{mde:Alpha TextFillColorPrimaryBrush, 0.3}\" />\n        </Style.Setters>\n    </Style>\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer Border.TableHeader ctxt|CTextBlock\">\n        <Style.Setters>\n            <Setter Property=\"FontWeight\" Value=\"DemiBold\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer Border.EvenTableRow\">\n        <Style.Setters>\n            <Setter Property=\"Background\" Value=\"{mde:Alpha ControlFillColorDefaultBrush, 0.9}\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer Border.CodeBlock\">\n        <Style.Setters>\n            <!--<Setter Property=\"BorderBrush\"     Value=\"{mde:Alpha TextFillColorPrimaryBrush, 0.2}\"/>-->\n            <!--<Setter Property=\"BorderThickness\" Value=\"0,5,0,5\"/>-->\n            <Setter Property=\"BorderThickness\" Value=\"0\" />\n            <Setter Property=\"CornerRadius\" Value=\"4\" />\n            <Setter Property=\"Margin\" Value=\"5,0,5,0\" />\n            <Setter Property=\"Background\" Value=\"{mde:Alpha ControlFillColorDefaultBrush, 0.9}\" />\n        </Style.Setters>\n    </Style>\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer TextBlock.CodeBlock\">\n        <Style.Setters>\n            <Setter Property=\"FontFamily\" Value=\"menlo,monaco,consolas,droid sans mono,inconsolata,courier new,monospace,dejavu sans mono\" />\n            <!--  Changed: DivideColor binding bork  -->\n            <!--<Setter Property=\"Foreground\"      Value=\"{mde:DivideColor Blue, TextFillColorPrimary, 0.4}\"/>-->\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource TextFillColorPrimary}\" />\n        </Style.Setters>\n    </Style>\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer Border.NoContainer\">\n        <Style.Setters>\n            <Setter Property=\"BorderBrush\" Value=\"Red\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer ctxt|CCode\">\n        <Style.Setters>\n            <!--  Changed: DivideColor binding bork  -->\n            <!--<Setter Property=\"Foreground\" Value=\"{mde:DivideColor Blue, TextFillColorPrimary, 0.4}\" />-->\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource TextFillColorPrimary}\" />\n            <Setter Property=\"Background\" Value=\"{mde:Alpha ControlFillColorDefaultBrush, 0.9}\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer Border.Note\">\n        <Style.Setters>\n            <Setter Property=\"Margin\" Value=\"5,0,5,0\" />\n            <Setter Property=\"BorderBrush\" Value=\"{mde:Alpha TextFillColorPrimaryBrush, 0.2}\" />\n            <Setter Property=\"BorderThickness\" Value=\"3,3,3,3\" />\n            <Setter Property=\"Background\" Value=\"{mde:Alpha ControlFillColorDefaultBrush, 0.9}\" />\n        </Style.Setters>\n    </Style>\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer ctxt|CTextBlock.Note\">\n        <Style.Setters>\n            <Setter Property=\"Margin\" Value=\"10, 5\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer Grid.List\">\n        <Style.Setters>\n            <Setter Property=\"Margin\" Value=\"16,0,0,0\" />\n        </Style.Setters>\n    </Style>\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer ctxt|CTextBlock.ListMarker\">\n        <Style.Setters>\n            <Setter Property=\"Margin\" Value=\"0,5,5,5\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer Border.Blockquote\">\n        <Style.Setters>\n            <Setter Property=\"BorderBrush\" Value=\"{mde:Alpha TextFillColorPrimaryBrush, 0.2}\" />\n            <Setter Property=\"BorderThickness\" Value=\"5,0,0,0\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer StackPanel.Blockquote\">\n        <Style.Setters>\n            <Setter Property=\"Margin\" Value=\"10, 5\" />\n        </Style.Setters>\n    </Style>\n\n    <Style Selector=\".Markdown_Avalonia_MarkdownViewer mdc|Rule\">\n        <Style.Setters>\n            <Setter Property=\"Margin\" Value=\"0,3\" />\n        </Style.Setters>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/Markdown/MarkdownStyleFluentAvalonia.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\n\nnamespace StabilityMatrix.Avalonia.Styles.Markdown;\n\npublic partial class MarkdownStyleFluentAvalonia : global::Avalonia.Styling.Styles\n{\n    public MarkdownStyleFluentAvalonia()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ProgressRing.axaml",
    "content": "﻿<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n        xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n        x:CompileBindings=\"True\">\n\n  <Design.PreviewWith>\n    <StackPanel>\n      <Border Padding=\"20\">\n        <controls:ProgressRing Value=\"50\" />\n      </Border>\n      <Border Padding=\"20\">\n        <controls:ProgressRing StartAngle=\"90\" EndAngle=\"450\" Value=\"50\" Foreground=\"Red\" StrokeThickness=\"3\" Width=\"100\" Height=\"100\" />\n      </Border>\n      <Border Padding=\"20\">\n        <controls:ProgressRing IsIndeterminate=\"True\" StrokeThickness=\"10\" Width=\"100\" Height=\"100\" />\n      </Border>\n    </StackPanel>\n  </Design.PreviewWith>\n\n  <Styles.Resources>\n    <converters:FitSquarelyWithinAspectRatioConverter x:Key=\"FitSquarelyWithinAspectRatioConverter\"/>\n  </Styles.Resources>\n\n  <Style Selector=\"controls|ProgressRing\">\n    <Setter Property=\"Foreground\" Value=\"{DynamicResource SystemAccentColor}\"/>\n    <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlBackgroundBaseLowBrush}\" />\n    <Setter Property=\"StrokeThickness\" Value=\"5\" />\n    <Setter Property=\"MinHeight\" Value=\"16\" />\n    <Setter Property=\"MinWidth\" Value=\"16\" />\n    \n    <Setter Property=\"Transitions\">\n      <Transitions>\n        <DoubleTransition \n          Property=\"SweepAngle\"\n          Duration=\"0:0:0.267\">\n          <DoubleTransition.Easing>\n            <LinearEasing/>\n          </DoubleTransition.Easing>\n        </DoubleTransition>\n      </Transitions>\n    </Setter>\n    \n    <Setter Property=\"Template\">\n      <ControlTemplate>\n        <Panel x:Name=\"PART_RingRoot\">\n          <Ellipse x:Name=\"PART_Track\"\n                   Stroke=\"{TemplateBinding Background}\"\n                   StrokeThickness=\"{TemplateBinding StrokeThickness}\" />\n          <Arc x:Name=\"PART_Fill\"\n               Stroke=\"{TemplateBinding Foreground}\"\n               StrokeThickness=\"{TemplateBinding StrokeThickness}\"\n               StrokeLineCap=\"Round\" />\n        </Panel>\n      </ControlTemplate>\n    </Setter>\n  </Style>\n\n  <Style Selector=\"controls|ProgressRing:not(:indeterminate) /template/ Arc#PART_Fill\">\n    <!--<Setter Property=\"StartAngle\" Value=\"{Binding StartAngle, RelativeSource={RelativeSource Mode=TemplatedParent}}\"/>-->\n    <!--<Setter Property=\"SweepAngle\" Value=\"{Binding SweepAngle, RelativeSource={RelativeSource Mode=TemplatedParent}}\"/>-->\n  </Style>\n\n  <Style Selector=\"controls|ProgressRing:preserveaspect\">\n    <Setter Property=\"MinWidth\" Value=\"32\" />\n    <Setter Property=\"MinHeight\" Value=\"32\" />\n  </Style>\n\n  <Style Selector=\"controls|ProgressRing:preserveaspect /template/ Panel#PART_RingRoot\">\n    <Setter Property=\"Width\" Value=\"{TemplateBinding Bounds, Converter={StaticResource FitSquarelyWithinAspectRatioConverter}}\"/>\n    <Setter Property=\"Height\" Value=\"{Binding Width, RelativeSource={RelativeSource Mode=Self}}\"/>\n  </Style>\n\n  <Style Selector=\"controls|ProgressRing[IsEnabled=True]:indeterminate /template/ Arc#PART_Fill\">\n    <Style.Animations>\n      <Animation Duration=\"0:0:4.567\" Easing=\"LinearEasing\" IterationCount=\"Infinite\" FillMode=\"Both\">\n        <KeyFrame Cue=\"0%\">\n          <Setter Property=\"StartAngle\" Value=\"-720\"/>\n          <Setter Property=\"SweepAngle\" Value=\"0\"/>\n        </KeyFrame>\n        <KeyFrame Cue=\"12.5%\">\n          <Setter Property=\"StartAngle\" Value=\"-540\"/>\n          <Setter Property=\"SweepAngle\" Value=\"50\"/>\n        </KeyFrame>\n        <KeyFrame Cue=\"25%\">\n          <Setter Property=\"StartAngle\" Value=\"-360\"/>\n          <Setter Property=\"SweepAngle\" Value=\"100\"/>\n        </KeyFrame>\n        <KeyFrame Cue=\"32.5%\">\n          <Setter Property=\"StartAngle\" Value=\"-180\"/>\n          <Setter Property=\"SweepAngle\" Value=\"50\"/>\n        </KeyFrame>\n        <KeyFrame Cue=\"50%\">\n          <Setter Property=\"StartAngle\" Value=\"0\"/>\n          <Setter Property=\"SweepAngle\" Value=\"5\"/>\n        </KeyFrame>\n        <KeyFrame Cue=\"62.5%\">\n          <Setter Property=\"StartAngle\" Value=\"180\"/>\n          <Setter Property=\"SweepAngle\" Value=\"50\"/>\n        </KeyFrame>\n        <KeyFrame Cue=\"75%\">\n          <Setter Property=\"StartAngle\" Value=\"360\"/>\n          <Setter Property=\"SweepAngle\" Value=\"100\"/>\n        </KeyFrame>\n        <KeyFrame Cue=\"87.5%\">\n          <Setter Property=\"StartAngle\" Value=\"540\"/>\n          <Setter Property=\"SweepAngle\" Value=\"50\"/>\n        </KeyFrame>\n        <KeyFrame Cue=\"100%\">\n          <Setter Property=\"StartAngle\" Value=\"720\"/>\n          <Setter Property=\"SweepAngle\" Value=\"0\"/>\n        </KeyFrame>\n      </Animation>\n    </Style.Animations>\n  </Style>\n\n  <Style Selector=\"controls|ProgressRing[IsEnabled=True]:not(:indeterminate) /template/ Ellipse#PART_Track\">\n    <Style.Animations>\n      <Animation Duration=\"0:0:1\" IterationCount=\"INFINITE\">\n        <KeyFrame Cue=\"0%\">\n          <Setter Property=\"Opacity\" Value=\"0.99\"/>\n        </KeyFrame>\n        <KeyFrame Cue=\"100%\">\n          <Setter Property=\"Opacity\" Value=\"1\"/>\n        </KeyFrame>\n      </Animation>\n    </Style.Animations>\n  </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/SemiStyles.axaml",
    "content": "﻿<Styles\n    x:Class=\"StabilityMatrix.Avalonia.Styles.SemiStyles\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:semi=\"https://irihi.tech/semi\">\n    <Styles.Resources>\n        <ResourceDictionary>\n            <ResourceDictionary.MergedDictionaries>\n                <!--  Shared  -->\n                <ResourceInclude Source=\"avares://Semi.Avalonia/Themes/Base.axaml\" />\n                <!--  Carousel  -->\n                <ResourceDictionary>\n                    <ResourceDictionary.ThemeDictionaries>\n                        <ResourceInclude x:Key=\"Default\" Source=\"avares://Semi.Avalonia/Themes/Light/Carousel.axaml\" />\n                        <ResourceInclude x:Key=\"Dark\" Source=\"avares://Semi.Avalonia/Themes/Dark/Carousel.axaml\" />\n                    </ResourceDictionary.ThemeDictionaries>\n                    <ResourceDictionary.MergedDictionaries>\n                        <ResourceInclude Source=\"avares://Semi.Avalonia/Controls/Carousel.axaml\" />\n                        <ResourceInclude Source=\"avares://Semi.Avalonia/Themes/Shared/Carousel.axaml\" />\n                    </ResourceDictionary.MergedDictionaries>\n                    <!--  Custom  -->\n                    <ControlTheme\n                        x:Key=\"FullCarousel\"\n                        BasedOn=\"{StaticResource FullCarousel}\"\n                        TargetType=\"Carousel\">\n                        <Setter Property=\"PageTransition\">\n                            <CompositePageTransition>\n                                <PageSlide Orientation=\"Horizontal\" Duration=\"0.167\">\n                                    <PageSlide.SlideInEasing>\n                                        <SineEaseIn />\n                                    </PageSlide.SlideInEasing>\n                                    <PageSlide.SlideOutEasing>\n                                        <SineEaseOut />\n                                    </PageSlide.SlideOutEasing>\n                                </PageSlide>\n                                <CrossFade Duration=\"0.167\" />\n                            </CompositePageTransition>\n                        </Setter>\n                    </ControlTheme>\n                </ResourceDictionary>\n            </ResourceDictionary.MergedDictionaries>\n        </ResourceDictionary>\n    </Styles.Resources>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/SemiStyles.axaml.cs",
    "content": "﻿using System;\nusing Avalonia.Markup.Xaml;\n\nnamespace StabilityMatrix.Avalonia.Styles;\n\npublic partial class SemiStyles : global::Avalonia.Styling.Styles\n{\n    public SemiStyles(IServiceProvider? provider = null)\n    {\n        AvaloniaXamlLoader.Load(provider, this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/SplitButtonStyles.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\">\n    <Design.PreviewWith>\n        <Border Padding=\"20\">\n            <StackPanel>\n                <SplitButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"success\"\n                    Content=\"Success Button\" />\n                <SplitButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"accent\"\n                    Content=\"FA Accent Button\" />\n                <SplitButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"systemaccent\"\n                    Content=\"System Accent Button\" />\n                <SplitButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"danger\"\n                    Content=\"Danger Button\" />\n                <SplitButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"info\"\n                    Content=\"Info Button\" />\n                <SplitButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"transparent-info\"\n                    Content=\"Semi-Transparent Info Button\" />\n                <SplitButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"transparent\"\n                    Content=\"Transparent Button\" />\n                <SplitButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"transparent-full\"\n                    Content=\"Full Transparent Button\" />\n                <SplitButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Content=\"Disabled Button\"\n                    IsEnabled=\"False\" />\n            </StackPanel>\n        </Border>\n    </Design.PreviewWith>\n\n    <!--  Success  -->\n    <Style Selector=\"SplitButton.success\">\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeGreenColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeGreenColor}\" />\n        </Style>\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <!--  Separator  -->\n        <Style Selector=\"^ /template/ Border#SeparatorBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n        </Style>\n\n        <!--  Pseudo-classes  -->\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkGreenColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkGreenColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkGreenColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkGreenColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Danger  -->\n    <Style Selector=\"SplitButton.danger\">\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeRedColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeRedColor}\" />\n        </Style>\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <!--  Separator  -->\n        <Style Selector=\"^ /template/ Border#SeparatorBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n        </Style>\n\n        <!--  Pseudo-classes  -->\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Info  -->\n    <Style Selector=\"SplitButton.info\">\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeLightBlueColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeLightBlueColor}\" />\n        </Style>\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <!--  Separator  -->\n        <Style Selector=\"^ /template/ Border#SeparatorBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n        </Style>\n\n        <!--  Pseudo-classes  -->\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeBlueColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeBlueColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Accent Button  -->\n    <Style Selector=\"SplitButton.accent\">\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackground}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrush}\" />\n        </Style>\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForeground}\" />\n        </Style>\n\n        <!--  Separator  -->\n        <Style Selector=\"^ /template/ Border#SeparatorBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeAccentColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeAccentColor}\" />\n        </Style>\n\n        <!--  Pseudo-classes  -->\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackgroundPointerOver}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrushPointerOver}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackgroundPointerOver}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrushPointerOver}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrushPressed}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrushPressed}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AccentButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  SystemAccent  -->\n    <Style Selector=\"SplitButton.systemaccent\">\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColor}\" />\n        </Style>\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <!--  Separator  -->\n        <Style Selector=\"^ /template/ Border#SeparatorBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n        </Style>\n\n        <!--  Pseudo-classes  -->\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark1}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColorDark1}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark1}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColorDark1}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Transparent  -->\n    <Style Selector=\"SplitButton.transparent\">\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrush}\" />\n        </Style>\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <!--  Pseudo-classes  -->\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Transparent-Full  -->\n    <Style Selector=\"SplitButton.transparent-full\">\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n        </Style>\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <!--  Pseudo-classes  -->\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Semi-Transparent Info  -->\n    <Style Selector=\"SplitButton.transparent-info\">\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeLightBlueColorTransparent}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeLightBlueColorTransparent}\" />\n        </Style>\n        <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <!--  Separator  -->\n        <Style Selector=\"^ /template/ Border#SeparatorBorder\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n        </Style>\n\n        <!--  Pseudo-classes  -->\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueColorTransparent}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeBlueColorTransparent}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueColorTransparent}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeBlueColorTransparent}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_PrimaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^ /template/ Button#PART_SecondaryButton:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ Button /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n</Styles>\n\n\n\n\n\n\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/TextBoxStyles.axaml",
    "content": "<Styles xmlns=\"https://github.com/avaloniaui\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:fluentIcons=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n        xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\">\n    <Design.PreviewWith>\n        <Panel MinWidth=\"300\">\n            <StackPanel Margin=\"16\" Spacing=\"6\">\n                <TextBox Classes=\"search\" />\n                <TextBox Classes=\"search\" Text=\"Some Text\" />\n            </StackPanel>\n        </Panel>\n    </Design.PreviewWith>\n    \n    <Styles.Resources>\n        <converters:FuncCommandConverter x:Key=\"FuncCommandConverter\"/>\n    </Styles.Resources>\n\n    <!-- Success -->\n    <Style Selector=\"TextBox.search\">\n        <Setter Property=\"InnerRightContent\">\n            <Template>\n                <Grid>\n                    <Button Classes=\"transparent-full\"\n                            IsVisible=\"{Binding $parent[TextBox].Text, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                            Command=\"{Binding $parent[TextBox], Converter={StaticResource FuncCommandConverter}, ConverterParameter=Clear}\">\n                        <fluentIcons:SymbolIcon Symbol=\"Cancel\" />\n                    </Button>\n                    <fluentIcons:SymbolIcon\n                        IsVisible=\"{Binding $parent[TextBox].Text, Converter={x:Static StringConverters.IsNullOrEmpty}}\"\n                        Margin=\"0,0,10,0\"\n                        FontSize=\"16\"\n                        Symbol=\"Find\" />\n                </Grid>\n            </Template>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ThemeColors.axaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:sty=\"clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia\">\n    <!--  Static colors  -->\n    <Color x:Key=\"ThemePrimaryColor\">#333333</Color>\n    <Color x:Key=\"ThemeDarkDarkRedColor\">#952923</Color>\n    <Color x:Key=\"ThemeDarkRedColor\">#C2362E</Color>\n    <Color x:Key=\"ThemeRedColor\">#F44336</Color>\n    <Color x:Key=\"ThemeCoralRedColor\">#D65A5A</Color>\n    <Color x:Key=\"ThemeDarkCoralRedColor\">#AA2A2A</Color>\n    <Color x:Key=\"ThemePinkColor\">#E91E63</Color>\n    <Color x:Key=\"ThemePurpleColor\">#9C27B0</Color>\n    <Color x:Key=\"ThemeDeepPurpleColor\">#673AB7</Color>\n    <Color x:Key=\"ThemeIndigoColor\">#3F51B5</Color>\n    <Color x:Key=\"ThemeDarkBlueColor\">#1A72BD</Color>\n    <Color x:Key=\"ThemeDarkBlueColorTransparent\">#AA1A72BD</Color>\n    <Color x:Key=\"ThemeBlueColor\">#2196F3</Color>\n    <Color x:Key=\"ThemeBlueColorTransparent\">#AA2196F3</Color>\n    <Color x:Key=\"ThemeLightBlueColor\">#03A9F4</Color>\n    <Color x:Key=\"ThemeLightBlueColorTransparent\">#AA03A9F4</Color>\n    <Color x:Key=\"ThemeCyanColor\">#00BCD4</Color>\n    <Color x:Key=\"ThemeTealColor\">#009688</Color>\n    <Color x:Key=\"ThemeDarkDarkGreenColor\">#2C582C</Color>\n    <SolidColorBrush x:Key=\"ThemeDarkDarkGreenColorBrush\">#2C582C</SolidColorBrush>\n    <Color x:Key=\"ThemeDarkGreenColor\">#3A783C</Color>\n    <Color x:Key=\"ThemeGreenColor\">#4BA04F</Color>\n    <Color x:Key=\"ThemeGreenColorTransparent\">#AA4BA04F</Color>\n    <Color x:Key=\"ThemeMediumSeaGreenColor\">#6CCB5F</Color>\n    <Color x:Key=\"ThemeLightGreenColor\">#8BC34A</Color>\n    <Color x:Key=\"ThemeLimeColor\">#CDDC39</Color>\n    <Color x:Key=\"ThemeYellowColor\">#FFEB3B</Color>\n    <Color x:Key=\"ThemeAmberColor\">#FFC107</Color>\n    <Color x:Key=\"ThemeOrangeColor\">#FF9800</Color>\n    <Color x:Key=\"ThemeDeepOrangeColor\">#FF5722</Color>\n    <Color x:Key=\"ThemeDeepOrangeColorTransparent\">#AAFF5722</Color>\n    <Color x:Key=\"ThemeEldenRingOrangeColor\">#FF4F00</Color>\n    <Color x:Key=\"ThemeBrownColor\">#795548</Color>\n    <Color x:Key=\"ThemeGreyColor\">#9E9E9E</Color>\n    <Color x:Key=\"ThemeLightGreyColor\">#C0C0C0</Color>\n    <Color x:Key=\"ThemeBlueGreyColor\">#607D8B</Color>\n\n    <!--  Dark / light dynamic colors  -->\n    <ResourceDictionary.ThemeDictionaries>\n        <ResourceDictionary x:Key=\"Default\">\n            <SolidColorBrush x:Key=\"ThemeBackgroundBrush\" Color=\"#AAAAAA\" />\n            <SolidColorBrush x:Key=\"ThemeDarkAccentForegroundBrush\" Color=\"#AAAAAA\" />\n            <SolidColorBrush x:Key=\"ThemeDarkAccentBackgroundBrush\" Color=\"#686868\" />\n            <SolidColorBrush x:Key=\"RegionColor\" Color=\"#AAAAAA\" />\n\n            <SolidColorBrush x:Key=\"CompletionBorderBrush\" Color=\"#43454A\" />\n            <SolidColorBrush x:Key=\"CompletionBackgroundBrush\" Color=\"#2B2D30\" />\n            <SolidColorBrush x:Key=\"CompletionSecondaryBackgroundBrush\" Color=\"#393B40\" />\n            <SolidColorBrush x:Key=\"CompletionForegroundBrush\" Color=\"#B4B8BF\" />\n            <SolidColorBrush x:Key=\"CompletionSecondaryForegroundBrush\" Color=\"#878B8D\" />\n            <SolidColorBrush x:Key=\"CompletionSelectionBackgroundBrush\" Color=\"#2E436E\" />\n            <SolidColorBrush x:Key=\"CompletionSelectionForegroundBrush\" Color=\"#5389F4\" />\n        </ResourceDictionary>\n\n        <ResourceDictionary x:Key=\"Dark\">\n            <SolidColorBrush x:Key=\"ThemeBackgroundBrush\" Color=\"#282828\" />\n            <SolidColorBrush x:Key=\"ThemeDarkAccentForegroundBrush\" Color=\"#AAAAAA\" />\n            <SolidColorBrush x:Key=\"ThemeDarkAccentBackgroundBrush\" Color=\"#252525\" />\n            <SolidColorBrush x:Key=\"RegionColor\" Color=\"#282828\" />\n\n            <SolidColorBrush x:Key=\"CompletionBorderBrush\" Color=\"#43454A\" />\n            <SolidColorBrush x:Key=\"CompletionBackgroundBrush\" Color=\"#2B2D30\" />\n            <SolidColorBrush x:Key=\"CompletionSecondaryBackgroundBrush\" Color=\"#393B40\" />\n            <SolidColorBrush x:Key=\"CompletionForegroundBrush\" Color=\"#B4B8BF\" />\n            <SolidColorBrush x:Key=\"CompletionSecondaryForegroundBrush\" Color=\"#878B8D\" />\n            <SolidColorBrush x:Key=\"CompletionSelectionBackgroundBrush\" Color=\"#2E436E\" />\n            <SolidColorBrush x:Key=\"CompletionSelectionForegroundBrush\" Color=\"#5389F4\" />\n        </ResourceDictionary>\n\n        <ResourceDictionary x:Key=\"{x:Static sty:FluentAvaloniaTheme.HighContrastTheme}\">\n            <SolidColorBrush x:Key=\"ThemeBackgroundBrush\" Color=\"#AAAAAA\" />\n            <SolidColorBrush x:Key=\"ThemeDarkAccentForegroundBrush\" Color=\"#AAAAAA\" />\n            <SolidColorBrush x:Key=\"ThemeDarkAccentBackgroundBrush\" Color=\"#686868\" />\n            <SolidColorBrush x:Key=\"RegionColor\" Color=\"#AAAAAA\" />\n\n            <SolidColorBrush x:Key=\"CompletionBorderBrush\" Color=\"#43454A\" />\n            <SolidColorBrush x:Key=\"CompletionBackgroundBrush\" Color=\"#2B2D30\" />\n            <SolidColorBrush x:Key=\"CompletionSecondaryBackgroundBrush\" Color=\"#393B40\" />\n            <SolidColorBrush x:Key=\"CompletionForegroundBrush\" Color=\"#B4B8BF\" />\n            <SolidColorBrush x:Key=\"CompletionSecondaryForegroundBrush\" Color=\"#878B8D\" />\n            <SolidColorBrush x:Key=\"CompletionSelectionBackgroundBrush\" Color=\"#2E436E\" />\n            <SolidColorBrush x:Key=\"CompletionSelectionForegroundBrush\" Color=\"#5389F4\" />\n        </ResourceDictionary>\n    </ResourceDictionary.ThemeDictionaries>\n\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ThemeColors.cs",
    "content": "﻿using Avalonia.Media;\n\nnamespace StabilityMatrix.Avalonia.Styles;\n\npublic static class ThemeColors\n{\n    public static readonly SolidColorBrush ThemeGreen = SolidColorBrush.Parse(\"#4caf50\");\n    public static readonly SolidColorBrush ThemeRed = SolidColorBrush.Parse(\"#f44336\");\n    public static readonly SolidColorBrush ThemeYellow = SolidColorBrush.Parse(\"#ffeb3b\");\n\n    public static readonly SolidColorBrush AmericanYellow = SolidColorBrush.Parse(\"#f2ac08\");\n    public static readonly SolidColorBrush HalloweenOrange = SolidColorBrush.Parse(\"#ed5D1f\");\n    public static readonly SolidColorBrush LightSteelBlue = SolidColorBrush.Parse(\"#b4c7d9\");\n    public static readonly SolidColorBrush DeepMagenta = SolidColorBrush.Parse(\"#dd00dd\");\n    public static readonly SolidColorBrush LuminousGreen = SolidColorBrush.Parse(\"#00aa00\");\n\n    public static readonly SolidColorBrush BrilliantAzure = SolidColorBrush.Parse(\"#3990f6\");\n\n    public static readonly SolidColorBrush CompletionSelectionBackgroundBrush =\n        SolidColorBrush.Parse(\"#2E436E\");\n    public static readonly SolidColorBrush CompletionSelectionForegroundBrush =\n        SolidColorBrush.Parse(\"#5389F4\");\n    public static readonly SolidColorBrush CompletionForegroundBrush = SolidColorBrush.Parse(\n        \"#B4B8BF\"\n    );\n\n    public static readonly SolidColorBrush EditorSelectionBrush = SolidColorBrush.Parse(\"#214283\");\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ThemeMaterials.axaml",
    "content": "﻿<ResourceDictionary xmlns=\"https://github.com/avaloniaui\"\n                    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                    xmlns:sty=\"clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia\">\n    \n    <ExperimentalAcrylicMaterial\n        x:Key=\"OpaqueDarkAcrylicMaterial\"\n        FallbackColor=\"#1E1F22\"\n        TintColor=\"Black\"\n        TintOpacity=\"0.78\"\n        MaterialOpacity=\"0.9\"\n        BackgroundSource=\"None\" />\n    \n    <ResourceDictionary.ThemeDictionaries>\n        <ResourceDictionary x:Key=\"Default\">\n            <ExperimentalAcrylicMaterial\n                x:Key=\"ThemeDarkAcrylicMaterial\"\n                FallbackColor=\"{DynamicResource ThemeBackgroundColor}\"\n                TintColor=\"Black\"\n                TintOpacity=\"0.78\"\n                MaterialOpacity=\"0.9\"\n                BackgroundSource=\"Digger\" />\n    \n            <ExperimentalAcrylicMaterial\n                x:Key=\"ThemeTransparentAcrylicMaterial\"\n                FallbackColor=\"{DynamicResource ThemeBackgroundColor}\"\n                TintColor=\"{DynamicResource ThemeBackgroundColor}\"\n                TintOpacity=\"0.7\"\n                MaterialOpacity=\"0.5\"\n                BackgroundSource=\"Digger\" />\n        </ResourceDictionary>\n        \n        <ResourceDictionary x:Key=\"Dark\">\n            <ExperimentalAcrylicMaterial\n                x:Key=\"ThemeDarkAcrylicMaterial\"\n                FallbackColor=\"{DynamicResource ThemeBackgroundColor}\"\n                TintColor=\"Black\"\n                TintOpacity=\"0.78\"\n                MaterialOpacity=\"0.9\"\n                BackgroundSource=\"Digger\" />\n    \n            <ExperimentalAcrylicMaterial\n                x:Key=\"ThemeTransparentAcrylicMaterial\"\n                FallbackColor=\"{DynamicResource ThemeBackgroundColor}\"\n                TintColor=\"Black\"\n                TintOpacity=\"0.7\"\n                MaterialOpacity=\"0.5\"\n                BackgroundSource=\"Digger\" />\n        </ResourceDictionary>\n        \n        <ResourceDictionary x:Key=\"{x:Static sty:FluentAvaloniaTheme.HighContrastTheme}\">\n            <ExperimentalAcrylicMaterial\n                x:Key=\"ThemeDarkAcrylicMaterial\"\n                FallbackColor=\"{DynamicResource ThemeBackgroundColor}\"\n                TintColor=\"Black\"\n                TintOpacity=\"0.78\"\n                MaterialOpacity=\"0.9\"\n                BackgroundSource=\"Digger\" />\n    \n            <ExperimentalAcrylicMaterial\n                x:Key=\"ThemeTransparentAcrylicMaterial\"\n                FallbackColor=\"{DynamicResource ThemeBackgroundColor}\"\n                TintColor=\"Black\"\n                TintOpacity=\"0.7\"\n                MaterialOpacity=\"0.5\"\n                BackgroundSource=\"Digger\" />\n        </ResourceDictionary>\n    </ResourceDictionary.ThemeDictionaries>\n</ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Styles/ToggleButtonStyles.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\">\n    <Design.PreviewWith>\n        <Border Padding=\"20\">\n            <StackPanel>\n                <ToggleButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Content=\"Button\" />\n                <ToggleButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"success\"\n                    Content=\"Success Button\" />\n                <ToggleButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"systemaccent\"\n                    Content=\"System Accent Button\" />\n                <ToggleButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"danger\"\n                    Content=\"Danger Button\" />\n                <ToggleButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"info\"\n                    Content=\"Info Button\" />\n                <ToggleButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"transparent-info\"\n                    Content=\"Semi-Transparent Info Button\" />\n                <ToggleButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"transparent\"\n                    Content=\"Transparent Button\" />\n                <ToggleButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"transparent-full\"\n                    Content=\"Transparent Button\" />\n                <ToggleButton\n                    Margin=\"8\"\n                    HorizontalAlignment=\"Center\"\n                    Content=\"Disabled Button\"\n                    IsEnabled=\"False\" />\n            </StackPanel>\n        </Border>\n    </Design.PreviewWith>\n\n    <!--  Success  -->\n    <Style Selector=\"ToggleButton.success:checked\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeGreenColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeGreenColor}\" />\n        </Style>\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkGreenColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkGreenColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkGreenColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Danger  -->\n    <Style Selector=\"ToggleButton.danger:checked\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeRedColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeRedColor}\" />\n        </Style>\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Info  -->\n    <Style Selector=\"ToggleButton.info:checked\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeLightBlueColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeLightBlueColor}\" />\n        </Style>\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeBlueColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Transparent Info  -->\n    <Style Selector=\"ToggleButton.transparent-info:checked\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource ThemeLightBlueColorTransparent}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeLightBlueColorTransparent}\" />\n        </Style>\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueColorTransparent}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeBlueColorTransparent}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColorTransparent}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  SystemAccent  -->\n    <Style Selector=\"ToggleButton.systemaccent:checked\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColor}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColor}\" />\n        </Style>\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark1}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColorDark1}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Transparent  -->\n    <Style Selector=\"ToggleButton.transparent:unchecked\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrush}\" />\n        </Style>\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n\n    <!--  Full Transparent  -->\n    <Style Selector=\"ToggleButton.transparent-full:unchecked\">\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n            <Setter Property=\"BorderBrush\" Value=\"{DynamicResource SystemControlTransparentBrush}\" />\n        </Style>\n        <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n        </Style>\n\n        <Style Selector=\"^:pointerover\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPointerOver}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:pressed\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBackgroundPressed}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundPressed}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"^:disabled\">\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Background\" Value=\"{DynamicResource ButtonBackgroundDisabled}\" />\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ButtonBorderBrushDisabled}\" />\n            </Style>\n            <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForegroundDisabled}\" />\n            </Style>\n        </Style>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewLocator.cs",
    "content": "using System;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Templates;\nusing FluentAvalonia.UI.Controls;\nusing NLog;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia;\n\npublic class ViewLocator : IDataTemplate, INavigationPageFactory\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    /*/// <summary>\n    /// Weak Dictionary of (DataContext, View) pairs to keep the view and layout alive\n    /// </summary>\n    private static readonly ConditionalWeakTable<object, Control> PersistentViewCache = new();*/\n\n    /// <inheritdoc />\n    public Control Build(object? data)\n    {\n        if (data is null)\n            throw new ArgumentNullException(nameof(data));\n\n        var type = data.GetType();\n\n        if (Attribute.GetCustomAttribute(type, typeof(ViewAttribute)) is ViewAttribute viewAttr)\n        {\n            var viewType = viewAttr.ViewType;\n            return GetView(viewType, data, viewAttr.IsPersistent);\n        }\n\n        return new TextBlock { Text = \"View Model Not Found: \" + data.GetType().FullName };\n    }\n\n    private Control GetView(Type viewType)\n    {\n        if (App.Services.GetService(viewType) is Control view)\n        {\n            return view;\n        }\n\n        return new TextBlock { Text = \"View Not Found: \" + viewType.FullName };\n    }\n\n    private Control GetView(Type viewType, object context, bool persistent)\n    {\n        // Disregard persistent settings in design mode\n        if (Design.IsDesignMode)\n        {\n            persistent = false;\n        }\n\n        if (persistent)\n        {\n            // Check assignable from IPersistentViewProvider\n            if (context is not IPersistentViewProvider persistentViewProvider)\n            {\n                throw new InvalidOperationException(\n                    $\"View {viewType.Name} is marked as persistent but does not implement IPersistentViewProvider\"\n                );\n            }\n\n            // Try get from context\n            if (persistentViewProvider.AttachedPersistentView is { } view)\n            {\n                Logger.Trace(\"Got persistent view {ViewType} from context\", viewType.Name);\n\n                return view;\n            }\n\n            // Otherwise get from service provider\n            if (App.Services.GetService(viewType) is Control newView)\n            {\n                // Set as attached view\n                persistentViewProvider.AttachedPersistentView = newView;\n                Logger.Trace(\"Attached persistent view {ViewType}\", viewType.Name);\n                return newView;\n            }\n        }\n        else\n        {\n            // Get from service provider\n            if (App.Services.GetService(viewType) is Control view)\n            {\n                return view;\n            }\n        }\n\n        return new TextBlock { Text = \"View Not Found: \" + viewType.FullName };\n    }\n\n    /// <inheritdoc />\n    public bool Match(object? data)\n    {\n        return data is ViewModelBase;\n    }\n\n    /// <inheritdoc />\n    public Control? GetPage(Type srcType)\n    {\n        if (\n            Attribute.GetCustomAttribute(srcType, typeof(ViewAttribute))\n            is not ViewAttribute viewAttr\n        )\n        {\n            throw new InvalidOperationException(\"View not found for \" + srcType.FullName);\n        }\n\n        // Get new view\n        var view = GetView(viewAttr.ViewType);\n        view.DataContext ??= App.Services.GetService(srcType);\n\n        return view;\n    }\n\n    /// <inheritdoc />\n    public Control GetPageFromObject(object target)\n    {\n        if (\n            Attribute.GetCustomAttribute(target.GetType(), typeof(ViewAttribute))\n            is not ViewAttribute viewAttr\n        )\n        {\n            throw new InvalidOperationException(\"View not found for \" + target.GetType().FullName);\n        }\n\n        var viewType = viewAttr.ViewType;\n        var view = GetView(viewType, target, viewAttr.IsPersistent);\n        view.DataContext ??= target;\n        return view;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/ConsoleProgressViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\npublic partial class ConsoleProgressViewModel : ProgressViewModel\n{\n    public ConsoleViewModel Console { get; } = new();\n\n    [ObservableProperty]\n    private bool closeWhenFinished;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/ContentDialogProgressViewModelBase.cs",
    "content": "﻿using System;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing FluentAvalonia.UI.Controls;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\npublic partial class ContentDialogProgressViewModelBase : ConsoleProgressViewModel\n{\n    [ObservableProperty]\n    private bool hideCloseButton;\n\n    [ObservableProperty]\n    private bool autoScrollToBottom = true;\n\n    public event EventHandler<ContentDialogResult>? PrimaryButtonClick;\n    public event EventHandler<ContentDialogResult>? SecondaryButtonClick;\n    public event EventHandler<ContentDialogResult>? CloseButtonClick;\n\n    public virtual void OnPrimaryButtonClick()\n    {\n        PrimaryButtonClick?.Invoke(this, ContentDialogResult.Primary);\n    }\n\n    public virtual void OnSecondaryButtonClick()\n    {\n        SecondaryButtonClick?.Invoke(this, ContentDialogResult.Secondary);\n    }\n\n    public virtual void OnCloseButtonClick()\n    {\n        CloseButtonClick?.Invoke(this, ContentDialogResult.None);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/ContentDialogViewModelBase.cs",
    "content": "﻿using System;\nusing Avalonia.Threading;\nusing FluentAvalonia.UI.Controls;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\npublic class ContentDialogViewModelBase : DisposableViewModelBase\n{\n    public virtual string? Title { get; set; }\n\n    // Events for button clicks\n    public event EventHandler<ContentDialogResult>? PrimaryButtonClick;\n    public event EventHandler<ContentDialogResult>? SecondaryButtonClick;\n    public event EventHandler<ContentDialogResult>? CloseButtonClick;\n\n    public virtual void OnPrimaryButtonClick()\n    {\n        PrimaryButtonClick?.Invoke(this, ContentDialogResult.Primary);\n    }\n\n    public virtual void OnSecondaryButtonClick()\n    {\n        SecondaryButtonClick?.Invoke(this, ContentDialogResult.Secondary);\n    }\n\n    public virtual void OnCloseButtonClick()\n    {\n        CloseButtonClick?.Invoke(this, ContentDialogResult.None);\n    }\n\n    /// <summary>\n    /// Return a <see cref=\"BetterContentDialog\"/> that uses this view model as its content\n    /// </summary>\n    public virtual BetterContentDialog GetDialog()\n    {\n        Dispatcher.UIThread.VerifyAccess();\n\n        var dialog = new BetterContentDialog { Title = Title, Content = this };\n\n        return dialog;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/DisposableLoadableViewModelBase.cs",
    "content": "﻿using System;\nusing System.Reactive.Disposables;\nusing JetBrains.Annotations;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\npublic abstract class DisposableLoadableViewModelBase : LoadableViewModelBase, IDisposable\n{\n    private readonly CompositeDisposable instanceDisposables = new();\n\n    /// <summary>\n    /// Adds a disposable to be disposed when this view model is disposed.\n    /// </summary>\n    /// <param name=\"disposable\">The disposable to add.</param>\n    protected void AddDisposable([HandlesResourceDisposal] IDisposable disposable)\n    {\n        instanceDisposables.Add(disposable);\n    }\n\n    /// <summary>\n    /// Adds disposables to be disposed when this view model is disposed.\n    /// </summary>\n    /// <param name=\"disposables\">The disposables to add.</param>\n    protected void AddDisposable([HandlesResourceDisposal] params IDisposable[] disposables)\n    {\n        foreach (var disposable in disposables)\n        {\n            instanceDisposables.Add(disposable);\n        }\n    }\n\n    /// <summary>\n    /// Adds a disposable to be disposed when this view model is disposed.\n    /// </summary>\n    /// <param name=\"disposable\">The disposable to add.</param>\n    /// <typeparam name=\"T\">The type of the disposable.</typeparam>\n    /// <returns>The disposable that was added.</returns>\n    protected T AddDisposable<T>([HandlesResourceDisposal] T disposable)\n        where T : IDisposable\n    {\n        instanceDisposables.Add(disposable);\n        return disposable;\n    }\n\n    /// <summary>\n    /// Adds disposables to be disposed when this view model is disposed.\n    /// </summary>\n    /// <param name=\"disposables\">The disposables to add.</param>\n    /// <typeparam name=\"T\">The type of the disposables.</typeparam>\n    /// <returns>The disposables that were added.</returns>\n    protected T[] AddDisposable<T>([HandlesResourceDisposal] params T[] disposables)\n        where T : IDisposable\n    {\n        foreach (var disposable in disposables)\n        {\n            instanceDisposables.Add(disposable);\n        }\n\n        return disposables;\n    }\n\n    protected virtual void Dispose(bool disposing)\n    {\n        if (disposing)\n        {\n            instanceDisposables.Dispose();\n        }\n    }\n\n    public void Dispose()\n    {\n        Dispose(true);\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/DisposableViewModelBase.cs",
    "content": "﻿using System;\nusing System.Reactive.Disposables;\nusing JetBrains.Annotations;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\npublic abstract class DisposableViewModelBase : ViewModelBase, IDisposable\n{\n    private readonly CompositeDisposable instanceDisposables = new();\n\n    /// <summary>\n    /// Adds a disposable to be disposed when this view model is disposed.\n    /// </summary>\n    /// <param name=\"disposable\">The disposable to add.</param>\n    protected void AddDisposable([HandlesResourceDisposal] IDisposable disposable)\n    {\n        instanceDisposables.Add(disposable);\n    }\n\n    /// <summary>\n    /// Adds disposables to be disposed when this view model is disposed.\n    /// </summary>\n    /// <param name=\"disposables\">The disposables to add.</param>\n    protected void AddDisposable([HandlesResourceDisposal] params IDisposable[] disposables)\n    {\n        foreach (var disposable in disposables)\n        {\n            instanceDisposables.Add(disposable);\n        }\n    }\n\n    /// <summary>\n    /// Adds a disposable to be disposed when this view model is disposed.\n    /// </summary>\n    /// <param name=\"disposable\">The disposable to add.</param>\n    /// <typeparam name=\"T\">The type of the disposable.</typeparam>\n    /// <returns>The disposable that was added.</returns>\n    protected T AddDisposable<T>([HandlesResourceDisposal] T disposable)\n        where T : IDisposable\n    {\n        instanceDisposables.Add(disposable);\n        return disposable;\n    }\n\n    /// <summary>\n    /// Adds disposables to be disposed when this view model is disposed.\n    /// </summary>\n    /// <param name=\"disposables\">The disposables to add.</param>\n    /// <typeparam name=\"T\">The type of the disposables.</typeparam>\n    /// <returns>The disposables that were added.</returns>\n    protected T[] AddDisposable<T>([HandlesResourceDisposal] params T[] disposables)\n        where T : IDisposable\n    {\n        foreach (var disposable in disposables)\n        {\n            instanceDisposables.Add(disposable);\n        }\n\n        return disposables;\n    }\n\n    protected virtual void Dispose(bool disposing)\n    {\n        if (disposing)\n        {\n            instanceDisposables.Dispose();\n        }\n    }\n\n    public void Dispose()\n    {\n        Dispose(true);\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.ComponentModel.DataAnnotations;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.IO;\nusing System.Linq;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.Input;\nusing ExifLibrary;\nusing FluentAvalonia.UI.Controls;\nusing NLog;\nusing Refit;\nusing Semver;\nusing SkiaSharp;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Inference;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Api.Comfy.WebSocketData;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Inference;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Services;\nusing Notification = DesktopNotifications.Notification;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\n/// <summary>\n/// Abstract base class for tab view models that generate images using ClientManager.\n/// This includes a progress reporter, image output view model, and generation virtual methods.\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"VirtualMemberNeverOverridden.Global\")]\npublic abstract partial class InferenceGenerationViewModelBase\n    : InferenceTabViewModelBase,\n        IImageGalleryComponent\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private readonly ISettingsManager settingsManager;\n    private readonly RunningPackageService runningPackageService;\n    private readonly INotificationService notificationService;\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n\n    [JsonPropertyName(\"ImageGallery\")]\n    public ImageGalleryCardViewModel ImageGalleryCardViewModel { get; }\n\n    [JsonIgnore]\n    public ImageFolderCardViewModel ImageFolderCardViewModel { get; }\n\n    [JsonIgnore]\n    public ProgressViewModel OutputProgress { get; } = new();\n\n    [JsonIgnore]\n    public IInferenceClientManager ClientManager { get; }\n\n    /// <inheritdoc />\n    protected InferenceGenerationViewModelBase(\n        IServiceManager<ViewModelBase> vmFactory,\n        IInferenceClientManager inferenceClientManager,\n        INotificationService notificationService,\n        ISettingsManager settingsManager,\n        RunningPackageService runningPackageService\n    )\n        : base(notificationService)\n    {\n        this.notificationService = notificationService;\n        this.settingsManager = settingsManager;\n        this.runningPackageService = runningPackageService;\n        this.vmFactory = vmFactory;\n\n        ClientManager = inferenceClientManager;\n\n        ImageGalleryCardViewModel = vmFactory.Get<ImageGalleryCardViewModel>();\n        ImageFolderCardViewModel = AddDisposable(vmFactory.Get<ImageFolderCardViewModel>());\n\n        GenerateImageCommand.WithConditionalNotificationErrorHandler(notificationService);\n    }\n\n    /// <summary>\n    /// Write an image to the default output folder\n    /// </summary>\n    protected Task<FilePath> WriteOutputImageAsync(\n        Stream imageStream,\n        ImageGenerationEventArgs args,\n        int batchNum = 0,\n        int batchTotal = 0,\n        bool isGrid = false,\n        string fileExtension = \"png\"\n    )\n    {\n        var defaultOutputDir = settingsManager.ImagesInferenceDirectory;\n        defaultOutputDir.Create();\n\n        return WriteOutputImageAsync(\n            imageStream,\n            defaultOutputDir,\n            args,\n            batchNum,\n            batchTotal,\n            isGrid,\n            fileExtension\n        );\n    }\n\n    /// <summary>\n    /// Write an image to an output folder\n    /// </summary>\n    protected async Task<FilePath> WriteOutputImageAsync(\n        Stream imageStream,\n        DirectoryPath outputDir,\n        ImageGenerationEventArgs args,\n        int batchNum = 0,\n        int batchTotal = 0,\n        bool isGrid = false,\n        string fileExtension = \"png\"\n    )\n    {\n        var formatTemplateStr = settingsManager.Settings.InferenceOutputImageFileNameFormat;\n\n        var formatProvider = new FileNameFormatProvider\n        {\n            GenerationParameters = args.Parameters,\n            ProjectType = args.Project?.ProjectType,\n            ProjectName = ProjectFile?.NameWithoutExtension,\n        };\n\n        // Parse to format\n        if (\n            string.IsNullOrEmpty(formatTemplateStr)\n            || !FileNameFormat.TryParse(formatTemplateStr, formatProvider, out var format)\n        )\n        {\n            // Fallback to default\n            Logger.Warn(\n                \"Failed to parse format template: {FormatTemplate}, using default\",\n                formatTemplateStr\n            );\n\n            format = FileNameFormat.Parse(FileNameFormat.DefaultTemplate, formatProvider);\n        }\n\n        if (isGrid)\n        {\n            format = format.WithGridPrefix();\n        }\n\n        if (batchNum >= 1 && batchTotal > 1)\n        {\n            format = format.WithBatchPostFix(batchNum, batchTotal);\n        }\n\n        var fileName = format.GetFileName();\n        var file = outputDir.JoinFile($\"{fileName}.{fileExtension}\");\n\n        // Until the file is free, keep adding _{i} to the end\n        for (var i = 0; i < 100; i++)\n        {\n            if (!file.Exists)\n                break;\n\n            file = outputDir.JoinFile($\"{fileName}_{i + 1}.{fileExtension}\");\n        }\n\n        // If that fails, append an 7-char uuid\n        if (file.Exists)\n        {\n            var uuid = Guid.NewGuid().ToString(\"N\")[..7];\n            file = outputDir.JoinFile($\"{fileName}_{uuid}.{fileExtension}\");\n        }\n\n        if (file.Info.DirectoryName != null)\n        {\n            Directory.CreateDirectory(file.Info.DirectoryName);\n        }\n\n        await using var fileStream = file.Info.OpenWrite();\n        await imageStream.CopyToAsync(fileStream);\n\n        return file;\n    }\n\n    /// <summary>\n    /// Builds the image generation prompt\n    /// </summary>\n    protected virtual void BuildPrompt(BuildPromptEventArgs args) { }\n\n    /// <summary>\n    /// Uploads files required for the prompt\n    /// </summary>\n    protected virtual async Task UploadPromptFiles(\n        IEnumerable<(string SourcePath, string DestinationRelativePath)> files,\n        ComfyClient client\n    )\n    {\n        foreach (var (sourcePath, destinationRelativePath) in files)\n        {\n            Logger.Debug(\n                \"Uploading prompt file {SourcePath} to relative path {DestinationPath}\",\n                sourcePath,\n                destinationRelativePath\n            );\n\n            await client.UploadFileAsync(sourcePath, destinationRelativePath);\n        }\n    }\n\n    /// <summary>\n    /// Gets ImageSources that need to be uploaded as inputs\n    /// </summary>\n    protected virtual IEnumerable<ImageSource> GetInputImages()\n    {\n        return Enumerable.Empty<ImageSource>();\n    }\n\n    protected async Task UploadInputImages(ComfyClient client)\n    {\n        foreach (var image in GetInputImages())\n        {\n            await ClientManager.UploadInputImageAsync(image);\n        }\n    }\n\n    public async Task RunCustomGeneration(\n        InferenceQueueCustomPromptEventArgs args,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (ClientManager.Client is not { } client)\n        {\n            throw new InvalidOperationException(\"Client is not connected\");\n        }\n\n        var generationArgs = new ImageGenerationEventArgs\n        {\n            Client = client,\n            Nodes = args.Builder.ToNodeDictionary(),\n            OutputNodeNames = args.Builder.Connections.OutputNodeNames.ToArray(),\n            Project = InferenceProjectDocument.FromLoadable(this),\n            FilesToTransfer = args.FilesToTransfer,\n            Parameters = new GenerationParameters(),\n            ClearOutputImages = true,\n        };\n\n        await RunGeneration(generationArgs, cancellationToken);\n    }\n\n    /// <summary>\n    /// Runs a generation task\n    /// </summary>\n    /// <exception cref=\"InvalidOperationException\">Thrown if args.Parameters or args.Project are null</exception>\n    protected async Task RunGeneration(ImageGenerationEventArgs args, CancellationToken cancellationToken)\n    {\n        var client = args.Client;\n        var nodes = args.Nodes;\n\n        // Checks\n        if (args.Parameters is null)\n            throw new InvalidOperationException(\"Parameters is null\");\n        if (args.Project is null)\n            throw new InvalidOperationException(\"Project is null\");\n        if (args.OutputNodeNames.Count == 0)\n            throw new InvalidOperationException(\"OutputNodeNames is empty\");\n        if (client.OutputImagesDir is null)\n            throw new InvalidOperationException(\"OutputImagesDir is null\");\n\n        // Only check extensions for first batch index\n        if (args.BatchIndex == 0)\n        {\n            if (!await CheckPromptExtensionsInstalled(args.Nodes))\n            {\n                throw new ValidationException(\"Prompt extensions not installed\");\n            }\n        }\n\n        // Upload input images\n        await UploadInputImages(client);\n\n        // Upload required files\n        await UploadPromptFiles(args.FilesToTransfer, client);\n\n        // Connect preview image handler\n        client.PreviewImageReceived += OnPreviewImageReceived;\n\n        // Register to interrupt if user cancels\n        var promptInterrupt = cancellationToken.Register(() =>\n        {\n            Logger.Info(\"Cancelling prompt\");\n            client\n                .InterruptPromptAsync(new CancellationTokenSource(5000).Token)\n                .SafeFireAndForget(ex =>\n                {\n                    Logger.Warn(ex, \"Error while interrupting prompt\");\n                });\n        });\n\n        ComfyTask? promptTask = null;\n\n        try\n        {\n            var timer = Stopwatch.StartNew();\n\n            try\n            {\n                promptTask = await client.QueuePromptAsync(nodes, cancellationToken);\n            }\n            catch (ApiException e)\n            {\n                Logger.Warn(e, \"Api exception while queuing prompt\");\n                await DialogHelper.CreateApiExceptionDialog(e, \"Api Error\").ShowAsync();\n                return;\n            }\n\n            // Register progress handler\n            promptTask.ProgressUpdate += OnProgressUpdateReceived;\n\n            // Delay attaching running node change handler to not show indeterminate progress\n            // if progress updates are received before the prompt starts\n            Task.Run(\n                    async () =>\n                    {\n                        try\n                        {\n                            var delayTime = 250 - (int)timer.ElapsedMilliseconds;\n                            if (delayTime > 0)\n                            {\n                                await Task.Delay(delayTime, cancellationToken);\n                            }\n\n                            // ReSharper disable once AccessToDisposedClosure\n                            AttachRunningNodeChangedHandler(promptTask);\n                        }\n                        catch (TaskCanceledException) { }\n                    },\n                    cancellationToken\n                )\n                .SafeFireAndForget(ex =>\n                {\n                    if (ex is TaskCanceledException)\n                        return;\n\n                    Logger.Error(ex, \"Error while attaching running node change handler\");\n                });\n\n            // Wait for prompt to finish\n            try\n            {\n                await promptTask.Task.WaitAsync(cancellationToken);\n                Logger.Debug($\"Prompt task {promptTask.Id} finished\");\n            }\n            catch (ComfyNodeException e)\n            {\n                Logger.Warn(e, \"Comfy node exception while queuing prompt\");\n                await DialogHelper\n                    .CreateJsonDialog(e.JsonData, \"Comfy Error\", \"Node execution encountered an error\")\n                    .ShowAsync();\n                return;\n            }\n\n            // Get output images\n            var imageOutputs = await client.GetImagesForExecutedPromptAsync(promptTask.Id, cancellationToken);\n\n            if (imageOutputs.Values.All(images => images is null or { Count: 0 }))\n            {\n                // No images match\n                notificationService.Show(\n                    \"No output\",\n                    \"Did not receive any output images\",\n                    NotificationType.Warning\n                );\n                return;\n            }\n\n            // Disable cancellation\n            await promptInterrupt.DisposeAsync();\n\n            if (args.ClearOutputImages)\n            {\n                ImageGalleryCardViewModel.ImageSources.Clear();\n            }\n\n            var outputImages = await ProcessAllOutputImages(imageOutputs, args);\n\n            var notificationImage = outputImages.FirstOrDefault()?.LocalFile;\n\n            await notificationService.ShowAsync(\n                NotificationKey.Inference_PromptCompleted,\n                new Notification\n                {\n                    Title = \"Prompt Completed\",\n                    Body = $\"Prompt [{promptTask.Id[..7].ToLower()}] completed successfully\",\n                    BodyImagePath = notificationImage?.FullPath,\n                }\n            );\n        }\n        finally\n        {\n            // Disconnect progress handler\n            client.PreviewImageReceived -= OnPreviewImageReceived;\n\n            // Clear progress\n            OutputProgress.ClearProgress();\n            // ImageGalleryCardViewModel.PreviewImage?.Dispose();\n            ImageGalleryCardViewModel.PreviewImage = null;\n            ImageGalleryCardViewModel.IsPreviewOverlayEnabled = false;\n\n            // Cleanup tasks\n            promptTask?.Dispose();\n        }\n    }\n\n    private async Task<IEnumerable<ImageSource>> ProcessAllOutputImages(\n        IReadOnlyDictionary<string, List<ComfyImage>?> images,\n        ImageGenerationEventArgs args\n    )\n    {\n        var results = new List<ImageSource>();\n\n        foreach (var (nodeName, imageList) in images)\n        {\n            if (imageList is null)\n            {\n                Logger.Warn(\"No images for node {NodeName}\", nodeName);\n                continue;\n            }\n\n            results.AddRange(await ProcessOutputImages(imageList, args, nodeName.Replace('_', ' ')));\n        }\n\n        return results;\n    }\n\n    /// <summary>\n    /// Handles image output metadata for generation runs\n    /// </summary>\n    private async Task<List<ImageSource>> ProcessOutputImages(\n        IReadOnlyCollection<ComfyImage> images,\n        ImageGenerationEventArgs args,\n        string? imageLabel = null\n    )\n    {\n        var client = args.Client;\n\n        // Write metadata to images\n        var outputImagesBytes = new List<byte[]>();\n        var outputImages = new List<ImageSource>();\n\n        foreach (var (i, comfyImage) in images.Enumerate())\n        {\n            Logger.Debug(\"Downloading image: {FileName}\", comfyImage.FileName);\n            var imageStream = await client.GetImageStreamAsync(comfyImage);\n\n            using var ms = new MemoryStream();\n            await imageStream.CopyToAsync(ms);\n\n            var imageArray = ms.ToArray();\n            outputImagesBytes.Add(imageArray);\n\n            var parameters = args.Parameters!;\n            var project = args.Project!;\n\n            // Lock seed\n            project.TryUpdateModel<SeedCardModel>(\"Seed\", model => model with { IsRandomizeEnabled = false });\n\n            // Seed and batch override for batches\n            if (images.Count > 1 && project.ProjectType is InferenceProjectType.TextToImage)\n            {\n                project = (InferenceProjectDocument)project.Clone();\n\n                // Set batch size indexes\n                project.TryUpdateModel(\n                    \"BatchSize\",\n                    node =>\n                    {\n                        node[nameof(BatchSizeCardViewModel.BatchCount)] = 1;\n                        node[nameof(BatchSizeCardViewModel.IsBatchIndexEnabled)] = true;\n                        node[nameof(BatchSizeCardViewModel.BatchIndex)] = i + 1;\n                        return node;\n                    }\n                );\n            }\n\n            if (comfyImage.FileName.EndsWith(\".png\"))\n            {\n                var bytesWithMetadata = PngDataHelper.AddMetadata(imageArray, parameters, project);\n\n                // Write using generated name\n                var filePath = await WriteOutputImageAsync(\n                    new MemoryStream(bytesWithMetadata),\n                    args,\n                    i + 1,\n                    images.Count\n                );\n\n                outputImages.Add(new ImageSource(filePath) { Label = imageLabel });\n                EventManager.Instance.OnImageFileAdded(filePath);\n            }\n            else if (comfyImage.FileName.EndsWith(\".webp\"))\n            {\n                var opts = new JsonSerializerOptions\n                {\n                    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,\n                    Converters = { new JsonStringEnumConverter() },\n                };\n                var paramsJson = JsonSerializer.Serialize(parameters, opts);\n                var smProject = JsonSerializer.Serialize(project, opts);\n                var metadata = new Dictionary<ExifTag, string>\n                {\n                    { ExifTag.ImageDescription, paramsJson },\n                    { ExifTag.Software, smProject },\n                };\n\n                var bytesWithMetadata = ImageMetadata.AddMetadataToWebp(imageArray, metadata);\n\n                // Write using generated name\n                var filePath = await WriteOutputImageAsync(\n                    new MemoryStream(bytesWithMetadata.ToArray()),\n                    args,\n                    i + 1,\n                    images.Count,\n                    fileExtension: Path.GetExtension(comfyImage.FileName).Replace(\".\", \"\")\n                );\n\n                outputImages.Add(new ImageSource(filePath) { Label = imageLabel });\n                EventManager.Instance.OnImageFileAdded(filePath);\n            }\n            else\n            {\n                // Write using generated name\n                var filePath = await WriteOutputImageAsync(\n                    new MemoryStream(imageArray),\n                    args,\n                    i + 1,\n                    images.Count,\n                    fileExtension: Path.GetExtension(comfyImage.FileName).Replace(\".\", \"\")\n                );\n\n                outputImages.Add(new ImageSource(filePath) { Label = imageLabel });\n                EventManager.Instance.OnImageFileAdded(filePath);\n            }\n        }\n\n        // Download all images to make grid, if multiple\n        if (outputImages.Count > 1)\n        {\n            var loadedImages = outputImagesBytes.Select(SKImage.FromEncodedData).ToImmutableArray();\n\n            var project = args.Project!;\n\n            // Lock seed\n            project.TryUpdateModel<SeedCardModel>(\"Seed\", model => model with { IsRandomizeEnabled = false });\n\n            var grid = ImageProcessor.CreateImageGrid(loadedImages);\n            var gridBytes = grid.Encode().ToArray();\n            var gridBytesWithMetadata = PngDataHelper.AddMetadata(gridBytes, args.Parameters!, args.Project!);\n\n            // Save to disk\n            var gridPath = await WriteOutputImageAsync(\n                new MemoryStream(gridBytesWithMetadata),\n                args,\n                isGrid: true\n            );\n\n            // Insert to start of images\n            var gridImage = new ImageSource(gridPath);\n            outputImages.Insert(0, gridImage);\n            EventManager.Instance.OnImageFileAdded(gridPath);\n        }\n\n        foreach (var img in outputImages)\n        {\n            // Preload\n            await img.GetBitmapAsync();\n            // Add images\n            ImageGalleryCardViewModel.ImageSources.Add(img);\n        }\n\n        return outputImages;\n    }\n\n    /// <summary>\n    /// Implementation for Generate Image\n    /// </summary>\n    protected virtual Task GenerateImageImpl(GenerateOverrides overrides, CancellationToken cancellationToken)\n    {\n        return Task.CompletedTask;\n    }\n\n    /// <summary>\n    /// Command for the Generate Image button\n    /// </summary>\n    /// <param name=\"options\">Optional overrides (side buttons)</param>\n    /// <param name=\"cancellationToken\">Cancellation token</param>\n    [RelayCommand(IncludeCancelCommand = true, FlowExceptionsToTaskScheduler = true)]\n    private async Task GenerateImage(\n        GenerateFlags options = default,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var overrides = GenerateOverrides.FromFlags(options);\n\n        try\n        {\n            await GenerateImageImpl(overrides, cancellationToken);\n        }\n        catch (OperationCanceledException)\n        {\n            Logger.Debug(\"Image Generation Canceled\");\n        }\n        catch (ValidationException e)\n        {\n            Logger.Debug(\"Image Generation Validation Error: {Message}\", e.Message);\n            notificationService.Show(\"Validation Error\", e.Message, NotificationType.Error);\n        }\n    }\n\n    /// <summary>\n    /// Shows a prompt and return false if client not connected\n    /// </summary>\n    protected async Task<bool> CheckClientConnectedWithPrompt()\n    {\n        if (ClientManager.IsConnected)\n            return true;\n\n        var vm = vmFactory.Get<InferenceConnectionHelpViewModel>();\n        await vm.CreateDialog().ShowAsync();\n\n        return ClientManager.IsConnected;\n    }\n\n    /// <summary>\n    /// Shows a dialog and return false if prompt required extensions not installed\n    /// </summary>\n    private async Task<bool> CheckPromptExtensionsInstalled(NodeDictionary nodeDictionary)\n    {\n        // Get prompt required extensions\n        // Just static for now but could do manifest lookup when we support custom workflows\n        var requiredExtensionSpecifiers = nodeDictionary\n            .RequiredExtensions.DistinctBy(ext => ext.Name)\n            .ToList();\n\n        // Skip if no extensions required\n        if (requiredExtensionSpecifiers.Count == 0)\n        {\n            return true;\n        }\n\n        // Get installed extensions\n        var localPackagePair = ClientManager.Client?.LocalServerPackage.Unwrap()!;\n        var manager = localPackagePair.BasePackage.ExtensionManager.Unwrap();\n\n        var localExtensions = (\n            await ((GitPackageExtensionManager)manager).GetInstalledExtensionsLiteAsync(\n                localPackagePair.InstalledPackage\n            )\n        ).ToList();\n\n        var localExtensionsByGitUrl = localExtensions\n            .Where(ext => ext.GitRepositoryUrl is not null)\n            .ToDictionary(ext => ext.GitRepositoryUrl!, ext => ext);\n\n        var requiredExtensionReferences = requiredExtensionSpecifiers\n            .Select(specifier => specifier.Name)\n            .ToHashSet();\n\n        var missingExtensions = new List<ExtensionSpecifier>();\n        var outOfDateExtensions =\n            new List<(ExtensionSpecifier Specifier, InstalledPackageExtension Installed)>();\n\n        // Check missing extensions and out of date extensions\n        foreach (var specifier in requiredExtensionSpecifiers)\n        {\n            if (!localExtensionsByGitUrl.TryGetValue(specifier.Name, out var localExtension))\n            {\n                missingExtensions.Add(specifier);\n                continue;\n            }\n\n            // Check if constraint is specified\n            if (specifier.Constraint is not null && specifier.TryGetSemVersionRange(out var semVersionRange))\n            {\n                // Get version to compare\n                localExtension = await manager.GetInstalledExtensionInfoAsync(localExtension);\n\n                // Try to parse local tag to semver\n                if (\n                    localExtension.Version?.Tag is not null\n                    && SemVersion.TryParse(\n                        localExtension.Version.Tag,\n                        SemVersionStyles.AllowV,\n                        out var localSemVersion\n                    )\n                )\n                {\n                    // Check if not satisfied\n                    if (!semVersionRange.Contains(localSemVersion))\n                    {\n                        outOfDateExtensions.Add((specifier, localExtension));\n                    }\n                }\n            }\n        }\n\n        if (missingExtensions.Count == 0 && outOfDateExtensions.Count == 0)\n        {\n            return true;\n        }\n\n        var dialog = DialogHelper.CreateMarkdownDialog(\n            $\"#### The following extensions are required for this workflow:\\n\"\n                + $\"{string.Join(\"\\n- \", missingExtensions.Select(ext => ext.Name))}\"\n                + $\"{string.Join(\"\\n- \", outOfDateExtensions.Select(pair => $\"{pair.Item1.Name} {pair.Specifier.Constraint} {pair.Specifier.Version} (Current Version: {pair.Installed.Version?.Tag})\"))}\",\n            \"Install Required Extensions?\"\n        );\n\n        dialog.IsPrimaryButtonEnabled = true;\n        dialog.DefaultButton = ContentDialogButton.Primary;\n        dialog.PrimaryButtonText =\n            $\"{Resources.Action_Install} ({localPackagePair.InstalledPackage.DisplayName.ToRepr()} will restart)\";\n        dialog.CloseButtonText = Resources.Action_Cancel;\n\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            var manifestExtensionsMap = await manager.GetManifestExtensionsMapAsync(\n                manager.GetManifests(localPackagePair.InstalledPackage)\n            );\n\n            var steps = new List<IPackageStep>();\n\n            // Add install for missing extensions\n            foreach (var missingExtension in missingExtensions)\n            {\n                if (!manifestExtensionsMap.TryGetValue(missingExtension.Name, out var extension))\n                {\n                    Logger.Warn(\n                        \"Extension {MissingExtensionUrl} not found in manifests\",\n                        missingExtension.Name\n                    );\n                    continue;\n                }\n\n                steps.Add(new InstallExtensionStep(manager, localPackagePair.InstalledPackage, extension));\n            }\n\n            // Add update for out of date extensions\n            foreach (var (specifier, installed) in outOfDateExtensions)\n            {\n                if (!manifestExtensionsMap.TryGetValue(specifier.Name, out var extension))\n                {\n                    Logger.Warn(\"Extension {MissingExtensionUrl} not found in manifests\", specifier.Name);\n                    continue;\n                }\n\n                steps.Add(new UpdateExtensionStep(manager, localPackagePair.InstalledPackage, installed));\n            }\n\n            var runner = new PackageModificationRunner\n            {\n                ShowDialogOnStart = true,\n                ModificationCompleteTitle = \"Extensions Installed\",\n                ModificationCompleteMessage = \"Finished installing required extensions\",\n            };\n            EventManager.Instance.OnPackageInstallProgressAdded(runner);\n\n            runner\n                .ExecuteSteps(steps)\n                .ContinueWith(async _ =>\n                {\n                    if (runner.Failed)\n                        return;\n\n                    // Restart Package\n                    try\n                    {\n                        await Dispatcher.UIThread.InvokeAsync(async () =>\n                        {\n                            await runningPackageService.StopPackage(localPackagePair.InstalledPackage.Id);\n                            await runningPackageService.StartPackage(localPackagePair.InstalledPackage);\n                        });\n                    }\n                    catch (Exception e)\n                    {\n                        Logger.Error(e, \"Error while restarting package\");\n\n                        notificationService.ShowPersistent(\n                            new AppException(\n                                \"Could not restart package\",\n                                \"Please manually restart the package for extension changes to take effect\"\n                            )\n                        );\n                    }\n                })\n                .SafeFireAndForget();\n        }\n\n        return false;\n    }\n\n    /// <summary>\n    /// Handles the preview image received event from the websocket.\n    /// Updates the preview image in the image gallery.\n    /// </summary>\n    protected virtual void OnPreviewImageReceived(object? sender, ComfyWebSocketImageData args)\n    {\n        ImageGalleryCardViewModel.SetPreviewImage(args.ImageBytes);\n    }\n\n    /// <summary>\n    /// Handles the progress update received event from the websocket.\n    /// Updates the progress view model.\n    /// </summary>\n    protected virtual void OnProgressUpdateReceived(object? sender, ComfyProgressUpdateEventArgs args)\n    {\n        Dispatcher.UIThread.Post(() =>\n        {\n            OutputProgress.Value = args.Value;\n            OutputProgress.Maximum = args.Maximum;\n            OutputProgress.IsIndeterminate = false;\n\n            OutputProgress.Text =\n                $\"({args.Value} / {args.Maximum})\" + (args.RunningNode != null ? $\" {args.RunningNode}\" : \"\");\n        });\n    }\n\n    private void AttachRunningNodeChangedHandler(ComfyTask comfyTask)\n    {\n        // Do initial update\n        if (comfyTask.RunningNodesHistory.TryPeek(out var lastNode))\n        {\n            OnRunningNodeChanged(comfyTask, lastNode);\n        }\n\n        comfyTask.RunningNodeChanged += OnRunningNodeChanged;\n    }\n\n    /// <summary>\n    /// Handles the node executing updates received event from the websocket.\n    /// </summary>\n    protected virtual void OnRunningNodeChanged(object? sender, string? nodeName)\n    {\n        var task = sender as ComfyTask;\n        if (task == null)\n        {\n            return;\n        }\n\n        // Ignore if regular progress updates started, unless the running node is different from the one reporting progress\n        if (task.HasProgressUpdateStarted && task.LastProgressUpdate?.RunningNode == nodeName)\n        {\n            return;\n        }\n\n        Dispatcher.UIThread.Post(() =>\n        {\n            OutputProgress.IsIndeterminate = true;\n            OutputProgress.Value = 100;\n            OutputProgress.Maximum = 100;\n            OutputProgress.Text = nodeName;\n        });\n    }\n\n    public class ImageGenerationEventArgs : EventArgs\n    {\n        public required ComfyClient Client { get; init; }\n        public required NodeDictionary Nodes { get; init; }\n        public required IReadOnlyList<string> OutputNodeNames { get; init; }\n        public int BatchIndex { get; init; }\n        public GenerationParameters? Parameters { get; init; }\n        public InferenceProjectDocument? Project { get; init; }\n        public bool ClearOutputImages { get; init; } = true;\n        public List<(string SourcePath, string DestinationRelativePath)> FilesToTransfer { get; init; } = [];\n    }\n\n    public class BuildPromptEventArgs : EventArgs\n    {\n        public ComfyNodeBuilder Builder { get; } = new();\n        public GenerateOverrides Overrides { get; init; } = new();\n        public long? SeedOverride { get; init; }\n        public List<(string SourcePath, string DestinationRelativePath)> FilesToTransfer { get; init; } = [];\n\n        public ModuleApplyStepEventArgs ToModuleApplyStepEventArgs()\n        {\n            var overrides = new Dictionary<Type, bool>();\n\n            if (Overrides.IsHiresFixEnabled.HasValue)\n            {\n                overrides[typeof(HiresFixModule)] = Overrides.IsHiresFixEnabled.Value;\n            }\n\n            return new ModuleApplyStepEventArgs\n            {\n                Builder = Builder,\n                IsEnabledOverrides = overrides,\n                FilesToTransfer = FilesToTransfer,\n            };\n        }\n\n        public static implicit operator ModuleApplyStepEventArgs(BuildPromptEventArgs args)\n        {\n            return args.ToModuleApplyStepEventArgs();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Input;\nusing Avalonia.Platform.Storage;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Media.Animation;\nusing Microsoft.Extensions.DependencyInjection;\nusing NLog;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\n#pragma warning disable CS0657 // Not a valid attribute location for this declaration\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\npublic abstract partial class InferenceTabViewModelBase\n    : DisposableLoadableViewModelBase,\n        IPersistentViewProvider,\n        IDropTarget\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private readonly INotificationService notificationService;\n\n    /// <summary>\n    /// The title of the tab\n    /// </summary>\n    public virtual string TabTitle => ProjectFile?.NameWithoutExtension ?? \"New Project\";\n\n    /// <summary>\n    /// Whether there are unsaved changes\n    /// </summary>\n    [ObservableProperty]\n    [property: JsonIgnore]\n    private bool hasUnsavedChanges;\n\n    /// <summary>\n    /// The tab's project file\n    /// </summary>\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(TabTitle))]\n    [property: JsonIgnore]\n    private FilePath? projectFile;\n\n    /// <inheritdoc />\n    Control? IPersistentViewProvider.AttachedPersistentView { get; set; }\n\n    #region Weak Events\n\n    private WeakEventManager<LoadViewStateEventArgs>? loadViewStateRequestedEventManager;\n\n    public event EventHandler<LoadViewStateEventArgs> LoadViewStateRequested\n    {\n        add\n        {\n            loadViewStateRequestedEventManager ??= new WeakEventManager<LoadViewStateEventArgs>();\n            loadViewStateRequestedEventManager.AddEventHandler(value);\n        }\n        remove => loadViewStateRequestedEventManager?.RemoveEventHandler(value);\n    }\n\n    protected void LoadViewState(LoadViewStateEventArgs args) =>\n        loadViewStateRequestedEventManager?.RaiseEvent(this, args, nameof(LoadViewStateRequested));\n\n    protected void ResetViewState() => LoadViewState(new LoadViewStateEventArgs());\n\n    private WeakEventManager<SaveViewStateEventArgs>? saveViewStateRequestedEventManager;\n\n    public event EventHandler<SaveViewStateEventArgs> SaveViewStateRequested\n    {\n        add\n        {\n            saveViewStateRequestedEventManager ??= new WeakEventManager<SaveViewStateEventArgs>();\n            saveViewStateRequestedEventManager.AddEventHandler(value);\n        }\n        remove => saveViewStateRequestedEventManager?.RemoveEventHandler(value);\n    }\n\n    protected async Task<ViewState> SaveViewState()\n    {\n        var eventArgs = new SaveViewStateEventArgs();\n        saveViewStateRequestedEventManager?.RaiseEvent(this, eventArgs, nameof(SaveViewStateRequested));\n\n        if (eventArgs.StateTask is not { } stateTask)\n        {\n            throw new InvalidOperationException(\n                \"SaveViewStateRequested event handler did not set the StateTask property\"\n            );\n        }\n\n        return await stateTask;\n    }\n\n    #endregion\n\n    protected InferenceTabViewModelBase(INotificationService notificationService)\n    {\n        this.notificationService = notificationService;\n    }\n\n    [RelayCommand]\n    private void RestoreDefaultViewState()\n    {\n        // ResetViewState();\n        // TODO: Dock reset not working, using this hack for now to get a new view\n\n        var navService = App.Services.GetRequiredService<INavigationService<MainWindowViewModel>>();\n        navService.NavigateTo<PackageManagerViewModel>(new SuppressNavigationTransitionInfo());\n        ((IPersistentViewProvider)this).AttachedPersistentView = null;\n        navService.NavigateTo<InferenceViewModel>(new BetterEntranceNavigationTransition());\n    }\n\n    [RelayCommand]\n    private async Task DebugSaveViewState()\n    {\n        var state = await SaveViewState();\n        if (state.DockLayout is { } layout)\n        {\n            await DialogHelper.CreateJsonDialog(layout).ShowAsync();\n        }\n        else\n        {\n            await DialogHelper.CreateTaskDialog(\"Failed\", \"No layout data\").ShowAsync();\n        }\n    }\n\n    [RelayCommand]\n    private async Task DebugLoadViewState()\n    {\n        var textFields = new TextBoxField[] { new() { Label = \"Json Data\" } };\n\n        var dialog = DialogHelper.CreateTextEntryDialog(\"Load Dock State\", \"\", textFields);\n        var result = await dialog.ShowAsync();\n\n        if (result == ContentDialogResult.Primary && textFields[0].Text is { } json)\n        {\n            LoadViewState(new LoadViewStateEventArgs { State = new ViewState { DockLayout = json } });\n        }\n    }\n\n    protected override void Dispose(bool disposing)\n    {\n        base.Dispose(disposing);\n\n        if (disposing)\n        {\n            ((IPersistentViewProvider)this).AttachedPersistentView = null;\n        }\n    }\n\n    /// <summary>\n    /// Loads image and metadata from a file path\n    /// </summary>\n    /// <remarks>This is safe to call from non-UI threads</remarks>\n    /// <param name=\"filePath\">File path</param>\n    public void LoadImageMetadata(FilePath? filePath)\n    {\n        Logger.Info(\"Loading image metadata from '{Path}'\", filePath?.FullPath);\n\n        if (filePath is not { Exists: true })\n        {\n            throw new FileNotFoundException(\"File does not exist\", filePath?.FullPath);\n        }\n\n        var metadata = ImageMetadata.GetAllFileMetadata(filePath);\n\n        LoadImageMetadata(filePath.FullPath, metadata);\n    }\n\n    /// <summary>\n    /// Loads image and metadata from a LocalImageFile\n    /// </summary>\n    /// <param name=\"localImageFile\">LocalImageFile</param>\n    public void LoadImageMetadata(LocalImageFile localImageFile)\n    {\n        Logger.Info(\"Loading image metadata from LocalImageFile at '{Path}'\", localImageFile.AbsolutePath);\n\n        var metadata = localImageFile.ReadMetadata();\n\n        LoadImageMetadata(localImageFile.AbsolutePath, metadata);\n    }\n\n    /// <summary>\n    /// Loads image and metadata from a file path and metadata tuple\n    /// </summary>\n    private void LoadImageMetadata(\n        string imageFilePath,\n        (\n            string? Parameters,\n            string? ParametersJson,\n            string? SMProject,\n            string? ComfyNodes,\n            string? CivitParameters\n        ) metadata\n    )\n    {\n        // Has SMProject metadata\n        if (metadata.SMProject is not null)\n        {\n            var project = JsonSerializer.Deserialize<InferenceProjectDocument>(metadata.SMProject);\n\n            // Check project type matches\n            var projectType = project?.ProjectType.ToViewModelType();\n            if (projectType != GetType())\n            {\n                Logger.Warn(\n                    \"Attempted to load project of mismatched type {Type} into {ThisType}, skipping\",\n                    projectType?.Name,\n                    GetType().Name\n                );\n                // Fallback to try loading as parameters\n            }\n            else if (project?.State is null)\n            {\n                Logger.Warn(\"Project State is null, skipping\");\n                // Fallback to try loading as parameters\n            }\n            else\n            {\n                Logger.Info(\"Loading Project State (Type: {Type})\", projectType.Name);\n                Dispatcher.UIThread.Invoke(() => LoadStateFromJsonObject(project.State));\n\n                // Load image\n                if (this is IImageGalleryComponent imageGalleryComponent)\n                {\n                    Dispatcher.UIThread.Invoke(() =>\n                        imageGalleryComponent.LoadImagesToGallery(new ImageSource(imageFilePath))\n                    );\n                }\n\n                return;\n            }\n        }\n\n        // Has generic metadata\n        if (metadata.Parameters is not null)\n        {\n            Logger.Info(\"Loading Parameters from metadata\");\n\n            if (!GenerationParameters.TryParse(metadata.Parameters, out var parameters))\n            {\n                throw new ApplicationException(\"Failed to parse parameters\");\n            }\n\n            if (this is IParametersLoadableState paramsLoadableVm)\n            {\n                Dispatcher.UIThread.Invoke(() => paramsLoadableVm.LoadStateFromParameters(parameters));\n            }\n            else\n            {\n                Logger.Warn(\n                    \"Load parameters target {Type} does not implement IParametersLoadableState, skipping\",\n                    GetType().Name\n                );\n            }\n\n            // Load image\n            if (this is IImageGalleryComponent imageGalleryComponent)\n            {\n                Dispatcher.UIThread.Invoke(() =>\n                    imageGalleryComponent.LoadImagesToGallery(new ImageSource(imageFilePath))\n                );\n            }\n\n            return;\n        }\n\n        // Civit generator metadata\n        if (metadata.CivitParameters is not null)\n        {\n            Logger.Info(\"Loading Parameters from metadata\");\n\n            if (!GenerationParameters.TryParse(metadata.CivitParameters, out var parameters))\n            {\n                throw new ApplicationException(\"Failed to parse parameters\");\n            }\n\n            if (this is IParametersLoadableState paramsLoadableVm)\n            {\n                Dispatcher.UIThread.Invoke(() => paramsLoadableVm.LoadStateFromParameters(parameters));\n            }\n            else\n            {\n                Logger.Warn(\n                    \"Load parameters target {Type} does not implement IParametersLoadableState, skipping\",\n                    GetType().Name\n                );\n            }\n\n            // Load image\n            if (this is IImageGalleryComponent imageGalleryComponent)\n            {\n                Dispatcher.UIThread.Invoke(() =>\n                    imageGalleryComponent.LoadImagesToGallery(new ImageSource(imageFilePath))\n                );\n            }\n\n            return;\n        }\n\n        throw new ApplicationException(\"File does not contain SMProject or Parameters Metadata\");\n    }\n\n    /// <inheritdoc />\n    public void DragOver(object? sender, DragEventArgs e)\n    {\n        // 1. Context drop for LocalImageFile\n        if (e.Data.GetDataFormats().Contains(\"Context\"))\n        {\n            if (e.Data.GetContext<LocalImageFile>() is { } imageFile)\n            {\n                e.Handled = true;\n                return;\n            }\n\n            e.DragEffects = DragDropEffects.None;\n        }\n        // 2. OS Files\n        if (e.Data.GetDataFormats().Contains(DataFormats.Files))\n        {\n            e.Handled = true;\n            return;\n        }\n\n        // Other kinds - not supported\n        e.DragEffects = DragDropEffects.None;\n    }\n\n    /// <inheritdoc />\n    public void Drop(object? sender, DragEventArgs e)\n    {\n        // 1. Context drop for LocalImageFile\n        if (e.Data.GetDataFormats().Contains(\"Context\"))\n        {\n            if (e.Data.GetContext<LocalImageFile>() is { } imageFile)\n            {\n                e.Handled = true;\n\n                Dispatcher.UIThread.Post(() =>\n                {\n                    try\n                    {\n                        LoadImageMetadata(imageFile);\n                    }\n                    catch (Exception ex)\n                    {\n                        Logger.Warn(ex, \"Failed to load image from context drop\");\n                        notificationService.ShowPersistent(\n                            $\"Could not parse image metadata\",\n                            $\"{imageFile.FileName} - {ex.Message}\",\n                            NotificationType.Warning\n                        );\n                    }\n                });\n\n                return;\n            }\n        }\n        // 2. OS Files\n        if (e.Data.GetDataFormats().Contains(DataFormats.Files))\n        {\n            e.Handled = true;\n\n            if (e.Data.Get(DataFormats.Files) is IEnumerable<IStorageItem> files)\n            {\n                if (files.Select(f => f.TryGetLocalPath()).FirstOrDefault() is { } path)\n                {\n                    var file = new FilePath(path);\n                    Dispatcher.UIThread.Post(() =>\n                    {\n                        try\n                        {\n                            LoadImageMetadata(file);\n                        }\n                        catch (Exception ex)\n                        {\n                            Logger.Warn(ex, \"Failed to load image from OS file drop\");\n                            notificationService.ShowPersistent(\n                                $\"Could not parse image metadata\",\n                                $\"{file.Name} - {ex.Message}\",\n                                NotificationType.Warning\n                            );\n                        }\n                    });\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/LoadableViewModelBase.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text.Json;\nusing System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\nusing System.Windows.Input;\nusing CommunityToolkit.Mvvm.Input;\nusing NLog;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\n[JsonDerivedType(typeof(StackExpanderViewModel), StackExpanderViewModel.ModuleKey)]\n[JsonDerivedType(typeof(SamplerCardViewModel), SamplerCardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(FreeUCardViewModel), FreeUCardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(UpscalerCardViewModel), UpscalerCardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(ControlNetCardViewModel), ControlNetCardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(PromptExpansionCardViewModel), PromptExpansionCardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(ExtraNetworkCardViewModel), ExtraNetworkCardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(LayerDiffuseCardViewModel), LayerDiffuseCardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(FaceDetailerViewModel), FaceDetailerViewModel.ModuleKey)]\n[JsonDerivedType(typeof(DiscreteModelSamplingCardViewModel), DiscreteModelSamplingCardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(RescaleCfgCardViewModel), RescaleCfgCardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(PlasmaNoiseCardViewModel), PlasmaNoiseCardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(NrsCardViewModel), NrsCardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(CfzCudnnToggleCardViewModel), CfzCudnnToggleCardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(TiledVAECardViewModel), TiledVAECardViewModel.ModuleKey)]\n[JsonDerivedType(typeof(FreeUModule))]\n[JsonDerivedType(typeof(HiresFixModule))]\n[JsonDerivedType(typeof(FluxHiresFixModule))]\n[JsonDerivedType(typeof(UpscalerModule))]\n[JsonDerivedType(typeof(ControlNetModule))]\n[JsonDerivedType(typeof(SaveImageModule))]\n[JsonDerivedType(typeof(PromptExpansionModule))]\n[JsonDerivedType(typeof(LoraModule))]\n[JsonDerivedType(typeof(LayerDiffuseModule))]\n[JsonDerivedType(typeof(FaceDetailerModule))]\n[JsonDerivedType(typeof(FluxGuidanceModule))]\n[JsonDerivedType(typeof(DiscreteModelSamplingModule))]\n[JsonDerivedType(typeof(RescaleCfgModule))]\n[JsonDerivedType(typeof(PlasmaNoiseModule))]\n[JsonDerivedType(typeof(NRSModule))]\n[JsonDerivedType(typeof(CfzCudnnToggleModule))]\n[JsonDerivedType(typeof(TiledVAEModule))]\npublic abstract class LoadableViewModelBase : ViewModelBase, IJsonLoadableState\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private static readonly Type[] SerializerIgnoredTypes = { typeof(ICommand), typeof(IRelayCommand) };\n\n    private static readonly string[] SerializerIgnoredNames = { nameof(HasErrors) };\n\n    private static readonly JsonSerializerOptions SerializerOptions = new()\n    {\n        IgnoreReadOnlyProperties = true,\n    };\n\n    private static bool ShouldIgnoreProperty(PropertyInfo property)\n    {\n        // Skip if read-only and not IJsonLoadableState\n        if (property.SetMethod is null && !typeof(IJsonLoadableState).IsAssignableFrom(property.PropertyType))\n        {\n            Logger.ConditionalTrace(\"Skipping {Property} - read-only\", property.Name);\n            return true;\n        }\n        // Check not JsonIgnore\n        if (property.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Length > 0)\n        {\n            Logger.ConditionalTrace(\"Skipping {Property} - has [JsonIgnore]\", property.Name);\n            return true;\n        }\n        // Check not excluded type\n        if (SerializerIgnoredTypes.Contains(property.PropertyType))\n        {\n            Logger.ConditionalTrace(\n                \"Skipping {Property} - serializer ignored type {Type}\",\n                property.Name,\n                property.PropertyType\n            );\n            return true;\n        }\n        // Check not ignored name\n        if (SerializerIgnoredNames.Contains(property.Name, StringComparer.Ordinal))\n        {\n            Logger.ConditionalTrace(\"Skipping {Property} - serializer ignored name\", property.Name);\n            return true;\n        }\n\n        return false;\n    }\n\n    /// <summary>\n    /// True if we should include property without checking exclusions\n    /// </summary>\n    private static bool ShouldIncludeProperty(PropertyInfo property)\n    {\n        // Has JsonIncludeAttribute\n        if (property.GetCustomAttributes(typeof(JsonIncludeAttribute), true).Length > 0)\n        {\n            Logger.ConditionalTrace(\"Including {Property} - has [JsonInclude]\", property.Name);\n            return true;\n        }\n\n        return false;\n    }\n\n    /// <summary>\n    /// Load the state of this view model from a JSON object.\n    /// The default implementation is a mirror of <see cref=\"SaveStateToJsonObject\"/>.\n    /// For the following properties on this class, we will try to set from the JSON object:\n    /// <list type=\"bullet\">\n    /// <item>Public</item>\n    /// <item>Not read-only</item>\n    /// <item>Not marked with [JsonIgnore]</item>\n    /// <item>Not a type within the SerializerIgnoredTypes</item>\n    /// <item>Not a name within the SerializerIgnoredNames</item>\n    /// </list>\n    /// </summary>\n    public virtual void LoadStateFromJsonObject(JsonObject state)\n    {\n        // Get all of our properties using reflection\n        var properties = GetType().GetProperties();\n        Logger.ConditionalTrace(\"Serializing {Type} with {Count} properties\", GetType(), properties.Length);\n\n        foreach (var property in properties)\n        {\n            var name = property.Name;\n\n            // If JsonPropertyName provided, use that as the key\n            if (\n                property.GetCustomAttributes(typeof(JsonPropertyNameAttribute), true).FirstOrDefault()\n                is JsonPropertyNameAttribute jsonPropertyName\n            )\n            {\n                Logger.ConditionalTrace(\n                    \"Deserializing {Property} ({Type}) with JsonPropertyName {JsonPropertyName}\",\n                    property.Name,\n                    property.PropertyType,\n                    jsonPropertyName.Name\n                );\n                name = jsonPropertyName.Name;\n            }\n\n            // Check if property is in the JSON object\n            if (!state.TryGetPropertyValue(name, out var value))\n            {\n                Logger.ConditionalTrace(\"Skipping {Property} - not in JSON object\", property.Name);\n                continue;\n            }\n\n            // Check if we should ignore this property\n            if (!ShouldIncludeProperty(property) && ShouldIgnoreProperty(property))\n            {\n                continue;\n            }\n\n            // For types that also implement IJsonLoadableState, defer to their load implementation\n            if (typeof(IJsonLoadableState).IsAssignableFrom(property.PropertyType))\n            {\n                Logger.ConditionalTrace(\n                    \"Loading {Property} ({Type}) with IJsonLoadableState\",\n                    property.Name,\n                    property.PropertyType\n                );\n\n                // Value must be non-null\n                if (value is null)\n                {\n                    throw new InvalidOperationException(\n                        $\"Property {property.Name} is IJsonLoadableState but value to be loaded is null\"\n                    );\n                }\n\n                // Check if the current object at this property is null\n                if (property.GetValue(this) is not IJsonLoadableState propertyValue)\n                {\n                    // If null, it must have a default constructor\n                    if (property.PropertyType.GetConstructor(Type.EmptyTypes) is not { } constructorInfo)\n                    {\n                        throw new InvalidOperationException(\n                            $\"Property {property.Name} is IJsonLoadableState but current object is null and has no default constructor\"\n                        );\n                    }\n\n                    // Create a new instance and set it\n                    propertyValue = (IJsonLoadableState)constructorInfo.Invoke(null);\n                    property.SetValue(this, propertyValue);\n                }\n\n                // Load the state from the JSON object\n                propertyValue.LoadStateFromJsonObject(value.AsObject());\n            }\n            else\n            {\n                Logger.ConditionalTrace(\"Loading {Property} ({Type})\", property.Name, property.PropertyType);\n\n                var propertyValue = value.Deserialize(property.PropertyType, SerializerOptions);\n                property.SetValue(this, propertyValue);\n            }\n        }\n    }\n\n    /// <summary>\n    /// Saves the state of this view model to a JSON object.\n    /// The default implementation uses reflection to\n    /// save all properties that are:\n    /// <list type=\"bullet\">\n    /// <item>Public</item>\n    /// <item>Not read-only</item>\n    /// <item>Not marked with [JsonIgnore]</item>\n    /// <item>Not a type within the SerializerIgnoredTypes</item>\n    /// <item>Not a name within the SerializerIgnoredNames</item>\n    /// </list>\n    /// </summary>\n    public virtual JsonObject SaveStateToJsonObject()\n    {\n        // Get all of our properties using reflection.\n        var properties = GetType().GetProperties();\n        Logger.ConditionalTrace(\"Serializing {Type} with {Count} properties\", GetType(), properties.Length);\n\n        // Create a JSON object to store the state.\n        var state = new JsonObject();\n\n        // Serialize each property marked with JsonIncludeAttribute.\n        foreach (var property in properties)\n        {\n            if (!ShouldIncludeProperty(property) && ShouldIgnoreProperty(property))\n            {\n                continue;\n            }\n\n            var name = property.Name;\n\n            // If JsonPropertyName provided, use that as the key.\n            if (\n                property.GetCustomAttributes(typeof(JsonPropertyNameAttribute), true).FirstOrDefault()\n                is JsonPropertyNameAttribute jsonPropertyName\n            )\n            {\n                Logger.ConditionalTrace(\n                    \"Serializing {Property} ({Type}) with JsonPropertyName {JsonPropertyName}\",\n                    property.Name,\n                    property.PropertyType,\n                    jsonPropertyName.Name\n                );\n                name = jsonPropertyName.Name;\n            }\n\n            // For types that also implement IJsonLoadableState, defer to their implementation.\n            if (typeof(IJsonLoadableState).IsAssignableFrom(property.PropertyType))\n            {\n                Logger.ConditionalTrace(\n                    \"Serializing {Property} ({Type}) with IJsonLoadableState\",\n                    property.Name,\n                    property.PropertyType\n                );\n                var value = property.GetValue(this);\n                if (value is not null)\n                {\n                    var model = (IJsonLoadableState)value;\n                    var modelState = model.SaveStateToJsonObject();\n                    state.Add(name, modelState);\n                }\n            }\n            else\n            {\n                Logger.ConditionalTrace(\n                    \"Serializing {Property} ({Type})\",\n                    property.Name,\n                    property.PropertyType\n                );\n                var value = property.GetValue(this);\n                if (value is not null)\n                {\n                    state.Add(name, JsonSerializer.SerializeToNode(value, SerializerOptions));\n                }\n            }\n        }\n\n        return state;\n    }\n\n    public virtual void LoadStateFromJsonObject(JsonObject state, int version)\n    {\n        LoadStateFromJsonObject(state);\n    }\n\n    /// <summary>\n    /// Serialize a model to a JSON object.\n    /// </summary>\n    protected static JsonObject SerializeModel<T>(T model)\n    {\n        var node = JsonSerializer.SerializeToNode(model);\n        return node?.AsObject()\n            ?? throw new NullReferenceException(\"Failed to serialize state to JSON object.\");\n    }\n\n    /// <summary>\n    /// Deserialize a model from a JSON object.\n    /// </summary>\n    protected static T DeserializeModel<T>(JsonObject state)\n    {\n        return state.Deserialize<T>()\n            ?? throw new NullReferenceException(\"Failed to deserialize state from JSON object.\");\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/PageViewModelBase.cs",
    "content": "﻿using FluentAvalonia.UI.Controls;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\n/// <summary>\n/// An abstract class for enabling page navigation.\n/// </summary>\npublic abstract class PageViewModelBase : DisposableViewModelBase\n{\n    /// <summary>\n    /// Gets if the user can navigate to the next page\n    /// </summary>\n    public virtual bool CanNavigateNext { get; protected set; }\n\n    /// <summary>\n    /// Gets if the user can navigate to the previous page\n    /// </summary>\n    public virtual bool CanNavigatePrevious { get; protected set; }\n\n    public abstract string Title { get; }\n    public abstract IconSource IconSource { get; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/PausableProgressItemViewModelBase.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Threading.Tasks;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\n[SuppressMessage(\"ReSharper\", \"VirtualMemberNeverOverridden.Global\")]\npublic abstract partial class PausableProgressItemViewModelBase : ProgressItemViewModelBase\n{\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(\n        nameof(IsPaused),\n        nameof(IsCompleted),\n        nameof(CanPauseResume),\n        nameof(CanCancel)\n    )]\n    private ProgressState state = ProgressState.Inactive;\n\n    /// <summary>\n    /// Whether the progress is paused\n    /// </summary>\n    public bool IsPaused => State is ProgressState.Inactive or ProgressState.Paused;\n    public bool IsPending => State == ProgressState.Pending;\n\n    /// <summary>\n    /// Whether the progress has succeeded, failed or was cancelled\n    /// </summary>\n    public override bool IsCompleted =>\n        State is ProgressState.Success or ProgressState.Failed or ProgressState.Cancelled;\n\n    public virtual bool SupportsPauseResume => true;\n    public virtual bool SupportsCancel => true;\n\n    public bool CanPauseResume => SupportsPauseResume && !IsCompleted && !IsPending;\n    public bool CanCancel => SupportsCancel && !IsCompleted;\n\n    private AsyncRelayCommand? pauseCommand;\n    public IAsyncRelayCommand PauseCommand => pauseCommand ??= new AsyncRelayCommand(Pause);\n\n    public virtual Task Pause() => Task.CompletedTask;\n\n    private AsyncRelayCommand? resumeCommand;\n    public IAsyncRelayCommand ResumeCommand => resumeCommand ??= new AsyncRelayCommand(Resume);\n\n    public virtual Task Resume() => Task.CompletedTask;\n\n    private AsyncRelayCommand? cancelCommand;\n    public IAsyncRelayCommand CancelCommand => cancelCommand ??= new AsyncRelayCommand(Cancel);\n\n    public virtual Task Cancel() => Task.CompletedTask;\n\n    [RelayCommand]\n    private Task TogglePauseResume()\n    {\n        return IsPaused ? Resume() : Pause();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/ProgressItemViewModelBase.cs",
    "content": "﻿using System;\nusing CommunityToolkit.Mvvm.ComponentModel;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\npublic abstract partial class ProgressItemViewModelBase : ViewModelBase\n{\n    [ObservableProperty]\n    private Guid id;\n\n    [ObservableProperty]\n    private string? name;\n\n    [ObservableProperty]\n    private bool failed;\n\n    public virtual bool IsCompleted => Progress.Value >= 100 || Failed;\n\n    public ContentDialogProgressViewModelBase Progress { get; init; } = new();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/ProgressViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\n/// <summary>\n/// Generic view model for progress reporting.\n/// </summary>\npublic partial class ProgressViewModel : DisposableViewModelBase\n{\n    [ObservableProperty, NotifyPropertyChangedFor(nameof(IsTextVisible))]\n    private string? text;\n\n    [ObservableProperty]\n    private string? description;\n\n    [ObservableProperty, NotifyPropertyChangedFor(nameof(IsProgressVisible))]\n    private double value;\n\n    [ObservableProperty]\n    private double maximum = 100;\n\n    [ObservableProperty, NotifyPropertyChangedFor(nameof(IsProgressVisible))]\n    private bool isIndeterminate;\n\n    [ObservableProperty, NotifyPropertyChangedFor(nameof(FormattedDownloadSpeed))]\n    private double downloadSpeedInMBps;\n\n    public string FormattedDownloadSpeed => $\"{DownloadSpeedInMBps:0.00} MB/s\";\n\n    public virtual bool IsProgressVisible => Value > 0 || IsIndeterminate;\n    public virtual bool IsTextVisible => !string.IsNullOrWhiteSpace(Text);\n\n    public void ClearProgress()\n    {\n        Value = 0;\n        Text = null;\n        IsIndeterminate = false;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/SelectableViewModelBase.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\npublic partial class SelectableViewModelBase : DisposableViewModelBase\n{\n    [ObservableProperty]\n    private bool isSelected;\n\n    [RelayCommand]\n    private void ToggleSelection()\n    {\n        IsSelected = !IsSelected;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/TabViewModelBase.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.ViewModels.Base;\n\npublic abstract class TabViewModelBase : DisposableViewModelBase\n{\n    public abstract string Header { get; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/TaskDialogViewModelBase.cs",
    "content": "﻿using System.Threading.Tasks;\nusing System.Windows.Input;\nusing Avalonia.Threading;\nusing FluentAvalonia.UI.Controls;\nusing StabilityMatrix.Avalonia.Languages;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\n/// <summary>\n/// Base class for view models that are used in <see cref=\"FluentAvalonia.UI.Controls.TaskDialog\"/>\n/// </summary>\npublic abstract class TaskDialogViewModelBase : ViewModelBase\n{\n    private TaskDialog? dialog;\n\n    public virtual string? Title { get; set; }\n\n    protected static TaskDialogCommand GetCommandButton(string text, ICommand command)\n    {\n        return new TaskDialogCommand\n        {\n            Text = text,\n            DialogResult = TaskDialogStandardResult.None,\n            Command = command,\n            IsDefault = true,\n            ClosesOnInvoked = false\n        };\n    }\n\n    protected static TaskDialogButton GetCloseButton()\n    {\n        return new TaskDialogButton\n        {\n            Text = Resources.Action_Close,\n            DialogResult = TaskDialogStandardResult.Close\n        };\n    }\n\n    protected static TaskDialogButton GetCloseButton(string text)\n    {\n        return new TaskDialogButton { Text = text, DialogResult = TaskDialogStandardResult.Close };\n    }\n\n    /// <summary>\n    /// Return a <see cref=\"TaskDialog\"/> that uses this view model as its content\n    /// </summary>\n    public virtual TaskDialog GetDialog()\n    {\n        Dispatcher.UIThread.VerifyAccess();\n\n        dialog = new TaskDialog\n        {\n            Header = Title,\n            Content = this,\n            XamlRoot = App.VisualRoot,\n            Buttons = { GetCloseButton() }\n        };\n\n        dialog.AttachedToVisualTree += (s, _) =>\n        {\n            ((TaskDialog)s!).Closing += OnDialogClosing;\n        };\n        dialog.DetachedFromVisualTree += (s, _) =>\n        {\n            ((TaskDialog)s!).Closing -= OnDialogClosing;\n        };\n\n        return dialog;\n    }\n\n    /// <summary>\n    /// Show the dialog from <see cref=\"GetDialog\"/> and return the result\n    /// </summary>\n    public async Task<TaskDialogStandardResult> ShowDialogAsync()\n    {\n        return (TaskDialogStandardResult)await GetDialog().ShowAsync(true);\n    }\n\n    protected void CloseDialog(TaskDialogStandardResult result)\n    {\n        dialog?.Hide(result);\n    }\n\n    protected virtual async void OnDialogClosing(object? sender, TaskDialogClosingEventArgs e) { }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Base/ViewModelBase.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing JetBrains.Annotations;\nusing CommunityToolkit.Mvvm.Input;\nusing StabilityMatrix.Avalonia.Models;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Base;\n\npublic partial class ViewModelBase : ObservableValidator, IRemovableListItem\n{\n    [PublicAPI]\n    protected ViewModelState ViewModelState { get; private set; }\n\n    private WeakEventManager? parentListRemoveRequestedEventManager;\n\n    public event EventHandler ParentListRemoveRequested\n    {\n        add\n        {\n            parentListRemoveRequestedEventManager ??= new WeakEventManager();\n            parentListRemoveRequestedEventManager.AddEventHandler(value);\n        }\n        remove => parentListRemoveRequestedEventManager?.RemoveEventHandler(value);\n    }\n\n    [RelayCommand]\n    protected void RemoveFromParentList() =>\n        parentListRemoveRequestedEventManager?.RaiseEvent(\n            this,\n            EventArgs.Empty,\n            nameof(ParentListRemoveRequested)\n        );\n\n    /// <summary>\n    /// Called when the view's LoadedEvent is fired.\n    /// </summary>\n    public virtual void OnLoaded()\n    {\n        if (!ViewModelState.HasFlag(ViewModelState.InitialLoaded))\n        {\n            ViewModelState |= ViewModelState.InitialLoaded;\n\n            OnInitialLoaded();\n\n            Dispatcher.UIThread.InvokeAsync(OnInitialLoadedAsync).SafeFireAndForget();\n        }\n    }\n\n    /// <summary>\n    /// Called the first time the view's LoadedEvent is fired.\n    /// Sets the <see cref=\"ViewModelState.InitialLoaded\"/> flag.\n    /// </summary>\n    protected virtual void OnInitialLoaded() { }\n\n    /// <summary>\n    /// Called asynchronously when the view's LoadedEvent is fired.\n    /// Runs on the UI thread via Dispatcher.UIThread.InvokeAsync.\n    /// The view loading will not wait for this to complete.\n    /// </summary>\n    public virtual Task OnLoadedAsync() => Task.CompletedTask;\n\n    /// <summary>\n    /// Called the first time the view's LoadedEvent is fired.\n    /// Sets the <see cref=\"ViewModelState.InitialLoaded\"/> flag.\n    /// </summary>\n    protected virtual Task OnInitialLoadedAsync() => Task.CompletedTask;\n\n    /// <summary>\n    /// Called when the view's UnloadedEvent is fired.\n    /// </summary>\n    public virtual void OnUnloaded() { }\n\n    /// <summary>\n    /// Called asynchronously when the view's UnloadedEvent is fired.\n    /// Runs on the UI thread via Dispatcher.UIThread.InvokeAsync.\n    /// The view loading will not wait for this to complete.\n    /// </summary>\n    public virtual Task OnUnloadedAsync() => Task.CompletedTask;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs",
    "content": "﻿using System.Text.RegularExpressions;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;\n\n[ManagedService]\n[RegisterTransient<CheckpointBrowserCardViewModel>]\npublic partial class CheckpointBrowserCardViewModel : ProgressViewModel\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private readonly IDownloadService downloadService;\n    private readonly ITrackedDownloadService trackedDownloadService;\n    private readonly ISettingsManager settingsManager;\n    private readonly IServiceManager<ViewModelBase> dialogFactory;\n    private readonly INotificationService notificationService;\n    private readonly IModelIndexService modelIndexService;\n    private readonly IModelImportService modelImportService;\n    private readonly ILiteDbContext liteDbContext;\n    private readonly CivitCompatApiManager civitApi;\n    private readonly INavigationService<MainWindowViewModel> navigationService;\n\n    public Action<CheckpointBrowserCardViewModel>? OnDownloadStart { get; set; }\n\n    public CivitModel CivitModel\n    {\n        get => civitModel;\n        set\n        {\n            civitModel = value;\n            IsFavorite = settingsManager.Settings.FavoriteModels.Contains(value.Id);\n            UpdateImage();\n            CheckIfInstalled();\n        }\n    }\n    private CivitModel civitModel;\n\n    public int Order { get; set; }\n\n    public override bool IsTextVisible => Value > 0;\n\n    [ObservableProperty]\n    private Uri? cardImage;\n\n    [ObservableProperty]\n    private bool isImporting;\n\n    [ObservableProperty]\n    private bool isLoading;\n\n    [ObservableProperty]\n    private string updateCardText = string.Empty;\n\n    [ObservableProperty]\n    private bool showUpdateCard;\n\n    [ObservableProperty]\n    private bool isFavorite;\n\n    [ObservableProperty]\n    private bool showSantaHats = true;\n\n    public CheckpointBrowserCardViewModel(\n        IDownloadService downloadService,\n        ITrackedDownloadService trackedDownloadService,\n        ISettingsManager settingsManager,\n        IServiceManager<ViewModelBase> dialogFactory,\n        INotificationService notificationService,\n        IModelIndexService modelIndexService,\n        IModelImportService modelImportService,\n        ILiteDbContext liteDbContext,\n        CivitCompatApiManager civitApi,\n        INavigationService<MainWindowViewModel> navigationService\n    )\n    {\n        this.downloadService = downloadService;\n        this.trackedDownloadService = trackedDownloadService;\n        this.settingsManager = settingsManager;\n        this.dialogFactory = dialogFactory;\n        this.notificationService = notificationService;\n        this.modelIndexService = modelIndexService;\n        this.modelImportService = modelImportService;\n        this.liteDbContext = liteDbContext;\n        this.civitApi = civitApi;\n        this.navigationService = navigationService;\n\n        // Update image when nsfw setting changes\n        AddDisposable(\n            settingsManager.RegisterPropertyChangedHandler(\n                s => s.ModelBrowserNsfwEnabled,\n                _ => Dispatcher.UIThread.Post(UpdateImage)\n            ),\n            settingsManager.RegisterPropertyChangedHandler(\n                s => s.HideEarlyAccessModels,\n                _ => Dispatcher.UIThread.Post(UpdateImage)\n            )\n        );\n\n        ShowSantaHats = settingsManager.Settings.IsHolidayModeActive;\n    }\n\n    private void CheckIfInstalled()\n    {\n        if (Design.IsDesignMode)\n        {\n            UpdateCardText = \"Installed\";\n            ShowUpdateCard = true;\n            return;\n        }\n\n        if (CivitModel.ModelVersions == null)\n            return;\n\n        var installedModels = modelIndexService.ModelIndexBlake3Hashes;\n        if (installedModels.Count == 0)\n            return;\n\n        // check if latest version is installed\n        var latestVersion = CivitModel.ModelVersions.FirstOrDefault();\n        if (latestVersion == null)\n            return;\n\n        var latestVersionInstalled =\n            latestVersion.Files != null\n            && latestVersion.Files.Any(file =>\n                file is { Type: CivitFileType.Model, Hashes.BLAKE3: not null }\n                && installedModels.Contains(file.Hashes.BLAKE3)\n            );\n\n        // check if any of the ModelVersion.Files.Hashes.BLAKE3 hashes are in the installedModels list\n        var anyVersionInstalled =\n            latestVersionInstalled\n            || CivitModel.ModelVersions.Any(version =>\n                version.Files != null\n                && version.Files.Any(file =>\n                    file is { Type: CivitFileType.Model, Hashes.BLAKE3: not null }\n                    && installedModels.Contains(file.Hashes.BLAKE3)\n                )\n            );\n\n        UpdateCardText =\n            latestVersionInstalled ? \"Installed\"\n            : anyVersionInstalled ? \"Update Available\"\n            : string.Empty;\n\n        ShowUpdateCard = anyVersionInstalled;\n    }\n\n    private void UpdateImage()\n    {\n        var nsfwEnabled = settingsManager.Settings.ModelBrowserNsfwEnabled;\n        var hideEarlyAccessModels = settingsManager.Settings.HideEarlyAccessModels;\n        var version = CivitModel.ModelVersions?.FirstOrDefault(v =>\n            !hideEarlyAccessModels || !v.IsEarlyAccess\n        );\n        var images = version?.Images;\n\n        // Try to find a valid image\n        var image = images\n            ?.Where(img =>\n                LocalModelFile.SupportedImageExtensions.Any(img.Url.Contains) && img.Type == \"image\"\n            )\n            .FirstOrDefault(image => nsfwEnabled || image.NsfwLevel <= 1);\n        if (image != null)\n        {\n            CardImage = new Uri(image.Url);\n            return;\n        }\n\n        // If no valid image found, use no image\n        CardImage = Assets.NoImage;\n    }\n\n    [RelayCommand]\n    private void OpenModel()\n    {\n        ProcessRunner.OpenUrl($\"https://civitai.com/models/{CivitModel.Id}\");\n    }\n\n    [RelayCommand]\n    private void ToggleFavorite()\n    {\n        if (settingsManager.Settings.FavoriteModels.Contains(CivitModel.Id))\n        {\n            settingsManager.Transaction(s => s.FavoriteModels.Remove(CivitModel.Id));\n        }\n        else\n        {\n            settingsManager.Transaction(s => s.FavoriteModels.Add(CivitModel.Id));\n        }\n\n        IsFavorite = settingsManager.Settings.FavoriteModels.Contains(CivitModel.Id);\n    }\n\n    [RelayCommand]\n    public void SearchAuthor()\n    {\n        EventManager.Instance.OnNavigateAndFindCivitAuthorRequested(CivitModel.Creator?.Username);\n    }\n\n    private async Task DoImport(\n        CivitModel model,\n        DirectoryPath downloadFolder,\n        CivitModelVersion? selectedVersion = null,\n        CivitFile? selectedFile = null\n    )\n    {\n        IsImporting = true;\n        IsLoading = true;\n        Text = \"Downloading...\";\n\n        OnDownloadStart?.Invoke(this);\n\n        // Get latest version\n        var modelVersion = selectedVersion ?? model.ModelVersions?.FirstOrDefault();\n        if (modelVersion is null)\n        {\n            notificationService.Show(\n                new Notification(\n                    \"Model has no versions available\",\n                    \"This model has no versions available for download\",\n                    NotificationType.Warning\n                )\n            );\n            Text = \"Unable to Download\";\n            return;\n        }\n\n        // Get latest version file\n        var modelFile =\n            selectedFile ?? modelVersion.Files?.FirstOrDefault(x => x.Type == CivitFileType.Model);\n        if (modelFile is null)\n        {\n            notificationService.Show(\n                new Notification(\n                    \"Model has no files available\",\n                    \"This model has no files available for download\",\n                    NotificationType.Warning\n                )\n            );\n            Text = \"Unable to Download\";\n            return;\n        }\n\n        await modelImportService.DoImport(\n            model,\n            downloadFolder,\n            modelVersion,\n            modelFile,\n            onImportComplete: () =>\n            {\n                Text = \"Import Complete\";\n\n                IsIndeterminate = false;\n                Value = 100;\n                CheckIfInstalled();\n                DelayedClearProgress(TimeSpan.FromMilliseconds(800));\n\n                return Task.CompletedTask;\n            },\n            onImportCanceled: () =>\n            {\n                Text = \"Cancelled\";\n                DelayedClearProgress(TimeSpan.FromMilliseconds(500));\n\n                return Task.CompletedTask;\n            },\n            onImportFailed: () =>\n            {\n                Text = \"Download Failed\";\n                DelayedClearProgress(TimeSpan.FromMilliseconds(800));\n\n                return Task.CompletedTask;\n            }\n        );\n    }\n\n    private void DelayedClearProgress(TimeSpan delay)\n    {\n        Task.Delay(delay)\n            .ContinueWith(_ =>\n            {\n                Text = string.Empty;\n                Value = 0;\n                IsImporting = false;\n                IsLoading = false;\n            });\n    }\n\n    [GeneratedRegex(\"<[^>]+>\")]\n    private static partial Regex HtmlRegex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs",
    "content": "﻿using System.Collections.ObjectModel;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing System.Reactive.Linq;\nusing System.Text.Json;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing Injectio.Attributes;\nusing NLog;\nusing Refit;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.CheckpointManager;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Services;\nusing ILogger = Microsoft.Extensions.Logging.ILogger;\nusing Notification = Avalonia.Controls.Notifications.Notification;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;\n\n[View(typeof(CivitAiBrowserPage))]\n[RegisterSingleton<CivitAiBrowserViewModel>]\npublic sealed partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinitelyScroll\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private readonly CivitCompatApiManager civitApi;\n    private readonly ISettingsManager settingsManager;\n    private readonly IServiceManager<ViewModelBase> dialogFactory;\n    private readonly ILiteDbContext liteDbContext;\n    private readonly IConnectedServiceManager connectedServiceManager;\n    private readonly INotificationService notificationService;\n    private readonly ICivitBaseModelTypeService baseModelTypeService;\n    private readonly INavigationService<MainWindowViewModel> navigationService;\n    private bool dontSearch = false;\n\n    private readonly SourceCache<OrderedValue<CivitModel>, int> modelCache = new(static ov => ov.Value.Id);\n\n    private const int TargetPageItemCount = 30;\n\n    [ObservableProperty]\n    private IObservableCollection<CheckpointBrowserCardViewModel> modelCards =\n        new ObservableCollectionExtended<CheckpointBrowserCardViewModel>();\n\n    [ObservableProperty]\n    private string searchQuery = string.Empty;\n\n    [ObservableProperty]\n    private bool showNsfw;\n\n    [ObservableProperty]\n    private bool showMainLoadingSpinner;\n\n    [ObservableProperty]\n    private CivitPeriod selectedPeriod = CivitPeriod.AllTime;\n\n    [ObservableProperty]\n    private CivitSortMode sortMode = CivitSortMode.HighestRated;\n\n    [ObservableProperty]\n    private CivitModelType selectedModelType = CivitModelType.Checkpoint;\n\n    [ObservableProperty]\n    private bool hasSearched;\n\n    [ObservableProperty]\n    private bool isIndeterminate;\n\n    [ObservableProperty]\n    private bool noResultsFound;\n\n    [ObservableProperty]\n    private string noResultsText = string.Empty;\n\n    [ObservableProperty]\n    private ObservableCollection<string> selectedBaseModels = [];\n\n    [ObservableProperty]\n    private bool showSantaHats = true;\n\n    [ObservableProperty]\n    private string? nextPageCursor;\n\n    [ObservableProperty]\n    private bool hideInstalledModels;\n\n    [ObservableProperty]\n    private bool hideEarlyAccessModels;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(StatsResizeFactor))]\n    private double resizeFactor;\n\n    private readonly SourceCache<string, string> baseModelCache = new(static s => s);\n\n    [ObservableProperty]\n    private IObservableCollection<BaseModelOptionViewModel> allBaseModels =\n        new ObservableCollectionExtended<BaseModelOptionViewModel>();\n\n    [ObservableProperty]\n    private bool civitUseDiscoveryApi;\n\n    public bool UseLocalCache => true;\n\n    public double StatsResizeFactor => Math.Clamp(ResizeFactor, 0.75d, 1.25d);\n\n    public IEnumerable<CivitPeriod> AllCivitPeriods =>\n        Enum.GetValues(typeof(CivitPeriod)).Cast<CivitPeriod>();\n    public IEnumerable<CivitSortMode> AllSortModes =>\n        Enum.GetValues(typeof(CivitSortMode)).Cast<CivitSortMode>();\n\n    public IEnumerable<CivitModelType> AllModelTypes =>\n        Enum.GetValues(typeof(CivitModelType))\n            .Cast<CivitModelType>()\n            .Where(t => t == CivitModelType.All || t.ConvertTo<SharedFolderType>() > 0)\n            .OrderBy(t => t.ToString());\n\n    public string ClearButtonText =>\n        SelectedBaseModels.Count == AllBaseModels.Count\n            ? Resources.Action_ClearSelection\n            : Resources.Action_SelectAll;\n\n    public bool ShowFilterNumber =>\n        SelectedBaseModels.Count > 0 && SelectedBaseModels.Count < AllBaseModels.Count;\n\n    public CivitAiBrowserViewModel(\n        CivitCompatApiManager civitApi,\n        ISettingsManager settingsManager,\n        IServiceManager<ViewModelBase> dialogFactory,\n        ILiteDbContext liteDbContext,\n        IConnectedServiceManager connectedServiceManager,\n        INotificationService notificationService,\n        ICivitBaseModelTypeService baseModelTypeService,\n        INavigationService<MainWindowViewModel> navigationService\n    )\n    {\n        this.civitApi = civitApi;\n        this.settingsManager = settingsManager;\n        this.dialogFactory = dialogFactory;\n        this.liteDbContext = liteDbContext;\n        this.connectedServiceManager = connectedServiceManager;\n        this.notificationService = notificationService;\n        this.baseModelTypeService = baseModelTypeService;\n        this.navigationService = navigationService;\n\n        EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested;\n\n        var filterPredicate = Observable\n            .FromEventPattern<PropertyChangedEventArgs>(this, nameof(PropertyChanged))\n            .Where(x =>\n                x.EventArgs.PropertyName\n                    is nameof(HideInstalledModels)\n                        or nameof(ShowNsfw)\n                        or nameof(HideEarlyAccessModels)\n            )\n            .Throttle(TimeSpan.FromMilliseconds(50))\n            .Select(_ => (Func<CheckpointBrowserCardViewModel, bool>)FilterModelCardsPredicate)\n            .StartWith(FilterModelCardsPredicate)\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        var sortPredicate = SortExpressionComparer<CheckpointBrowserCardViewModel>.Ascending(static x =>\n            x.Order\n        );\n\n        AddDisposable(\n            modelCache\n                .Connect()\n                .DeferUntilLoaded()\n                .Transform(ov =>\n                    dialogFactory.Get<CheckpointBrowserCardViewModel>(vm =>\n                    {\n                        vm.CivitModel = ov.Value;\n                        vm.Order = ov.Order;\n                        return vm;\n                    })\n                )\n                .DisposeMany()\n                .Filter(filterPredicate)\n                .SortAndBind(ModelCards, sortPredicate)\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe()\n        );\n\n        AddDisposable(\n            baseModelCache\n                .Connect()\n                .DeferUntilLoaded()\n                .Transform(baseModel => new BaseModelOptionViewModel\n                {\n                    ModelType = baseModel,\n                    IsSelected = settingsManager.Settings.SelectedCivitBaseModels.Contains(baseModel),\n                })\n                .SortAndBind(\n                    AllBaseModels,\n                    SortExpressionComparer<BaseModelOptionViewModel>.Ascending(m => m.ModelType)\n                )\n                .WhenPropertyChanged(p => p.IsSelected)\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe(next =>\n                {\n                    if (next.Sender.IsSelected)\n                        SelectedBaseModels.Add(next.Sender.ModelType);\n                    else\n                        SelectedBaseModels.Remove(next.Sender.ModelType);\n\n                    OnPropertyChanged(nameof(ClearButtonText));\n                    OnPropertyChanged(nameof(SelectedBaseModels));\n                    OnPropertyChanged(nameof(ShowFilterNumber));\n                })\n        );\n\n        if (Design.IsDesignMode)\n            return;\n\n        var settingsTransactionObservable = this.WhenPropertyChanged(x => x.SelectedBaseModels)\n            .Throttle(TimeSpan.FromMilliseconds(50))\n            .Skip(1)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(_ =>\n            {\n                if (!settingsManager.IsLibraryDirSet)\n                    return;\n\n                settingsManager.Transaction(settings =>\n                    settings.SelectedCivitBaseModels = SelectedBaseModels.ToList()\n                );\n\n                if (!dontSearch)\n                {\n                    TrySearchAgain().SafeFireAndForget();\n                }\n            });\n\n        AddDisposable(settingsTransactionObservable);\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                model => model.ShowNsfw,\n                settings => settings.ModelBrowserNsfwEnabled,\n                true\n            )\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                model => model.HideInstalledModels,\n                settings => settings.HideInstalledModelsInModelBrowser,\n                true\n            )\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                model => model.ResizeFactor,\n                settings => settings.CivitBrowserResizeFactor,\n                true\n            )\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                model => model.HideEarlyAccessModels,\n                settings => settings.HideEarlyAccessModels,\n                true\n            )\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                model => model.CivitUseDiscoveryApi,\n                settings => settings.CivitUseDiscoveryApi,\n                true\n            )\n        );\n\n        EventManager.Instance.NavigateAndFindCivitAuthorRequested += OnNavigateAndFindCivitAuthorRequested;\n    }\n\n    private void OnNavigateAndFindCivitAuthorRequested(object? sender, string? e)\n    {\n        if (string.IsNullOrWhiteSpace(e))\n            return;\n\n        SearchQuery = $\"@{e}\";\n        SearchModelsCommand.ExecuteAsync(false).SafeFireAndForget();\n    }\n\n    private void OnNavigateAndFindCivitModelRequested(object? sender, int e)\n    {\n        if (e <= 0)\n            return;\n\n        SearchQuery = $\"$#{e}\";\n        SearchModelsCommand.ExecuteAsync(false).SafeFireAndForget();\n    }\n\n    public override void OnLoaded()\n    {\n        if (Design.IsDesignMode)\n            return;\n\n        var searchOptions = settingsManager.Settings.ModelSearchOptions;\n\n        // Fix SelectedModelType if someone had selected the obsolete \"Model\" option\n        if (searchOptions is { SelectedModelType: CivitModelType.Model })\n        {\n            settingsManager.Transaction(s =>\n                s.ModelSearchOptions = new ModelSearchOptions(\n                    SelectedPeriod,\n                    SortMode,\n                    CivitModelType.Checkpoint,\n                    string.Empty\n                )\n            );\n            searchOptions = settingsManager.Settings.ModelSearchOptions;\n        }\n\n        SelectedPeriod = searchOptions?.SelectedPeriod ?? CivitPeriod.AllTime;\n        SortMode = searchOptions?.SortMode ?? CivitSortMode.HighestRated;\n        SelectedModelType = searchOptions?.SelectedModelType ?? CivitModelType.Checkpoint;\n\n        base.OnLoaded();\n    }\n\n    protected override async Task OnInitialLoadedAsync()\n    {\n        if (Design.IsDesignMode)\n            return;\n\n        await base.OnInitialLoadedAsync();\n\n        if (settingsManager.Settings.AutoLoadCivitModels)\n        {\n            await SearchModelsCommand.ExecuteAsync(false);\n        }\n    }\n\n    public override async Task OnLoadedAsync()\n    {\n        if (Design.IsDesignMode)\n            return;\n\n        var baseModels = await baseModelTypeService.GetBaseModelTypes(includeAllOption: false);\n        baseModels = baseModels.Except(settingsManager.Settings.DisabledBaseModelTypes).ToList();\n        if (baseModels.Count == 0)\n        {\n            return;\n        }\n\n        dontSearch = true;\n        baseModelCache.EditDiff(baseModels, static (a, b) => a.Equals(b, StringComparison.OrdinalIgnoreCase));\n        dontSearch = false;\n    }\n\n    /// <summary>\n    /// Filter predicate for model cards\n    /// </summary>\n    private bool FilterModelCardsPredicate(CheckpointBrowserCardViewModel card)\n    {\n        if (HideInstalledModels && card.UpdateCardText == \"Installed\")\n            return false;\n\n        if (\n            HideEarlyAccessModels\n            && card.CivitModel.ModelVersions != null\n            && card.CivitModel.ModelVersions.All(x => x.Availability == \"EarlyAccess\")\n        )\n            return false;\n\n        return !card.CivitModel.Nsfw || ShowNsfw;\n    }\n\n    [RelayCommand]\n    private async Task OnUseDiscoveryToggle()\n    {\n        if (CivitUseDiscoveryApi)\n        {\n            CivitUseDiscoveryApi = false;\n        }\n        else\n        {\n            if (!await connectedServiceManager.PromptEnableCivitUseDiscoveryApi())\n                return;\n\n            CivitUseDiscoveryApi = true;\n        }\n\n        // Reset cache in case model differences\n        Logger.Info(\"Toggled Discovery API, clearing cache\");\n\n        await liteDbContext.CivitModels.DeleteAllAsync();\n        await liteDbContext.CivitModelVersions.DeleteAllAsync();\n        var items = await liteDbContext.CivitModelQueryCache.DeleteAllAsync();\n\n        Logger.Info(\"Deleted {Count} Civit model query cache entries\", items);\n    }\n\n    /// <summary>\n    /// Background update task\n    /// </summary>\n    private async Task CivitModelQuery(CivitModelsRequest request, bool isInfiniteScroll = false)\n    {\n        var timer = Stopwatch.StartNew();\n        var queryText = request.Query;\n        var models = new List<CivitModel>();\n        // Store original request for caching\n        var originalRequestStr = JsonSerializer.Serialize(request);\n\n        CivitModelsResponse? modelsResponse = null;\n        try\n        {\n            if (!string.IsNullOrWhiteSpace(request.CommaSeparatedModelIds))\n            {\n                // count IDs\n                var ids = request.CommaSeparatedModelIds.Split(',');\n                if (ids.Length > 100)\n                {\n                    var idChunks = ids.Chunk(100);\n                    foreach (var chunk in idChunks)\n                    {\n                        request.CommaSeparatedModelIds = string.Join(\",\", chunk);\n                        request.Limit = 100;\n                        var chunkModelsResponse = await civitApi.GetModels(request);\n\n                        if (chunkModelsResponse.Items != null)\n                        {\n                            models.AddRange(chunkModelsResponse.Items);\n                        }\n                    }\n                }\n                else\n                {\n                    modelsResponse = await civitApi.GetModels(request);\n                    models = modelsResponse.Items;\n                }\n            }\n            else\n            {\n                // Auto-paginate via cursor until we fill the target page size or run out\n                var collectedById = new HashSet<int>();\n                var targetCount = request.Limit ?? TargetPageItemCount;\n                var safetyGuard = 0;\n\n                while (true)\n                {\n                    var resp = await civitApi.GetModels(request);\n                    modelsResponse = resp;\n\n                    if (resp.Items != null)\n                    {\n                        foreach (var item in resp.Items)\n                        {\n                            if (collectedById.Add(item.Id))\n                            {\n                                models.Add(item);\n                            }\n                        }\n                    }\n\n                    // Check how many items survive local filtering\n                    var filteredCount = models\n                        .Where(m => m.Type.ConvertTo<SharedFolderType>() > 0)\n                        .Count(m => m.Mode == null);\n\n                    var next = resp.Metadata?.NextCursor;\n                    if (filteredCount >= targetCount || string.IsNullOrEmpty(next))\n                    {\n                        break;\n                    }\n\n                    request.Cursor = next;\n\n                    if (++safetyGuard >= 10)\n                    {\n                        // Avoid unbounded looping on unexpected cursors\n                        break;\n                    }\n                }\n            }\n\n            if (models is null)\n            {\n                Logger.Debug(\n                    \"CivitAI Query {Text} returned no results (in {Elapsed:F1} s)\",\n                    queryText,\n                    timer.Elapsed.TotalSeconds\n                );\n                return;\n            }\n\n            Logger.Debug(\n                \"CivitAI Query {Text} returned {Results} results (in {Elapsed:F1} s)\",\n                queryText,\n                models.Count,\n                timer.Elapsed.TotalSeconds\n            );\n\n            var unknown = models.Where(m => m.Type == CivitModelType.Unknown).ToList();\n            if (unknown.Any())\n            {\n                var names = unknown.Select(m => m.Name).ToList();\n                Logger.Warn(\"Excluded {Unknown} unknown model types: {Models}\", unknown.Count, names);\n            }\n\n            // Filter out unknown model types and archived/taken-down models\n            models = models\n                .Where(m => m.Type.ConvertTo<SharedFolderType>() > 0)\n                .Where(m => m.Mode == null)\n                .ToList();\n\n            var cacheNew = true;\n            if (UseLocalCache)\n            {\n                // Database update calls will invoke `OnModelsUpdated`\n                // Add to database\n                await liteDbContext.UpsertCivitModelAsync(models);\n                // Add as cache entry\n\n                var originalRequest = JsonSerializer.Deserialize<CivitModelsRequest>(originalRequestStr);\n                cacheNew = await liteDbContext.UpsertCivitModelQueryCacheEntryAsync(\n                    new CivitModelQueryCacheEntry\n                    {\n                        Id = ObjectHash.GetMd5Guid(originalRequest),\n                        InsertedAt = DateTimeOffset.UtcNow,\n                        Request = request,\n                        Items = models,\n                        Metadata = modelsResponse?.Metadata,\n                    }\n                );\n            }\n\n            if (cacheNew)\n            {\n                var doesBaseModelTypeMatch =\n                    SelectedBaseModels.Count == 0\n                        ? request.BaseModels == null || request.BaseModels.Length == 0\n                        : SelectedBaseModels.SequenceEqual(request.BaseModels ?? []);\n                var doesModelTypeMatch =\n                    SelectedModelType == CivitModelType.All\n                        ? request.Types == null || request.Types.Length == 0\n                        : SelectedModelType == request.Types?.FirstOrDefault();\n\n                if (doesBaseModelTypeMatch && doesModelTypeMatch)\n                {\n                    UpdateModelCards(models, isInfiniteScroll);\n                }\n            }\n\n            NextPageCursor = modelsResponse?.Metadata?.NextCursor;\n        }\n        catch (OperationCanceledException)\n        {\n            notificationService.Show(\n                new Notification(\"Request to CivitAI timed out\", \"Please try again in a few minutes\")\n            );\n            Logger.Warn($\"CivitAI query timed out ({request})\");\n        }\n        catch (HttpRequestException e)\n        {\n            notificationService.Show(\n                new Notification(\"CivitAI can't be reached right now\", \"Please try again in a few minutes\")\n            );\n            Logger.Warn(e, $\"CivitAI query HttpRequestException ({request})\");\n        }\n        catch (ApiException e)\n        {\n            // Additional details\n            var responseContent = e.Content ?? \"[No Content]\";\n            var responseCode = e.StatusCode;\n            var responseCodeName = e.StatusCode.ToString();\n\n            Logger.Warn(\n                e,\n                \"CivitAI query ApiException ({Request}), ({Code}: {Response})\",\n                request,\n                responseCode,\n                responseContent\n            );\n\n            notificationService.Show(\n                new Notification(\n                    \"CivitAI can't be reached right now\",\n                    $\"Please try again in a few minutes. ({responseCode}: {responseCodeName})\",\n                    NotificationType.Warning,\n                    expiration: TimeSpan.Zero,\n                    onClick: () =>\n                        Dispatcher.UIThread.InvokeAsync(async () =>\n                            await DialogHelper.CreateApiExceptionDialog(e).ShowAsync()\n                        )\n                )\n            );\n        }\n        catch (Exception e)\n        {\n            notificationService.Show(\n                new Notification(\n                    \"CivitAI can't be reached right now\",\n                    $\"Unknown exception during CivitAI query: {e.GetType().Name}\"\n                )\n            );\n            Logger.Error(e, $\"CivitAI query unknown exception ({request})\");\n        }\n        finally\n        {\n            ShowMainLoadingSpinner = false;\n            UpdateResultsText();\n        }\n    }\n\n    /// <summary>\n    /// Updates model cards using api response object.\n    /// </summary>\n    private void UpdateModelCards(List<CivitModel>? models, bool addCards = false)\n    {\n        if (models is null)\n        {\n            modelCache.Clear();\n            return;\n        }\n\n        var startIndex = modelCache.Count;\n\n        var modelsToAdd = models.Select((m, i) => new OrderedValue<CivitModel>(startIndex + i, m));\n\n        if (addCards)\n        {\n            var newModels = modelsToAdd.Where(x => !modelCache.Keys.Contains(x.Value.Id));\n            modelCache.AddOrUpdate(newModels);\n        }\n        else\n        {\n            modelCache.EditDiff(modelsToAdd, static (a, b) => a.Order == b.Order && a.Value.Id == b.Value.Id);\n        }\n\n        // Status update\n        ShowMainLoadingSpinner = false;\n        IsIndeterminate = false;\n        HasSearched = true;\n    }\n\n    private string previousSearchQuery = string.Empty;\n\n    [RelayCommand]\n    private async Task SearchModels(bool isInfiniteScroll = false)\n    {\n        var timer = Stopwatch.StartNew();\n\n        if (SearchQuery != previousSearchQuery || !isInfiniteScroll)\n        {\n            // Reset page number\n            previousSearchQuery = SearchQuery;\n            NextPageCursor = null;\n        }\n\n        // Build request\n        var modelRequest = new CivitModelsRequest\n        {\n            Limit = TargetPageItemCount + 20, // Fetch a few extra to account for local filtering\n            Nsfw = \"true\", // Handled by local view filter\n            Sort = SortMode,\n            Period = SelectedPeriod,\n        };\n\n        if (NextPageCursor != null)\n        {\n            modelRequest.Cursor = NextPageCursor;\n        }\n\n        if (SelectedModelType != CivitModelType.All)\n        {\n            modelRequest.Types = [SelectedModelType];\n        }\n\n        if (SelectedBaseModels.Count > 0 && SelectedBaseModels.Count < AllBaseModels.Count)\n        {\n            modelRequest.BaseModels = SelectedBaseModels.ToArray();\n        }\n\n        if (SearchQuery.StartsWith(\"#\"))\n        {\n            modelRequest.Tag = SearchQuery[1..];\n        }\n        else if (SearchQuery.StartsWith(\"@\"))\n        {\n            modelRequest.Username = SearchQuery[1..];\n        }\n        else if (SearchQuery.StartsWith(\"$#\"))\n        {\n            modelRequest.Period = CivitPeriod.AllTime;\n            modelRequest.BaseModels = null;\n            modelRequest.Types = null;\n            modelRequest.CommaSeparatedModelIds = SearchQuery[2..];\n\n            if (modelRequest.Sort is CivitSortMode.Favorites or CivitSortMode.Installed)\n            {\n                SortMode = CivitSortMode.HighestRated;\n                modelRequest.Sort = CivitSortMode.HighestRated;\n            }\n        }\n        else if (SearchQuery.StartsWith(\"https://civitai.com/models/\"))\n        {\n            /* extract model ID from URL, could be one of:\n                https://civitai.com/models/443821?modelVersionId=1957537\n                https://civitai.com/models/443821/cyberrealistic-pony\n                https://civitai.com/models/443821\n            */\n            var modelId = SearchQuery\n                .Replace(\"https://civitai.com/models/\", string.Empty)\n                .Split(['?', '/'], StringSplitOptions.RemoveEmptyEntries)\n                .FirstOrDefault();\n\n            modelRequest.Period = CivitPeriod.AllTime;\n            modelRequest.BaseModels = null;\n            modelRequest.Types = null;\n            modelRequest.CommaSeparatedModelIds = modelId;\n\n            if (modelRequest.Sort is CivitSortMode.Favorites or CivitSortMode.Installed)\n            {\n                SortMode = CivitSortMode.HighestRated;\n                modelRequest.Sort = CivitSortMode.HighestRated;\n            }\n        }\n        else\n        {\n            modelRequest.Query = SearchQuery;\n        }\n\n        if (SortMode == CivitSortMode.Installed)\n        {\n            var connectedModels = await liteDbContext.LocalModelFiles.FindAsync(m =>\n                m.ConnectedModelInfo != null\n            );\n\n            connectedModels = connectedModels.Where(x => x.HasCivitMetadata);\n\n            modelRequest.CommaSeparatedModelIds = string.Join(\n                \",\",\n                connectedModels\n                    .Select(c => c.ConnectedModelInfo!.ModelId)\n                    .GroupBy(m => m)\n                    .Select(g => g.First())\n            );\n\n            modelRequest.Sort = null;\n            modelRequest.Period = null;\n        }\n        else if (SortMode == CivitSortMode.Favorites)\n        {\n            var favoriteModels = settingsManager.Settings.FavoriteModels;\n\n            if (!favoriteModels.Any())\n            {\n                notificationService.Show(\n                    \"No Favorites\",\n                    \"You have not added any models to your Favorites.\",\n                    NotificationType.Error\n                );\n                return;\n            }\n\n            modelRequest.CommaSeparatedModelIds = string.Join(\",\", favoriteModels);\n            modelRequest.Sort = null;\n            modelRequest.Period = null;\n        }\n\n        // See if query is cached\n        CivitModelQueryCacheEntry? cachedQuery = null;\n\n        if (UseLocalCache)\n        {\n            cachedQuery = await liteDbContext.TryQueryWithClearOnExceptionAsync(\n                liteDbContext.CivitModelQueryCache,\n                liteDbContext\n                    .CivitModelQueryCache.IncludeAll()\n                    .FindByIdAsync(ObjectHash.GetMd5Guid(modelRequest))\n            );\n        }\n\n        // If cached, update model cards\n        if (cachedQuery is not null)\n        {\n            var elapsed = timer.Elapsed;\n            Logger.Debug(\n                \"Using cached query for {Text} [{RequestHash}] (in {Elapsed:F1} s)\",\n                SearchQuery,\n                modelRequest.GetHashCode(),\n                elapsed.TotalSeconds\n            );\n            NextPageCursor = cachedQuery.Metadata?.NextCursor;\n            UpdateModelCards(cachedQuery.Items, isInfiniteScroll);\n\n            // Start remote query (background mode)\n            // Skip when last query was less than 2 min ago\n            var timeSinceCache = DateTimeOffset.UtcNow - cachedQuery.InsertedAt;\n            if (timeSinceCache?.TotalMinutes >= 2)\n            {\n                CivitModelQuery(modelRequest, isInfiniteScroll).SafeFireAndForget();\n                Logger.Debug(\n                    \"Cached query was more than 2 minutes ago ({Seconds:F0} s), updating cache with remote query\",\n                    timeSinceCache.Value.TotalSeconds\n                );\n            }\n        }\n        else\n        {\n            // Not cached, wait for remote query\n            ShowMainLoadingSpinner = true;\n            await CivitModelQuery(modelRequest, isInfiniteScroll);\n        }\n\n        UpdateResultsText();\n    }\n\n    [RelayCommand]\n    private void ClearOrSelectAllBaseModels()\n    {\n        if (SelectedBaseModels.Count == AllBaseModels.Count)\n            AllBaseModels.ForEach(x => x.IsSelected = false);\n        else\n            AllBaseModels.ForEach(x => x.IsSelected = true);\n    }\n\n    [RelayCommand]\n    private void ShowVersionDialog(CivitModel model)\n    {\n        var versions = model.ModelVersions;\n        if (versions is null || versions.Count == 0)\n        {\n            notificationService.Show(\n                new Notification(\n                    \"Model has no versions available\",\n                    \"This model has no versions available for download\",\n                    NotificationType.Warning\n                )\n            );\n            return;\n        }\n\n        var newVm = dialogFactory.Get<CivitDetailsPageViewModel>(vm =>\n        {\n            var allModelIds = ModelCards.Select(x => x.CivitModel.Id).Distinct().ToList();\n            var index = ModelCards\n                .Select((x, i) => (x.CivitModel.Id, Index: i))\n                .FirstOrDefault(x => x.Id == model.Id)\n                .Index;\n\n            vm.ModelIdList = allModelIds;\n            vm.CurrentIndex = index;\n            vm.CivitModel = model;\n            return vm;\n        });\n\n        navigationService.NavigateTo(newVm, BetterSlideNavigationTransition.PageSlideFromRight);\n    }\n\n    public void ClearSearchQuery()\n    {\n        SearchQuery = string.Empty;\n    }\n\n    public async Task LoadNextPageAsync()\n    {\n        if (NextPageCursor != null)\n        {\n            await SearchModelsCommand.ExecuteAsync(true);\n        }\n    }\n\n    partial void OnSelectedPeriodChanged(CivitPeriod value)\n    {\n        TrySearchAgain().SafeFireAndForget();\n        settingsManager.Transaction(s =>\n            s.ModelSearchOptions = new ModelSearchOptions(value, SortMode, SelectedModelType, string.Empty)\n        );\n        NextPageCursor = null;\n    }\n\n    partial void OnSortModeChanged(CivitSortMode value)\n    {\n        TrySearchAgain().SafeFireAndForget();\n        settingsManager.Transaction(s =>\n            s.ModelSearchOptions = new ModelSearchOptions(\n                SelectedPeriod,\n                value,\n                SelectedModelType,\n                string.Empty\n            )\n        );\n        NextPageCursor = null;\n    }\n\n    partial void OnSelectedModelTypeChanged(CivitModelType value)\n    {\n        TrySearchAgain().SafeFireAndForget();\n        settingsManager.Transaction(s =>\n            s.ModelSearchOptions = new ModelSearchOptions(SelectedPeriod, SortMode, value, string.Empty)\n        );\n        NextPageCursor = null;\n    }\n\n    private async Task TrySearchAgain(bool shouldUpdatePageNumber = true)\n    {\n        if (!HasSearched)\n            return;\n\n        modelCache.Clear();\n\n        if (shouldUpdatePageNumber)\n        {\n            NextPageCursor = null;\n        }\n\n        // execute command instead of calling method directly so that the IsRunning property gets updated\n        await SearchModelsCommand.ExecuteAsync(false);\n    }\n\n    private void UpdateResultsText()\n    {\n        NoResultsFound = ModelCards?.Count <= 0;\n        NoResultsText = \"No results found\";\n    }\n\n    public override string Header => Resources.Label_CivitAi;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitDetailsPageViewModel.cs",
    "content": "﻿using System.Collections.ObjectModel;\nusing System.ComponentModel;\nusing System.ComponentModel.DataAnnotations;\nusing System.Globalization;\nusing System.Reactive.Linq;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Platform.Storage;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing Refit;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Api.CivitTRPC;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;\n\n[View(typeof(CivitDetailsPage))]\n[ManagedService]\n[RegisterTransient<CivitDetailsPageViewModel>]\npublic partial class CivitDetailsPageViewModel(\n    ISettingsManager settingsManager,\n    CivitCompatApiManager civitApi,\n    ICivitTRPCApi civitTrpcApi,\n    ILogger<CivitDetailsPageViewModel> logger,\n    INotificationService notificationService,\n    INavigationService<MainWindowViewModel> navigationService,\n    IModelIndexService modelIndexService,\n    IServiceManager<ViewModelBase> vmFactory,\n    IModelImportService modelImportService\n) : DisposableViewModelBase\n{\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ShowInferenceDefaultsSection))]\n    public required partial CivitModel CivitModel { get; set; }\n\n    [ObservableProperty]\n    public required partial List<int> ModelIdList { get; set; }\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(CanGoNext), nameof(CanGoPrevious))]\n    public required partial int CurrentIndex { get; set; }\n\n    private List<string> ignoredFileNameFormatVars =\n    [\n        \"seed\",\n        \"prompt\",\n        \"negative_prompt\",\n        \"model_hash\",\n        \"sampler\",\n        \"cfgscale\",\n        \"steps\",\n        \"width\",\n        \"height\",\n        \"project_type\",\n        \"project_name\",\n    ];\n\n    public IEnumerable<FileNameFormatVar> ModelFileNameFormatVars =>\n        FileNameFormatProvider\n            .GetSampleForModelBrowser()\n            .Substitutions.Where(kv => !ignoredFileNameFormatVars.Contains(kv.Key))\n            .Select(kv => new FileNameFormatVar { Variable = $\"{{{kv.Key}}}\", Example = kv.Value.Invoke() });\n\n    private SourceCache<CivitImage, string> imageCache = new(x => x.Url);\n\n    public IObservableCollection<ImageSource> ImageSources { get; set; } =\n        new ObservableCollectionExtended<ImageSource>();\n\n    private SourceCache<CivitModelVersion, int> modelVersionCache = new(x => x.Id);\n\n    public IObservableCollection<ModelVersionViewModel> ModelVersions { get; set; } =\n        new ObservableCollectionExtended<ModelVersionViewModel>();\n\n    private SourceCache<CivitFile, int> civitFileCache = new(x => x.Id);\n\n    public IObservableCollection<CivitFileViewModel> CivitFiles { get; set; } =\n        new ObservableCollectionExtended<CivitFileViewModel>();\n\n    [ObservableProperty]\n    public partial ObservableCollection<CivitFileViewModel> SelectedFiles { get; set; } = [];\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(\n        nameof(LastUpdated),\n        nameof(ShortSha256),\n        nameof(BaseModelType),\n        nameof(ModelFileNameFormat),\n        nameof(IsEarlyAccess)\n    )]\n    public partial ModelVersionViewModel? SelectedVersion { get; set; }\n\n    [ObservableProperty]\n    public partial string Description { get; set; } = string.Empty;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(DescriptionRowSpan))]\n    public partial string ModelVersionDescription { get; set; } = string.Empty;\n\n    [ObservableProperty]\n    public partial bool ShowNsfw { get; set; }\n\n    [ObservableProperty]\n    public partial bool HideEarlyAccess { get; set; }\n\n    [ObservableProperty]\n    public partial bool ShowTrainingData { get; set; }\n\n    [ObservableProperty]\n    public partial bool HideInstalledModels { get; set; }\n\n    [ObservableProperty]\n    public partial string SelectedInstallLocation { get; set; } = string.Empty;\n\n    [ObservableProperty]\n    public partial ObservableCollection<string> AvailableInstallLocations { get; set; } = [];\n\n    [ObservableProperty]\n    [CustomValidation(typeof(CivitDetailsPageViewModel), nameof(ValidateModelFileNameFormat))]\n    public partial string? ModelFileNameFormat { get; set; }\n\n    [ObservableProperty]\n    public partial string? ModelNameFormatSample { get; set; }\n\n    [ObservableProperty]\n    public partial SamplerCardViewModel SamplerCardViewModel { get; set; } =\n        vmFactory.Get<SamplerCardViewModel>(samplerCard =>\n        {\n            samplerCard.IsDimensionsEnabled = true;\n            samplerCard.IsCfgScaleEnabled = true;\n            samplerCard.IsSamplerSelectionEnabled = true;\n            samplerCard.IsSchedulerSelectionEnabled = true;\n            samplerCard.DenoiseStrength = 1.0d;\n            samplerCard.EnableAddons = false;\n            samplerCard.IsDenoiseStrengthEnabled = false;\n        });\n\n    [ObservableProperty]\n    public partial bool IsInferenceDefaultsEnabled { get; set; } = false;\n\n    public string LastUpdated =>\n        SelectedVersion?.ModelVersion.PublishedAt?.ToString(\"g\", CultureInfo.CurrentCulture) ?? string.Empty;\n\n    public string ShortSha256 =>\n        SelectedVersion?.ModelVersion.Files?.FirstOrDefault()?.Hashes.ShortSha256 ?? string.Empty;\n\n    public string BaseModelType => SelectedVersion?.ModelVersion.BaseModel?.Trim() ?? string.Empty;\n\n    public bool IsEarlyAccess => SelectedVersion?.ModelVersion.IsEarlyAccess ?? false;\n\n    public string CivitUrl => $@\"https://civitai.com/models/{CivitModel.Id}\";\n\n    public int DescriptionRowSpan => string.IsNullOrWhiteSpace(ModelVersionDescription) ? 3 : 1;\n\n    public bool ShowInferenceDefaultsSection => CivitModel.Type == CivitModelType.Checkpoint;\n\n    public bool CanGoNext => CurrentIndex < ModelIdList.Count - 1;\n    public bool CanGoPrevious => CurrentIndex > 0;\n\n    protected override async Task OnInitialLoadedAsync()\n    {\n        if (\n            !Design.IsDesignMode\n            && (CivitModel.ModelVersions?.Select(x => x.Files).Any(x => x == null || x.Count == 0) ?? true)\n        )\n        {\n            try\n            {\n                CivitModel = await civitApi.GetModelById(CivitModel.Id);\n            }\n            catch (Exception e)\n            {\n                logger.LogError(e, \"Failed to load CivitModel {Id}\", CivitModel.Id);\n                notificationService.Show(\n                    Resources.Label_UnexpectedErrorOccurred,\n                    e.Message,\n                    NotificationType.Error\n                );\n                return;\n            }\n        }\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.ShowNsfw,\n                settings => settings.ModelBrowserNsfwEnabled,\n                true\n            )\n        );\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.HideEarlyAccess,\n                settings => settings.HideEarlyAccessModels,\n                true\n            )\n        );\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.ShowTrainingData,\n                settings => settings.ShowTrainingDataInModelBrowser,\n                true\n            )\n        );\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.HideInstalledModels,\n                settings => settings.HideInstalledModelsInModelBrowser,\n                true\n            )\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.ModelFileNameFormat,\n                settings => settings.CivitModelBrowserFileNamePattern,\n                true\n            )\n        );\n\n        AddDisposable(\n            this.WhenPropertyChanged(vm => vm.ModelFileNameFormat)\n                .Throttle(TimeSpan.FromMilliseconds(50))\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe(formatProperty =>\n                {\n                    var provider = new FileNameFormatProvider\n                    {\n                        CivitModel = CivitModel,\n                        CivitModelVersion = SelectedVersion?.ModelVersion,\n                        CivitFile = SelectedVersion?.ModelVersion.Files?.FirstOrDefault(),\n                    };\n\n                    var template = formatProperty.Value ?? string.Empty;\n\n                    if (\n                        !string.IsNullOrEmpty(template)\n                        && provider.Validate(template) == ValidationResult.Success\n                    )\n                    {\n                        var format = FileNameFormat.Parse(template, provider);\n                        ModelNameFormatSample = \"Example: \" + format.GetFileName() + \".safetensors\";\n                    }\n                    else\n                    {\n                        // Use default format if empty\n                        var defaultFormat = FileNameFormat.Parse(\n                            FileNameFormat.DefaultModelBrowserTemplate,\n                            provider\n                        );\n                        ModelNameFormatSample = \"Example: \" + defaultFormat.GetFileName() + \".safetensors\";\n                    }\n                })\n        );\n\n        var earlyAccessPredicate = Observable\n            .FromEventPattern<PropertyChangedEventArgs>(this, nameof(PropertyChanged))\n            .Where(x => x.EventArgs.PropertyName is nameof(HideEarlyAccess) or nameof(HideInstalledModels))\n            .Select(_ => (Func<ModelVersionViewModel, bool>)ShouldIncludeVersion)\n            .StartWith(ShouldIncludeVersion)\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        AddDisposable(\n            modelVersionCache\n                .Connect()\n                .DeferUntilLoaded()\n                .Transform(modelVersion => new ModelVersionViewModel(modelIndexService, modelVersion))\n                .DisposeMany()\n                .Filter(earlyAccessPredicate)\n                .SortAndBind(\n                    ModelVersions,\n                    SortExpressionComparer<ModelVersionViewModel>.Descending(v => v.ModelVersion.PublishedAt)\n                )\n                .ObserveOn(SynchronizationContext.Current!)\n                .DisposeMany()\n                .Subscribe()\n        );\n\n        var showNsfwPredicate = Observable\n            .FromEventPattern<PropertyChangedEventArgs>(this, nameof(PropertyChanged))\n            .Where(x => x.EventArgs.PropertyName is nameof(ShowNsfw))\n            .Select(_ => (Func<CivitImage, bool>)ShouldShowNsfw)\n            .StartWith(ShouldShowNsfw)\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        AddDisposable(\n            imageCache\n                .Connect()\n                .Filter(showNsfwPredicate)\n                .Filter(img => img.Type == \"image\")\n                .Transform(x => new ImageSource(new Uri(x.Url)))\n                .Bind(ImageSources)\n                .ObserveOn(SynchronizationContext.Current!)\n                .DisposeMany()\n                .Subscribe()\n        );\n\n        var includeTrainingDataPredicate = Observable\n            .FromEventPattern<PropertyChangedEventArgs>(this, nameof(PropertyChanged))\n            .Where(x => x.EventArgs.PropertyName is nameof(ShowTrainingData))\n            .Select(_ => (Func<CivitFile, bool>)(ShouldIncludeCivitFile))\n            .StartWith(ShouldIncludeCivitFile)\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        AddDisposable(\n            civitFileCache\n                .Connect()\n                .Filter(includeTrainingDataPredicate)\n                .Transform(file => new CivitFileViewModel(\n                    modelIndexService,\n                    settingsManager,\n                    file,\n                    vmFactory,\n                    DownloadModelAsync\n                )\n                {\n                    InstallLocations = new ObservableCollection<string>(LoadInstallLocations(file)),\n                })\n                .Bind(CivitFiles)\n                .ObserveOn(SynchronizationContext.Current!)\n                .DisposeMany()\n                .Subscribe()\n        );\n\n        modelVersionCache.EditDiff(CivitModel.ModelVersions ?? [], (a, b) => a.Id == b.Id);\n\n        SelectedVersion = ModelVersions.FirstOrDefault();\n        Description = $\"\"\"<html><body class=\"markdown-body\">{CivitModel.Description}</body></html>\"\"\";\n    }\n\n    [RelayCommand]\n    private void GoBack() => navigationService.GoBack();\n\n    public async Task DownloadModelAsync(CivitFileViewModel viewModel, string? locationKey = null)\n    {\n        DirectoryPath? finalDestinationDir = null;\n        var effectiveLocationKeyForPreference = string.Empty;\n\n        switch (locationKey)\n        {\n            case null:\n            {\n                var preferenceUsed = false;\n                if (\n                    settingsManager.Settings.ModelTypeDownloadPreferences.TryGetValue(\n                        CivitModel.Type.ToString(),\n                        out var preference\n                    )\n                )\n                {\n                    if (\n                        preference.SelectedInstallLocation == \"Custom...\"\n                        && !string.IsNullOrWhiteSpace(preference.CustomInstallLocation)\n                    )\n                    {\n                        finalDestinationDir = new DirectoryPath(preference.CustomInstallLocation);\n                        effectiveLocationKeyForPreference = \"Custom...\";\n                        preferenceUsed = true;\n                    }\n                    else if (\n                        !string.IsNullOrWhiteSpace(preference.SelectedInstallLocation)\n                        && viewModel.InstallLocations.Contains(preference.SelectedInstallLocation)\n                    )\n                    {\n                        var basePath = new DirectoryPath(settingsManager.ModelsDirectory);\n                        finalDestinationDir = new DirectoryPath(\n                            Path.Combine(\n                                basePath.ToString(),\n                                preference\n                                    .SelectedInstallLocation.Replace(\"Models\\\\\", \"\")\n                                    .Replace(\"Models/\", \"\")\n                            )\n                        );\n                        effectiveLocationKeyForPreference = preference.SelectedInstallLocation;\n                        preferenceUsed = true;\n                    }\n                }\n\n                if (!preferenceUsed)\n                {\n                    finalDestinationDir = GetSharedFolderPath(\n                        settingsManager.ModelsDirectory,\n                        viewModel.CivitFile.Type,\n                        CivitModel.Type,\n                        CivitModel.BaseModelType\n                    );\n                    effectiveLocationKeyForPreference =\n                        viewModel.InstallLocations.FirstOrDefault(loc =>\n                            loc != \"Custom...\"\n                            && finalDestinationDir\n                                .ToString()\n                                .Contains(loc.Replace(\"Models\\\\\", \"\").Replace(\"Models/\", \"\"))\n                        ) ?? viewModel.InstallLocations.First();\n                }\n\n                break;\n            }\n            case \"Custom...\":\n            {\n                effectiveLocationKeyForPreference = \"Custom...\";\n                var files = await App.StorageProvider.OpenFolderPickerAsync(\n                    new FolderPickerOpenOptions\n                    {\n                        Title = \"Select Download Folder\",\n                        AllowMultiple = false,\n                        SuggestedStartLocation = await App.StorageProvider.TryGetFolderFromPathAsync(\n                            Path.Combine(\n                                settingsManager.ModelsDirectory,\n                                CivitModel.Type.ConvertTo<SharedFolderType>().GetStringValue()\n                            )\n                        ),\n                    }\n                );\n\n                if (files.FirstOrDefault()?.TryGetLocalPath() is { } customPath)\n                {\n                    finalDestinationDir = new DirectoryPath(customPath);\n                }\n                else\n                {\n                    return;\n                }\n\n                break;\n            }\n            default:\n            {\n                effectiveLocationKeyForPreference = locationKey;\n                var basePath = new DirectoryPath(settingsManager.ModelsDirectory);\n                finalDestinationDir = new DirectoryPath(\n                    Path.Combine(\n                        basePath.ToString(),\n                        locationKey.Replace(\"Models\\\\\", \"\").Replace(\"Models/\", \"\")\n                    )\n                );\n                break;\n            }\n        }\n\n        if (finalDestinationDir is null)\n        {\n            notificationService.Show(\n                Resources.Label_UnexpectedErrorOccurred,\n                \"Could not determine final destination directory.\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        var fileNameOverride = ParseFileNameFormat(\n            CivitModel,\n            SelectedVersion?.ModelVersion,\n            viewModel.CivitFile\n        );\n\n        await modelImportService.DoImport(\n            CivitModel,\n            finalDestinationDir,\n            SelectedVersion?.ModelVersion,\n            viewModel.CivitFile,\n            fileNameOverride,\n            inferenceDefaults: IsInferenceDefaultsEnabled ? SamplerCardViewModel : null\n        );\n\n        notificationService.Show(\n            Resources.Label_DownloadStarted,\n            string.Format(\n                Resources.Label_DownloadWillBeSavedToLocation,\n                viewModel.CivitFile.Name,\n                finalDestinationDir.JoinFile(fileNameOverride).Directory\n            )\n        );\n\n        if (CivitModel.Type != CivitModelType.Unknown)\n        {\n            var modelTypeKey = CivitModel.Type.ToString();\n            var newPreference = new LastDownloadLocationInfo\n            {\n                SelectedInstallLocation = effectiveLocationKeyForPreference,\n                CustomInstallLocation =\n                    (effectiveLocationKeyForPreference == \"Custom...\")\n                        ? finalDestinationDir.ToString()\n                        : null,\n            };\n            settingsManager.Transaction(s =>\n            {\n                s.ModelTypeDownloadPreferences[modelTypeKey] = newPreference;\n            });\n        }\n    }\n\n    [RelayCommand]\n    private async Task ShowBulkDownloadDialogAsync()\n    {\n        var dialogVm = vmFactory.Get<ConfirmBulkDownloadDialogViewModel>(vm => vm.Model = CivitModel);\n        var dialog = dialogVm.GetDialog();\n        var result = await dialog.ShowAsync();\n\n        if (result != ContentDialogResult.Primary)\n            return;\n\n        foreach (var file in dialogVm.FilesToDownload)\n        {\n            var sharedFolderPath = GetSharedFolderPath(\n                new DirectoryPath(settingsManager.ModelsDirectory),\n                file.FileViewModel.CivitFile.Type,\n                CivitModel.Type,\n                CivitModel.BaseModelType\n            );\n\n            var folderName = Path.GetInvalidFileNameChars()\n                .Aggregate(CivitModel.Name, (current, c) => current.Replace(c, '_'));\n\n            var destinationDir = new DirectoryPath(sharedFolderPath, folderName);\n            destinationDir.Create();\n\n            var fileNameOverride = ParseFileNameFormat(\n                CivitModel,\n                file.ModelVersion,\n                file.FileViewModel.CivitFile\n            );\n\n            await modelImportService.DoImport(\n                CivitModel,\n                destinationDir,\n                file.ModelVersion,\n                file.FileViewModel.CivitFile,\n                fileNameOverride\n            );\n        }\n\n        notificationService.Show(\n            Resources.Label_BulkDownloadStarted,\n            string.Format(Resources.Label_BulkDownloadStartedMessage, dialogVm.FilesToDownload.Count),\n            NotificationType.Success\n        );\n    }\n\n    [RelayCommand]\n    private async Task ShowImageDialog(ImageSource? image)\n    {\n        if (image is null)\n            return;\n\n        var currentIndex = ImageSources.IndexOf(image);\n\n        // Preload\n        await image.GetBitmapAsync();\n\n        var vm = vmFactory.Get<ImageViewerViewModel>();\n        vm.ImageSource = image;\n\n        var url = image.RemoteUrl;\n        if (url is null)\n            return;\n\n        try\n        {\n            var imageId = Path.GetFileNameWithoutExtension(url.Segments.Last());\n            var imageData = await civitTrpcApi.GetImageGenerationData($$$\"\"\"{\"json\":{\"id\":{{{imageId}}}}}\"\"\");\n            vm.CivitImageMetadata = imageData.Result.Data.Json;\n            vm.CivitImageMetadata.OtherMetadata = GetOtherMetadata(vm.CivitImageMetadata);\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Failed to load CivitImageMetadata for {Url}\", url);\n        }\n\n        using var onNext = Observable\n            .FromEventPattern<DirectionalNavigationEventArgs>(\n                vm,\n                nameof(ImageViewerViewModel.NavigationRequested)\n            )\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(ctx =>\n            {\n                Dispatcher\n                    .UIThread.InvokeAsync(async () =>\n                    {\n                        var sender = (ImageViewerViewModel)ctx.Sender!;\n                        var newIndex = currentIndex + (ctx.EventArgs.IsNext ? 1 : -1);\n\n                        if (newIndex >= 0 && newIndex < ImageSources.Count)\n                        {\n                            var newImageSource = ImageSources[newIndex];\n\n                            // Preload\n                            await newImageSource.GetBitmapAsync();\n                            await newImageSource.GetOrRefreshTemplateKeyAsync();\n                            sender.ImageSource = newImageSource;\n\n                            try\n                            {\n                                sender.CivitImageMetadata = null;\n                                if (newImageSource.RemoteUrl is not { } newUrl)\n                                    return;\n                                var imageId = Path.GetFileNameWithoutExtension(newUrl.Segments.Last());\n                                var imageData = await civitTrpcApi.GetImageGenerationData(\n                                    $$$\"\"\"{\"json\":{\"id\":{{{imageId}}}}}\"\"\"\n                                );\n                                imageData.Result.Data.Json.OtherMetadata = GetOtherMetadata(\n                                    imageData.Result.Data.Json\n                                );\n                                sender.CivitImageMetadata = imageData.Result.Data.Json;\n                            }\n                            catch (Exception e)\n                            {\n                                logger.LogError(e, \"Failed to load CivitImageMetadata for {Url}\", url);\n                            }\n\n                            currentIndex = newIndex;\n                        }\n                    })\n                    .SafeFireAndForget();\n            });\n\n        vm.NavigateToModelRequested += VmOnNavigateToModelRequested;\n\n        await vm.GetDialog().ShowAsync();\n    }\n\n    [RelayCommand]\n    private void SearchByAuthor()\n    {\n        navigationService.GoBack();\n        EventManager.Instance.OnNavigateAndFindCivitAuthorRequested(CivitModel.Creator?.Username);\n    }\n\n    [RelayCommand]\n    private async Task DeleteModelVersion(CivitModelVersion modelVersion)\n    {\n        if (modelVersion.Files == null)\n            return;\n\n        var pathsToDelete = new List<string>();\n\n        foreach (var file in modelVersion.Files)\n        {\n            if (file is not { Type: CivitFileType.Model, Hashes.BLAKE3: not null })\n                continue;\n\n            var matchingModels = (await modelIndexService.FindByHashAsync(file.Hashes.BLAKE3)).ToList();\n\n            if (matchingModels.Count == 0)\n            {\n                await modelIndexService.RefreshIndex();\n                matchingModels = (await modelIndexService.FindByHashAsync(file.Hashes.BLAKE3)).ToList();\n            }\n\n            if (matchingModels.Count == 0)\n            {\n                logger.LogWarning(\n                    \"No matching models found for file {FileName} with hash {Hash}\",\n                    file.Name,\n                    file.Hashes.BLAKE3\n                );\n                continue;\n            }\n\n            foreach (var localModel in matchingModels)\n            {\n                var checkpointPath = new FilePath(localModel.GetFullPath(settingsManager.ModelsDirectory));\n                if (File.Exists(checkpointPath))\n                {\n                    pathsToDelete.Add(checkpointPath);\n                }\n\n                var previewPath = localModel.GetPreviewImageFullPath(settingsManager.ModelsDirectory);\n                if (File.Exists(previewPath))\n                {\n                    pathsToDelete.Add(previewPath);\n                }\n\n                var cmInfoPath = checkpointPath.ToString().Replace(checkpointPath.Extension, \".cm-info.json\");\n                if (File.Exists(cmInfoPath))\n                {\n                    pathsToDelete.Add(cmInfoPath);\n                }\n            }\n        }\n\n        var confirmDeleteVm = vmFactory.Get<ConfirmDeleteDialogViewModel>();\n        confirmDeleteVm.PathsToDelete = pathsToDelete;\n\n        var dialog = confirmDeleteVm.GetDialog();\n        var result = await dialog.ShowAsync();\n\n        if (result != ContentDialogResult.Primary)\n            return;\n\n        try\n        {\n            await confirmDeleteVm.ExecuteCurrentDeleteOperationAsync(failFast: true);\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Failed to delete model files for {ModelName}\", modelVersion.Name);\n        }\n        finally\n        {\n            await modelIndexService.RefreshIndex();\n        }\n    }\n\n    [RelayCommand]\n    public Task NextModel() => CanGoNext ? NavigateToModelByIndexOffset(1) : Task.CompletedTask;\n\n    [RelayCommand]\n    public Task PreviousModel() => CanGoPrevious ? NavigateToModelByIndexOffset(-1) : Task.CompletedTask;\n\n    private async Task NavigateToModelByIndexOffset(int offset)\n    {\n        var newIndex = CurrentIndex + offset;\n        var modelId = ModelIdList[newIndex];\n\n        try\n        {\n            var newModel = await civitApi.GetModelById(modelId);\n            CivitModel = newModel;\n            CurrentIndex = newIndex;\n\n            // reload caches for new model\n            modelVersionCache.EditDiff(CivitModel.ModelVersions ?? [], (a, b) => a.Id == b.Id);\n            SelectedVersion = ModelVersions.FirstOrDefault();\n\n            imageCache.EditDiff(SelectedVersion?.ModelVersion.Images ?? [], (a, b) => a.Url == b.Url);\n            civitFileCache.EditDiff(SelectedVersion?.ModelVersion.Files ?? [], (a, b) => a.Id == b.Id);\n\n            Description = $\"\"\"<html><body class=\"markdown-body\">{CivitModel.Description}</body></html>\"\"\";\n            ModelVersionDescription = string.IsNullOrWhiteSpace(SelectedVersion?.ModelVersion.Description)\n                ? string.Empty\n                : $\"\"\"<html><body class=\"markdown-body\">{SelectedVersion.ModelVersion.Description}</body></html>\"\"\";\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Failed to load CivitModel {Id}\", modelId);\n            notificationService.Show(\n                Resources.Label_UnexpectedErrorOccurred,\n                e.Message,\n                NotificationType.Error\n            );\n        }\n    }\n\n    private void VmOnNavigateToModelRequested(object? sender, int modelId)\n    {\n        if (sender is not ImageViewerViewModel vm)\n            return;\n\n        var detailsPageVm = vmFactory.Get<CivitDetailsPageViewModel>(x =>\n            x.CivitModel = new CivitModel { Id = modelId }\n        );\n        navigationService.NavigateTo(detailsPageVm, BetterSlideNavigationTransition.PageSlideFromRight);\n\n        vm.NavigateToModelRequested -= VmOnNavigateToModelRequested;\n        vm.OnCloseButtonClick();\n    }\n\n    private bool ShouldIncludeCivitFile(CivitFile file)\n    {\n        if (ShowTrainingData)\n            return true;\n\n        return file.Type is CivitFileType.Model or CivitFileType.PrunedModel or CivitFileType.VAE;\n    }\n\n    partial void OnSelectedVersionChanged(ModelVersionViewModel? value)\n    {\n        imageCache.EditDiff(value?.ModelVersion.Images ?? [], (a, b) => a.Url == b.Url);\n        civitFileCache.EditDiff(value?.ModelVersion.Files ?? [], (a, b) => a.Id == b.Id);\n        SelectedFiles = new ObservableCollection<CivitFileViewModel>([CivitFiles.FirstOrDefault()]);\n\n        ModelVersionDescription = string.IsNullOrWhiteSpace(value?.ModelVersion.Description)\n            ? string.Empty\n            : $\"\"\"<html><body class=\"markdown-body\">{value.ModelVersion.Description}</body></html>\"\"\";\n    }\n\n    public override void OnUnloaded()\n    {\n        ModelVersions.ForEach(x => x.Dispose());\n        CivitFiles.ForEach(x => x.Dispose());\n        Dispose(true);\n        base.OnUnloaded();\n    }\n\n    public static ValidationResult ValidateModelFileNameFormat(string? format, ValidationContext context)\n    {\n        return FileNameFormatProvider.GetSampleForModelBrowser().Validate(format ?? string.Empty);\n    }\n\n    private bool ShouldShowNsfw(CivitImage? image)\n    {\n        if (Design.IsDesignMode)\n            return true;\n\n        if (image == null)\n            return false;\n\n        return image.NsfwLevel switch\n        {\n            null or <= 1 => true,\n            _ => ShowNsfw,\n        };\n    }\n\n    private bool ShouldIncludeVersion(ModelVersionViewModel? versionVm)\n    {\n        if (Design.IsDesignMode)\n            return true;\n\n        if (versionVm == null)\n            return false;\n\n        var version = versionVm.ModelVersion;\n\n        if (HideInstalledModels && versionVm.IsInstalled)\n            return false;\n\n        return !version.IsEarlyAccess || !HideEarlyAccess;\n    }\n\n    private ObservableCollection<string> LoadInstallLocations(CivitFile selectedFile)\n    {\n        if (Design.IsDesignMode)\n            return [\"Models/StableDiffusion\", \"Custom...\"];\n\n        var installLocations = new List<string>();\n\n        var rootModelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory);\n\n        var downloadDirectory = GetSharedFolderPath(\n            rootModelsDirectory,\n            selectedFile.Type,\n            CivitModel.Type,\n            CivitModel.BaseModelType\n        );\n\n        if (!downloadDirectory.ToString().EndsWith(\"Unknown\"))\n        {\n            installLocations.Add(\n                Path.Combine(\"Models\", Path.GetRelativePath(rootModelsDirectory, downloadDirectory))\n            );\n            foreach (\n                var directory in downloadDirectory.EnumerateDirectories(\n                    \"*\",\n                    EnumerationOptionConstants.AllDirectories\n                )\n            )\n            {\n                installLocations.Add(\n                    Path.Combine(\"Models\", Path.GetRelativePath(rootModelsDirectory, directory))\n                );\n            }\n        }\n\n        if (downloadDirectory.ToString().EndsWith(SharedFolderType.DiffusionModels.GetStringValue()))\n        {\n            // also add StableDiffusion in case we have an AIO version\n            var stableDiffusionDirectory = rootModelsDirectory.JoinDir(\n                SharedFolderType.StableDiffusion.GetStringValue()\n            );\n            installLocations.Add(\n                Path.Combine(\"Models\", Path.GetRelativePath(rootModelsDirectory, stableDiffusionDirectory))\n            );\n        }\n\n        installLocations.Add(\"Custom...\");\n        return new ObservableCollection<string>(\n            installLocations.OrderBy(s => s.Replace(Path.DirectorySeparatorChar.ToString(), string.Empty))\n        );\n    }\n\n    private static DirectoryPath GetSharedFolderPath(\n        DirectoryPath rootModelsDirectory,\n        CivitFileType? fileType,\n        CivitModelType modelType,\n        string? baseModelType\n    )\n    {\n        if (fileType is CivitFileType.VAE)\n        {\n            return rootModelsDirectory.JoinDir(SharedFolderType.VAE.GetStringValue());\n        }\n\n        if (\n            modelType is CivitModelType.Checkpoint\n            && (\n                baseModelType == CivitBaseModelType.Flux1D.GetStringValue()\n                || baseModelType == CivitBaseModelType.Flux1S.GetStringValue()\n                || baseModelType == CivitBaseModelType.WanVideo.GetStringValue()\n                || baseModelType?.StartsWith(\"Wan\", StringComparison.OrdinalIgnoreCase) is true\n                || baseModelType?.StartsWith(\"Flux\", StringComparison.OrdinalIgnoreCase) is true\n                || baseModelType?.StartsWith(\"Hunyuan\", StringComparison.OrdinalIgnoreCase) is true\n            )\n        )\n        {\n            return rootModelsDirectory.JoinDir(SharedFolderType.DiffusionModels.GetStringValue());\n        }\n\n        return rootModelsDirectory.JoinDir(modelType.ConvertTo<SharedFolderType>().GetStringValue());\n    }\n\n    private IReadOnlyDictionary<string, string> GetOtherMetadata(CivitImageGenerationDataResponse value)\n    {\n        var metaDict = new Dictionary<string, string>();\n        if (value.Metadata?.CfgScale is not null)\n            metaDict[\"CFG\"] = value.Metadata.CfgScale.ToString();\n\n        if (value.Metadata?.Steps is not null)\n            metaDict[\"Steps\"] = value.Metadata.Steps.ToString();\n\n        if (value.Metadata?.Sampler is not null)\n            metaDict[\"Sampler\"] = value.Metadata.Sampler;\n\n        if (value.Metadata?.Seed is not null)\n            metaDict[\"Seed\"] = value.Metadata.Seed.ToString();\n\n        if (value.Metadata?.ScheduleType is not null)\n            metaDict[\"Scheduler\"] = value.Metadata.ScheduleType;\n\n        if (value.Metadata?.Scheduler is not null)\n            metaDict[\"Scheduler\"] = value.Metadata.Scheduler;\n\n        if (value.Metadata?.Rng is not null)\n            metaDict[\"RNG\"] = value.Metadata.Rng;\n\n        return metaDict;\n    }\n\n    private string ParseFileNameFormat(\n        CivitModel? civitModel,\n        CivitModelVersion? modelVersion,\n        CivitFile? civitFile\n    )\n    {\n        var formatProvider = new FileNameFormatProvider\n        {\n            CivitModel = civitModel,\n            CivitModelVersion = modelVersion,\n            CivitFile = civitFile,\n        };\n\n        // Parse to format\n        if (\n            string.IsNullOrEmpty(ModelFileNameFormat)\n            || !FileNameFormat.TryParse(ModelFileNameFormat, formatProvider, out var format)\n        )\n        {\n            // Fallback to default\n            logger.LogWarning(\n                \"Failed to parse format template: {ModelFileNameFormat}, using default\",\n                ModelFileNameFormat\n            );\n\n            format = FileNameFormat.Parse(FileNameFormat.DefaultModelBrowserTemplate, formatProvider);\n        }\n\n        return format.GetFileName();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/HuggingFacePageViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Concurrent;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models.HuggingFace;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.HuggingFacePage;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;\n\n[View(typeof(Views.HuggingFacePage))]\n[RegisterSingleton<HuggingFacePageViewModel>]\npublic partial class HuggingFacePageViewModel : TabViewModelBase\n{\n    private readonly ITrackedDownloadService trackedDownloadService;\n    private readonly ISettingsManager settingsManager;\n    private readonly INotificationService notificationService;\n\n    public SourceCache<HuggingfaceItem, string> ItemsCache { get; } =\n        new(i => i.RepositoryPath + i.ModelName);\n\n    public IObservableCollection<CategoryViewModel> Categories { get; set; } =\n        new ObservableCollectionExtended<CategoryViewModel>();\n\n    public string DownloadPercentText =>\n        Math.Abs(TotalProgress.Percentage - 100f) < 0.001f\n            ? \"Download Complete\"\n            : $\"Downloading {TotalProgress.Percentage:0}%\";\n\n    [ObservableProperty]\n    private int numSelected;\n\n    private ConcurrentDictionary<Guid, ProgressReport> progressReports = new();\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(DownloadPercentText))]\n    private ProgressReport totalProgress;\n\n    private readonly DispatcherTimer progressTimer = new() { Interval = TimeSpan.FromMilliseconds(100) };\n\n    public HuggingFacePageViewModel(\n        ITrackedDownloadService trackedDownloadService,\n        ISettingsManager settingsManager,\n        INotificationService notificationService\n    )\n    {\n        this.trackedDownloadService = trackedDownloadService;\n        this.settingsManager = settingsManager;\n        this.notificationService = notificationService;\n\n        ItemsCache\n            .Connect()\n            .DeferUntilLoaded()\n            .Group(i => i.ModelCategory)\n            .Transform(\n                g =>\n                    new CategoryViewModel(\n                        g.Cache.Items,\n                        Design.IsDesignMode ? string.Empty : settingsManager.ModelsDirectory\n                    )\n                    {\n                        Title = g.Key.GetDescription() ?? g.Key.ToString()\n                    }\n            )\n            .SortBy(vm => vm.Title ?? \"\")\n            .Bind(Categories)\n            .WhenAnyPropertyChanged()\n            .Throttle(TimeSpan.FromMilliseconds(50))\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(_ => NumSelected = Categories.Sum(c => c.NumSelected));\n\n        progressTimer.Tick += (_, _) =>\n        {\n            var currentSum = 0ul;\n            var totalSum = 0ul;\n            foreach (var progress in progressReports.Values)\n            {\n                currentSum += progress.Current ?? 0;\n                totalSum += progress.Total ?? 0;\n            }\n\n            TotalProgress = new ProgressReport(current: currentSum, total: totalSum);\n        };\n    }\n\n    public override void OnLoaded()\n    {\n        if (ItemsCache.Count > 0)\n            return;\n\n        using var reader = new StreamReader(Assets.HfPackagesJson.Open());\n        var packages =\n            JsonSerializer.Deserialize<IReadOnlyList<HuggingfaceItem>>(\n                reader.ReadToEnd(),\n                new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } }\n            ) ?? throw new InvalidOperationException(\"Failed to read hf-packages.json\");\n\n        ItemsCache.EditDiff(packages, (a, b) => a.RepositoryPath == b.RepositoryPath);\n    }\n\n    public void ClearSelection()\n    {\n        foreach (var category in Categories)\n        {\n            category.IsChecked = true;\n            category.IsChecked = false;\n        }\n    }\n\n    public void SelectAll()\n    {\n        foreach (var category in Categories)\n        {\n            category.IsChecked = true;\n        }\n    }\n\n    public void Refresh()\n    {\n        ItemsCache.Clear();\n        OnLoaded();\n    }\n\n    [RelayCommand]\n    private async Task ImportSelected()\n    {\n        var selected = Categories.SelectMany(c => c.Items).Where(i => i.IsSelected).ToArray();\n\n        foreach (var viewModel in selected)\n        {\n            foreach (var file in viewModel.Item.Files)\n            {\n                var url =\n                    $\"https://huggingface.co/{viewModel.Item.RepositoryPath}/resolve/main/{file}?download=true\";\n                var sharedFolderType = viewModel.Item.ModelCategory.ConvertTo<SharedFolderType>();\n                var fileName = Path.GetFileName(file);\n\n                if (\n                    fileName.Equals(\"ae.safetensors\", StringComparison.OrdinalIgnoreCase)\n                    && viewModel.Item.ModelName == \"HiDream I1 VAE\"\n                )\n                {\n                    fileName = \"hidream_vae.safetensors\";\n                }\n\n                var downloadPath = new FilePath(\n                    Path.Combine(\n                        Design.IsDesignMode ? string.Empty : settingsManager.ModelsDirectory,\n                        sharedFolderType.ToString(),\n                        viewModel.Item.Subfolder ?? string.Empty,\n                        fileName\n                    )\n                );\n\n                downloadPath.Directory?.Create();\n                var download = trackedDownloadService.NewDownload(url, downloadPath);\n                download.ProgressUpdate += DownloadOnProgressUpdate;\n                download.ProgressStateChanged += (_, e) =>\n                {\n                    if (e == ProgressState.Success)\n                    {\n                        viewModel.NotifyExistsChanged();\n                    }\n                };\n                await trackedDownloadService.TryStartDownload(download);\n\n                await Task.Delay(Random.Shared.Next(50, 100));\n            }\n\n            viewModel.IsSelected = false;\n        }\n        progressTimer.Start();\n    }\n\n    private void DownloadOnProgressUpdate(object? sender, ProgressReport e)\n    {\n        if (sender is not TrackedDownload trackedDownload)\n            return;\n\n        progressReports[trackedDownload.Id] = e;\n    }\n\n    partial void OnTotalProgressChanged(ProgressReport value)\n    {\n        if (Math.Abs(value.Percentage - 100) < 0.001f)\n        {\n            notificationService.Show(\n                \"Download complete\",\n                \"All selected models have been downloaded.\",\n                NotificationType.Success\n            );\n            progressTimer.Stop();\n\n            ClearSelection();\n            DelayedClearProgress(TimeSpan.FromSeconds(1.5));\n        }\n    }\n\n    private void DelayedClearProgress(TimeSpan delay)\n    {\n        Task.Delay(delay)\n            .ContinueWith(_ =>\n            {\n                TotalProgress = new ProgressReport(0, 0);\n            });\n    }\n\n    public override string Header => Resources.Label_HuggingFace;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserCardViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Nito.Disposables.Internals;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Api.OpenModelsDb;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;\n\n[Localizable(false)]\npublic sealed class OpenModelDbBrowserCardViewModel(OpenModelDbManager openModelDbManager)\n{\n    public OpenModelDbKeyedModel? Model { get; set; }\n\n    public Uri? ModelUri => Model is { } model ? openModelDbManager.ModelsBaseUri.Append(model.Id) : null;\n\n    public Uri? ThumbnailUri =>\n        Model?.Thumbnail?.GetImageAbsoluteUris().FirstOrDefault()\n        ?? Model\n            ?.Images\n            ?.Select(image => image.GetImageAbsoluteUris().FirstOrDefault())\n            .WhereNotNull()\n            .FirstOrDefault()\n        ?? Assets.NoImage;\n\n    public IEnumerable<OpenModelDbTag> Tags =>\n        Model?.Tags?.Select(tagId => openModelDbManager.Tags?.GetValueOrDefault(tagId)).WhereNotNull() ?? [];\n\n    public OpenModelDbArchitecture? Architecture =>\n        Model?.Architecture is { } architectureId\n            ? openModelDbManager.Architectures?.GetValueOrDefault(architectureId)\n            : null;\n\n    public string? DisplayScale => Model?.Scale is { } scale ? $\"{scale}x\" : null;\n\n    public string? DefaultAuthor\n    {\n        get\n        {\n            if (Model?.Author?.Value is string author)\n            {\n                return author;\n            }\n            if (Model?.Author?.Value is string[] { Length: > 0 } authorArray)\n            {\n                return authorArray.First();\n            }\n            return null;\n        }\n    }\n\n    public Uri? DefaultAuthorProfileUri =>\n        DefaultAuthor is { } author ? openModelDbManager.UsersBaseUri.Append(author) : null;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.Filters.cs",
    "content": "using System;\nusing System.ComponentModel;\nusing System.Linq;\nusing System.Reactive;\nusing System.Reactive.Linq;\nusing System.Reactive.Subjects;\nusing System.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing DynamicData.Binding;\nusing StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;\n\npublic partial class OpenModelDbBrowserViewModel\n{\n    [ObservableProperty]\n    private string? searchQuery;\n\n    private Subject<Unit> SearchQueryReload { get; } = new();\n\n    private IObservable<Func<OpenModelDbModel, bool>> SearchQueryPredicate =>\n        SearchQueryReload\n            .Select(pv => CreateSearchQueryPredicate(SearchQuery))\n            .StartWith(CreateSearchQueryPredicate(null))\n            .ObserveOn(SynchronizationContext.Current!)\n            .AsObservable();\n\n    private IObservable<SortExpressionComparer<OpenModelDbBrowserCardViewModel>> SortComparer =>\n        Observable\n            .FromEventPattern<PropertyChangedEventArgs>(this, nameof(PropertyChanged))\n            .Where(x => x.EventArgs.PropertyName is nameof(SelectedSortOption))\n            .Throttle(TimeSpan.FromMilliseconds(50))\n            .Select(_ => GetSortComparer(SelectedSortOption))\n            .StartWith(GetSortComparer(SelectedSortOption))\n            .ObserveOn(SynchronizationContext.Current!)\n            .AsObservable();\n\n    private static Func<OpenModelDbModel, bool> CreateSearchQueryPredicate(string? text)\n    {\n        if (string.IsNullOrWhiteSpace(text))\n        {\n            return static _ => true;\n        }\n\n        return x =>\n            x.Name?.Contains(text, StringComparison.OrdinalIgnoreCase) == true\n            || x.Tags?.Any(tag => tag.StartsWith(text, StringComparison.OrdinalIgnoreCase)) == true;\n    }\n\n    private static SortExpressionComparer<OpenModelDbBrowserCardViewModel> GetSortComparer(\n        string sortOption\n    ) =>\n        sortOption switch\n        {\n            \"Latest\"\n                => SortExpressionComparer<OpenModelDbBrowserCardViewModel>.Descending(x => x.Model?.Date),\n            \"Largest Scale\"\n                => SortExpressionComparer<OpenModelDbBrowserCardViewModel>.Descending(x => x.Model?.Scale),\n            \"Smallest Scale\"\n                => SortExpressionComparer<OpenModelDbBrowserCardViewModel>.Ascending(x => x.Model?.Scale),\n            \"Largest Size\"\n                => SortExpressionComparer<OpenModelDbBrowserCardViewModel>.Descending(\n                    x => x.Model?.Size?.FirstOrDefault()\n                ),\n            \"Smallest Size\"\n                => SortExpressionComparer<OpenModelDbBrowserCardViewModel>.Ascending(\n                    x => x.Model?.Size?.FirstOrDefault()\n                ),\n            _ => SortExpressionComparer<OpenModelDbBrowserCardViewModel>.Descending(x => x.Model?.Date)\n        };\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/OpenModelDbBrowserViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Reactive;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Apizr;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing Fusillade;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Api.OpenModelsDb;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;\n\n[View(typeof(OpenModelDbBrowserPage))]\n[RegisterSingleton<OpenModelDbBrowserViewModel>]\npublic sealed partial class OpenModelDbBrowserViewModel(\n    ILogger<OpenModelDbBrowserViewModel> logger,\n    IServiceManager<ViewModelBase> vmManager,\n    OpenModelDbManager openModelDbManager,\n    INotificationService notificationService\n) : TabViewModelBase\n{\n    // ReSharper disable once LocalizableElement\n    public override string Header => \"OpenModelDB\";\n\n    [ObservableProperty]\n    private bool isLoading;\n\n    [ObservableProperty]\n    private string selectedSortOption = \"Latest\";\n\n    public SourceCache<OpenModelDbKeyedModel, string> ModelCache { get; } = new(static x => x.Id);\n\n    public IObservableCollection<OpenModelDbBrowserCardViewModel> FilteredModelCards { get; } =\n        new ObservableCollectionExtended<OpenModelDbBrowserCardViewModel>();\n\n    public List<string> SortOptions =>\n        [\"Latest\", \"Largest Scale\", \"Smallest Scale\", \"Largest Size\", \"Smallest Size\"];\n\n    protected override void OnInitialLoaded()\n    {\n        base.OnInitialLoaded();\n\n        ModelCache\n            .Connect()\n            .DeferUntilLoaded()\n            .Filter(SearchQueryPredicate)\n            .Transform(model => new OpenModelDbBrowserCardViewModel(openModelDbManager) { Model = model })\n            .SortAndBind(FilteredModelCards, SortComparer)\n            .ObserveOn(SynchronizationContext.Current!)\n            .Subscribe();\n    }\n\n    [RelayCommand]\n    private async Task SearchAsync()\n    {\n        try\n        {\n            await LoadDataAsync();\n        }\n        catch (ApizrException<OpenModelDbModelsResponse> e)\n        {\n            logger.LogWarning(e, \"Failed to load models from OpenModelDB\");\n            notificationService.ShowPersistent(\"Failed to load models from OpenModelDB\", e.Message);\n        }\n\n        SearchQueryReload.OnNext(Unit.Default);\n    }\n\n    [RelayCommand]\n    private async Task OpenModelCardAsync(OpenModelDbBrowserCardViewModel? card)\n    {\n        if (card?.Model is not { } model)\n        {\n            return;\n        }\n\n        var vm = vmManager.Get<OpenModelDbModelDetailsViewModel>();\n        vm.Model = model;\n\n        var dialog = vm.GetDialog();\n        dialog.MaxDialogHeight = 920;\n        await dialog.ShowAsync();\n    }\n\n    /// <summary>\n    /// Populate the model cache from api.\n    /// </summary>\n    private async Task LoadDataAsync(Priority priority = default)\n    {\n        await openModelDbManager.EnsureMetadataLoadedAsync();\n\n        var response = await openModelDbManager.ExecuteAsync(\n            api => api.GetModels(),\n            options => options.WithPriority(priority)\n        );\n\n        if (ModelCache.Count == 0)\n        {\n            ModelCache.Edit(innerCache =>\n            {\n                innerCache.Load(response.GetKeyedModels());\n            });\n        }\n        else\n        {\n            ModelCache.EditDiff(response.GetKeyedModels());\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing Avalonia.Controls;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing FluentAvalonia.UI.Controls;\nusing FluentIcons.Common;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(CheckpointBrowserPage))]\n[RegisterSingleton<CheckpointBrowserViewModel>]\npublic partial class CheckpointBrowserViewModel : PageViewModelBase\n{\n    public override string Title => Resources.Label_ModelBrowser;\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.BrainCircuit, IconVariant = IconVariant.Filled };\n\n    public IReadOnlyList<TabItem> Pages { get; }\n\n    [ObservableProperty]\n    private TabItem? selectedPage;\n\n    /// <inheritdoc/>\n    public CheckpointBrowserViewModel(\n        CivitAiBrowserViewModel civitAiBrowserViewModel,\n        HuggingFacePageViewModel huggingFaceViewModel,\n        OpenModelDbBrowserViewModel openModelDbBrowserViewModel\n    )\n    {\n        Pages = new List<TabItem>(\n            new List<TabViewModelBase>(\n                [civitAiBrowserViewModel, huggingFaceViewModel, openModelDbBrowserViewModel]\n            ).Select(vm => new TabItem { Header = vm.Header, Content = vm })\n        );\n        SelectedPage = Pages.FirstOrDefault();\n        EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested;\n    }\n\n    private void OnNavigateAndFindCivitModelRequested(object? sender, int e)\n    {\n        SelectedPage = Pages.FirstOrDefault();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/CheckpointManager/BaseModelOptionViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.CheckpointManager;\n\npublic partial class BaseModelOptionViewModel : ObservableObject\n{\n    [ObservableProperty]\n    private bool isSelected;\n\n    [ObservableProperty]\n    private string modelType = string.Empty;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFileViewModel.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.ComponentModel;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Data;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.CheckpointManager;\n\npublic partial class CheckpointFileViewModel : SelectableViewModelBase\n{\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(HasEarlyAccessUpdateOnly))]\n    [NotifyPropertyChangedFor(nameof(HasStandardUpdate))]\n    private LocalModelFile checkpointFile;\n\n    [ObservableProperty]\n    private string thumbnailUri;\n\n    [ObservableProperty]\n    private ProgressReport progress;\n\n    [ObservableProperty]\n    private bool isLoading;\n\n    [ObservableProperty]\n    private long fileSize;\n\n    [ObservableProperty]\n    private string? noImageMessage;\n\n    [ObservableProperty]\n    private bool hideImage;\n\n    [ObservableProperty]\n    private DateTimeOffset lastModified;\n\n    [ObservableProperty]\n    private DateTimeOffset created;\n\n    [ObservableProperty]\n    public partial FilePath FullPath { get; set; } = string.Empty;\n\n    private readonly ISettingsManager settingsManager;\n    private readonly IModelIndexService modelIndexService;\n    private readonly INotificationService notificationService;\n    private readonly IDownloadService downloadService;\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n    private readonly ILogger logger;\n\n    public bool CanShowTriggerWords => CheckpointFile.ConnectedModelInfo?.TrainedWords?.Length > 0;\n    public string BaseModelName => CheckpointFile.ConnectedModelInfo?.BaseModel ?? string.Empty;\n    public CivitModelType ModelType => CheckpointFile.ConnectedModelInfo?.ModelType ?? CivitModelType.Unknown;\n    public bool HasEarlyAccessUpdateOnly => CheckpointFile.HasEarlyAccessUpdateOnly;\n    public bool HasStandardUpdate => CheckpointFile.HasUpdate && !CheckpointFile.HasEarlyAccessUpdateOnly;\n\n    /// <inheritdoc/>\n    public CheckpointFileViewModel(\n        ISettingsManager settingsManager,\n        IModelIndexService modelIndexService,\n        INotificationService notificationService,\n        IDownloadService downloadService,\n        IServiceManager<ViewModelBase> vmFactory,\n        ILogger logger,\n        LocalModelFile checkpointFile\n    )\n    {\n        this.settingsManager = settingsManager;\n        this.modelIndexService = modelIndexService;\n        this.notificationService = notificationService;\n        this.downloadService = downloadService;\n        this.vmFactory = vmFactory;\n        this.logger = logger;\n        CheckpointFile = checkpointFile;\n\n        UpdateImage();\n\n        if (!settingsManager.IsLibraryDirSet)\n            return;\n\n        AddDisposable(\n            settingsManager.RegisterPropertyChangedHandler(\n                s => s.ShowNsfwInCheckpointsPage,\n                _ => Dispatcher.UIThread.Post(UpdateImage)\n            )\n        );\n\n        FileSize = GetFileSize(CheckpointFile.GetFullPath(settingsManager.ModelsDirectory));\n        LastModified = GetLastModified(CheckpointFile.GetFullPath(settingsManager.ModelsDirectory));\n        Created = GetCreated(CheckpointFile.GetFullPath(settingsManager.ModelsDirectory));\n        FullPath = new FilePath(CheckpointFile.GetFullPath(settingsManager.ModelsDirectory));\n    }\n\n    [RelayCommand]\n    private Task CopyTriggerWords()\n    {\n        if (!CheckpointFile.HasConnectedModel)\n            return Task.CompletedTask;\n\n        var words = CheckpointFile.ConnectedModelInfo.TrainedWordsString;\n        return string.IsNullOrWhiteSpace(words) ? Task.CompletedTask : App.Clipboard.SetTextAsync(words);\n    }\n\n    [RelayCommand]\n    private void FindOnModelBrowser()\n    {\n        if (CheckpointFile.ConnectedModelInfo?.ModelId == null)\n            return;\n\n        EventManager.Instance.OnNavigateAndFindCivitModelRequested(\n            CheckpointFile.ConnectedModelInfo.ModelId.Value\n        );\n    }\n\n    [RelayCommand]\n    [Localizable(false)]\n    private void OpenOnCivitAi()\n    {\n        if (CheckpointFile.ConnectedModelInfo?.ModelId == null)\n            return;\n        ProcessRunner.OpenUrl($\"https://civitai.com/models/{CheckpointFile.ConnectedModelInfo.ModelId}\");\n    }\n\n    [RelayCommand]\n    [Localizable(false)]\n    private Task CopyModelUrl()\n    {\n        if (!CheckpointFile.HasConnectedModel)\n            return Task.CompletedTask;\n\n        return CheckpointFile.ConnectedModelInfo.Source switch\n        {\n            ConnectedModelSource.Civitai when CheckpointFile.ConnectedModelInfo.ModelId == null =>\n                Task.CompletedTask,\n            ConnectedModelSource.Civitai when CheckpointFile.ConnectedModelInfo.ModelId != null =>\n                App.Clipboard.SetTextAsync(\n                    $\"https://civitai.com/models/{CheckpointFile.ConnectedModelInfo.ModelId}\"\n                ),\n\n            ConnectedModelSource.OpenModelDb => App.Clipboard.SetTextAsync(\n                $\"https://openmodeldb.info/models/{CheckpointFile.ConnectedModelInfo.ModelName}\"\n            ),\n            _ => Task.CompletedTask,\n        };\n    }\n\n    [RelayCommand]\n    private async Task FindConnectedMetadata(bool forceReimport = false)\n    {\n        if (\n            App.Services.GetService(typeof(IMetadataImportService))\n            is not IMetadataImportService importService\n        )\n            return;\n\n        IsLoading = true;\n\n        try\n        {\n            var progressReport = new Progress<ProgressReport>(report =>\n            {\n                Progress = report;\n            });\n\n            var cmInfo = await importService.GetMetadataForFile(\n                CheckpointFile.GetFullPath(settingsManager.ModelsDirectory),\n                progressReport,\n                forceReimport\n            );\n            if (cmInfo != null)\n            {\n                CheckpointFile.ConnectedModelInfo = cmInfo;\n\n                var uri =\n                    CheckpointFile.GetPreviewImageFullPath(settingsManager.ModelsDirectory)\n                    ?? cmInfo.ThumbnailImageUrl;\n                if (string.IsNullOrWhiteSpace(uri))\n                {\n                    HideImage = true;\n                    NoImageMessage = Resources.Label_NoImageFound;\n                }\n                else\n                {\n                    ThumbnailUri = uri;\n                    HideImage = false;\n                }\n\n                await modelIndexService.RefreshIndex();\n            }\n        }\n        finally\n        {\n            IsLoading = false;\n        }\n    }\n\n    [RelayCommand]\n    private async Task DeleteAsync(bool showConfirmation = true)\n    {\n        var pathsToDelete = CheckpointFile\n            .GetDeleteFullPaths(settingsManager.ModelsDirectory)\n            .ToImmutableArray();\n\n        if (pathsToDelete.IsEmpty)\n            return;\n\n        var confirmDeleteVm = vmFactory.Get<ConfirmDeleteDialogViewModel>();\n        confirmDeleteVm.PathsToDelete = pathsToDelete;\n\n        if (showConfirmation)\n        {\n            if (await confirmDeleteVm.GetDialog().ShowAsync() != ContentDialogResult.Primary)\n            {\n                return;\n            }\n        }\n\n        await using var delay = new MinimumDelay(200, 500);\n\n        IsLoading = true;\n        Progress = new ProgressReport(0f, \"Deleting...\");\n\n        try\n        {\n            await confirmDeleteVm.ExecuteCurrentDeleteOperationAsync(failFast: true);\n        }\n        catch (Exception e)\n        {\n            notificationService.ShowPersistent(\"Error deleting files\", e.Message, NotificationType.Error);\n\n            await modelIndexService.RefreshIndex();\n\n            return;\n        }\n        finally\n        {\n            IsLoading = false;\n        }\n\n        await modelIndexService.RemoveModelAsync(CheckpointFile);\n    }\n\n    [RelayCommand]\n    private async Task RenameAsync()\n    {\n        // Parent folder path\n        var parentPath =\n            Path.GetDirectoryName((string?)CheckpointFile.GetFullPath(settingsManager.ModelsDirectory)) ?? \"\";\n\n        var textFields = new TextBoxField[]\n        {\n            new()\n            {\n                Label = \"File name\",\n                Validator = text =>\n                {\n                    if (string.IsNullOrWhiteSpace(text))\n                        throw new DataValidationException(\"File name is required\");\n\n                    if (File.Exists(Path.Combine(parentPath, text)))\n                        throw new DataValidationException(\"File name already exists\");\n                },\n                Text = CheckpointFile.FileName,\n            },\n        };\n\n        var dialog = DialogHelper.CreateTextEntryDialog(\"Rename Model\", \"\", textFields);\n\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            var name = textFields[0].Text;\n            var nameNoExt = Path.GetFileNameWithoutExtension(name);\n            var originalNameNoExt = Path.GetFileNameWithoutExtension(CheckpointFile.FileName);\n            // Rename file in OS\n            try\n            {\n                var newFilePath = Path.Combine(parentPath, name);\n                File.Move(CheckpointFile.GetFullPath(settingsManager.ModelsDirectory), newFilePath);\n\n                // If preview image exists, rename it too\n                var previewPath = CheckpointFile.GetPreviewImageFullPath(settingsManager.ModelsDirectory);\n                if (previewPath != null && File.Exists(previewPath))\n                {\n                    var newPreviewImagePath = Path.Combine(\n                        parentPath,\n                        $\"{nameNoExt}.preview{Path.GetExtension(previewPath)}\"\n                    );\n                    File.Move(previewPath, newPreviewImagePath);\n                }\n\n                // If connected model info exists, rename it too (<name>.cm-info.json)\n                if (CheckpointFile.HasConnectedModel)\n                {\n                    var cmInfoPath = Path.Combine(parentPath, $\"{originalNameNoExt}.cm-info.json\");\n                    if (File.Exists(cmInfoPath))\n                    {\n                        File.Move(cmInfoPath, Path.Combine(parentPath, $\"{nameNoExt}.cm-info.json\"));\n                    }\n                }\n\n                await modelIndexService.RefreshIndex();\n            }\n            catch (Exception e)\n            {\n                logger.LogError(e, \"Failed to rename checkpoint file\");\n            }\n        }\n    }\n\n    [RelayCommand]\n    private async Task OpenSafetensorMetadataViewer()\n    {\n        if (\n            !settingsManager.IsLibraryDirSet\n            || new DirectoryPath(settingsManager.ModelsDirectory) is not { Exists: true } modelsDir\n        )\n        {\n            return;\n        }\n\n        SafetensorMetadata? metadata;\n        try\n        {\n            var safetensorPath = CheckpointFile.GetFullPath(modelsDir);\n            metadata = await SafetensorMetadata.ParseAsync(safetensorPath);\n        }\n        catch (Exception ex)\n        {\n            logger.LogWarning(ex, \"Failed to parse safetensor metadata\");\n            notificationService.Show(\n                \"No Metadata Found\",\n                \"This safetensor file does not contain any embedded metadata.\",\n                NotificationType.Warning\n            );\n            return;\n        }\n\n        var vm = vmFactory.Get<SafetensorMetadataViewModel>(vm =>\n        {\n            vm.ModelName = CheckpointFile.DisplayModelName;\n            vm.Metadata = metadata;\n        });\n\n        var dialog = vm.GetDialog();\n        dialog.MinDialogHeight = 800;\n        dialog.MinDialogWidth = 700;\n        dialog.CloseButtonText = \"Close\";\n        dialog.DefaultButton = ContentDialogButton.Close;\n\n        await dialog.ShowAsync();\n    }\n\n    [RelayCommand]\n    private async Task OpenMetadataEditor()\n    {\n        var vm = vmFactory.Get<ModelMetadataEditorDialogViewModel>(vm =>\n        {\n            vm.CheckpointFiles = [this];\n        });\n\n        var dialog = vm.GetDialog();\n        dialog.MinDialogHeight = 800;\n        dialog.MinDialogWidth = 700;\n        dialog.IsPrimaryButtonEnabled = true;\n        dialog.IsFooterVisible = true;\n        dialog.PrimaryButtonText = \"Save\";\n        dialog.DefaultButton = ContentDialogButton.Primary;\n        dialog.CloseButtonText = \"Cancel\";\n\n        var result = await dialog.ShowAsync();\n\n        if (result != ContentDialogResult.Primary)\n            return;\n\n        // not supported yet\n        if (vm.IsEditingMultipleCheckpoints)\n            return;\n\n        try\n        {\n            var hasCmInfoAlready = CheckpointFile.HasConnectedModel;\n            var cmInfo = CheckpointFile.ConnectedModelInfo ?? new ConnectedModelInfo();\n            var hasThumbnailChanged =\n                vm.ThumbnailFilePath\n                != CheckpointFile.GetPreviewImageFullPath(settingsManager.ModelsDirectory);\n\n            cmInfo.ModelName = vm.ModelName;\n            cmInfo.ModelDescription = vm.ModelDescription;\n            cmInfo.Nsfw = vm.IsNsfw;\n            cmInfo.Tags = vm.Tags.Split(',').Select(x => x.Trim()).ToArray();\n            cmInfo.BaseModel = vm.BaseModelType;\n            cmInfo.TrainedWords = string.IsNullOrWhiteSpace(vm.TrainedWords)\n                ? null\n                : vm.TrainedWords.Split(',').Select(x => x.Trim()).ToArray();\n            cmInfo.ThumbnailImageUrl = vm.ThumbnailFilePath;\n            cmInfo.ModelType = vm.ModelType;\n            cmInfo.VersionName = vm.VersionName;\n            cmInfo.InferenceDefaults = vm.IsInferenceDefaultsEnabled\n                ? new InferenceDefaults\n                {\n                    Sampler = vm.SamplerCardViewModel.SelectedSampler,\n                    Scheduler = vm.SamplerCardViewModel.SelectedScheduler,\n                    Width = vm.SamplerCardViewModel.Width,\n                    Height = vm.SamplerCardViewModel.Height,\n                    CfgScale = vm.SamplerCardViewModel.CfgScale,\n                    Steps = vm.SamplerCardViewModel.Steps,\n                }\n                : null;\n\n            var modelFilePath = new FilePath(\n                Path.Combine(settingsManager.ModelsDirectory, CheckpointFile.RelativePath)\n            );\n\n            IsLoading = true;\n            Progress = new ProgressReport(0f, \"Saving metadata...\", isIndeterminate: true);\n\n            if (!hasCmInfoAlready)\n            {\n                cmInfo.Hashes = new CivitFileHashes\n                {\n                    BLAKE3 = await FileHash.GetBlake3Async(\n                        modelFilePath,\n                        new Progress<ProgressReport>(report =>\n                        {\n                            Progress = report with { Title = \"Calculating hash...\" };\n                        })\n                    ),\n                };\n                cmInfo.ImportedAt = DateTimeOffset.Now;\n            }\n\n            var modelFileName = modelFilePath.NameWithoutExtension;\n            var modelFolder =\n                modelFilePath.Directory\n                ?? Path.Combine(settingsManager.ModelsDirectory, CheckpointFile.SharedFolderType.ToString());\n\n            await cmInfo.SaveJsonToDirectory(modelFolder, modelFileName);\n\n            if (string.IsNullOrWhiteSpace(cmInfo.ThumbnailImageUrl))\n                return;\n\n            if (File.Exists(cmInfo.ThumbnailImageUrl) && hasThumbnailChanged)\n            {\n                // delete existing preview image\n                var existingPreviewPath = CheckpointFile.GetPreviewImageFullPath(\n                    settingsManager.ModelsDirectory\n                );\n                if (existingPreviewPath != null && File.Exists(existingPreviewPath))\n                {\n                    File.Delete(existingPreviewPath);\n                }\n\n                var filePath = new FilePath(cmInfo.ThumbnailImageUrl);\n                var previewPath = new FilePath(\n                    modelFolder,\n                    @$\"{modelFileName}.preview{Path.GetExtension(cmInfo.ThumbnailImageUrl)}\"\n                );\n                await filePath.CopyToAsync(previewPath);\n            }\n            else if (cmInfo.ThumbnailImageUrl.StartsWith(\"http\"))\n            {\n                var imageExtension = Path.GetExtension(cmInfo.ThumbnailImageUrl).TrimStart('.');\n                if (imageExtension is \"jpg\" or \"jpeg\" or \"png\" or \"webp\")\n                {\n                    var imageDownloadPath = modelFilePath.Directory!.JoinFile(\n                        $\"{modelFilePath.NameWithoutExtension}.preview.{imageExtension}\"\n                    );\n\n                    var imageTask = downloadService.DownloadToFileAsync(\n                        cmInfo.ThumbnailImageUrl,\n                        imageDownloadPath,\n                        new Progress<ProgressReport>(report =>\n                        {\n                            Progress = report with { Title = \"Downloading image\" };\n                        })\n                    );\n\n                    await notificationService.TryAsync(imageTask, \"Could not download preview image\");\n                }\n            }\n\n            await modelIndexService.RefreshIndex();\n            notificationService.Show(\n                \"Metadata saved\",\n                \"Metadata has been saved successfully\",\n                NotificationType.Success\n            );\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Failed to save metadata\");\n            notificationService.Show(\"Failed to save metadata\", e.Message, NotificationType.Error);\n        }\n        finally\n        {\n            IsLoading = false;\n        }\n    }\n\n    [RelayCommand]\n    private async Task OpenInExplorer()\n    {\n        if (string.IsNullOrWhiteSpace(FullPath))\n            return;\n\n        await ProcessRunner.OpenFileBrowser(FullPath);\n    }\n\n    private long GetFileSize(string filePath)\n    {\n        if (!File.Exists(filePath))\n            return 0;\n\n        var fileInfo = new FileInfo(filePath);\n        return fileInfo.Length;\n    }\n\n    private DateTimeOffset GetLastModified(string filePath)\n    {\n        if (!File.Exists(filePath))\n            return DateTimeOffset.MinValue;\n\n        var fileInfo = new FileInfo(filePath);\n        return fileInfo.LastWriteTime;\n    }\n\n    private DateTimeOffset GetCreated(string filePath)\n    {\n        if (!File.Exists(filePath))\n            return DateTimeOffset.MinValue;\n\n        var fileInfo = new FileInfo(filePath);\n        return fileInfo.CreationTime;\n    }\n\n    private void UpdateImage()\n    {\n        if (\n            !settingsManager.Settings.ShowNsfwInCheckpointsPage\n            && CheckpointFile.ConnectedModelInfo?.Nsfw == true\n        )\n        {\n            HideImage = true;\n            NoImageMessage = Resources.Label_ImageHidden;\n        }\n        else\n        {\n            var previewPath = settingsManager.IsLibraryDirSet\n                ? CheckpointFile.GetPreviewImageFullPath(settingsManager.ModelsDirectory)\n                    ?? CheckpointFile.ConnectedModelInfo?.ThumbnailImageUrl\n                : null;\n\n            if (string.IsNullOrWhiteSpace(previewPath))\n            {\n                HideImage = true;\n                NoImageMessage = Resources.Label_NoImageFound;\n            }\n            else\n            {\n                ThumbnailUri = previewPath;\n                HideImage = false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Collections.ObjectModel;\nusing System.ComponentModel;\nusing System.Reactive.Linq;\nusing Apizr;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing FluentAvalonia.UI.Controls;\nusing FluentIcons.Common;\nusing Fusillade;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;\nusing StabilityMatrix.Avalonia.ViewModels.CheckpointManager;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\nusing CheckpointSortMode = StabilityMatrix.Core.Models.CheckpointSortMode;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\nusing TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(CheckpointsPage))]\n[RegisterSingleton<CheckpointsPageViewModel>]\npublic partial class CheckpointsPageViewModel(\n    ILogger<CheckpointsPageViewModel> logger,\n    ISettingsManager settingsManager,\n    IModelIndexService modelIndexService,\n    ModelFinder modelFinder,\n    IDownloadService downloadService,\n    INotificationService notificationService,\n    IMetadataImportService metadataImportService,\n    IModelImportService modelImportService,\n    OpenModelDbManager openModelDbManager,\n    IServiceManager<ViewModelBase> dialogFactory,\n    ICivitBaseModelTypeService baseModelTypeService,\n    INavigationService<MainWindowViewModel> navigationService\n) : PageViewModelBase\n{\n    public override string Title => Resources.Label_CheckpointManager;\n\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.Notebook, IconVariant = IconVariant.Filled };\n\n    private SourceCache<CheckpointCategory, string> categoriesCache = new(category => category.GetId());\n\n    public IObservableCollection<CheckpointCategory> Categories { get; set; } =\n        new ObservableCollectionExtended<CheckpointCategory>();\n\n    public SourceCache<LocalModelFile, string> ModelsCache { get; } = new(file => file.RelativePath);\n\n    public IObservableCollection<CheckpointFileViewModel> Models { get; set; } =\n        new ObservableCollectionExtended<CheckpointFileViewModel>();\n\n    [ObservableProperty]\n    private bool showFolders = true;\n\n    [ObservableProperty]\n    private bool showModelsInSubfolders = true;\n\n    [ObservableProperty]\n    private CheckpointCategory? selectedCategory;\n\n    [ObservableProperty]\n    private string searchQuery = string.Empty;\n\n    [ObservableProperty]\n    private bool isImportAsConnectedEnabled;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(NumImagesSelected))]\n    private int numItemsSelected;\n\n    [ObservableProperty]\n    private bool sortConnectedModelsFirst = true;\n\n    [ObservableProperty]\n    private CheckpointSortMode selectedSortOption = CheckpointSortMode.Title;\n\n    public List<CheckpointSortMode> SortOptions => Enum.GetValues<CheckpointSortMode>().ToList();\n\n    [ObservableProperty]\n    private ListSortDirection selectedSortDirection = ListSortDirection.Ascending;\n\n    [ObservableProperty]\n    private ProgressViewModel progress = new();\n\n    [ObservableProperty]\n    private bool isLoading;\n\n    [ObservableProperty]\n    private bool isDragOver;\n\n    [ObservableProperty]\n    private ObservableCollection<string> selectedBaseModels = [];\n\n    [ObservableProperty]\n    private bool dragMovesAllSelected = true;\n\n    [ObservableProperty]\n    private bool hideEmptyRootCategories;\n\n    [ObservableProperty]\n    private bool showNsfwImages;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ModelCardBottomResizeFactor))]\n    private double resizeFactor;\n\n    public double ModelCardBottomResizeFactor => Math.Clamp(ResizeFactor, 0.85d, 1.25d);\n\n    public string ClearButtonText =>\n        SelectedBaseModels.Count == BaseModelOptions.Count\n            ? Resources.Action_ClearSelection\n            : Resources.Action_SelectAll;\n\n    public List<ListSortDirection> SortDirections => Enum.GetValues<ListSortDirection>().ToList();\n\n    public string ModelsFolder => settingsManager.ModelsDirectory;\n\n    public string NumImagesSelected =>\n        NumItemsSelected == 1\n            ? Resources.Label_OneImageSelected.Replace(\"images \", \"\")\n            : string.Format(Resources.Label_NumImagesSelected, NumItemsSelected).Replace(\"images \", \"\");\n\n    private SourceCache<string, string> BaseModelCache { get; } = new(s => s);\n\n    public IObservableCollection<BaseModelOptionViewModel> BaseModelOptions { get; set; } =\n        new ObservableCollectionExtended<BaseModelOptionViewModel>();\n\n    protected override async Task OnInitialLoadedAsync()\n    {\n        if (Design.IsDesignMode)\n            return;\n\n        await base.OnInitialLoadedAsync();\n\n        AddDisposable(\n            BaseModelCache\n                .Connect()\n                .DeferUntilLoaded()\n                .Transform(baseModel => new BaseModelOptionViewModel\n                {\n                    ModelType = baseModel,\n                    IsSelected = settingsManager.Settings.SelectedBaseModels.Contains(baseModel),\n                })\n                .SortAndBind(\n                    BaseModelOptions,\n                    SortExpressionComparer<BaseModelOptionViewModel>.Ascending(vm => vm.ModelType)\n                )\n                .WhenPropertyChanged(p => p.IsSelected)\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe(next =>\n                {\n                    switch (next.Sender.IsSelected)\n                    {\n                        case true when !SelectedBaseModels.Contains(next.Sender.ModelType):\n                            SelectedBaseModels.Add(next.Sender.ModelType);\n                            break;\n                        case false when SelectedBaseModels.Contains(next.Sender.ModelType):\n                            SelectedBaseModels.Remove(next.Sender.ModelType);\n                            break;\n                    }\n\n                    OnPropertyChanged(nameof(ClearButtonText));\n                    OnPropertyChanged(nameof(SelectedBaseModels));\n                })\n        );\n\n        var settingsTransactionObservable = this.WhenPropertyChanged(x => x.SelectedBaseModels)\n            .Throttle(TimeSpan.FromMilliseconds(50))\n            .Skip(1)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(_ =>\n            {\n                settingsManager.Transaction(settings =>\n                    settings.SelectedBaseModels = SelectedBaseModels.ToList()\n                );\n            });\n\n        AddDisposable(settingsTransactionObservable);\n\n        // Observable predicate from SearchQuery changes\n        var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchQuery)\n            .Throttle(TimeSpan.FromMilliseconds(100))\n            .Select(_ =>\n                (Func<LocalModelFile, bool>)(\n                    file =>\n                        string.IsNullOrWhiteSpace(SearchQuery)\n                        || (\n                            SearchQuery.StartsWith(\"#\")\n                            && (\n                                file.ConnectedModelInfo?.Tags.Contains(\n                                    SearchQuery.Substring(1),\n                                    StringComparer.OrdinalIgnoreCase\n                                ) ?? false\n                            )\n                        )\n                        || file.DisplayModelFileName.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase)\n                        || file.DisplayModelName.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase)\n                        || file.DisplayModelVersion.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase)\n                        || (\n                            file.ConnectedModelInfo?.TrainedWordsString.Contains(\n                                SearchQuery,\n                                StringComparison.OrdinalIgnoreCase\n                            ) ?? false\n                        )\n                )\n            )\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        var filterPredicate = Observable\n            .FromEventPattern<PropertyChangedEventArgs>(this, nameof(PropertyChanged))\n            .Where(x =>\n                x.EventArgs.PropertyName\n                    is nameof(SelectedCategory)\n                        or nameof(ShowModelsInSubfolders)\n                        or nameof(SelectedBaseModels)\n            )\n            .Throttle(TimeSpan.FromMilliseconds(50))\n            .Select(_ => (Func<LocalModelFile, bool>)FilterModels)\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        var comparerObservable = Observable\n            .FromEventPattern<PropertyChangedEventArgs>(this, nameof(PropertyChanged))\n            .Where(x =>\n                x.EventArgs.PropertyName\n                    is nameof(SelectedSortOption)\n                        or nameof(SelectedSortDirection)\n                        or nameof(SortConnectedModelsFirst)\n            )\n            .Select(_ =>\n            {\n                var comparer = new SortExpressionComparer<CheckpointFileViewModel>();\n                if (SortConnectedModelsFirst)\n                    comparer = comparer.ThenByDescending(vm => vm.CheckpointFile.HasConnectedModel);\n\n                switch (SelectedSortOption)\n                {\n                    case CheckpointSortMode.FileName:\n                        comparer =\n                            SelectedSortDirection == ListSortDirection.Ascending\n                                ? comparer.ThenByAscending(vm => vm.CheckpointFile.FileName)\n                                : comparer.ThenByDescending(vm => vm.CheckpointFile.FileName);\n                        break;\n                    case CheckpointSortMode.Title:\n                        comparer =\n                            SelectedSortDirection == ListSortDirection.Ascending\n                                ? comparer.ThenByAscending(vm => vm.CheckpointFile.DisplayModelName)\n                                : comparer.ThenByDescending(vm => vm.CheckpointFile.DisplayModelName);\n                        break;\n                    case CheckpointSortMode.BaseModel:\n                        comparer =\n                            SelectedSortDirection == ListSortDirection.Ascending\n                                ? comparer.ThenByAscending(vm =>\n                                    vm.CheckpointFile.ConnectedModelInfo?.BaseModel\n                                )\n                                : comparer.ThenByDescending(vm =>\n                                    vm.CheckpointFile.ConnectedModelInfo?.BaseModel\n                                );\n\n                        comparer = comparer.ThenByAscending(vm => vm.CheckpointFile.DisplayModelName);\n                        break;\n                    case CheckpointSortMode.SharedFolderType:\n                        comparer =\n                            SelectedSortDirection == ListSortDirection.Ascending\n                                ? comparer.ThenByAscending(vm => vm.CheckpointFile.SharedFolderType)\n                                : comparer.ThenByDescending(vm => vm.CheckpointFile.SharedFolderType);\n\n                        comparer = comparer.ThenByAscending(vm => vm.CheckpointFile.DisplayModelName);\n                        break;\n                    case CheckpointSortMode.UpdateAvailable:\n                        comparer =\n                            SelectedSortDirection == ListSortDirection.Ascending\n                                ? comparer.ThenByAscending(vm => vm.CheckpointFile.HasUpdate)\n                                : comparer.ThenByDescending(vm => vm.CheckpointFile.HasUpdate);\n                        comparer = comparer.ThenByAscending(vm => vm.CheckpointFile.DisplayModelName);\n                        comparer = comparer.ThenByDescending(vm => vm.CheckpointFile.DisplayModelVersion);\n                        break;\n                    case CheckpointSortMode.FileSize:\n                        comparer =\n                            SelectedSortDirection == ListSortDirection.Ascending\n                                ? comparer.ThenByAscending(vm => vm.FileSize)\n                                : comparer.ThenByDescending(vm => vm.FileSize);\n                        break;\n                    case CheckpointSortMode.Created:\n                        comparer =\n                            SelectedSortDirection == ListSortDirection.Ascending\n                                ? comparer.ThenByAscending(vm => vm.Created)\n                                : comparer.ThenByDescending(vm => vm.Created);\n                        break;\n                    case CheckpointSortMode.LastModified:\n                        comparer =\n                            SelectedSortDirection == ListSortDirection.Ascending\n                                ? comparer.ThenByAscending(vm => vm.LastModified)\n                                : comparer.ThenByDescending(vm => vm.LastModified);\n                        break;\n                    default:\n                        throw new ArgumentOutOfRangeException();\n                }\n\n                return comparer;\n            })\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        AddDisposable(\n            ModelsCache\n                .Connect()\n                .DeferUntilLoaded()\n                .Filter(filterPredicate)\n                .Filter(searchPredicate)\n                .Transform(x => new CheckpointFileViewModel(\n                    settingsManager,\n                    modelIndexService,\n                    notificationService,\n                    downloadService,\n                    dialogFactory,\n                    logger,\n                    x\n                ))\n                .DisposeMany()\n                .SortAndBind(Models, comparerObservable)\n                .WhenPropertyChanged(p => p.IsSelected)\n                .Throttle(TimeSpan.FromMilliseconds(50))\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe(_ =>\n                {\n                    NumItemsSelected = Models.Count(o => o.IsSelected);\n                })\n        );\n\n        var categoryFilterPredicate = Observable\n            .FromEventPattern<PropertyChangedEventArgs>(this, nameof(PropertyChanged))\n            .Where(x => x.EventArgs.PropertyName is nameof(HideEmptyRootCategories))\n            .Throttle(TimeSpan.FromMilliseconds(50))\n            .Select(_ => (Func<CheckpointCategory, bool>)FilterCategories)\n            .StartWith(FilterCategories)\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        AddDisposable(\n            categoriesCache\n                .Connect()\n                .DeferUntilLoaded()\n                .Filter(categoryFilterPredicate)\n                .SortAndBind(\n                    Categories,\n                    SortExpressionComparer<CheckpointCategory>\n                        .Descending(x => x.Name == \"All Models\")\n                        .ThenByAscending(x => x.Name)\n                )\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe()\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.IsImportAsConnectedEnabled,\n                s => s.IsImportAsConnected,\n                true\n            )\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.ResizeFactor,\n                s => s.CheckpointsPageResizeFactor,\n                true\n            )\n        );\n\n        Refresh().SafeFireAndForget();\n\n        EventManager.Instance.ModelIndexChanged += (_, _) =>\n        {\n            // Dispatch to UI thread to prevent race conditions with Avalonia's selection model.\n            // The ModelIndexChanged event may be raised from a background thread.\n            Dispatcher.UIThread.Post(() =>\n            {\n                RefreshCategories();\n                ModelsCache.EditDiff(\n                    modelIndexService.ModelIndex.Values.SelectMany(x => x),\n                    LocalModelFile.RelativePathConnectedModelInfoComparer\n                );\n            });\n        };\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.SortConnectedModelsFirst,\n                settings => settings.SortConnectedModelsFirst,\n                true\n            )\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.SelectedSortOption,\n                settings => settings.CheckpointSortMode,\n                true\n            )\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.SelectedSortDirection,\n                settings => settings.CheckpointSortDirection,\n                true\n            )\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.ShowModelsInSubfolders,\n                settings => settings.ShowModelsInSubfolders,\n                true\n            )\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.DragMovesAllSelected,\n                settings => settings.DragMovesAllSelected,\n                true\n            )\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.HideEmptyRootCategories,\n                settings => settings.HideEmptyRootCategories,\n                true\n            )\n        );\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.ShowNsfwImages,\n                settings => settings.ShowNsfwInCheckpointsPage,\n                true\n            )\n        );\n\n        // make sure a sort happens\n        OnPropertyChanged(nameof(SortConnectedModelsFirst));\n    }\n\n    public override async Task OnLoadedAsync()\n    {\n        if (Design.IsDesignMode)\n            return;\n\n        var baseModelTypes = await baseModelTypeService.GetBaseModelTypes(includeAllOption: false);\n        baseModelTypes = baseModelTypes.Except(settingsManager.Settings.DisabledBaseModelTypes).ToList();\n        BaseModelCache.Edit(updater => updater.Load(baseModelTypes));\n\n        await ShowFolderMapTipIfNecessaryAsync();\n    }\n\n    public void ClearSearchQuery()\n    {\n        SearchQuery = string.Empty;\n    }\n\n    [RelayCommand]\n    private async Task Refresh()\n    {\n        await modelIndexService.RefreshIndex();\n        Task.Run(async () => await modelIndexService.CheckModelsForUpdateAsync()).SafeFireAndForget();\n    }\n\n    [RelayCommand]\n    private void ClearSelection()\n    {\n        var selected = Models.Where(x => x.IsSelected).ToList();\n        foreach (var model in selected)\n            model.IsSelected = false;\n\n        NumItemsSelected = 0;\n    }\n\n    [RelayCommand]\n    private async Task DeleteAsync()\n    {\n        if (\n            NumItemsSelected <= 0\n            || Models.Where(o => o.IsSelected).Select(vm => vm.CheckpointFile).ToList()\n                is not { Count: > 0 } selectedModelFiles\n        )\n            return;\n\n        var pathsToDelete = selectedModelFiles\n            .SelectMany(x => x.GetDeleteFullPaths(settingsManager.ModelsDirectory))\n            .ToList();\n\n        var confirmDeleteVm = dialogFactory.Get<ConfirmDeleteDialogViewModel>();\n        confirmDeleteVm.PathsToDelete = pathsToDelete;\n\n        if (await confirmDeleteVm.GetDialog().ShowAsync() != ContentDialogResult.Primary)\n            return;\n\n        try\n        {\n            await confirmDeleteVm.ExecuteCurrentDeleteOperationAsync(failFast: true);\n        }\n        catch (Exception e)\n        {\n            notificationService.ShowPersistent(\"Error deleting files\", e.Message, NotificationType.Error);\n\n            await modelIndexService.RefreshIndex();\n\n            return;\n        }\n\n        await modelIndexService.RemoveModelsAsync(selectedModelFiles);\n\n        NumItemsSelected = 0;\n    }\n\n    [RelayCommand]\n    private async Task ScanMetadata(bool updateExistingMetadata)\n    {\n        if (SelectedCategory == null)\n        {\n            notificationService.Show(\n                \"No Category Selected\",\n                \"Please select a category to scan for metadata.\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        var scanMetadataStep = new ScanMetadataStep(\n            SelectedCategory.Path,\n            metadataImportService,\n            updateExistingMetadata\n        );\n\n        var runner = new PackageModificationRunner\n        {\n            ModificationCompleteMessage = \"Metadata scan complete\",\n            HideCloseButton = false,\n            ShowDialogOnStart = true,\n        };\n\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n        await Dispatcher.UIThread.InvokeAsync(async () => await runner.ExecuteSteps([scanMetadataStep]));\n\n        await modelIndexService.RefreshIndex();\n        var message = updateExistingMetadata\n            ? \"Finished updating metadata.\"\n            : \"Finished scanning for missing metadata.\";\n        notificationService.Show(\"Scan Complete\", message, NotificationType.Success);\n    }\n\n    [RelayCommand]\n    private Task OnItemClick(CheckpointFileViewModel item)\n    {\n        // Select item if we're in \"select mode\"\n        if (NumItemsSelected > 0)\n            item.IsSelected = !item.IsSelected;\n        else if (item.CheckpointFile.HasConnectedModel)\n            return ShowVersionDialog(item);\n        else\n            item.IsSelected = !item.IsSelected;\n\n        return Task.CompletedTask;\n    }\n\n    [RelayCommand]\n    private async Task ShowVersionDialog(CheckpointFileViewModel item)\n    {\n        if (item.CheckpointFile is { HasCivitMetadata: false, HasOpenModelDbMetadata: false })\n        {\n            notificationService.Show(\n                \"Cannot show version dialog\",\n                \"This model has custom metadata.\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        if (item.CheckpointFile.HasCivitMetadata)\n        {\n            ShowCivitVersionDialog(item);\n        }\n        else if (item.CheckpointFile.HasOpenModelDbMetadata)\n        {\n            await ShowOpenModelDbDialog(item);\n        }\n    }\n\n    private void ShowCivitVersionDialog(CheckpointFileViewModel item)\n    {\n        var model = item.CheckpointFile.LatestModelInfo;\n        if (item.CheckpointFile.ConnectedModelInfo?.ModelId == null)\n        {\n            notificationService.Show(\n                \"Model not found\",\n                \"Model not found in index, please try again later.\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        var allModelIds = Models\n            .Where(x => x.CheckpointFile.ConnectedModelInfo?.ModelId != null)\n            .Select(x => x.CheckpointFile.ConnectedModelInfo!.ModelId!.Value)\n            .Distinct()\n            .ToList();\n        var index = Models.IndexOf(item);\n\n        var newVm = dialogFactory.Get<CivitDetailsPageViewModel>(vm =>\n        {\n            vm.CivitModel =\n                model ?? new CivitModel { Id = item.CheckpointFile.ConnectedModelInfo.ModelId.Value };\n            vm.ModelIdList = allModelIds;\n            vm.CurrentIndex = index;\n            return vm;\n        });\n\n        navigationService.NavigateTo(newVm, BetterSlideNavigationTransition.PageSlideFromRight);\n    }\n\n    private async Task ShowOpenModelDbDialog(CheckpointFileViewModel item)\n    {\n        if (!item.CheckpointFile.HasOpenModelDbMetadata)\n            return;\n\n        await openModelDbManager.EnsureMetadataLoadedAsync();\n\n        var response = await openModelDbManager.ExecuteAsync(\n            api => api.GetModels(),\n            options => options.WithPriority(Priority.UserInitiated)\n        );\n\n        var model = response\n            .GetKeyedModels()\n            .FirstOrDefault(x => x.Id == item.CheckpointFile.ConnectedModelInfo.ModelName);\n\n        if (model == null)\n        {\n            notificationService.Show(\"Model not found\", \"Could not find model info\", NotificationType.Error);\n            return;\n        }\n\n        var vm = dialogFactory.Get<OpenModelDbModelDetailsViewModel>();\n        vm.Model = model;\n\n        var dialog = vm.GetDialog();\n        dialog.MaxDialogHeight = 800;\n        await dialog.ShowAsync();\n    }\n\n    [RelayCommand]\n    private void ClearOrSelectAllBaseModels()\n    {\n        if (SelectedBaseModels.Count == BaseModelOptions.Count)\n            BaseModelOptions.ForEach(x => x.IsSelected = false);\n        else\n            BaseModelOptions.ForEach(x => x.IsSelected = true);\n    }\n\n    [RelayCommand]\n    private async Task CreateFolder(object? treeViewItem)\n    {\n        if (treeViewItem is not CheckpointCategory category)\n            return;\n\n        var parentFolder = category.Path;\n\n        if (string.IsNullOrWhiteSpace(parentFolder))\n            return;\n\n        var fields = new TextBoxField[]\n        {\n            new()\n            {\n                Label = \"Folder Name\",\n                InnerLeftText =\n                    $@\"{parentFolder.Replace(settingsManager.ModelsDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}\",\n                MinWidth = 400,\n            },\n        };\n\n        var dialog = DialogHelper.CreateTextEntryDialog(\"Create Folder\", string.Empty, fields);\n        var result = await dialog.ShowAsync();\n\n        if (result != ContentDialogResult.Primary)\n            return;\n\n        var folderName = fields[0].Text;\n        var folderPath = Path.Combine(parentFolder, folderName);\n\n        await notificationService.TryAsync(\n            Task.Run(() => Directory.CreateDirectory(folderPath)),\n            message: \"Could not create folder\"\n        );\n\n        RefreshCategories();\n\n        SelectedCategory = Categories.SelectMany(c => c.Flatten()).FirstOrDefault(x => x.Path == folderPath);\n    }\n\n    [RelayCommand]\n    private Task OpenFolderFromTreeview(object? treeViewItem)\n    {\n        return treeViewItem is CheckpointCategory category && !string.IsNullOrWhiteSpace(category.Path)\n            ? ProcessRunner.OpenFolderBrowser(category.Path)\n            : Task.CompletedTask;\n    }\n\n    [RelayCommand]\n    private async Task DeleteFolderAsync(object? treeViewItem)\n    {\n        if (treeViewItem is not CheckpointCategory category)\n            return;\n\n        var folderPath = category.Path;\n        if (string.IsNullOrWhiteSpace(folderPath))\n            return;\n\n        var confirmDeleteVm = dialogFactory.Get<ConfirmDeleteDialogViewModel>();\n        confirmDeleteVm.PathsToDelete = category.Flatten().Select(x => x.Path).ToList();\n\n        if (await confirmDeleteVm.GetDialog().ShowAsync() != ContentDialogResult.Primary)\n            return;\n\n        confirmDeleteVm.PathsToDelete = [folderPath];\n\n        try\n        {\n            await confirmDeleteVm.ExecuteCurrentDeleteOperationAsync(failFast: true);\n        }\n        catch (Exception e)\n        {\n            notificationService.ShowPersistent(\"Error deleting folder\", e.Message, NotificationType.Error);\n            return;\n        }\n\n        RefreshCategories();\n    }\n\n    [RelayCommand]\n    private void SelectAll()\n    {\n        Models.ForEach(x => x.IsSelected = true);\n    }\n\n    [RelayCommand]\n    private async Task ShowFolderReference()\n    {\n        var dialog = DialogHelper.CreateMarkdownDialog(MarkdownSnippets.SMFolderMap);\n        await dialog.ShowAsync();\n    }\n\n    public async Task ImportFilesAsync(IEnumerable<string> files, DirectoryPath destinationFolder)\n    {\n        if (destinationFolder.FullPath == settingsManager.ModelsDirectory)\n        {\n            notificationService.Show(\n                \"Invalid Folder\",\n                \"Please select a different folder to import the files into.\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        var fileList = files.ToList();\n        if (\n            fileList.Any(file =>\n                !LocalModelFile.SupportedCheckpointExtensions.Contains(Path.GetExtension(file))\n            )\n        )\n        {\n            notificationService.Show(\n                \"Invalid File\",\n                \"Please select only checkpoint files to import.\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        var importModelsStep = new ImportModelsStep(\n            modelFinder,\n            downloadService,\n            modelIndexService,\n            fileList,\n            destinationFolder,\n            IsImportAsConnectedEnabled,\n            settingsManager.Settings.MoveFilesOnImport\n        );\n\n        var runner = new PackageModificationRunner\n        {\n            ModificationCompleteMessage = \"Import Complete\",\n            HideCloseButton = false,\n            ShowDialogOnStart = true,\n        };\n\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n        await runner.ExecuteSteps([importModelsStep]);\n\n        SelectedCategory = Categories\n            .SelectMany(c => c.Flatten())\n            .FirstOrDefault(x => x.Path == destinationFolder.FullPath);\n    }\n\n    public async Task MoveBetweenFolders(\n        List<CheckpointFileViewModel>? sourceFiles,\n        DirectoryPath destinationFolder\n    )\n    {\n        if (sourceFiles != null && sourceFiles.Count() > 0)\n        {\n            var sourceDirectory = Path.GetDirectoryName(\n                sourceFiles[0].CheckpointFile.GetFullPath(settingsManager.ModelsDirectory)\n            );\n            foreach (CheckpointFileViewModel sourceFile in sourceFiles)\n            {\n                if (\n                    destinationFolder.FullPath == settingsManager.ModelsDirectory\n                    || (sourceDirectory != null && sourceDirectory == destinationFolder.FullPath)\n                )\n                {\n                    notificationService.Show(\n                        \"Invalid Folder\",\n                        \"Please select a different folder to import the files into.\",\n                        NotificationType.Error\n                    );\n                    return;\n                }\n\n                try\n                {\n                    var sourcePath = new FilePath(\n                        sourceFile.CheckpointFile.GetFullPath(settingsManager.ModelsDirectory)\n                    );\n                    var fileNameWithoutExt = Path.GetFileNameWithoutExtension(sourcePath);\n                    var sourceCmInfoPath = Path.Combine(\n                        sourcePath.Directory!,\n                        $\"{fileNameWithoutExt}.cm-info.json\"\n                    );\n                    var sourcePreviewPath = Path.Combine(\n                        sourcePath.Directory!,\n                        $\"{fileNameWithoutExt}.preview.jpeg\"\n                    );\n                    var destinationFilePath = Path.Combine(destinationFolder, sourcePath.Name);\n                    var destinationCmInfoPath = Path.Combine(\n                        destinationFolder,\n                        $\"{fileNameWithoutExt}.cm-info.json\"\n                    );\n                    var destinationPreviewPath = Path.Combine(\n                        destinationFolder,\n                        $\"{fileNameWithoutExt}.preview.jpeg\"\n                    );\n\n                    // Move files\n                    if (File.Exists(sourcePath))\n                        await FileTransfers.MoveFileAsync(sourcePath, destinationFilePath);\n\n                    if (File.Exists(sourceCmInfoPath))\n                        await FileTransfers.MoveFileAsync(sourceCmInfoPath, destinationCmInfoPath);\n\n                    if (File.Exists(sourcePreviewPath))\n                        await FileTransfers.MoveFileAsync(sourcePreviewPath, destinationPreviewPath);\n\n                    notificationService.Show(\n                        \"Model moved successfully\",\n                        $\"Moved '{sourcePath.Name}' to '{Path.GetFileName(destinationFolder)}'\"\n                    );\n                }\n                catch (FileTransferExistsException)\n                {\n                    notificationService.Show(\n                        \"Failed to move file\",\n                        \"Destination file exists\",\n                        NotificationType.Error\n                    );\n                }\n            }\n\n            SelectedCategory = Categories\n                .SelectMany(c => c.Flatten())\n                .FirstOrDefault(x => x.Path == sourceDirectory);\n\n            await modelIndexService.RefreshIndex();\n            DelayedClearProgress(TimeSpan.FromSeconds(1.5));\n        }\n    }\n\n    public async Task MoveBetweenFolders(LocalModelFile sourceFile, DirectoryPath destinationFolder)\n    {\n        var sourceDirectory = Path.GetDirectoryName(sourceFile.GetFullPath(settingsManager.ModelsDirectory));\n        if (\n            destinationFolder.FullPath == settingsManager.ModelsDirectory\n            || (sourceDirectory != null && sourceDirectory == destinationFolder.FullPath)\n        )\n        {\n            notificationService.Show(\n                \"Invalid Folder\",\n                \"Please select a different folder to import the files into.\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        try\n        {\n            var sourcePath = new FilePath(sourceFile.GetFullPath(settingsManager.ModelsDirectory));\n            var fileNameWithoutExt = Path.GetFileNameWithoutExtension(sourcePath);\n            var sourceCmInfoPath = Path.Combine(sourcePath.Directory!, $\"{fileNameWithoutExt}.cm-info.json\");\n            var sourcePreviewPath = Path.Combine(sourcePath.Directory!, $\"{fileNameWithoutExt}.preview.jpeg\");\n            var destinationFilePath = Path.Combine(destinationFolder, sourcePath.Name);\n            var destinationCmInfoPath = Path.Combine(destinationFolder, $\"{fileNameWithoutExt}.cm-info.json\");\n            var destinationPreviewPath = Path.Combine(\n                destinationFolder,\n                $\"{fileNameWithoutExt}.preview.jpeg\"\n            );\n\n            // Move files\n            if (File.Exists(sourcePath))\n                await FileTransfers.MoveFileAsync(sourcePath, destinationFilePath);\n\n            if (File.Exists(sourceCmInfoPath))\n                await FileTransfers.MoveFileAsync(sourceCmInfoPath, destinationCmInfoPath);\n\n            if (File.Exists(sourcePreviewPath))\n                await FileTransfers.MoveFileAsync(sourcePreviewPath, destinationPreviewPath);\n\n            notificationService.Show(\n                \"Model moved successfully\",\n                $\"Moved '{sourcePath.Name}' to '{Path.GetFileName(destinationFolder)}'\"\n            );\n        }\n        catch (FileTransferExistsException)\n        {\n            notificationService.Show(\n                \"Failed to move file\",\n                \"Destination file exists\",\n                NotificationType.Error\n            );\n        }\n        finally\n        {\n            SelectedCategory = Categories\n                .SelectMany(c => c.Flatten())\n                .FirstOrDefault(x => x.Path == destinationFolder.FullPath);\n\n            await modelIndexService.RefreshIndex();\n            DelayedClearProgress(TimeSpan.FromSeconds(1.5));\n        }\n    }\n\n    private void RefreshCategories()\n    {\n        if (Design.IsDesignMode)\n            return;\n\n        if (!settingsManager.IsLibraryDirSet)\n            return;\n\n        var previouslySelectedCategory = SelectedCategory;\n\n        var modelCategories = Directory\n            .EnumerateDirectories(\n                settingsManager.ModelsDirectory,\n                \"*\",\n                EnumerationOptionConstants.TopLevelOnly\n            )\n            // Ignore hacky \"diffusion_models\" folder for Swarm\n            .Where(d => !Path.GetFileName(d).EndsWith(\"diffusion_models\", StringComparison.OrdinalIgnoreCase))\n            .Select(d =>\n            {\n                var folderName = Path.GetFileName(d);\n                if (\n                    Enum.TryParse(folderName, out SharedFolderType folderType)\n                    && folderType.GetDescription() != folderName\n                )\n                {\n                    return new CheckpointCategory\n                    {\n                        Path = d,\n                        Name = folderName,\n                        Tooltip = folderType.GetDescription() ?? folderType.GetStringValue(),\n                        SubDirectories = GetSubfolders(d),\n                    };\n                }\n\n                return new CheckpointCategory\n                {\n                    Path = d,\n                    Name = folderName,\n                    SubDirectories = GetSubfolders(d),\n                };\n            })\n            .ToList();\n\n        foreach (var checkpointCategory in modelCategories.SelectMany(c => c.Flatten()))\n            checkpointCategory.Count = Directory\n                .EnumerateFileSystemEntries(\n                    checkpointCategory.Path,\n                    \"*\",\n                    EnumerationOptionConstants.AllDirectories\n                )\n                .Count(x => LocalModelFile.SupportedCheckpointExtensions.Contains(Path.GetExtension(x)));\n\n        var rootCategory = new CheckpointCategory\n        {\n            Path = settingsManager.ModelsDirectory,\n            Name = \"All Models\",\n            Tooltip = \"All Models\",\n            Count = modelIndexService.ModelIndex.Values.SelectMany(x => x).Count(),\n        };\n\n        categoriesCache.Edit(updater =>\n        {\n            updater.Load([rootCategory, .. modelCategories]);\n        });\n\n        SelectedCategory =\n            previouslySelectedCategory\n            ?? Categories.FirstOrDefault(x => x.Path == previouslySelectedCategory?.Path)\n            ?? Categories.FirstOrDefault()\n            ?? (categoriesCache.Items.Any() ? categoriesCache.Items[0] : null);\n\n        var dirPath = new DirectoryPath(SelectedCategory.Path);\n\n        while (dirPath.FullPath != settingsManager.ModelsDirectory && dirPath.Parent != null)\n        {\n            var category = Categories\n                .SelectMany(x => x.Flatten())\n                .FirstOrDefault(x => x.Path == dirPath.FullPath);\n            if (category != null)\n                category.IsExpanded = true;\n\n            dirPath = dirPath.Parent;\n        }\n\n        Dispatcher.UIThread.Post(() =>\n        {\n            SelectedCategory =\n                previouslySelectedCategory\n                ?? Categories.FirstOrDefault(x => x.Path == previouslySelectedCategory?.Path)\n                ?? Categories.FirstOrDefault()\n                ?? (categoriesCache.Items.Any() ? categoriesCache.Items[0] : null);\n        });\n    }\n\n    private ObservableCollection<CheckpointCategory> GetSubfolders(string strPath)\n    {\n        var subfolders = new ObservableCollection<CheckpointCategory>();\n\n        if (!Directory.Exists(strPath))\n            return subfolders;\n\n        var directories = Directory.EnumerateDirectories(\n            strPath,\n            \"*\",\n            EnumerationOptionConstants.TopLevelOnly\n        );\n\n        foreach (var dir in directories)\n        {\n            var dirInfo = new DirectoryPath(dir);\n            if (dirInfo.IsSymbolicLink)\n            {\n                if (!Directory.Exists(dirInfo.Info.LinkTarget))\n                    continue;\n            }\n\n            var folderName = Path.GetFileName(dir);\n            var category = new CheckpointCategory\n            {\n                Name = folderName,\n                Tooltip = folderName,\n                Path = dir,\n                Count = dirInfo\n                    .Info.EnumerateFileSystemInfos(\"*\", EnumerationOptionConstants.AllDirectories)\n                    .Count(x => LocalModelFile.SupportedCheckpointExtensions.Contains(x.Extension)),\n            };\n\n            if (Directory.GetDirectories(dir, \"*\", EnumerationOptionConstants.TopLevelOnly).Length > 0)\n                category.SubDirectories = GetSubfolders(dir);\n\n            subfolders.Add(category);\n        }\n\n        return subfolders;\n    }\n\n    private string GetConnectedModelInfoFilePath(string filePath)\n    {\n        if (string.IsNullOrEmpty(filePath))\n            throw new InvalidOperationException(\n                \"Cannot get connected model info file path when filePath is empty\"\n            );\n\n        var modelNameNoExt = Path.GetFileNameWithoutExtension((string?)filePath);\n        var modelDir = Path.GetDirectoryName((string?)filePath) ?? \"\";\n        return Path.Combine(modelDir, $\"{modelNameNoExt}.cm-info.json\");\n    }\n\n    private void DelayedClearProgress(TimeSpan delay)\n    {\n        Task.Delay(delay)\n            .ContinueWith(_ =>\n            {\n                IsLoading = false;\n                Progress.Value = 0;\n            });\n    }\n\n    private void DelayedClearViewModelProgress(CheckpointFileViewModel viewModel, TimeSpan delay)\n    {\n        Task.Delay(delay)\n            .ContinueWith(_ =>\n            {\n                viewModel.IsLoading = false;\n                viewModel.Progress = new ProgressReport(0f, \"\");\n            });\n    }\n\n    private bool FilterModels(LocalModelFile file)\n    {\n        var folderPath = Path.GetDirectoryName(file.RelativePath);\n\n        // Ignore hacky \"diffusion_models\" folder for Swarm\n        if (folderPath?.Contains(\"diffusion_models\", StringComparison.OrdinalIgnoreCase) ?? false)\n            return false;\n\n        if (SelectedCategory?.Path is null || SelectedCategory?.Path == settingsManager.ModelsDirectory)\n            return file.HasConnectedModel\n                ? SelectedBaseModels.Count == 0\n                    || SelectedBaseModels.Contains(file.ConnectedModelInfo.BaseModel ?? \"Other\")\n                : SelectedBaseModels.Count == 0 || SelectedBaseModels.Contains(\"Other\");\n\n        var categoryRelativePath = SelectedCategory\n            ?.Path.Replace(settingsManager.ModelsDirectory, string.Empty)\n            .TrimStart(Path.DirectorySeparatorChar);\n\n        if (categoryRelativePath == null || folderPath == null)\n            return false;\n\n        if (\n            (\n                file.HasConnectedModel\n                    ? SelectedBaseModels.Count == 0\n                        || SelectedBaseModels.Contains(file.ConnectedModelInfo?.BaseModel ?? \"Other\")\n                    : SelectedBaseModels.Count == 0 || SelectedBaseModels.Contains(\"Other\")\n            )\n            is false\n        )\n            return false;\n\n        // If not showing nested models, just check if the file is directly in this folder\n        if (!ShowModelsInSubfolders)\n            return categoryRelativePath.Equals(folderPath, StringComparison.OrdinalIgnoreCase);\n\n        // Split paths into segments\n        var categorySegments = categoryRelativePath.Split(Path.DirectorySeparatorChar);\n        var folderSegments = folderPath.Split(Path.DirectorySeparatorChar);\n\n        // Check if folder is a subfolder of category by comparing path segments\n        if (folderSegments.Length < categorySegments.Length)\n            return false;\n\n        // Compare each segment of the category path with the folder path\n        return !categorySegments\n            .Where((t, i) => !t.Equals(folderSegments[i], StringComparison.OrdinalIgnoreCase))\n            .Any();\n    }\n\n    private bool FilterCategories(CheckpointCategory category)\n    {\n        return !HideEmptyRootCategories || category is { Count: > 0 };\n    }\n\n    private async Task ShowFolderMapTipIfNecessaryAsync()\n    {\n        if (\n            settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.FolderMapTip)\n            || settingsManager.Settings.InstalledPackages.Count == 0\n        )\n            return;\n\n        var folderReference = DialogHelper.CreateMarkdownDialog(MarkdownSnippets.SMFolderMap);\n        folderReference.CloseButtonText = Resources.Action_OK;\n        await folderReference.ShowAsync();\n\n        settingsManager.Transaction(s => s.SeenTeachingTips.Add(TeachingTip.FolderMapTip));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/ConsoleViewModel.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Threading.Tasks.Dataflow;\nusing System.Web;\nusing Avalonia.Threading;\nusing AvaloniaEdit;\nusing AvaloniaEdit.Document;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Nito.AsyncEx;\nusing Nito.AsyncEx.Synchronous;\nusing NLog;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\npublic partial class ConsoleViewModel : ObservableObject, IDisposable, IAsyncDisposable\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private bool isDisposed;\n\n    // Queue for console updates\n    private BufferBlock<ProcessOutput> buffer = new();\n\n    // Task that updates the console (runs on UI thread)\n    private Task? updateTask;\n\n    // Cancellation token source for updateTask\n    private CancellationTokenSource? updateCts;\n\n    public int MaxLines { get; set; } = -1;\n\n    public bool IsUpdatesRunning => updateTask?.IsCompleted == false;\n\n    [ObservableProperty]\n    private TextDocument document = new();\n\n    /// <summary>\n    /// Current offset for write operations.\n    /// </summary>\n    private int writeCursor;\n\n    /// <summary>\n    /// Lock for accessing <see cref=\"writeCursor\"/>\n    /// </summary>\n    private readonly AsyncLock writeCursorLock = new();\n\n    /// <summary>\n    /// Timeout for acquiring locks on <see cref=\"writeCursor\"/>\n    /// </summary>\n    // ReSharper disable once MemberCanBePrivate.Global\n    public TimeSpan WriteCursorLockTimeout { get; init; } = TimeSpan.FromMilliseconds(100);\n\n    /// <summary>\n    /// Gets a cancellation token using the cursor lock timeout\n    /// </summary>\n    private CancellationToken WriteCursorLockTimeoutToken =>\n        new CancellationTokenSource(WriteCursorLockTimeout).Token;\n\n    /// <summary>\n    /// Event invoked when an ApcMessage of type Input is received.\n    /// </summary>\n    public event EventHandler<ApcMessage>? ApcInput;\n\n    /// <summary>\n    /// Starts update task for processing Post messages.\n    /// </summary>\n    /// <exception cref=\"InvalidOperationException\">If update task is already running</exception>\n    public void StartUpdates()\n    {\n        if (updateTask is not null)\n        {\n            throw new InvalidOperationException(\"Update task is already running\");\n        }\n        updateCts = new CancellationTokenSource();\n        updateTask = Dispatcher.UIThread.InvokeAsync(ConsoleUpdateLoop, DispatcherPriority.Send);\n    }\n\n    /// <summary>\n    /// Cancels the update task and waits for it to complete.\n    /// </summary>\n    public async Task StopUpdatesAsync()\n    {\n        Logger.Trace($\"Stopping console updates, current buffer items: {buffer.Count}\");\n        // First complete the buffer\n        buffer.Complete();\n        // Wait for buffer to complete, max 3 seconds\n        var completionCts = new CancellationTokenSource(3000);\n        try\n        {\n            await buffer.Completion.WaitAsync(completionCts.Token);\n        }\n        catch (TaskCanceledException e)\n        {\n            // We can still continue since this just means we lose\n            // some remaining output\n            Logger.Warn(\"Buffer completion timed out: \" + e.Message);\n        }\n\n        // Cancel update task\n        updateCts?.Cancel();\n        updateCts = null;\n\n        // Wait for update task\n        if (updateTask is not null)\n        {\n            await updateTask;\n            updateTask = null;\n        }\n        Logger.Trace($\"Stopped console updates with {buffer.Count} buffer items remaining\");\n    }\n\n    /// <summary>\n    /// Clears the console and sets a new buffer.\n    /// This also resets the write cursor to 0.\n    /// </summary>\n    public async Task Clear()\n    {\n        // Clear document\n        Document.Text = string.Empty;\n        // Reset write cursor\n        await ResetWriteCursor();\n        // Clear buffer and create new one\n        buffer.Complete();\n        buffer = new BufferBlock<ProcessOutput>();\n    }\n\n    /// <summary>\n    /// Resets the write cursor to be equal to the document length.\n    /// </summary>\n    public async Task ResetWriteCursor()\n    {\n        using (await writeCursorLock.LockAsync(WriteCursorLockTimeoutToken))\n        {\n            Logger.ConditionalTrace($\"Reset cursor to end: ({writeCursor} -> {Document.TextLength})\");\n            writeCursor = Document.TextLength;\n        }\n        DebugPrintDocument();\n    }\n\n    [RelayCommand]\n    private async Task CopySelection(TextEditor textEditor)\n    {\n        await App.Clipboard.SetTextAsync(textEditor.SelectedText);\n    }\n\n    [RelayCommand]\n    private void SelectAll(TextEditor textEditor)\n    {\n        textEditor.SelectAll();\n    }\n\n    [Localizable(false)]\n    [RelayCommand]\n    private void SearchWithGoogle(TextEditor textEditor)\n    {\n        var url = $\"https://google.com/search?q={HttpUtility.UrlEncode(textEditor.SelectedText)}\";\n        ProcessRunner.OpenUrl(url);\n    }\n\n    [Localizable(false)]\n    [RelayCommand]\n    private void SearchWithChatGpt(TextEditor textEditor)\n    {\n        var url = $\"https://chatgpt.com/?q={HttpUtility.UrlEncode(textEditor.SelectedText)}\";\n        ProcessRunner.OpenUrl(url);\n    }\n\n    private async Task ConsoleUpdateLoop()\n    {\n        // This must be run in the UI thread\n        Dispatcher.UIThread.VerifyAccess();\n\n        // Get cancellation token\n        var ct =\n            updateCts?.Token ?? throw new InvalidOperationException(\"Update cancellation token must be set\");\n\n        try\n        {\n            while (!ct.IsCancellationRequested)\n            {\n                ProcessOutput output;\n\n                try\n                {\n                    output = await buffer.ReceiveAsync(ct);\n                }\n                catch (InvalidOperationException e)\n                {\n                    // Thrown when buffer is completed, convert to OperationCanceledException\n                    throw new OperationCanceledException(\"Update buffer completed\", e);\n                }\n\n                var outputType = output.IsStdErr ? \"stderr\" : \"stdout\";\n                Logger.ConditionalTrace(\n                    $\"Processing: [{outputType}] (Text = {output.Text.ToRepr()}, \"\n                        + $\"Raw = {output.RawText?.ToRepr()}, \"\n                        + $\"CarriageReturn = {output.CarriageReturn}, \"\n                        + $\"CursorUp = {output.CursorUp}, \"\n                        + $\"AnsiCommand = {output.AnsiCommand})\"\n                );\n\n                // Link the cancellation token to the write cursor lock timeout\n                var linkedCt = CancellationTokenSource\n                    .CreateLinkedTokenSource(ct, WriteCursorLockTimeoutToken)\n                    .Token;\n\n                using (await writeCursorLock.LockAsync(linkedCt))\n                {\n                    ConsoleUpdateOne(output);\n                }\n            }\n        }\n        catch (OperationCanceledException e)\n        {\n            Logger.Debug($\"Console update loop canceled: {e.Message}\");\n        }\n        catch (Exception e)\n        {\n            // Log other errors and continue here to not crash the UI thread\n            Logger.Error(e, $\"Unexpected error in console update loop: {e.GetType().Name} {e.Message}\");\n        }\n    }\n\n    /// <summary>\n    /// Handle one instance of ProcessOutput.\n    /// Calls to this function must be synchronized with <see cref=\"writeCursorLock\"/>\n    /// </summary>\n    /// <remarks>Not checked, but must be run in the UI thread.</remarks>\n    private void ConsoleUpdateOne(ProcessOutput output)\n    {\n        Debug.Assert(Dispatcher.UIThread.CheckAccess());\n\n        // Check for Apc messages\n        if (output.ApcMessage is not null)\n        {\n            // Handle Apc message, for now just input audit events\n            var message = output.ApcMessage.Value;\n            if (message.Type == ApcType.Input)\n            {\n                ApcInput?.Invoke(this, message);\n            }\n            // Ignore further processing\n            return;\n        }\n\n        // If we have a carriage return,\n        // start current write at the beginning of the current line\n        if (output.CarriageReturn > 0)\n        {\n            var currentLine = Document.GetLineByOffset(writeCursor);\n\n            // Get the start of current line as new write cursor\n            var lineStartOffset = currentLine.Offset;\n\n            // See if we need to move the cursor\n            if (lineStartOffset == writeCursor)\n            {\n                Logger.ConditionalTrace(\n                    $\"Cursor already at start for carriage return \"\n                        + $\"(offset = {lineStartOffset}, line = {currentLine.LineNumber})\"\n                );\n            }\n            else\n            {\n                // Also remove everything on current line\n                // We'll temporarily do this for now to fix progress\n                var lineEndOffset = currentLine.EndOffset;\n                var lineLength = lineEndOffset - lineStartOffset;\n                Document.Remove(lineStartOffset, lineLength);\n\n                Logger.ConditionalTrace(\n                    $\"Moving cursor to start for carriage return \" + $\"({writeCursor} -> {lineStartOffset})\"\n                );\n                writeCursor = lineStartOffset;\n            }\n        }\n\n        // Write new text\n        if (!string.IsNullOrEmpty(output.Text))\n        {\n            DirectWriteLinesToConsole(output.Text);\n        }\n\n        // Handle cursor movements\n        if (output.CursorUp > 0)\n        {\n            // Get the line and column of the current cursor\n            var currentLocation = Document.GetLocation(writeCursor);\n\n            if (currentLocation.Line == 1)\n            {\n                // We are already on the first line, ignore\n                Logger.ConditionalTrace($\"Cursor up: Already on first line\");\n            }\n            else\n            {\n                // We want to move up one line\n                var targetLocation = new TextLocation(currentLocation.Line - 1, currentLocation.Column);\n                var targetOffset = Document.GetOffset(targetLocation);\n\n                // Update cursor to target offset\n                Logger.ConditionalTrace(\n                    $\"Cursor up: Moving (line {currentLocation.Line}, {writeCursor})\"\n                        + $\" -> (line {targetLocation.Line}, {targetOffset})\"\n                );\n\n                writeCursor = targetOffset;\n            }\n        }\n\n        // Handle erase commands, different to cursor move as they don't move the cursor\n        // We'll insert blank spaces instead\n        if (output.AnsiCommand.HasFlag(AnsiCommand.EraseLine))\n        {\n            // Get the current line, we'll insert spaces from start to end\n            var currentLine = Document.GetLineByOffset(writeCursor);\n            // Must be smaller than total lines\n            currentLine =\n                currentLine.LineNumber < Document.LineCount\n                    ? currentLine\n                    : Document.GetLineByNumber(Document.LineCount - 1);\n\n            // Make some spaces to insert\n            var spaces = new string(' ', currentLine.Length);\n\n            // Insert the text\n            Logger.ConditionalTrace(\n                $\"Erasing line {currentLine.LineNumber}: (length = {currentLine.Length})\"\n            );\n            using (Document.RunUpdate())\n            {\n                Document.Replace(currentLine.Offset, currentLine.Length, spaces);\n            }\n        }\n\n        DebugPrintDocument();\n    }\n\n    /// <summary>\n    /// Write text potentially containing line breaks to the console.\n    /// <remarks>This call will hold a upgradeable read lock</remarks>\n    /// </summary>\n    private void DirectWriteLinesToConsole(string text)\n    {\n        // When our cursor is not at end, newlines should be interpreted as commands to\n        // move cursor forward to the next linebreak instead of inserting a newline.\n\n        // If text contains no newlines, we can just call DirectWriteToConsole\n        // Also if cursor is equal to document length\n        if (!text.Contains(Environment.NewLine) || writeCursor == Document.TextLength)\n        {\n            DirectWriteToConsole(text);\n            return;\n        }\n\n        // Otherwise we need to handle how linebreaks are treated\n        // Split text into lines\n        var lines = text.Split(Environment.NewLine).ToList();\n\n        foreach (var lineText in lines.SkipLast(1))\n        {\n            // Insert text\n            DirectWriteToConsole(lineText);\n\n            // Set cursor to start of next line, if we're not already there\n            var currentLine = Document.GetLineByOffset(writeCursor);\n            // If next line is available, move cursor to start of next line\n            if (currentLine.LineNumber < Document.LineCount)\n            {\n                var nextLine = Document.GetLineByNumber(currentLine.LineNumber + 1);\n                Logger.ConditionalTrace(\n                    $\"Moving cursor to start of next line \" + $\"({writeCursor} -> {nextLine.Offset})\"\n                );\n                writeCursor = nextLine.Offset;\n            }\n            else\n            {\n                // Otherwise move to end of current line, and direct insert a newline\n                var lineEndOffset = currentLine.EndOffset;\n                Logger.ConditionalTrace(\n                    $\"Moving cursor to end of current line \" + $\"({writeCursor} -> {lineEndOffset})\"\n                );\n                writeCursor = lineEndOffset;\n                DirectWriteToConsole(Environment.NewLine);\n            }\n        }\n    }\n\n    /// <summary>\n    /// Write text to the console, does not handle newlines.\n    /// This should probably only be used by <see cref=\"DirectWriteLinesToConsole\"/>\n    /// <remarks>This call will hold a upgradeable read lock</remarks>\n    /// </summary>\n    private void DirectWriteToConsole(string text)\n    {\n        CheckMaxLines();\n\n        using (Document.RunUpdate())\n        {\n            // Need to replace text first if cursor lower than document length\n            var replaceLength = Math.Min(Document.TextLength - writeCursor, text.Length);\n            if (replaceLength > 0)\n            {\n                var newText = text[..replaceLength];\n                Logger.ConditionalTrace(\n                    $\"Replacing: (cursor = {writeCursor}, length = {replaceLength}, \"\n                        + $\"text = {Document.GetText(writeCursor, replaceLength).ToRepr()} \"\n                        + $\"-> {newText.ToRepr()})\"\n                );\n\n                Document.Replace(writeCursor, replaceLength, newText);\n                writeCursor += replaceLength;\n            }\n\n            // If we replaced less than content.Length, we need to insert the rest\n            var remainingLength = text.Length - replaceLength;\n            if (remainingLength > 0)\n            {\n                var textToInsert = text[replaceLength..];\n                Logger.ConditionalTrace(\n                    $\"Inserting: (cursor = {writeCursor}, \" + $\"text = {textToInsert.ToRepr()})\"\n                );\n\n                Document.Insert(writeCursor, textToInsert);\n                writeCursor += textToInsert.Length;\n            }\n        }\n    }\n\n    private void CheckMaxLines()\n    {\n        // Ignore limit if MaxLines is negative\n        if (MaxLines < 0)\n            return;\n\n        if (Document.LineCount <= MaxLines)\n            return;\n\n        // Minimum lines to remove\n        const int removeLinesBatchSize = 1;\n\n        using (Document.RunUpdate())\n        {\n            var currentLines = Document.LineCount;\n            var linesExceeded = currentLines - MaxLines;\n            var linesToRemove = Math.Min(currentLines, Math.Max(linesExceeded, removeLinesBatchSize));\n\n            Logger.ConditionalTrace(\n                \"Exceeded max lines ({Current} > {Max}), removing {Remove} lines\",\n                currentLines,\n                MaxLines,\n                linesToRemove\n            );\n\n            // Remove lines from the start\n            var firstLine = Document.GetLineByNumber(1);\n            var lastLine = Document.GetLineByNumber(linesToRemove);\n            var removeStart = firstLine.Offset;\n\n            // If a next line exists, use the start offset of that instead in case of weird newlines\n            var removeEnd = lastLine.EndOffset;\n            if (lastLine.NextLine is not null)\n            {\n                removeEnd = lastLine.NextLine.Offset;\n            }\n\n            var removeLength = removeEnd - removeStart;\n\n            Logger.ConditionalTrace(\n                \"Removing {LinesExceeded} lines from start: ({RemoveStart} -> {RemoveEnd})\",\n                linesExceeded,\n                removeStart,\n                removeEnd\n            );\n\n            Document.Remove(removeStart, removeLength);\n\n            // Update cursor position\n            writeCursor -= removeLength;\n        }\n    }\n\n    /// <summary>\n    /// Debug function to print the current document to the console.\n    /// Includes formatted cursor position.\n    /// </summary>\n    [Conditional(\"DEBUG\")]\n    private void DebugPrintDocument()\n    {\n        if (!Logger.IsTraceEnabled)\n            return;\n\n        var text = Document.Text;\n        // Add a number for each line\n        // Add an arrow line for where the cursor is, for example (cursor on offset 3):\n        //\n        // 1 | This is the first line\n        // ~~~~~~~^ (3)\n        // 2 | This is the second line\n        //\n\n        var lines = text.Split(Environment.NewLine).ToList();\n        var numberPadding = lines.Count.ToString().Length;\n        for (var i = 0; i < lines.Count; i++)\n        {\n            lines[i] = $\"{(i + 1).ToString().PadLeft(numberPadding)} | {lines[i]}\";\n        }\n        var cursorLine = Document.GetLineByOffset(writeCursor);\n        var cursorLineOffset = writeCursor - cursorLine.Offset;\n\n        // Need to account for padding + line number + space + cursor line offset\n        var linePadding = numberPadding + 3 + cursorLineOffset;\n        var cursorLineArrow = new string('~', linePadding) + $\"^ ({writeCursor})\";\n\n        // If more than line count, append to end\n        if (cursorLine.LineNumber >= lines.Count)\n        {\n            lines.Add(cursorLineArrow);\n        }\n        else\n        {\n            lines.Insert(cursorLine.LineNumber, cursorLineArrow);\n        }\n\n        var textWithCursor = string.Join(Environment.NewLine, lines);\n\n        Logger.ConditionalTrace(\"[Current Document]\");\n        Logger.ConditionalTrace(textWithCursor);\n    }\n\n    /// <summary>\n    /// Posts an update to the console\n    /// <remarks>Safe to call on non-UI threads</remarks>\n    /// </summary>\n    public void Post(ProcessOutput output)\n    {\n        // If update task is running, send to buffer\n        if (updateTask != null)\n        {\n            buffer.Post(output);\n            return;\n        }\n        // Otherwise, use manual update one\n        Logger.Debug(\"Synchronous post update to console: {@Output}\", output);\n        Dispatcher.UIThread.Post(() => ConsoleUpdateOne(output));\n    }\n\n    /// <summary>\n    /// Posts an update to the console.\n    /// Helper for calling Post(ProcessOutput) with strings\n    /// </summary>\n    public void Post(string text)\n    {\n        Post(new ProcessOutput { Text = text });\n    }\n\n    /// <summary>\n    /// Posts an update to the console.\n    /// Equivalent to Post(text + Environment.NewLine)\n    /// </summary>\n    public void PostLine(string text)\n    {\n        Post(new ProcessOutput { Text = text + Environment.NewLine });\n    }\n\n    public void Dispose()\n    {\n        if (isDisposed)\n            return;\n\n        updateCts?.Cancel();\n        updateCts?.Dispose();\n        updateCts = null;\n\n        buffer.Complete();\n\n        if (updateTask is not null)\n        {\n            Logger.Debug(\"Shutting down console update task\");\n\n            try\n            {\n                updateTask.WaitWithoutException(new CancellationTokenSource(1000).Token);\n                updateTask.Dispose();\n                updateTask = null;\n            }\n            catch (OperationCanceledException)\n            {\n                Logger.Warn(\"During shutdown - Console update task cancellation timed out\");\n            }\n            catch (InvalidOperationException e)\n            {\n                Logger.Warn(e, \"During shutdown - Console update task dispose failed\");\n            }\n        }\n\n        isDisposed = true;\n\n        GC.SuppressFinalize(this);\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (isDisposed)\n            return;\n\n        updateCts?.Cancel();\n        updateCts?.Dispose();\n        updateCts = null;\n\n        if (updateTask is not null)\n        {\n            Logger.Debug(\"Waiting for console update task shutdown...\");\n\n            await updateTask;\n            updateTask.Dispose();\n            updateTask = null;\n\n            Logger.Debug(\"Console update task shutdown complete\");\n        }\n\n        isDisposed = true;\n\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Controls/GitVersionSelectorViewModel.cs",
    "content": "﻿using System;\nusing Avalonia.Data;\nusing Avalonia.Layout;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing FluentAvalonia.UI.Controls;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Controls.Models;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Core.Git;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Controls;\n\npublic partial class GitVersionSelectorViewModel : ObservableObject\n{\n    [ObservableProperty]\n    private IGitVersionProvider? gitVersionProvider;\n\n    [ObservableProperty]\n    private string? selectedBranch;\n\n    [ObservableProperty]\n    private string? selectedCommit;\n\n    [ObservableProperty]\n    private string? selectedTag;\n\n    [ObservableProperty]\n    private GitVersionSelectorVersionType selectedVersionType;\n\n    /// <summary>\n    /// Gets or sets the selected <see cref=\"GitVersion\"/>.\n    /// </summary>\n    /// <exception cref=\"ArgumentOutOfRangeException\"></exception>\n    public GitVersion SelectedGitVersion\n    {\n        get\n        {\n            return SelectedVersionType switch\n            {\n                GitVersionSelectorVersionType.BranchCommit\n                    => new GitVersion { Branch = SelectedBranch, CommitSha = SelectedCommit },\n                GitVersionSelectorVersionType.Tag => new GitVersion { Tag = SelectedTag },\n                _ => throw new InvalidOperationException()\n            };\n        }\n        set\n        {\n            SelectedVersionType = value switch\n            {\n                { Tag: not null } => GitVersionSelectorVersionType.Tag,\n                { Branch: not null, CommitSha: not null } => GitVersionSelectorVersionType.BranchCommit,\n                // Default to branch commit\n                _ => GitVersionSelectorVersionType.BranchCommit\n            };\n\n            SelectedBranch = value.Branch;\n            SelectedCommit = value.CommitSha;\n            SelectedTag = value.Tag;\n        }\n    }\n\n    public BetterContentDialog GetDialog()\n    {\n        Dispatcher.UIThread.VerifyAccess();\n\n        var selector = new GitVersionSelector\n        {\n            DataContext = this,\n            Height = 400,\n            Width = 600,\n            [!GitVersionSelector.GitVersionProviderProperty] = new Binding(nameof(GitVersionProvider)),\n            [!GitVersionSelector.SelectedVersionTypeProperty] = new Binding(nameof(SelectedVersionType)),\n            [!GitVersionSelector.SelectedBranchProperty] = new Binding(nameof(SelectedBranch)),\n            [!GitVersionSelector.SelectedCommitProperty] = new Binding(nameof(SelectedCommit)),\n            [!GitVersionSelector.SelectedTagProperty] = new Binding(nameof(SelectedTag))\n        };\n\n        var dialog = new BetterContentDialog\n        {\n            Content = selector,\n            VerticalAlignment = VerticalAlignment.Stretch,\n            VerticalContentAlignment = VerticalAlignment.Stretch,\n            PrimaryButtonText = Resources.Action_Save,\n            CloseButtonText = Resources.Action_Cancel,\n            DefaultButton = ContentDialogButton.Primary,\n            MinDialogWidth = 400\n        };\n\n        return dialog;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Controls/PaintCanvasViewModel.Serializer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Drawing;\nusing System.Linq;\nusing System.Text.Json;\nusing System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Avalonia.Controls.Models;\nusing StabilityMatrix.Avalonia.Models;\nusing Color = Avalonia.Media.Color;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Controls;\n\npublic partial class PaintCanvasViewModel\n{\n    public override JsonObject SaveStateToJsonObject()\n    {\n        var model = SaveState();\n\n        return JsonSerializer\n                .SerializeToNode(model, PaintCanvasModelSerializerContext.Default.Options)\n                ?.AsObject() ?? throw new InvalidOperationException();\n    }\n\n    /// <inheritdoc />\n    public override void LoadStateFromJsonObject(JsonObject state)\n    {\n        var model = state.Deserialize<PaintCanvasModel>(PaintCanvasModelSerializerContext.Default.Options);\n\n        if (model is null)\n            return;\n\n        LoadState(model);\n\n        RefreshCanvas?.Invoke();\n    }\n\n    protected PaintCanvasModel SaveState()\n    {\n        var model = new PaintCanvasModel\n        {\n            TemporaryPaths = TemporaryPaths.ToDictionary(x => x.Key, x => x.Value),\n            Paths = Paths,\n            PaintBrushColor = PaintBrushColor,\n            PaintBrushSize = PaintBrushSize,\n            PaintBrushAlpha = PaintBrushAlpha,\n            CurrentPenPressure = CurrentPenPressure,\n            CurrentZoom = CurrentZoom,\n            IsPenDown = IsPenDown,\n            SelectedTool = SelectedTool,\n            CanvasSize = CanvasSize\n        };\n\n        return model;\n    }\n\n    protected void LoadState(PaintCanvasModel model)\n    {\n        TemporaryPaths.Clear();\n        foreach (var (key, value) in model.TemporaryPaths)\n        {\n            TemporaryPaths.TryAdd(key, value);\n        }\n\n        Paths = model.Paths;\n        PaintBrushColor = model.PaintBrushColor;\n        PaintBrushSize = model.PaintBrushSize;\n        PaintBrushAlpha = model.PaintBrushAlpha;\n        CurrentPenPressure = model.CurrentPenPressure;\n        CurrentZoom = model.CurrentZoom;\n        IsPenDown = model.IsPenDown;\n        SelectedTool = model.SelectedTool;\n        CanvasSize = model.CanvasSize;\n\n        RefreshCanvas?.Invoke();\n    }\n\n    public class PaintCanvasModel\n    {\n        public Dictionary<long, PenPath> TemporaryPaths { get; init; } = new();\n        public ImmutableList<PenPath> Paths { get; init; } = ImmutableList<PenPath>.Empty;\n        public Color? PaintBrushColor { get; init; }\n        public double PaintBrushSize { get; init; }\n        public double PaintBrushAlpha { get; init; }\n        public double CurrentPenPressure { get; init; }\n        public double CurrentZoom { get; init; }\n        public bool IsPenDown { get; init; }\n        public PaintCanvasTool SelectedTool { get; init; }\n        public Size CanvasSize { get; init; }\n    }\n\n    [JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault)]\n    [JsonSerializable(typeof(PaintCanvasModel))]\n    internal partial class PaintCanvasModelSerializerContext : JsonSerializerContext;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Controls/PaintCanvasViewModel.cs",
    "content": "﻿using System.Collections.Concurrent;\nusing System.Collections.Immutable;\nusing System.ComponentModel;\nusing System.Text.Json.Serialization;\nusing Avalonia.Media;\nusing Avalonia.Skia;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing SkiaSharp;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Controls.Models;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing Color = Avalonia.Media.Color;\nusing Size = System.Drawing.Size;\n\n#pragma warning disable CS0657 // Not a valid attribute location for this declaration\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Controls;\n\n[RegisterTransient<PaintCanvasViewModel>]\n[ManagedService]\npublic partial class PaintCanvasViewModel(ILogger<PaintCanvasViewModel> logger) : LoadableViewModelBase\n{\n    public ConcurrentDictionary<long, PenPath> TemporaryPaths { get; set; } = new();\n\n    [ObservableProperty]\n    [NotifyCanExecuteChangedFor(nameof(UndoCommand))]\n    private ImmutableList<PenPath> paths = [];\n\n    [ObservableProperty]\n    private Color? paintBrushColor = Colors.White;\n\n    public SKColor PaintBrushSKColor => (PaintBrushColor ?? Colors.Transparent).ToSKColor();\n\n    [ObservableProperty]\n    private double paintBrushSize = 12;\n\n    [ObservableProperty]\n    private double paintBrushAlpha = 1;\n\n    [ObservableProperty]\n    private double currentPenPressure;\n\n    [ObservableProperty]\n    private double currentZoom;\n\n    [ObservableProperty]\n    private bool isPenDown;\n\n    [ObservableProperty]\n    private PaintCanvasTool selectedTool = PaintCanvasTool.PaintBrush;\n\n    [ObservableProperty]\n    private Size canvasSize = Size.Empty;\n\n    [JsonIgnore]\n    private SKCanvas? SourceCanvas { set; get; }\n\n    [Localizable(false)]\n    [JsonIgnore]\n    private OrderedDictionary<string, SKLayer> Layers { get; } =\n        new()\n        {\n            [\"Background\"] = new SKLayer(),\n            [\"Images\"] = new SKLayer(),\n            [\"Brush\"] = new SKLayer(),\n        };\n\n    [JsonIgnore]\n    private SKLayer BrushLayer => Layers[\"Brush\"];\n\n    [JsonIgnore]\n    private SKLayer ImagesLayer => Layers[\"Images\"];\n\n    [JsonIgnore]\n    private SKLayer BackgroundLayer => Layers[\"Background\"];\n\n    [JsonIgnore]\n    public SKBitmap? BackgroundImage\n    {\n        get => BackgroundLayer.Bitmaps.FirstOrDefault();\n        set\n        {\n            if (value is not null)\n            {\n                CanvasSize = new Size(value.Width, value.Height);\n                BackgroundLayer.Bitmaps = [value];\n            }\n            else\n            {\n                CanvasSize = Size.Empty;\n                BackgroundLayer.Bitmaps = [];\n            }\n        }\n    }\n\n    /// <summary>\n    /// Set by <see cref=\"PaintCanvas\"/> to allow the view model to\n    /// refresh the canvas view after updating points or bitmap layers.\n    /// </summary>\n    [JsonIgnore]\n    public Action? RefreshCanvas { get; set; }\n\n    public void SetSourceCanvas(SKCanvas canvas)\n    {\n        ArgumentNullException.ThrowIfNull(canvas, nameof(canvas));\n        SourceCanvas = canvas;\n    }\n\n    public void LoadCanvasFromBitmap(SKBitmap bitmap)\n    {\n        ImagesLayer.Bitmaps = [bitmap];\n\n        RefreshCanvas?.Invoke();\n    }\n\n    [RelayCommand(CanExecute = nameof(CanExecuteUndo))]\n    public void Undo()\n    {\n        // Remove last path\n        var currentPaths = Paths;\n\n        if (currentPaths.IsEmpty)\n        {\n            return;\n        }\n\n        Paths = currentPaths.RemoveAt(currentPaths.Count - 1);\n\n        RefreshCanvas?.Invoke();\n    }\n\n    private bool CanExecuteUndo()\n    {\n        return Paths.Count > 0;\n    }\n\n    public SKImage? RenderToWhiteChannelImage()\n    {\n        using var _ = CodeTimer.StartDebug();\n\n        if (CanvasSize == Size.Empty)\n        {\n            logger.LogWarning($\"RenderToImage: {nameof(CanvasSize)} is not set, returning null.\");\n            return null;\n        }\n\n        using var surface = SKSurface.Create(new SKImageInfo(CanvasSize.Width, CanvasSize.Height));\n\n        RenderToSurface(surface);\n\n        using var originalImage = surface.Snapshot();\n        // Replace all colors to white (255, 255, 255), keep original alpha\n        // csharpier-ignore\n        using var colorFilter = SKColorFilter.CreateColorMatrix(\n            [\n                // R, G, B, A, Bias\n                -1, 0, 0, 0, 255,\n                0, -1, 0, 0, 255,\n                0, 0, -1, 0, 255,\n                0, 0, 0, 1, 0\n            ]\n        );\n\n        using var paint = new SKPaint();\n        paint.ColorFilter = colorFilter;\n\n        surface.Canvas.Clear(SKColors.Transparent);\n        surface.Canvas.DrawImage(originalImage, originalImage.Info.Rect, paint);\n\n        return surface.Snapshot();\n    }\n\n    public SKImage? RenderToImage()\n    {\n        using var _ = CodeTimer.StartDebug();\n\n        if (CanvasSize == Size.Empty)\n        {\n            logger.LogWarning($\"RenderToImage: {nameof(CanvasSize)} is not set, returning null.\");\n            return null;\n        }\n\n        using var surface = SKSurface.Create(new SKImageInfo(CanvasSize.Width, CanvasSize.Height));\n\n        RenderToSurface(surface);\n\n        return surface.Snapshot();\n    }\n\n    public void RenderToSurface(\n        SKSurface surface,\n        bool renderBackgroundFill = false,\n        bool renderBackgroundImage = false\n    )\n    {\n        // Initialize canvas layers\n        foreach (var layer in Layers.Values)\n        {\n            lock (layer)\n            {\n                if (layer.Surface is null)\n                {\n                    layer.Surface = SKSurface.Create(new SKImageInfo(CanvasSize.Width, CanvasSize.Height));\n                    /*layer.Surface = SKSurface.Create(\n                        surface.Context,\n                        true,\n                        new SKImageInfo(CanvasSize.Width, CanvasSize.Height)\n                    );*/\n                }\n                else\n                {\n                    // If we need to resize:\n                    var currentInfo = layer.Surface.Canvas.DeviceClipBounds;\n                    if (currentInfo.Width != CanvasSize.Width || currentInfo.Height != CanvasSize.Height)\n                    {\n                        // Dispose the old surface\n                        layer.Surface.Dispose();\n\n                        // Create a brand-new SKSurface with the new size\n                        layer.Surface = SKSurface.Create(\n                            new SKImageInfo(CanvasSize.Width, CanvasSize.Height)\n                        );\n                    }\n                    else\n                    {\n                        // No resize needed, just clear\n                        layer.Surface.Canvas.Clear(SKColors.Transparent);\n                    }\n                }\n            }\n        }\n\n        // Render all layer images in order\n        foreach (var (layerName, layer) in Layers)\n        {\n            // Skip background image if not requested\n            if (!renderBackgroundImage && layerName == \"Background\")\n            {\n                continue;\n            }\n\n            lock (layer)\n            {\n                var layerCanvas = layer.Surface!.Canvas;\n                foreach (var bitmap in layer.Bitmaps)\n                {\n                    layerCanvas.DrawBitmap(bitmap, new SKPoint(0, 0));\n                }\n            }\n        }\n\n        // Render paint layer\n        var paintLayerCanvas = BrushLayer.Surface!.Canvas;\n\n        using var paint = new SKPaint();\n\n        // Draw the paths\n        foreach (var penPath in Paths)\n        {\n            RenderPenPath(paintLayerCanvas, penPath, paint);\n        }\n\n        foreach (var penPath in TemporaryPaths.Values)\n        {\n            RenderPenPath(paintLayerCanvas, penPath, paint);\n        }\n\n        // Draw background color\n        surface.Canvas.Clear(SKColors.Transparent);\n\n        // Draw the layers to the main surface\n        foreach (var layer in Layers.Values)\n        {\n            lock (layer)\n            {\n                layer.Surface!.Canvas.Flush();\n\n                surface.Canvas.DrawSurface(layer.Surface!, new SKPoint(0, 0));\n            }\n        }\n\n        surface.Canvas!.Flush();\n    }\n\n    private static void RenderPenPath(SKCanvas canvas, PenPath penPath, SKPaint paint)\n    {\n        if (penPath.Points.Count == 0)\n        {\n            return;\n        }\n\n        // Apply Color\n        if (penPath.IsErase)\n        {\n            // paint.BlendMode = SKBlendMode.SrcIn;\n            paint.BlendMode = SKBlendMode.Clear;\n            paint.Color = SKColors.Transparent;\n        }\n        else\n        {\n            paint.BlendMode = SKBlendMode.SrcOver;\n            paint.Color = penPath.FillColor;\n        }\n\n        // Defaults\n        paint.IsDither = true;\n        paint.IsAntialias = true;\n\n        // Track if we have any pen points\n        var hasPenPoints = false;\n\n        // Can't use foreach since this list may be modified during iteration\n        // ReSharper disable once ForCanBeConvertedToForeach\n        for (var i = 0; i < penPath.Points.Count; i++)\n        {\n            var penPoint = penPath.Points[i];\n\n            // Skip non-pen points\n            if (!penPoint.IsPen)\n            {\n                continue;\n            }\n\n            hasPenPoints = true;\n\n            var radius = penPoint.Radius;\n            var pressure = penPoint.Pressure ?? 1;\n            var thickness = pressure * radius * 2.5;\n\n            // Draw path\n            if (i < penPath.Points.Count - 1)\n            {\n                paint.Style = SKPaintStyle.Stroke;\n                paint.StrokeWidth = (float)thickness;\n                paint.StrokeCap = SKStrokeCap.Round;\n                paint.StrokeJoin = SKStrokeJoin.Round;\n\n                var nextPoint = penPath.Points[i + 1];\n                canvas.DrawLine(penPoint.X, penPoint.Y, nextPoint.X, nextPoint.Y, paint);\n            }\n\n            // Draw circles for pens\n            paint.Style = SKPaintStyle.Fill;\n            canvas.DrawCircle(penPoint.X, penPoint.Y, (float)thickness / 2, paint);\n        }\n\n        // Draw paths directly if we didn't have any pen points\n        if (!hasPenPoints)\n        {\n            var point = penPath.Points[0];\n            var thickness = point.Radius * 2;\n\n            paint.Style = SKPaintStyle.Stroke;\n            paint.StrokeWidth = (float)thickness;\n            paint.StrokeCap = SKStrokeCap.Round;\n            paint.StrokeJoin = SKStrokeJoin.Round;\n\n            var skPath = penPath.ToSKPath();\n            canvas.DrawPath(skPath, paint);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/AnalyticsOptInViewModel.cs",
    "content": "﻿using FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(AnalyticsOptInDialog))]\n[ManagedService]\n[RegisterTransient<AnalyticsOptInViewModel>]\npublic class AnalyticsOptInViewModel : ContentDialogViewModelBase\n{\n    public string ChangeThisBehaviorInSettings =>\n        string.Format(Resources.TextTemplate_YouCanChangeThisBehavior, \"Settings > System > Analytics\")\n            .Trim();\n\n    public override BetterContentDialog GetDialog()\n    {\n        var dialog = base.GetDialog();\n        dialog.IsPrimaryButtonEnabled = true;\n        dialog.PrimaryButtonText = \"Don't Share Analytics\";\n        dialog.SecondaryButtonText = \"Share Analytics\";\n        dialog.CloseOnClickOutside = false;\n        dialog.DefaultButton = ContentDialogButton.Secondary;\n\n        return dialog;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/CivitFileDisplayViewModel.cs",
    "content": "using StabilityMatrix.Core.Models.Api;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\npublic class CivitFileDisplayViewModel\n{\n    public required CivitModelVersion ModelVersion { get; init; }\n    public required CivitFileViewModel FileViewModel { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/CivitFileViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.ObjectModel;\nusing System.Threading.Tasks;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing NLog;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\npublic partial class CivitFileViewModel : DisposableViewModelBase\n{\n    private readonly IModelIndexService modelIndexService;\n    private readonly ISettingsManager settingsManager;\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n    private readonly Func<CivitFileViewModel, string?, Task>? downloadAction;\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    [ObservableProperty]\n    private CivitFile civitFile;\n\n    [ObservableProperty]\n    private bool isInstalled;\n\n    [ObservableProperty]\n    public required partial ObservableCollection<string> InstallLocations { get; set; }\n\n    [ObservableProperty]\n    public partial string DownloadTooltip { get; set; } = string.Empty;\n\n    [ObservableProperty]\n    public partial bool CanImport { get; set; } = true;\n\n    public CivitFileViewModel(\n        IModelIndexService modelIndexService,\n        ISettingsManager settingsManager,\n        CivitFile civitFile,\n        IServiceManager<ViewModelBase> vmFactory,\n        Func<CivitFileViewModel, string?, Task>? downloadAction\n    )\n    {\n        this.modelIndexService = modelIndexService;\n        this.settingsManager = settingsManager;\n        this.vmFactory = vmFactory;\n        this.downloadAction = downloadAction;\n        CivitFile = civitFile;\n        IsInstalled =\n            CivitFile is { Type: CivitFileType.Model, Hashes.BLAKE3: not null }\n            && modelIndexService.ModelIndexBlake3Hashes.Contains(CivitFile.Hashes.BLAKE3);\n        EventManager.Instance.ModelIndexChanged += ModelIndexChanged;\n\n        try\n        {\n            if (settingsManager.IsLibraryDirSet)\n            {\n                var fileSizeBytes = CivitFile.SizeKb * 1024;\n                var freeSizeBytes =\n                    SystemInfo.GetDiskFreeSpaceBytes(settingsManager.ModelsDirectory) ?? long.MaxValue;\n                CanImport = fileSizeBytes < freeSizeBytes;\n                DownloadTooltip = CanImport\n                    ? \"Free space after download: \"\n                        + (\n                            freeSizeBytes < long.MaxValue\n                                ? Size.FormatBytes(Convert.ToUInt64(freeSizeBytes - fileSizeBytes))\n                                : \"Unknown\"\n                        )\n                    : $\"Not enough space on disk. Need {Size.FormatBytes(Convert.ToUInt64(fileSizeBytes))} but only have {Size.FormatBytes(Convert.ToUInt64(freeSizeBytes))}\";\n            }\n            else\n            {\n                DownloadTooltip = \"Please set the library directory in settings\";\n            }\n        }\n        catch (Exception e)\n        {\n            LogManager\n                .GetCurrentClassLogger()\n                .Error(e, \"Failed to check disk space for {FileName}\", civitFile.Name);\n            DownloadTooltip = \"Failed to check disk space\";\n        }\n    }\n\n    private void ModelIndexChanged(object? sender, EventArgs e)\n    {\n        // Dispatch to UI thread since the event may be raised from a background thread\n        Dispatcher.UIThread.Post(() =>\n        {\n            IsInstalled =\n                CivitFile is { Type: CivitFileType.Model, Hashes.BLAKE3: not null }\n                && modelIndexService.ModelIndexBlake3Hashes.Contains(CivitFile.Hashes.BLAKE3);\n        });\n    }\n\n    [RelayCommand(CanExecute = nameof(CanExecuteDownload))]\n    private async Task DownloadToDefaultAsync()\n    {\n        if (downloadAction != null)\n        {\n            await downloadAction(this, null);\n        }\n    }\n\n    [RelayCommand(CanExecute = nameof(CanExecuteDownload))]\n    private async Task DownloadToSelectedLocationAsync(string locationKey)\n    {\n        if (downloadAction != null)\n        {\n            await downloadAction(this, locationKey);\n        }\n    }\n\n    [RelayCommand]\n    private async Task Delete()\n    {\n        var hash = CivitFile.Hashes.BLAKE3;\n        if (string.IsNullOrWhiteSpace(hash))\n        {\n            return;\n        }\n\n        var matchingModels = (await modelIndexService.FindByHashAsync(hash)).ToList();\n\n        if (matchingModels.Count == 0)\n        {\n            await modelIndexService.RefreshIndex();\n            matchingModels = (await modelIndexService.FindByHashAsync(hash)).ToList();\n\n            if (matchingModels.Count == 0)\n            {\n                return;\n            }\n        }\n\n        var confirmDeleteVm = vmFactory.Get<ConfirmDeleteDialogViewModel>();\n        var paths = new List<string>();\n\n        foreach (var localModel in matchingModels)\n        {\n            var checkpointPath = new FilePath(localModel.GetFullPath(settingsManager.ModelsDirectory));\n            if (File.Exists(checkpointPath))\n            {\n                paths.Add(checkpointPath);\n            }\n\n            var previewPath = localModel.GetPreviewImageFullPath(settingsManager.ModelsDirectory);\n            if (File.Exists(previewPath))\n            {\n                paths.Add(previewPath);\n            }\n\n            var cmInfoPath = checkpointPath.ToString().Replace(checkpointPath.Extension, \".cm-info.json\");\n            if (File.Exists(cmInfoPath))\n            {\n                paths.Add(cmInfoPath);\n            }\n        }\n\n        confirmDeleteVm.PathsToDelete = paths;\n\n        if (await confirmDeleteVm.GetDialog().ShowAsync() != ContentDialogResult.Primary)\n        {\n            return;\n        }\n\n        try\n        {\n            await confirmDeleteVm.ExecuteCurrentDeleteOperationAsync(failFast: true);\n        }\n        catch (Exception e)\n        {\n            Logger.Error(e, \"Failed to delete model files for {ModelName}\", CivitFile.Name);\n            await modelIndexService.RefreshIndex();\n            return;\n        }\n        finally\n        {\n            IsInstalled = false;\n        }\n\n        await modelIndexService.RemoveModelsAsync(matchingModels);\n    }\n\n    private bool CanExecuteDownload()\n    {\n        return downloadAction != null;\n    }\n\n    protected override void Dispose(bool disposing)\n    {\n        if (disposing)\n        {\n            EventManager.Instance.ModelIndexChanged -= ModelIndexChanged;\n        }\n        base.Dispose(disposing);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/CivitImageViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing StabilityMatrix.Avalonia.Models;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\npublic partial class CivitImageViewModel : ObservableObject\n{\n    [ObservableProperty]\n    public partial int ImageId { get; set; }\n\n    [ObservableProperty]\n    public partial ImageSource ImageSource { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/ConfirmBulkDownloadDialogViewModel.cs",
    "content": "﻿using System.Collections.ObjectModel;\nusing System.Reactive.Linq;\nusing Avalonia.Controls.Primitives;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing DynamicData;\nusing DynamicData.Binding;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(ConfirmBulkDownloadDialog))]\n[ManagedService]\n[RegisterTransient<ConfirmBulkDownloadDialogViewModel>]\npublic partial class ConfirmBulkDownloadDialogViewModel(\n    IModelIndexService modelIndexService,\n    ISettingsManager settingsManager,\n    IServiceManager<ViewModelBase> vmFactory\n) : ContentDialogViewModelBase\n{\n    public required CivitModel Model { get; set; }\n\n    [ObservableProperty]\n    public partial double TotalSizeKb { get; set; }\n\n    [ObservableProperty]\n    public partial CivitModelFpType FpTypePreference { get; set; } = CivitModelFpType.fp16;\n\n    [ObservableProperty]\n    public partial bool IncludeVae { get; set; }\n\n    [ObservableProperty]\n    public partial string DownloadFollowingFilesText { get; set; } = string.Empty;\n\n    [ObservableProperty]\n    public partial bool PreferPruned { get; set; } = true;\n\n    private readonly SourceCache<CivitFileDisplayViewModel, int> allFilesCache = new(displayVm =>\n        displayVm.FileViewModel.CivitFile.Id\n    );\n\n    public IObservableCollection<CivitFileDisplayViewModel> FilesToDownload { get; } =\n        new ObservableCollectionExtended<CivitFileDisplayViewModel>();\n\n    public ObservableCollection<CivitModelFpType> AvailableFpTypes => new(Enum.GetValues<CivitModelFpType>());\n\n    public override async Task OnLoadedAsync()\n    {\n        await base.OnLoadedAsync();\n\n        if (Model.ModelVersions == null || Model.ModelVersions.Count == 0)\n        {\n            FilesToDownload.Clear();\n            DownloadFollowingFilesText = \"No files available for download.\";\n            TotalSizeKb = 0;\n            allFilesCache.Clear(); // Clear cache if model is empty\n            return;\n        }\n\n        var allFilesFromModel = Model\n            .ModelVersions.SelectMany(v =>\n                v.Files?.Select(f => new CivitFileDisplayViewModel\n                {\n                    ModelVersion = v,\n                    FileViewModel = new CivitFileViewModel(\n                        modelIndexService,\n                        settingsManager,\n                        f,\n                        vmFactory,\n                        null\n                    )\n                    {\n                        InstallLocations = [],\n                    },\n                }) ?? []\n            )\n            .ToList();\n\n        allFilesCache.Edit(updater =>\n        {\n            updater.Clear();\n            updater.AddOrUpdate(allFilesFromModel);\n        });\n\n        var fpPreferenceObservable = this.WhenPropertyChanged(x => x.FpTypePreference)\n            .Select(_ =>\n                (Func<CivitFileDisplayViewModel, bool>)(\n                    displayVm => IsPreferredPrecision(displayVm.FileViewModel)\n                )\n            );\n\n        var includeVaeObservable = this.WhenPropertyChanged(x => x.IncludeVae)\n            .Select(include =>\n                (Func<CivitFileDisplayViewModel, bool>)(\n                    displayVm => include.Value || displayVm.FileViewModel.CivitFile.Type != CivitFileType.VAE\n                )\n            );\n\n        var preferPrunedFilter = this.WhenPropertyChanged(x => x.PreferPruned)\n            .Select(_ =>\n                (Func<CivitFileDisplayViewModel, bool>)(\n                    displayVm =>\n                    {\n                        var file = displayVm.FileViewModel.CivitFile;\n                        if (file.Metadata.Size is null)\n                            return true;\n\n                        if (\n                            PreferPruned\n                            && file.Metadata.Size.Equals(\"pruned\", StringComparison.OrdinalIgnoreCase)\n                        )\n                            return true;\n\n                        if (\n                            !PreferPruned\n                            && file.Metadata.Size.Equals(\"full\", StringComparison.OrdinalIgnoreCase)\n                        )\n                            return true;\n\n                        return false;\n                    }\n                )\n            );\n\n        var defaultFilter =\n            (Func<CivitFileDisplayViewModel, bool>)(\n                displayVm =>\n                {\n                    var fileVm = displayVm.FileViewModel;\n                    if (fileVm.IsInstalled)\n                        return false;\n\n                    return fileVm.CivitFile.Type\n                        is CivitFileType.Model\n                            or CivitFileType.VAE\n                            or CivitFileType.PrunedModel;\n                }\n            );\n\n        var filteredFilesObservable = allFilesCache\n            .Connect()\n            .Filter(defaultFilter)\n            .Filter(fpPreferenceObservable)\n            .Filter(includeVaeObservable)\n            .Filter(preferPrunedFilter);\n\n        AddDisposable(\n            filteredFilesObservable\n                .SortAndBind(\n                    FilesToDownload,\n                    SortExpressionComparer<CivitFileDisplayViewModel>.Ascending(s =>\n                        s.FileViewModel.CivitFile.DisplayName\n                    )\n                )\n                .ObserveOn(SynchronizationContext.Current!)\n                .Subscribe()\n        );\n\n        AddDisposable(\n            filteredFilesObservable\n                .ToCollection()\n                .ObserveOn(SynchronizationContext.Current!) // Or AvaloniaScheduler.Instance\n                .Subscribe(filteredFiles =>\n                {\n                    TotalSizeKb = filteredFiles.Sum(f => f.FileViewModel.CivitFile.SizeKb);\n                    DownloadFollowingFilesText =\n                        $\"You are about to download {filteredFiles.Count} files totaling {new FileSizeType(TotalSizeKb)}.\";\n                })\n        );\n\n        if (\n            FilesToDownload.All(x =>\n                x.FileViewModel.CivitFile.Metadata.Size?.Equals(\"full\", StringComparison.OrdinalIgnoreCase)\n                ?? false\n            ) && PreferPruned\n        )\n        {\n            PreferPruned = false;\n        }\n    }\n\n    public override BetterContentDialog GetDialog()\n    {\n        var dialog = base.GetDialog();\n\n        dialog.MinDialogWidth = 550;\n        dialog.MaxDialogHeight = 600;\n        dialog.IsFooterVisible = false;\n        dialog.CloseOnClickOutside = true;\n        dialog.ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled;\n\n        return dialog;\n    }\n\n    private bool IsPreferredPrecision(CivitFileViewModel file)\n    {\n        if (file.CivitFile.Metadata.Fp is null || string.IsNullOrWhiteSpace(file.CivitFile.Metadata.Fp))\n            return true;\n\n        var preference = FpTypePreference.GetStringValue();\n        var fpType = file.CivitFile.Metadata.Fp;\n        return preference == fpType;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/ConfirmDeleteDialogViewModel.cs",
    "content": "﻿using Avalonia.Controls.Primitives;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Native;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(ConfirmDeleteDialog))]\n[RegisterTransient<ConfirmDeleteDialogViewModel>]\n[ManagedService]\npublic partial class ConfirmDeleteDialogViewModel(ILogger<ConfirmDeleteDialogViewModel> logger)\n    : ContentDialogViewModelBase\n{\n    [ObservableProperty]\n    private string title = \"Confirm Delete\";\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ConfirmDeleteButtonText))]\n    [NotifyPropertyChangedFor(nameof(IsPermanentDelete))]\n    [NotifyPropertyChangedFor(nameof(DeleteFollowingFilesText))]\n    private bool isRecycleBinAvailable = NativeFileOperations.IsRecycleBinAvailable;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ConfirmDeleteButtonText))]\n    [NotifyPropertyChangedFor(nameof(IsPermanentDelete))]\n    [NotifyPropertyChangedFor(nameof(DeleteFollowingFilesText))]\n    private bool isRecycleBinOptOutChecked;\n\n    public bool IsPermanentDelete => !IsRecycleBinAvailable || IsRecycleBinOptOutChecked;\n\n    public string ConfirmDeleteButtonText =>\n        IsPermanentDelete ? Resources.Action_Delete : Resources.Action_MoveToTrash;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(DeleteFollowingFilesText))]\n    private IReadOnlyList<string> pathsToDelete = [];\n\n    public string DeleteFollowingFilesText =>\n        PathsToDelete.Count is var count and > 1\n            ? string.Format(Resources.TextTemplate_DeleteFollowingCountItems, count)\n            : Resources.Text_DeleteFollowingItems;\n\n    public bool ShowActionCannotBeUndoneNotice { get; set; } = true;\n\n    /// <inheritdoc />\n    public override BetterContentDialog GetDialog()\n    {\n        var dialog = base.GetDialog();\n\n        dialog.MinDialogWidth = 550;\n        dialog.MaxDialogHeight = 600;\n        dialog.IsFooterVisible = false;\n        dialog.CloseOnClickOutside = true;\n        dialog.ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled;\n\n        return dialog;\n    }\n\n    [RelayCommand(CanExecute = nameof(CanExecuteConfirmDelete))]\n    private void OnConfirmDeleteClick()\n    {\n        OnPrimaryButtonClick();\n    }\n\n    private bool CanExecuteConfirmDelete()\n    {\n        return !HasErrors && IsValid();\n    }\n\n    private bool IsValid()\n    {\n        return true;\n    }\n\n    public async Task ExecuteCurrentDeleteOperationAsync(bool ignoreErrors = false, bool failFast = false)\n    {\n        var paths = PathsToDelete;\n\n        var exceptions = new List<Exception>();\n\n        if (!IsPermanentDelete)\n        {\n            // Recycle bin\n            if (!NativeFileOperations.IsRecycleBinAvailable)\n            {\n                throw new NotSupportedException(\"Recycle bin is not available on this platform\");\n            }\n\n            try\n            {\n                await NativeFileOperations.RecycleBin.MoveFilesToRecycleBinAsync(paths);\n            }\n            catch (Exception e)\n            {\n                logger.LogWarning(e, \"Failed to move path to recycle bin\");\n\n                if (!ignoreErrors)\n                {\n                    exceptions.Add(e);\n\n                    if (failFast)\n                    {\n                        throw new AggregateException(exceptions);\n                    }\n                }\n            }\n        }\n        else\n        {\n            await Task.Run(() =>\n            {\n                foreach (var path in paths)\n                {\n                    try\n                    {\n                        if (Directory.Exists(path))\n                        {\n                            Directory.Delete(path, true);\n                        }\n                        else\n                        {\n                            File.Delete(path);\n                        }\n                    }\n                    catch (Exception e)\n                    {\n                        logger.LogWarning(e, \"Failed to delete path\");\n\n                        if (!ignoreErrors)\n                        {\n                            exceptions.Add(e);\n\n                            if (failFast)\n                            {\n                                throw new AggregateException(exceptions);\n                            }\n                        }\n                    }\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/ConfirmPackageDeleteDialogViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(ConfirmPackageDeleteDialog))]\n[ManagedService]\n[RegisterTransient<ConfirmPackageDeleteDialogViewModel>]\npublic partial class ConfirmPackageDeleteDialogViewModel : ContentDialogViewModelBase\n{\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsValid), nameof(ExpectedPackageName))]\n    public required partial InstalledPackage Package { get; set; }\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsValid))]\n    public partial string PackageName { get; set; } = string.Empty;\n\n    public string? ExpectedPackageName => Package.DisplayName;\n    public bool IsValid => ExpectedPackageName?.Equals(PackageName, StringComparison.Ordinal) ?? false;\n    public string DeleteWarningText\n    {\n        get\n        {\n            var items = new List<string>\n            {\n                $\"• The {ExpectedPackageName} application\",\n                $\"• {(Package.PackageName == \"ComfyUI\" ? \"Custom nodes\" : \"Extensions\")}\",\n            };\n\n            if (!Package.UseSharedOutputFolder)\n                items.Add(\"• Images/outputs\");\n\n            if (Package.PreferredSharedFolderMethod is SharedFolderMethod.None)\n                items.Add(\"• Models/checkpoints placed in the package's model folders\");\n\n            items.Add(\"• Any custom files in the package folder\");\n\n            return string.Join(Environment.NewLine, items);\n        }\n    }\n\n    [RelayCommand]\n    private async Task CopyExpectedPackageName()\n    {\n        await App.Clipboard?.SetTextAsync(ExpectedPackageName);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/DownloadResourceViewModel.cs",
    "content": "﻿using Avalonia.Controls;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(DownloadResourceDialog))]\n[ManagedService]\n[RegisterTransient<DownloadResourceViewModel>]\npublic partial class DownloadResourceViewModel(\n    IDownloadService downloadService,\n    ISettingsManager settingsManager,\n    ITrackedDownloadService trackedDownloadService\n) : ContentDialogViewModelBase\n{\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(FileNameWithHash))]\n    private string? fileName;\n\n    public string FileNameWithHash => $\"{FileName} [{Resource.HashSha256?.ToLowerInvariant()[..7]}]\";\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(FileSizeText))]\n    private long fileSize;\n\n    [ObservableProperty]\n    private RemoteResource resource;\n\n    public string? FileSizeText => FileSize == 0 ? null : Size.FormatBase10Bytes(FileSize);\n\n    public string? ShortHash => Resource.HashSha256?.ToLowerInvariant()[..7];\n\n    /// <inheritdoc />\n    public override async Task OnLoadedAsync()\n    {\n        await base.OnLoadedAsync();\n\n        // Get download size\n        if (!Design.IsDesignMode && Resource.Url is { } url)\n        {\n            FileSize = await downloadService.GetFileSizeAsync(url.ToString());\n        }\n    }\n\n    [RelayCommand]\n    private void OpenInfoUrl()\n    {\n        if (Resource.InfoUrl is { } url)\n        {\n            ProcessRunner.OpenUrl(url);\n        }\n    }\n\n    public TrackedDownload StartDownload()\n    {\n        var sharedFolderType =\n            Resource.ContextType as SharedFolderType?\n            ?? throw new InvalidOperationException(\"ContextType is not SharedFolderType\");\n\n        var modelsDir = new DirectoryPath(settingsManager.ModelsDirectory).JoinDir(\n            sharedFolderType.GetStringValue()\n        );\n\n        if (Resource.RelativeDirectory is not null)\n        {\n            modelsDir = modelsDir.JoinDir(Resource.RelativeDirectory);\n        }\n\n        var download = trackedDownloadService.NewDownload(\n            Resource.Url,\n            modelsDir.JoinFile(Resource.FileName)\n        );\n\n        // Set extraction properties\n        download.AutoExtractArchive = Resource.AutoExtractArchive;\n        download.ExtractRelativePath = Resource.ExtractRelativePath;\n\n        download.ContextAction = new ModelPostDownloadContextAction();\n        trackedDownloadService.TryStartDownload(download);\n\n        EventManager.Instance.OnToggleProgressFlyout();\n\n        return download;\n    }\n\n    public override BetterContentDialog GetDialog()\n    {\n        var dialog = base.GetDialog();\n\n        dialog.MinDialogWidth = 400;\n        dialog.Title = \"Download Model\";\n        dialog.Content = new DownloadResourceDialog { DataContext = this };\n        dialog.PrimaryButtonText = Resources.Action_Continue;\n        dialog.CloseButtonText = Resources.Action_Cancel;\n        dialog.DefaultButton = ContentDialogButton.Primary;\n\n        return dialog;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/EnvVarsViewModel.cs",
    "content": "﻿using System.Collections.ObjectModel;\nusing System.Diagnostics;\nusing Avalonia.Collections;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(EnvVarsDialog))]\n[ManagedService]\n[RegisterTransient<EnvVarsViewModel>]\npublic partial class EnvVarsViewModel : ContentDialogViewModelBase\n{\n    [ObservableProperty]\n    private string title = Resources.Label_EnvironmentVariables;\n\n    [ObservableProperty, NotifyPropertyChangedFor(nameof(EnvVarsView))]\n    private ObservableCollection<EnvVarKeyPair> envVars = new();\n\n    public DataGridCollectionView EnvVarsView => new(EnvVars);\n\n    [RelayCommand]\n    private void AddRow()\n    {\n        EnvVars.Add(new EnvVarKeyPair());\n    }\n\n    [RelayCommand]\n    private void RemoveSelectedRow(int selectedIndex)\n    {\n        try\n        {\n            EnvVars.RemoveAt(selectedIndex);\n        }\n        catch (ArgumentOutOfRangeException)\n        {\n            Debug.WriteLine($\"RemoveSelectedRow: Index {selectedIndex} out of range\");\n        }\n    }\n\n    public override BetterContentDialog GetDialog()\n    {\n        var dialog = base.GetDialog();\n\n        dialog.PrimaryButtonText = Resources.Action_Save;\n        dialog.IsPrimaryButtonEnabled = true;\n        dialog.CloseButtonText = Resources.Action_Cancel;\n\n        return dialog;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/ExceptionViewModel.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.IO;\nusing System.IO.Compression;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing NLog;\nusing Sentry;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(ExceptionDialog))]\n[ManagedService]\n[RegisterTransient<ExceptionViewModel>]\npublic partial class ExceptionViewModel : ViewModelBase\n{\n    public Exception? Exception { get; set; }\n\n    public SentryId? SentryId { get; set; }\n\n    public bool IsRecoverable { get; set; }\n\n    public string Description =>\n        IsRecoverable\n            ? Resources.Text_UnexpectedErrorRecoverable_Description\n            : Resources.Text_UnexpectedError_Description;\n\n    public string? Message => Exception?.Message;\n\n    public string? ExceptionType => Exception?.GetType().Name ?? \"\";\n\n    public bool IsContinueResult { get; set; }\n\n    public string? LogZipPath { get; set; }\n\n    public static async Task<string> CreateLogFolderZip()\n    {\n        var tcs = new TaskCompletionSource();\n        LogManager.Flush(\n            ex =>\n            {\n                if (ex is null)\n                {\n                    tcs.SetResult();\n                }\n                else\n                {\n                    tcs.SetException(ex);\n                }\n            },\n            TimeSpan.FromSeconds(15)\n        );\n        await tcs.Task;\n\n        using var suspend = LogManager.SuspendLogging();\n\n        var logDir = Compat.AppDataHome.JoinDir(\"Logs\");\n\n        // Copy logs to temp directory\n        using var tempDir = new TempDirectoryPath();\n        var tempLogDir = tempDir.JoinDir(\"Logs\");\n        tempLogDir.Create();\n        foreach (var logFile in logDir.EnumerateFiles(\"*.log\"))\n        {\n            // Need FileShare.ReadWrite since NLog keeps the file open\n            await logFile.CopyToAsync(\n                tempLogDir.JoinFile(logFile.Name),\n                FileShare.ReadWrite,\n                overwrite: true\n            );\n        }\n\n        // Find a unique name for the output archive\n        var archiveNameBase = $\"stabilitymatrix-log-{DateTime.Now:yyyy-MM-dd-HH-mm-ss}\";\n        var archiveName = archiveNameBase;\n        var archivePath = Compat.AppDataHome.JoinFile(archiveName + \".zip\");\n        var i = 1;\n        while (File.Exists(archivePath))\n        {\n            archiveName = $\"{archiveNameBase}-{i++}\";\n            archivePath = Compat.AppDataHome.JoinFile(archiveName + \".zip\");\n        }\n\n        // Create the archive\n        ZipFile.CreateFromDirectory(tempLogDir, archivePath, CompressionLevel.Optimal, false);\n\n        return archivePath;\n    }\n\n    [RelayCommand]\n    private async Task OpenLogZipInFileBrowser()\n    {\n        if (string.IsNullOrWhiteSpace(LogZipPath) || !File.Exists(LogZipPath))\n        {\n            LogZipPath = await CreateLogFolderZip();\n        }\n\n        await ProcessRunner.OpenFileBrowser(LogZipPath);\n    }\n\n    [Localizable(false)]\n    public string? FormatAsMarkdown()\n    {\n        var msgBuilder = new StringBuilder();\n        msgBuilder.AppendLine();\n\n        if (Exception is not null)\n        {\n            msgBuilder.AppendLine(\"## Exception\");\n            msgBuilder.AppendLine($\"```{ExceptionType}: {Message}```\");\n\n            if (Exception.InnerException is not null)\n            {\n                msgBuilder.AppendLine(\n                    $\"```{Exception.InnerException.GetType().Name}: {Exception.InnerException.Message}```\"\n                );\n            }\n        }\n        else\n        {\n            msgBuilder.AppendLine(\"## Exception\");\n            msgBuilder.AppendLine(\"```(None)```\");\n        }\n\n        if (SentryId is { } id)\n        {\n            msgBuilder.AppendLine(\"### Sentry ID\");\n            msgBuilder.AppendLine($\"[`{id.ToString()[..8]}`]({GetIssueUrl(id)})\");\n        }\n\n        if (Exception?.StackTrace is not null)\n        {\n            msgBuilder.AppendLine(\"### Stack Trace\");\n            msgBuilder.AppendLine($\"```{Exception.StackTrace}```\");\n        }\n\n        if (Exception?.InnerException is { StackTrace: not null } innerException)\n        {\n            msgBuilder.AppendLine($\"```{innerException.StackTrace}```\");\n        }\n\n        return msgBuilder.ToString();\n    }\n\n    [Localizable(false)]\n    private static string GetIssueUrl(SentryId sentryId)\n    {\n        return $\"https://stability-matrix.sentry.io/issues/?query=id%3A{sentryId.ToString()}&referrer=sm-app-ex&statsPeriod=90d\";\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/ImageViewerViewModel.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing Avalonia;\nusing Avalonia.Controls.Primitives;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.Core;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Api.CivitTRPC;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Services;\nusing Path = System.IO.Path;\nusing Size = StabilityMatrix.Core.Helper.Size;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(ImageViewerDialog))]\n[ManagedService]\n[RegisterTransient<ImageViewerViewModel>]\npublic partial class ImageViewerViewModel(\n    ILogger<ImageViewerViewModel> logger,\n    ISettingsManager settingsManager\n) : ContentDialogViewModelBase\n{\n    [ObservableProperty]\n    private ImageSource? imageSource;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(HasLocalGenerationParameters))]\n    private LocalImageFile? localImageFile;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(HasLocalGenerationParameters))]\n    public partial CivitImageGenerationDataResponse? CivitImageMetadata { get; set; }\n\n    [ObservableProperty]\n    private bool isFooterEnabled;\n\n    [ObservableProperty]\n    private string? fileNameText;\n\n    [ObservableProperty]\n    private string? fileSizeText;\n\n    [ObservableProperty]\n    private string? imageSizeText;\n\n    /// <summary>\n    /// Whether local generation parameters are available.\n    /// </summary>\n    public bool HasLocalGenerationParameters => LocalImageFile?.GenerationParameters is not null;\n\n    /// <summary>\n    /// Whether Civitai image metadata is available.\n    /// </summary>\n    public bool HasCivitImageMetadata => CivitImageMetadata is not null;\n\n    public event EventHandler<DirectionalNavigationEventArgs>? NavigationRequested;\n    public event EventHandler<int>? NavigateToModelRequested;\n\n    partial void OnLocalImageFileChanged(LocalImageFile? value)\n    {\n        if (value?.ImageSize is { IsEmpty: false } size)\n        {\n            ImageSizeText = $\"{size.Width} x {size.Height}\";\n        }\n    }\n\n    partial void OnImageSourceChanged(ImageSource? value)\n    {\n        if (value?.LocalFile is { Exists: true } localFile)\n        {\n            FileNameText = localFile.Name;\n            FileSizeText = Size.FormatBase10Bytes(localFile.GetSize(true));\n        }\n    }\n\n    partial void OnCivitImageMetadataChanged(CivitImageGenerationDataResponse? value)\n    {\n        if (value is null)\n            return;\n\n        ImageSizeText = value.Metadata?.Dimensions ?? string.Empty;\n    }\n\n    [RelayCommand]\n    private void OnNavigateNext()\n    {\n        NavigationRequested?.Invoke(this, DirectionalNavigationEventArgs.Down);\n    }\n\n    [RelayCommand]\n    private void OnNavigatePrevious()\n    {\n        NavigationRequested?.Invoke(this, DirectionalNavigationEventArgs.Up);\n    }\n\n    [RelayCommand]\n    private void OnNavigateToModel(int modelId)\n    {\n        NavigateToModelRequested?.Invoke(this, modelId);\n    }\n\n    [RelayCommand]\n    private async Task CopyImage(ImageSource? image)\n    {\n        if (image is null)\n            return;\n\n        if (image.LocalFile is { } imagePath)\n        {\n            await App.Clipboard.SetFileDataObjectAsync(imagePath);\n        }\n        else if (await image.GetBitmapAsync() is { } bitmap)\n        {\n            // Write to temp file\n            var tempFile = new FilePath(Path.GetTempFileName() + \".png\");\n\n            bitmap.Save(tempFile);\n\n            await App.Clipboard.SetFileDataObjectAsync(tempFile);\n        }\n        else\n        {\n            logger.LogWarning(\"Failed to copy image, no file path or bitmap: {Image}\", image);\n        }\n    }\n\n    [RelayCommand]\n    private async Task CopyImageAsBitmap(ImageSource? image)\n    {\n        if (image is null || !Compat.IsWindows)\n            return;\n\n        if (await image.GetBitmapAsync() is { } bitmap)\n        {\n            await WindowsClipboard.SetBitmapAsync(bitmap);\n        }\n        else\n        {\n            logger.LogWarning(\"Failed to copy image, no bitmap: {Image}\", image);\n        }\n    }\n\n    [RelayCommand]\n    private async Task CopyThingToClipboard(object? thing)\n    {\n        if (thing is null)\n            return;\n\n        await App.Clipboard.SetTextAsync(thing.ToString());\n    }\n\n    public override BetterContentDialog GetDialog()\n    {\n        var margins = new Thickness(64, 32);\n\n        var mainWindowSize = App.Services.GetService<MainWindow>()?.ClientSize;\n        var dialogSize = new global::Avalonia.Size(\n            Math.Floor((mainWindowSize?.Width * 0.6 ?? 1000) - margins.Horizontal()),\n            Math.Floor((mainWindowSize?.Height ?? 1000) - margins.Vertical())\n        );\n\n        var dialog = new BetterContentDialog\n        {\n            MaxDialogWidth = dialogSize.Width,\n            MaxDialogHeight = dialogSize.Height,\n            ContentMargin = margins,\n            FullSizeDesired = true,\n            IsFooterVisible = false,\n            CloseOnClickOutside = true,\n            ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled,\n            Content = new ImageViewerDialog\n            {\n                Width = dialogSize.Width,\n                Height = dialogSize.Height,\n                DataContext = this,\n            },\n        };\n\n        return dialog;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/InferenceConnectionHelpViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(InferenceConnectionHelpDialog))]\n[ManagedService]\n[RegisterTransient<InferenceConnectionHelpViewModel>]\npublic partial class InferenceConnectionHelpViewModel : ContentDialogViewModelBase\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly INavigationService<MainWindowViewModel> navigationService;\n    private readonly IPackageFactory packageFactory;\n    private readonly RunningPackageService runningPackageService;\n\n    [ObservableProperty]\n    private string title = \"Hello\";\n\n    [ObservableProperty]\n    private IReadOnlyList<InstalledPackage> installedPackages = Array.Empty<InstalledPackage>();\n\n    [ObservableProperty]\n    private InstalledPackage? selectedPackage;\n\n    [ObservableProperty]\n    private bool isFirstTimeWelcome;\n\n    /// <summary>\n    /// When the user has no Comfy packages, and we need to prompt to install\n    /// </summary>\n    [ObservableProperty]\n    private bool isInstallMode;\n\n    /// <summary>\n    /// When the user has Comfy packages, and we need to prompt to launch\n    /// </summary>\n    [ObservableProperty]\n    private bool isLaunchMode;\n\n    public InferenceConnectionHelpViewModel(\n        ISettingsManager settingsManager,\n        INavigationService<MainWindowViewModel> navigationService,\n        IPackageFactory packageFactory,\n        RunningPackageService runningPackageService\n    )\n    {\n        this.settingsManager = settingsManager;\n        this.navigationService = navigationService;\n        this.packageFactory = packageFactory;\n        this.runningPackageService = runningPackageService;\n\n        // Get comfy type installed packages\n        var comfyPackages = this.settingsManager.Settings.InstalledPackages.Where(\n            p => p.PackageName is \"ComfyUI\" or \"ComfyUI-Zluda\"\n        )\n            .ToImmutableArray();\n\n        InstalledPackages = comfyPackages;\n\n        // If no comfy packages, install mode, otherwise launch mode\n        if (comfyPackages.Length == 0)\n        {\n            IsInstallMode = true;\n        }\n        else\n        {\n            IsLaunchMode = true;\n\n            // Use active package if its comfy, otherwise use the first comfy type\n            if (\n                this.settingsManager.Settings.ActiveInstalledPackage is\n                { PackageName: \"ComfyUI\" or \"ComfyUI-Zluda\" } activePackage\n            )\n            {\n                SelectedPackage = activePackage;\n            }\n            else\n            {\n                SelectedPackage ??= comfyPackages[0];\n            }\n        }\n    }\n\n    /// <summary>\n    /// Navigate to the package install page\n    /// </summary>\n    [RelayCommand]\n    private void NavigateToInstall()\n    {\n        Dispatcher.UIThread.Post(() =>\n        {\n            navigationService.NavigateTo<PackageManagerViewModel>(\n                param: new PackageManagerNavigationOptions\n                {\n                    OpenInstallerDialog = true,\n                    InstallerSelectedPackage = packageFactory\n                        .GetAllAvailablePackages()\n                        .OfType<ComfyUI>()\n                        .First()\n                }\n            );\n        });\n    }\n\n    /// <summary>\n    /// Request launch of the selected package\n    /// </summary>\n    [RelayCommand]\n    private async Task LaunchSelectedPackage()\n    {\n        if (SelectedPackage is not null)\n        {\n            await runningPackageService.StartPackage(SelectedPackage);\n        }\n    }\n\n    /// <summary>\n    /// Create a better content dialog for this view model\n    /// </summary>\n    public BetterContentDialog CreateDialog()\n    {\n        var dialog = new BetterContentDialog\n        {\n            Content = new InferenceConnectionHelpDialog { DataContext = this },\n            PrimaryButtonCommand = IsInstallMode ? NavigateToInstallCommand : LaunchSelectedPackageCommand,\n            PrimaryButtonText = IsInstallMode ? Resources.Action_Install : Resources.Action_Launch,\n            CloseButtonText = Resources.Action_Close,\n            DefaultButton = ContentDialogButton.Primary\n        };\n\n        return dialog;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/LaunchOptionsViewModel.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.ComponentModel;\nusing System.Reactive.Linq;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing FuzzySharp;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(LaunchOptionsDialog))]\n[ManagedService]\n[RegisterTransient<LaunchOptionsViewModel>]\npublic partial class LaunchOptionsViewModel : ContentDialogViewModelBase\n{\n    private readonly ILogger<LaunchOptionsViewModel> logger;\n    private readonly LRUCache<string, ImmutableList<LaunchOptionCard>> cache = new(100);\n\n    [ObservableProperty]\n    private string title = \"Launch Options\";\n\n    [ObservableProperty]\n    private bool isSearchBoxEnabled = true;\n\n    [ObservableProperty]\n    private string searchText = string.Empty;\n\n    [ObservableProperty]\n    private IReadOnlyList<LaunchOptionCard>? filteredCards;\n\n    public IReadOnlyList<LaunchOptionCard>? Cards { get; set; }\n\n    /// <summary>\n    /// Return cards that match the search text\n    /// </summary>\n    private IReadOnlyList<LaunchOptionCard>? GetFilteredCards(string? text)\n    {\n        if (string.IsNullOrWhiteSpace(text) || text.Length < 2)\n        {\n            return Cards;\n        }\n        // Try cache\n        if (cache.Get(text, out var cachedCards))\n        {\n            return cachedCards!;\n        }\n\n        var searchCard = new LaunchOptionCard\n        {\n            Title = text.ToLowerInvariant(),\n            Type = LaunchOptionType.Bool,\n            Options = Array.Empty<LaunchOption>(),\n        };\n\n        var extracted = Process.ExtractTop(searchCard, Cards, c => c.Title.ToLowerInvariant());\n        var results = extracted.Where(r => r.Score > 40).Select(r => r.Value).ToImmutableList();\n        cache.Add(text, results);\n        return results;\n    }\n\n    public void UpdateFilterCards() => FilteredCards = GetFilteredCards(SearchText);\n\n    public LaunchOptionsViewModel(ILogger<LaunchOptionsViewModel> logger)\n    {\n        this.logger = logger;\n\n        Observable\n            .FromEventPattern<PropertyChangedEventArgs>(this, nameof(PropertyChanged))\n            .Where(x => x.EventArgs.PropertyName == nameof(SearchText))\n            .Throttle(TimeSpan.FromMilliseconds(50))\n            .Select(_ => SearchText)\n            .ObserveOn(SynchronizationContext.Current!)\n            .Subscribe(\n                text => FilteredCards = GetFilteredCards(text),\n                err => logger.LogError(err, \"Error while filtering launch options\")\n            );\n    }\n\n    public override void OnLoaded()\n    {\n        base.OnLoaded();\n        UpdateFilterCards();\n    }\n\n    /// <summary>\n    /// Export the current cards options to a list of strings\n    /// </summary>\n    public List<LaunchOption> AsLaunchArgs()\n    {\n        var launchArgs = new List<LaunchOption>();\n        if (Cards is null)\n            return launchArgs;\n\n        foreach (var card in Cards)\n        {\n            launchArgs.AddRange(card.Options);\n        }\n        return launchArgs;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/LykosLoginViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing System.Net;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing Refit;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Validators;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(LykosLoginDialog))]\n[RegisterTransient<LykosLoginViewModel>, ManagedService]\npublic partial class LykosLoginViewModel(\n    IAccountsService accountsService,\n    IServiceManager<ViewModelBase> vmFactory\n) : TaskDialogViewModelBase\n{\n    [ObservableProperty]\n    [NotifyCanExecuteChangedFor(nameof(ContinueButtonClickCommand))]\n    private bool isSignupMode;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo, NotifyCanExecuteChangedFor(nameof(ContinueButtonClickCommand))]\n    [EmailAddress(ErrorMessage = \"Email is not valid\")]\n    private string? email;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo, NotifyCanExecuteChangedFor(nameof(ContinueButtonClickCommand))]\n    [Required]\n    private string? username;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo, NotifyCanExecuteChangedFor(nameof(ContinueButtonClickCommand))]\n    [Required]\n    private string? password;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo, NotifyCanExecuteChangedFor(nameof(ContinueButtonClickCommand))]\n    [Required, RequiresMatch<string>(nameof(Password))]\n    private string? confirmPassword;\n\n    [ObservableProperty]\n    private AppException? loginError;\n\n    [ObservableProperty]\n    private AppException? signupError;\n\n    public string SignupFooterMarkdown { get; } =\n        \"\"\"\n            By signing up, you are creating a\n            [lykos.ai](https://lykos.ai) Account and agree to our\n            [Terms](https://lykos.ai/terms-and-conditions) and\n            [Privacy Policy](https://lykos.ai/privacy)\n            \"\"\";\n\n    private bool CanExecuteContinueButtonClick()\n    {\n        return !HasErrors && IsValid();\n    }\n\n    [RelayCommand(CanExecute = nameof(CanExecuteContinueButtonClick))]\n    private Task OnContinueButtonClick()\n    {\n        return IsSignupMode ? SignupAsync() : LoginAsync();\n    }\n\n    private async Task LoginAsync()\n    {\n        try\n        {\n            await accountsService.LykosLoginAsync(Email!, Password!);\n\n            CloseDialog(TaskDialogStandardResult.OK);\n        }\n        catch (OperationCanceledException)\n        {\n            LoginError = new AppException(\"Request timed out\", \"Please try again later\");\n        }\n        catch (ApiException e)\n        {\n            LoginError = e.StatusCode switch\n            {\n                HttpStatusCode.Unauthorized => new AppException(\n                    \"Incorrect email or password\",\n                    \"Please try again or reset your password\"\n                ),\n                _ => new AppException(\"Failed to login\", $\"{e.StatusCode} - {e.Message}\"),\n            };\n        }\n    }\n\n    private async Task SignupAsync()\n    {\n        try\n        {\n            await accountsService.LykosSignupAsync(Email!, Password!, Username!);\n\n            CloseDialog(TaskDialogStandardResult.OK);\n        }\n        catch (OperationCanceledException)\n        {\n            SignupError = new AppException(\"Request timed out\", \"Please try again later\");\n        }\n        catch (ApiException e)\n        {\n            SignupError = new AppException(\"Failed to signup\", $\"{e.StatusCode} - {e.Message}\");\n        }\n    }\n\n    [RelayCommand]\n    private async Task OnGoogleOAuthButtonClick()\n    {\n        var vm = vmFactory.Get<OAuthGoogleLoginViewModel>();\n\n        if (await vm.GetDialog().ShowAsync() is ContentDialogResult.Primary)\n        {\n            CloseDialog(TaskDialogStandardResult.OK);\n        }\n    }\n\n    /// <inheritdoc />\n    public override TaskDialog GetDialog()\n    {\n        var dialog = base.GetDialog();\n        dialog.Buttons = new List<TaskDialogButton>\n        {\n            GetCommandButton(Resources.Action_Continue, ContinueButtonClickCommand),\n            GetCloseButton(),\n        };\n        return dialog;\n    }\n\n    private bool IsValid()\n    {\n        if (IsSignupMode)\n        {\n            return !(\n                string.IsNullOrEmpty(Email)\n                || string.IsNullOrEmpty(Username)\n                || string.IsNullOrEmpty(Password)\n                || string.IsNullOrEmpty(ConfirmPassword)\n            );\n        }\n\n        return !(string.IsNullOrEmpty(Email) || string.IsNullOrEmpty(Password));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/MaskEditorViewModel.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Runtime.CompilerServices;\nusing System.Text.Json;\nusing System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\nusing Avalonia;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Platform.Storage;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing SkiaSharp;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Controls;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing ContentDialogButton = FluentAvalonia.UI.Controls.ContentDialogButton;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[RegisterTransient<MaskEditorViewModel>]\n[ManagedService]\n[View(typeof(MaskEditorDialog))]\npublic partial class MaskEditorViewModel(IServiceManager<ViewModelBase> vmFactory)\n    : LoadableViewModelBase,\n        IDisposable\n{\n    private static FilePickerFileType MaskImageFilePickerType { get; } =\n        new(\"Mask image or json\")\n        {\n            Patterns = new[] { \"*.png\", \"*.jpg\", \"*.jpeg\", \"*.webp\", \"*.json\" },\n            AppleUniformTypeIdentifiers = new[] { \"public.image\", \"public.json\" },\n            MimeTypes = new[] { \"image/*\", \"application/json\" }\n        };\n\n    [JsonIgnore]\n    private ImageSource? _cachedMaskRenderInverseAlphaImage;\n\n    [JsonIgnore]\n    private ImageSource? _cachedMaskRenderImage;\n\n    /// <summary>\n    /// When true, the mask will be applied to the image.\n    /// </summary>\n    [ObservableProperty]\n    private bool isMaskEnabled;\n\n    /// <summary>\n    /// When true, the alpha channel of the image will be used as the mask.\n    /// </summary>\n    [ObservableProperty]\n    private bool useImageAlphaAsMask;\n\n    [JsonInclude]\n    public PaintCanvasViewModel PaintCanvasViewModel { get; } = vmFactory.Get<PaintCanvasViewModel>();\n\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public ImageSource GetCachedOrNewMaskRenderInverseAlphaImage()\n    {\n        if (_cachedMaskRenderInverseAlphaImage is null)\n        {\n            using var skImage = PaintCanvasViewModel.RenderToWhiteChannelImage();\n\n            if (skImage is null)\n            {\n                throw new InvalidOperationException(\n                    \"RenderToWhiteChannelImage returned null, BackgroundImageSize likely not set\"\n                );\n            }\n\n            _cachedMaskRenderInverseAlphaImage = new ImageSource(skImage.ToAvaloniaBitmap());\n        }\n\n        return _cachedMaskRenderInverseAlphaImage;\n    }\n\n    public ImageSource? CachedOrNewMaskRenderImage\n    {\n        get\n        {\n            if (_cachedMaskRenderImage is null)\n            {\n                using var skImage = PaintCanvasViewModel.RenderToImage();\n\n                if (skImage is not null)\n                {\n                    _cachedMaskRenderImage = new ImageSource(skImage.ToAvaloniaBitmap());\n                }\n            }\n\n            return _cachedMaskRenderImage;\n        }\n    }\n\n    public void InvalidateCachedMaskRenderImage()\n    {\n        _cachedMaskRenderImage?.Dispose();\n        _cachedMaskRenderImage = null;\n\n        _cachedMaskRenderInverseAlphaImage?.Dispose();\n        _cachedMaskRenderInverseAlphaImage = null;\n\n        OnPropertyChanged(nameof(CachedOrNewMaskRenderImage));\n    }\n\n    public BetterContentDialog GetDialog()\n    {\n        Dispatcher.UIThread.VerifyAccess();\n\n        var dialog = new BetterContentDialog\n        {\n            Content = this,\n            ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled,\n            MaxDialogHeight = 2000,\n            MaxDialogWidth = 2000,\n            ContentMargin = new Thickness(16),\n            FullSizeDesired = true,\n            PrimaryButtonText = Resources.Action_Save,\n            CloseButtonText = Resources.Action_Cancel,\n            DefaultButton = ContentDialogButton.Primary\n        };\n\n        return dialog;\n    }\n\n    [RelayCommand]\n    private async Task DebugSelectFileLoadMask()\n    {\n        var files = await App.StorageProvider.OpenFilePickerAsync(\n            new FilePickerOpenOptions { Title = \"Select a mask\", FileTypeFilter = [MaskImageFilePickerType] }\n        );\n\n        if (files.Count == 0)\n        {\n            return;\n        }\n\n        var file = files[0];\n        await using var stream = await file.OpenReadAsync();\n\n        if (file.Name.EndsWith(\".json\"))\n        {\n            var json = await JsonSerializer.DeserializeAsync<JsonObject>(stream);\n            PaintCanvasViewModel.LoadStateFromJsonObject(json!);\n        }\n        else\n        {\n            var bitmap = SKBitmap.Decode(stream);\n            PaintCanvasViewModel.LoadCanvasFromBitmap(bitmap);\n        }\n    }\n\n    [RelayCommand]\n    private async Task DebugSelectFileSaveMask()\n    {\n        var file = await App.StorageProvider.SaveFilePickerAsync(\n            new FilePickerSaveOptions\n            {\n                Title = \"Save mask image\",\n                DefaultExtension = \".json\",\n                FileTypeChoices = [MaskImageFilePickerType],\n                SuggestedFileName = \"mask.json\",\n            }\n        );\n\n        if (file is null)\n        {\n            return;\n        }\n\n        await using var stream = await file.OpenWriteAsync();\n\n        if (file.Name.EndsWith(\".json\"))\n        {\n            var json = PaintCanvasViewModel.SaveStateToJsonObject();\n            await JsonSerializer.SerializeAsync(stream, json);\n        }\n        else\n        {\n            var image = PaintCanvasViewModel.RenderToImage();\n            await image!\n                .Encode(\n                    Path.GetExtension(file.Name.ToLowerInvariant()) switch\n                    {\n                        \".png\" => SKEncodedImageFormat.Png,\n                        \".jpg\" or \".jpeg\" => SKEncodedImageFormat.Jpeg,\n                        \".webp\" => SKEncodedImageFormat.Webp,\n                        _ => throw new NotSupportedException(\"Unsupported image format\")\n                    },\n                    100\n                )\n                .AsStream()\n                .CopyToAsync(stream);\n        }\n    }\n\n    public override void LoadStateFromJsonObject(JsonObject state)\n    {\n        base.LoadStateFromJsonObject(state);\n\n        InvalidateCachedMaskRenderImage();\n    }\n\n    /*\n\n    public void LoadStateFromJsonObject(JsonObject state)\n    {\n        var model = state.Deserialize<MaskEditorModel>()!;\n        IsMaskEnabled = model.IsMaskEnabled;\n        UseImageAlphaAsMask = model.UseImageAlphaAsMask;\n        \n        if (model.PaintCanvasViewModel is not null)\n        {\n            PaintCanvasViewModel.LoadStateFromJsonObject(model.PaintCanvasViewModel);\n        }\n    }\n\n    public JsonObject SaveStateToJsonObject()\n    {\n        var model = new MaskEditorModel\n        {\n            IsMaskEnabled = IsMaskEnabled,\n            UseImageAlphaAsMask = UseImageAlphaAsMask,\n            PaintCanvasViewModel = PaintCanvasViewModel.SaveStateToJsonObject()\n        };\n\n        return JsonSerializer.SerializeToNode(model)!.AsObject();\n    }\n\n    public record MaskEditorModel\n    {\n        public bool IsMaskEnabled { get; init; }\n        public bool UseImageAlphaAsMask { get; init; }\n        public JsonObject? PaintCanvasViewModel { get; init; }\n    }*/\n    public void Dispose()\n    {\n        _cachedMaskRenderInverseAlphaImage?.Dispose();\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelMetadataEditorDialogViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Avalonia.Controls;\nusing Avalonia.Input;\nusing Avalonia.Platform.Storage;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.CheckpointManager;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(ModelMetadataEditorDialog))]\n[ManagedService]\n[RegisterTransient<ModelMetadataEditorDialogViewModel>]\npublic partial class ModelMetadataEditorDialogViewModel(\n    ISettingsManager settingsManager,\n    ICivitBaseModelTypeService baseModelTypeService,\n    IServiceManager<ViewModelBase> vmFactory\n) : ContentDialogViewModelBase, IDropTarget\n{\n    [ObservableProperty]\n    private List<CheckpointFileViewModel> checkpointFiles = [];\n\n    [ObservableProperty]\n    private string modelName = string.Empty;\n\n    [ObservableProperty]\n    private string modelDescription = string.Empty;\n\n    [ObservableProperty]\n    private bool isNsfw;\n\n    [ObservableProperty]\n    private string tags = string.Empty;\n\n    [ObservableProperty]\n    private CivitModelType modelType = CivitModelType.Other;\n\n    [ObservableProperty]\n    private string versionName = string.Empty;\n\n    [ObservableProperty]\n    private string baseModelType = \"Other\";\n\n    [ObservableProperty]\n    private List<string> baseModelTypes = [];\n\n    [ObservableProperty]\n    private string trainedWords = string.Empty;\n\n    [ObservableProperty]\n    private string thumbnailFilePath = string.Empty;\n\n    [ObservableProperty]\n    public partial SamplerCardViewModel SamplerCardViewModel { get; set; } =\n        vmFactory.Get<SamplerCardViewModel>(samplerCard =>\n        {\n            samplerCard.IsDimensionsEnabled = true;\n            samplerCard.IsCfgScaleEnabled = true;\n            samplerCard.IsSamplerSelectionEnabled = true;\n            samplerCard.IsSchedulerSelectionEnabled = true;\n            samplerCard.DenoiseStrength = 1.0d;\n            samplerCard.EnableAddons = false;\n            samplerCard.IsDenoiseStrengthEnabled = false;\n        });\n\n    [ObservableProperty]\n    public partial bool IsInferenceDefaultsEnabled { get; set; }\n\n    [ObservableProperty]\n    public partial bool ShowInferenceDefaults { get; set; }\n\n    public bool IsEditingMultipleCheckpoints => CheckpointFiles.Count > 1;\n\n    [RelayCommand]\n    private async Task OpenFilePickerDialog()\n    {\n        var files = await App.StorageProvider.OpenFilePickerAsync(\n            new FilePickerOpenOptions\n            {\n                Title = \"Select an image\",\n                FileTypeFilter = [FilePickerFileTypes.ImageAll],\n            }\n        );\n\n        if (files.Count == 0)\n            return;\n\n        var sourceFile = new FilePath(files[0].TryGetLocalPath()!);\n\n        ThumbnailFilePath = sourceFile.FullPath;\n    }\n\n    public override async Task OnLoadedAsync()\n    {\n        if (!Design.IsDesignMode)\n            BaseModelTypes = await baseModelTypeService.GetBaseModelTypes(includeAllOption: false);\n\n        if (IsEditingMultipleCheckpoints)\n            return;\n\n        var firstCheckpoint = CheckpointFiles.FirstOrDefault();\n        if (firstCheckpoint == null)\n            return;\n\n        if (!firstCheckpoint.CheckpointFile.HasConnectedModel)\n        {\n            ModelName = firstCheckpoint.CheckpointFile.DisplayModelName;\n            ThumbnailFilePath = GetImagePath(firstCheckpoint.CheckpointFile);\n            BaseModelType = \"Other\";\n            ModelType = CivitModelType.Other;\n            return;\n        }\n\n        ShowInferenceDefaults = firstCheckpoint.ModelType == CivitModelType.Checkpoint;\n        BaseModelType = firstCheckpoint.CheckpointFile.ConnectedModelInfo.BaseModel ?? \"Other\";\n        ModelName = firstCheckpoint.CheckpointFile.ConnectedModelInfo.ModelName;\n        ModelDescription = firstCheckpoint.CheckpointFile.ConnectedModelInfo.ModelDescription;\n        IsNsfw = firstCheckpoint.CheckpointFile.ConnectedModelInfo.Nsfw;\n        Tags = string.Join(\", \", firstCheckpoint.CheckpointFile.ConnectedModelInfo.Tags);\n        ModelType = firstCheckpoint.CheckpointFile.ConnectedModelInfo.ModelType;\n        VersionName = firstCheckpoint.CheckpointFile.ConnectedModelInfo.VersionName;\n        TrainedWords =\n            firstCheckpoint.CheckpointFile.ConnectedModelInfo.TrainedWords == null\n                ? string.Empty\n                : string.Join(\", \", firstCheckpoint.CheckpointFile.ConnectedModelInfo.TrainedWords);\n        ThumbnailFilePath = GetImagePath(firstCheckpoint.CheckpointFile);\n        IsInferenceDefaultsEnabled = false;\n\n        if (firstCheckpoint.CheckpointFile.ConnectedModelInfo.InferenceDefaults is { } defaults)\n        {\n            IsInferenceDefaultsEnabled = true;\n            SamplerCardViewModel.Height = defaults.Height;\n            SamplerCardViewModel.Width = defaults.Width;\n            SamplerCardViewModel.CfgScale = defaults.CfgScale;\n            SamplerCardViewModel.Steps = defaults.Steps;\n            SamplerCardViewModel.SelectedSampler = defaults.Sampler;\n            SamplerCardViewModel.SelectedScheduler = defaults.Scheduler;\n        }\n    }\n\n    private string GetImagePath(LocalModelFile checkpointFile)\n    {\n        return checkpointFile.HasConnectedModel\n            ? checkpointFile.GetPreviewImageFullPath(settingsManager.ModelsDirectory)\n                ?? checkpointFile.ConnectedModelInfo?.ThumbnailImageUrl\n                ?? Assets.NoImage.ToString()\n            : checkpointFile.GetPreviewImageFullPath(settingsManager.ModelsDirectory)\n                ?? Assets.NoImage.ToString();\n    }\n\n    public void DragOver(object? sender, DragEventArgs e)\n    {\n        if (\n            e.Data.GetDataFormats().Contains(DataFormats.Files)\n            || e.Data.GetContext<LocalImageFile>() is not null\n        )\n        {\n            e.Handled = true;\n            return;\n        }\n\n        e.DragEffects = DragDropEffects.None;\n    }\n\n    public void Drop(object? sender, DragEventArgs e)\n    {\n        if (\n            e.Data.GetFiles() is not { } files\n            || files.Select(f => f.TryGetLocalPath()).FirstOrDefault() is not { } path\n        )\n        {\n            return;\n        }\n\n        e.Handled = true;\n        ThumbnailFilePath = path;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelVersionViewModel.cs",
    "content": "﻿using Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\npublic partial class ModelVersionViewModel : DisposableViewModelBase\n{\n    private readonly IModelIndexService modelIndexService;\n\n    [ObservableProperty]\n    public partial CivitModelVersion ModelVersion { get; set; }\n\n    [ObservableProperty]\n    public partial bool IsInstalled { get; set; }\n\n    public ModelVersionViewModel(IModelIndexService modelIndexService, CivitModelVersion modelVersion)\n    {\n        this.modelIndexService = modelIndexService;\n\n        ModelVersion = modelVersion;\n\n        IsInstalled =\n            ModelVersion.Files?.Any(file =>\n                file is { Type: CivitFileType.Model, Hashes.BLAKE3: not null }\n                && modelIndexService.ModelIndexBlake3Hashes.Contains(file.Hashes.BLAKE3)\n            ) ?? false;\n\n        EventManager.Instance.ModelIndexChanged += ModelIndexChanged;\n    }\n\n    public void RefreshInstallStatus()\n    {\n        IsInstalled =\n            ModelVersion.Files?.Any(file =>\n                file is { Type: CivitFileType.Model, Hashes.BLAKE3: not null }\n                && modelIndexService.ModelIndexBlake3Hashes.Contains(file.Hashes.BLAKE3)\n            ) ?? false;\n    }\n\n    private void ModelIndexChanged(object? sender, EventArgs e)\n    {\n        // Dispatch to UI thread since the event may be raised from a background thread\n        Dispatcher.UIThread.Post(RefreshInstallStatus);\n    }\n\n    protected override void Dispose(bool disposing)\n    {\n        if (disposing)\n        {\n            EventManager.Instance.ModelIndexChanged -= ModelIndexChanged;\n        }\n        base.Dispose(disposing);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models.PackageSteps;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[RegisterTransient<NewOneClickInstallViewModel>]\n[ManagedService]\npublic partial class NewOneClickInstallViewModel : ContentDialogViewModelBase\n{\n    private readonly IPackageFactory packageFactory;\n    private readonly ISettingsManager settingsManager;\n    private readonly IPrerequisiteHelper prerequisiteHelper;\n    private readonly ILogger<NewOneClickInstallViewModel> logger;\n    private readonly IPyRunner pyRunner;\n    private readonly INavigationService<MainWindowViewModel> navigationService;\n    private readonly INotificationService notificationService;\n\n    public SourceCache<BasePackage, string> AllPackagesCache { get; } = new(p => p.Author + p.Name);\n\n    public IObservableCollection<BasePackage> ShownPackages { get; set; } =\n        new ObservableCollectionExtended<BasePackage>();\n\n    [ObservableProperty]\n    private bool showIncompatiblePackages;\n\n    private bool isInferenceInstall;\n\n    public NewOneClickInstallViewModel(\n        IPackageFactory packageFactory,\n        ISettingsManager settingsManager,\n        IPrerequisiteHelper prerequisiteHelper,\n        ILogger<NewOneClickInstallViewModel> logger,\n        IPyRunner pyRunner,\n        INavigationService<MainWindowViewModel> navigationService,\n        INotificationService notificationService\n    )\n    {\n        this.packageFactory = packageFactory;\n        this.settingsManager = settingsManager;\n        this.prerequisiteHelper = prerequisiteHelper;\n        this.logger = logger;\n        this.pyRunner = pyRunner;\n        this.navigationService = navigationService;\n        this.notificationService = notificationService;\n\n        var incompatiblePredicate = this.WhenPropertyChanged(vm => vm.ShowIncompatiblePackages)\n            .Select(_ => new Func<BasePackage, bool>(p => p.IsCompatible || ShowIncompatiblePackages))\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        AllPackagesCache\n            .Connect()\n            .DeferUntilLoaded()\n            .Filter(incompatiblePredicate)\n            .Filter(p => p is { OfferInOneClickInstaller: true, PackageType: PackageType.SdInference })\n            .SortAndBind(\n                ShownPackages,\n                SortExpressionComparer<BasePackage>\n                    .Ascending(p => p.InstallerSortOrder)\n                    .ThenByAscending(p => p.DisplayName)\n            )\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(_ =>\n            {\n                if (ShownPackages.Count > 0)\n                    return;\n\n                ShowIncompatiblePackages = true;\n            });\n\n        AllPackagesCache.AddOrUpdate(packageFactory.GetAllAvailablePackages());\n    }\n\n    public override void OnLoaded()\n    {\n        base.OnLoaded();\n        if (ShownPackages.Count > 0)\n            return;\n\n        ShowIncompatiblePackages = true;\n    }\n\n    [RelayCommand]\n    private async Task InstallComfyForInference()\n    {\n        var comfyPackage = ShownPackages.FirstOrDefault(x => x is ComfyUI);\n        if (comfyPackage == null)\n            return;\n\n        isInferenceInstall = true;\n        await InstallPackage(comfyPackage);\n    }\n\n    [RelayCommand]\n    private async Task InstallPackage(BasePackage selectedPackage)\n    {\n        OnPrimaryButtonClick();\n\n        var installLocation = Path.Combine(settingsManager.LibraryDir, \"Packages\", selectedPackage.Name);\n        var recommendedPython = selectedPackage.RecommendedPythonVersion;\n\n        var steps = new List<IPackageStep>\n        {\n            new SetPackageInstallingStep(settingsManager, selectedPackage.Name),\n            new SetupPrerequisitesStep(prerequisiteHelper, selectedPackage, recommendedPython),\n        };\n\n        // get latest version & download & install\n        if (Directory.Exists(installLocation))\n        {\n            var installPath = new DirectoryPath(installLocation);\n            await installPath.DeleteVerboseAsync(logger);\n        }\n\n        var downloadVersion = await selectedPackage.GetLatestVersion();\n        var installedVersion = new InstalledPackageVersion { IsPrerelease = false };\n\n        if (selectedPackage.ShouldIgnoreReleases)\n        {\n            installedVersion.InstalledBranch = downloadVersion.BranchName;\n            installedVersion.InstalledCommitSha = downloadVersion.CommitHash;\n        }\n        else\n        {\n            installedVersion.InstalledReleaseVersion = downloadVersion.VersionTag;\n        }\n\n        var torchVersion = selectedPackage.GetRecommendedTorchVersion();\n        var recommendedSharedFolderMethod = selectedPackage.RecommendedSharedFolderMethod;\n\n        var installedPackage = new InstalledPackage\n        {\n            DisplayName = selectedPackage.DisplayName,\n            LibraryPath = Path.Combine(\"Packages\", selectedPackage.Name),\n            Id = Guid.NewGuid(),\n            PackageName = selectedPackage.Name,\n            Version = installedVersion,\n            LaunchCommand = selectedPackage.LaunchCommand,\n            LastUpdateCheck = DateTimeOffset.Now,\n            PreferredTorchIndex = torchVersion,\n            PreferredSharedFolderMethod = recommendedSharedFolderMethod,\n            UseSharedOutputFolder = selectedPackage.SharedOutputFolders is { Count: > 0 },\n            PythonVersion = recommendedPython.StringValue,\n        };\n\n        var downloadStep = new DownloadPackageVersionStep(\n            selectedPackage,\n            installLocation,\n            new DownloadPackageOptions { VersionOptions = downloadVersion }\n        );\n        steps.Add(downloadStep);\n\n        var unpackSiteCustomizeStep = new UnpackSiteCustomizeStep(Path.Combine(installLocation, \"venv\"));\n        steps.Add(unpackSiteCustomizeStep);\n\n        var installStep = new InstallPackageStep(\n            selectedPackage,\n            installLocation,\n            installedPackage,\n            new InstallPackageOptions\n            {\n                SharedFolderMethod = recommendedSharedFolderMethod,\n                VersionOptions = downloadVersion,\n                PythonOptions = { TorchIndex = torchVersion, PythonVersion = recommendedPython },\n            }\n        );\n        steps.Add(installStep);\n\n        var setupModelFoldersStep = new SetupModelFoldersStep(\n            selectedPackage,\n            recommendedSharedFolderMethod,\n            installLocation\n        );\n        steps.Add(setupModelFoldersStep);\n\n        var setupOutputSharingStep = new SetupOutputSharingStep(selectedPackage, installLocation);\n        steps.Add(setupOutputSharingStep);\n\n        var addInstalledPackageStep = new AddInstalledPackageStep(settingsManager, installedPackage);\n        steps.Add(addInstalledPackageStep);\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = false,\n            HideCloseButton = false,\n            ModificationCompleteMessage = $\"{selectedPackage.DisplayName} installed successfully\",\n        };\n\n        runner\n            .ExecuteSteps(steps)\n            .ContinueWith(_ =>\n            {\n                notificationService.OnPackageInstallCompleted(runner);\n\n                EventManager.Instance.OnOneClickInstallFinished(false);\n\n                if (!isInferenceInstall)\n                    return;\n\n                Dispatcher.UIThread.Post(() =>\n                {\n                    navigationService.NavigateTo<InferenceViewModel>();\n                });\n            })\n            .SafeFireAndForget();\n\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthConnectViewModel.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing System.Web;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing MessagePipe;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(OAuthConnectDialog))]\n[RegisterTransient<OAuthConnectViewModel>, ManagedService]\npublic partial class OAuthConnectViewModel : ContentDialogViewModelBase\n{\n    private readonly ILogger<OAuthConnectViewModel> logger;\n    private readonly IDistributedSubscriber<string, Uri> uriHandlerSubscriber;\n\n    private IAsyncDisposable? uriHandlerSubscription;\n\n    [ObservableProperty]\n    private string? title = \"Connect OAuth\";\n\n    [ObservableProperty]\n    private string? url;\n\n    [ObservableProperty]\n    private string? description =\n        \"Please login and click 'Allow' in the opened browser window to connect with StabilityMatrix.\\n\\n\"\n        + \"Once you have done so, close this prompt to complete the connection.\";\n\n    [ObservableProperty]\n    private string? footer = \"Once you have done so, close this prompt to complete the connection.\";\n\n    public OAuthConnectViewModel(\n        ILogger<OAuthConnectViewModel> logger,\n        IDistributedSubscriber<string, Uri> uriHandlerSubscriber\n    )\n    {\n        this.logger = logger;\n        this.uriHandlerSubscriber = uriHandlerSubscriber;\n    }\n\n    /// <inheritdoc />\n    public override async Task OnLoadedAsync()\n    {\n        await base.OnLoadedAsync();\n\n        uriHandlerSubscription = await uriHandlerSubscriber.SubscribeAsync(\n            UriHandler.IpcKeySend,\n            receivedUri =>\n            {\n                logger.LogDebug(\"UriHandler Received URI: {Uri}\", receivedUri.ToString());\n\n                // Ignore if path not matching\n                if (\n                    !receivedUri.PathAndQuery.StartsWith(\n                        \"/oauth/patreon/callback\",\n                        StringComparison.OrdinalIgnoreCase\n                    )\n                )\n                {\n                    return;\n                }\n\n                var queryCollection = HttpUtility.ParseQueryString(receivedUri.Query);\n                var status = queryCollection.Get(\"status\");\n                var error = queryCollection.Get(\"error\");\n\n                if (status == \"success\")\n                {\n                    logger.LogInformation(\"OAuth connection successful\");\n                    OnPrimaryButtonClick();\n                }\n                else if (status == \"failure\")\n                {\n                    logger.LogError(\"OAuth connection failed ({Status}): {Error}\", status, error);\n\n                    Dispatcher\n                        .UIThread.InvokeAsync(async () =>\n                        {\n                            var dialog = DialogHelper.CreateMarkdownDialog(\n                                $\"- {error}\",\n                                Resources.Label_ConnectAccountFailed\n                            );\n                            await dialog.ShowAsync();\n                            OnCloseButtonClick();\n                        })\n                        .SafeFireAndForget();\n                }\n                else\n                {\n                    logger.LogError(\"OAuth connection unknown status ({Status}): {Error}\", status, error);\n\n                    Dispatcher\n                        .UIThread.InvokeAsync(async () =>\n                        {\n                            var dialog = DialogHelper.CreateMarkdownDialog(\n                                $\"- {error}\",\n                                Resources.Label_ConnectAccountFailed\n                            );\n                            await dialog.ShowAsync();\n                            OnCloseButtonClick();\n                        })\n                        .SafeFireAndForget();\n                }\n            }\n        );\n    }\n\n    /// <inheritdoc />\n    public override async Task OnUnloadedAsync()\n    {\n        if (uriHandlerSubscription is not null)\n        {\n            await uriHandlerSubscription.DisposeAsync();\n            uriHandlerSubscription = null;\n        }\n    }\n\n    public override BetterContentDialog GetDialog()\n    {\n        return new BetterContentDialog\n        {\n            Title = Title,\n            Content = this,\n            CloseButtonText = Resources.Action_Close\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthDeviceAuthViewModel.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Avalonia.Controls;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing OpenIddict.Client;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n/// <summary>\n/// ViewModel for OAuth Device Authentication\n/// </summary>\n[View(typeof(OAuthDeviceAuthDialog))]\n[ManagedService]\n[RegisterTransient<OAuthDeviceAuthViewModel>]\npublic partial class OAuthDeviceAuthViewModel(\n    ILogger<OAuthDeviceAuthViewModel> logger,\n    OpenIddictClientService openIdClient\n) : TaskDialogViewModelBase\n{\n    private CancellationTokenSource authenticationCts = new();\n\n    public OpenIddictClientModels.DeviceChallengeRequest? ChallengeRequest { get; set; }\n\n    public OpenIddictClientModels.DeviceChallengeResult? ChallengeResult { get; private set; }\n\n    public OpenIddictClientModels.DeviceAuthenticationRequest? AuthenticationRequest { get; private set; }\n\n    public OpenIddictClientModels.DeviceAuthenticationResult? AuthenticationResult { get; private set; }\n\n    public virtual string? ServiceName => ChallengeRequest?.ProviderName;\n\n    [ObservableProperty]\n    private string? description = Resources.Text_OAuthDeviceAuthDescription;\n\n    [ObservableProperty]\n    private Uri? verificationUri;\n\n    [ObservableProperty]\n    private string? userCode;\n\n    [ObservableProperty]\n    private bool isLoading;\n\n    public override TaskDialog GetDialog()\n    {\n        var dialog = base.GetDialog();\n        dialog.Title = string.Format(Resources.TextTemplate_OAuthLoginTitle, ServiceName);\n        dialog.Header = dialog.Title;\n        dialog.Buttons =\n        [\n            GetCommandButton(Resources.Action_CopyAndOpen, CopyCodeAndOpenUrlCommand),\n            GetCloseButton(Resources.Action_Cancel)\n        ];\n        return dialog;\n    }\n\n    [RelayCommand]\n    private async Task CopyCodeAndOpenUrl()\n    {\n        if (VerificationUri is null)\n            return;\n\n        try\n        {\n            await App.Clipboard.SetTextAsync(UserCode);\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Failed to copy user code to clipboard\");\n        }\n\n        ProcessRunner.OpenUrl(VerificationUri);\n        IsLoading = true;\n    }\n\n    /*/// <summary>\n    /// Prompt to authenticate with the service using a dialog\n    /// </summary>\n    public async Task<OpenIddictClientModels.DeviceAuthenticationResult?> AuthenticateWithDialogAsync()\n    {\n        using var cts = new CancellationTokenSource();\n\n        var dialogTask = ShowDialogAsync();\n\n        await TryAuthenticateAsync(ChallengeRequest!, cts.Token);\n\n        var result = await dialogTask;\n        await cts.CancelAsync();\n\n        return result switch\n        {\n            TaskDialogStandardResult.OK => AuthenticationResult,\n            _ => null\n        };\n    }*/\n\n    public override async Task OnLoadedAsync()\n    {\n        await base.OnLoadedAsync();\n\n        if (!Design.IsDesignMode && ChallengeRequest is not null && ChallengeResult is null)\n        {\n            await TryAuthenticateAsync();\n        }\n    }\n\n    protected override void OnDialogClosing(object? sender, TaskDialogClosingEventArgs e)\n    {\n        base.OnDialogClosing(sender, e);\n\n        authenticationCts.Cancel();\n    }\n\n    public async Task ChallengeAsync()\n    {\n        if (ChallengeRequest is null)\n        {\n            throw new InvalidOperationException(\n                \"ChallengeRequest must be set before calling StartChallengeAsync\"\n            );\n        }\n\n        ChallengeResult = await openIdClient.ChallengeUsingDeviceAsync(ChallengeRequest);\n\n        UserCode = ChallengeResult.DeviceCode;\n        VerificationUri = ChallengeResult.VerificationUri;\n    }\n\n    public async Task TryAuthenticateAsync()\n    {\n        if (ChallengeRequest is null)\n        {\n            throw new InvalidOperationException(\"ChallengeRequest must be set\");\n        }\n\n        try\n        {\n            // Get challenge result\n            ChallengeResult = await openIdClient.ChallengeUsingDeviceAsync(ChallengeRequest);\n\n            UserCode = ChallengeResult.UserCode;\n            VerificationUri = ChallengeResult.VerificationUri;\n\n            // Wait for user to complete auth\n            var result = await openIdClient.AuthenticateWithDeviceAsync(\n                new OpenIddictClientModels.DeviceAuthenticationRequest\n                {\n                    DeviceCode = ChallengeResult.DeviceCode,\n                    Interval = ChallengeResult.Interval,\n                    Timeout = ChallengeResult.ExpiresIn,\n                    CancellationToken = authenticationCts.Token\n                }\n            );\n\n            logger.LogInformation(\"Device authentication completed\");\n            AuthenticationResult = result;\n            CloseDialog(TaskDialogStandardResult.OK);\n        }\n        catch (OperationCanceledException e)\n        {\n            logger.LogInformation(e, \"Device authentication was cancelled\");\n            AuthenticationResult = null;\n            CloseDialog(TaskDialogStandardResult.Close);\n        }\n        catch (Exception e)\n        {\n            logger.LogWarning(e, \"Device authentication error\");\n            AuthenticationResult = null;\n            await CloseDialogWithErrorResultAsync(e.Message);\n        }\n        finally\n        {\n            IsLoading = false;\n        }\n    }\n\n    private async Task CloseDialogWithErrorResultAsync(string message)\n    {\n        var dialog = DialogHelper.CreateMarkdownDialog(message, Resources.Label_ConnectAccountFailed);\n\n        await dialog.ShowAsync();\n\n        CloseDialog(TaskDialogStandardResult.Close);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthGoogleLoginViewModel.cs",
    "content": "﻿using System;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Web;\nusing Avalonia.Controls;\nusing DeviceId.Encoders;\nusing Injectio.Attributes;\nusing MessagePipe;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Options;\nusing NSec.Cryptography;\nusing Refit;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Api.Lykos;\nusing StabilityMatrix.Core.Models.Configs;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[RegisterTransient<OAuthGoogleLoginViewModel>]\n[ManagedService]\n[View(typeof(OAuthLoginDialog))]\npublic class OAuthGoogleLoginViewModel(\n    ILogger<OAuthConnectViewModel> baseLogger,\n    IDistributedSubscriber<string, Uri> uriHandlerSubscriber,\n    ILogger<OAuthGoogleLoginViewModel> logger,\n    ILykosAuthApiV1 lykosAuthApi,\n    IAccountsService accountsService,\n    IOptions<ApiOptions> apiOptions\n) : OAuthLoginViewModel(baseLogger, uriHandlerSubscriber)\n{\n    private string? challenge;\n    private string? verifier;\n    private string? state;\n\n    public override Uri? IconUri { get; set; } =\n        new(\"avares://StabilityMatrix.Avalonia/Assets/brands-google-oauth-icon.svg\");\n\n    // ReSharper disable once LocalizableElement\n    public override string ServiceName { get; set; } = \"Google\";\n\n    // ReSharper disable once LocalizableElement\n    public override string CallbackUriPath { get; set; } = \"/oauth/google/callback\";\n\n    protected override async Task OnCallbackUriMatchedAsync(Uri uri)\n    {\n        IsLoading = true;\n\n        try\n        {\n            // Bring the app to the front\n            (App.TopLevel as Window)?.Activate();\n\n            if (string.IsNullOrEmpty(verifier))\n            {\n                // ReSharper disable once LocalizableElement\n                throw new InvalidOperationException(\"Verifier is not set\");\n            }\n\n            var response = GoogleOAuthResponse.ParseFromQueryString(uri.Query);\n\n            if (!string.IsNullOrEmpty(response.Error))\n            {\n                logger.LogWarning(\"Response has error: {Error}\", response.Error);\n                OnLoginFailed([(\"Error\", response.Error)]);\n                return;\n            }\n\n            if (string.IsNullOrEmpty(response.Code) || string.IsNullOrEmpty(response.State))\n            {\n                logger.LogWarning(\"Response missing code or state: {Uri}\", uri.RedactQueryValues());\n                OnLoginFailed([(\"Invalid Response\", \"code and state are required\")]);\n                return;\n            }\n\n            if (response.State != state)\n            {\n                logger.LogWarning(\"Response state mismatch: {Uri}\", uri.RedactQueryValues());\n                OnLoginFailed([(\"Invalid Response\", \"state mismatch\")]);\n                return;\n            }\n\n            await accountsService.LykosLoginViaGoogleOAuthAsync(response.Code, response.State, verifier);\n\n            // Success\n            OnPrimaryButtonClick();\n        }\n        catch (ApiException e)\n        {\n            logger.LogError(e, \"Api error while handling callback uri\");\n\n            OnLoginFailed([(e.StatusCode.ToString(), e.Content)]);\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Failed to handle callback uri\");\n\n            OnLoginFailed([(e.GetType().Name, e.Message)]);\n        }\n        finally\n        {\n            IsLoading = false;\n        }\n    }\n\n    public override async Task OnLoadedAsync()\n    {\n        await base.OnLoadedAsync();\n\n        IsLoading = true;\n\n        try\n        {\n            await GenerateUrlAsync();\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Failed to generate url\");\n\n            OnLoginFailed([(e.GetType().Name, e.Message)]);\n\n            return;\n        }\n        finally\n        {\n            IsLoading = false;\n        }\n\n        // Open in browser\n        ProcessRunner.OpenUrl(Url!);\n    }\n\n    private async Task GenerateUrlAsync()\n    {\n        (challenge, verifier) = GeneratePkceSha256ChallengePair();\n\n        var redirectUri = apiOptions.Value.LykosAuthApiBaseUrl.Append(\"/api/open/sm/oauth/google/callback\");\n\n        logger.LogDebug(\"Requesting Google OAuth URL...\");\n\n        var link = await lykosAuthApi.GetOAuthGoogleLoginOrSignupLink(\n            redirectUri.ToString(),\n            codeChallenge: challenge,\n            codeChallengeMethod: \"S256\"\n        );\n\n        var queryCollection = HttpUtility.ParseQueryString(link.Query);\n        // ReSharper disable once LocalizableElement\n        state = queryCollection.Get(\"state\");\n\n        Url = link.ToString();\n\n        logger.LogInformation(\"Generated Google OAuth URL: {Url}\", Url);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/OAuthLoginViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Linq;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing MessagePipe;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n/// <summary>\n/// Like <see cref=\"OAuthConnectViewModel\"/>, but for handling full code responses from OAuth providers,\n/// instead of being able to just close and refresh state.\n/// </summary>\n[RegisterTransient<OAuthLoginViewModel>]\n[ManagedService]\n[View(typeof(OAuthLoginDialog))]\n[Localizable(false)]\npublic partial class OAuthLoginViewModel(\n    ILogger<OAuthConnectViewModel> logger,\n    IDistributedSubscriber<string, Uri> uriHandlerSubscriber\n) : ContentDialogViewModelBase, IAsyncDisposable\n{\n    private IAsyncDisposable? uriHandlerSubscription;\n\n    /// <summary>\n    /// Name of the service to connect to\n    /// </summary>\n    public virtual string ServiceName { get; set; } = \"\";\n\n    /// <summary>\n    /// Url to open in the browser\n    /// </summary>\n    [ObservableProperty]\n    private string? url;\n\n    public override string Title => string.Format(TitleTemplate, ServiceName).Trim();\n\n    public virtual string TitleTemplate => Resources.TextTemplate_OAuthLoginTitle;\n\n    public virtual string? Description { get; set; } = Resources.Text_OAuthLoginDescription;\n\n    public virtual string? AppLinkInstructions { get; set; } = Resources.Text_AllowBrowserOpenAppLink;\n\n    // ReSharper disable once LocalizableElement\n    public virtual string CallbackUriPath { get; set; } = \"/oauth/default/callback\";\n\n    public virtual Uri? IconUri { get; set; }\n\n    [ObservableProperty]\n    private bool isLoading = true;\n\n    /// <inheritdoc />\n    public override async Task OnLoadedAsync()\n    {\n        await base.OnLoadedAsync();\n\n        uriHandlerSubscription = await uriHandlerSubscriber.SubscribeAsync(\n            UriHandler.IpcKeySend,\n            OnCallbackUriReceived\n        );\n    }\n\n    /// <inheritdoc />\n    public override async Task OnUnloadedAsync()\n    {\n        if (uriHandlerSubscription is not null)\n        {\n            await uriHandlerSubscription.DisposeAsync();\n            uriHandlerSubscription = null;\n        }\n    }\n\n    protected virtual void OnLoginFailed(IEnumerable<(string Message, string? Detail)> errors)\n    {\n        // ReSharper disable twice LocalizableElement\n        var content = string.Join(\n            \"\\n\",\n            errors.Select(e => e.Detail is null ? $\"- **{e.Message}**\" : $\"- **{e.Message}**: {e.Detail}\")\n        );\n\n        Dispatcher.UIThread.Post(() =>\n        {\n            var dialog = DialogHelper.CreateMarkdownDialog(content, Resources.Label_ConnectAccountFailed);\n\n            dialog.ShowAsync().ContinueWith(_ => OnCloseButtonClick()).SafeFireAndForget();\n        });\n    }\n\n    protected virtual Task OnCallbackUriMatchedAsync(Uri uri) => Task.CompletedTask;\n\n    private void OnCallbackUriReceived(Uri uri)\n    {\n        // Ignore if path not matching\n        if (uri.AbsolutePath != CallbackUriPath)\n        {\n            logger.LogDebug(\"Received Callback URI: {Uri}\", uri.RedactQueryValues());\n\n            return;\n        }\n\n        logger.LogInformation(\"Matched Callback URI: {Uri}\", uri.RedactQueryValues());\n\n        OnCallbackUriMatchedAsync(uri).SafeFireAndForget();\n    }\n\n    /// <inheritdoc />\n    public override BetterContentDialog GetDialog()\n    {\n        var dialog = base.GetDialog();\n\n        dialog.CloseButtonText = Resources.Action_Cancel;\n\n        return dialog;\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (uriHandlerSubscription is not null)\n        {\n            await uriHandlerSubscription.DisposeAsync();\n            uriHandlerSubscription = null;\n        }\n\n        GC.SuppressFinalize(this);\n    }\n\n    protected static (string Challenge, string Verifier) GeneratePkceSha256ChallengePair()\n    {\n        var verifier = RandomNumberGenerator.GetHexString(128, true);\n\n        var hash = SHA256.HashData(Encoding.ASCII.GetBytes(verifier));\n\n        // Convert to base64url\n        var base64UrlHash = Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_');\n\n        return (base64UrlHash, verifier);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[ManagedService]\n[RegisterTransient<OneClickInstallViewModel>]\npublic partial class OneClickInstallViewModel : ContentDialogViewModelBase\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly IPackageFactory packageFactory;\n    private readonly IPrerequisiteHelper prerequisiteHelper;\n    private readonly ILogger<OneClickInstallViewModel> logger;\n    private readonly IPyRunner pyRunner;\n    private readonly INavigationService<MainWindowViewModel> navigationService;\n    private const string DefaultPackageName = \"stable-diffusion-webui\";\n\n    [ObservableProperty]\n    private string headerText;\n\n    [ObservableProperty]\n    private string subHeaderText;\n\n    [ObservableProperty]\n    private string subSubHeaderText = string.Empty;\n\n    [ObservableProperty]\n    private bool showInstallButton;\n\n    [ObservableProperty]\n    private bool isIndeterminate;\n\n    [ObservableProperty]\n    private bool showIncompatiblePackages;\n\n    [ObservableProperty]\n    private ObservableCollection<BasePackage> allPackages;\n\n    [ObservableProperty]\n    private BasePackage selectedPackage;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsProgressBarVisible))]\n    private int oneClickInstallProgress;\n\n    private bool isInferenceInstall;\n\n    public bool IsProgressBarVisible => OneClickInstallProgress > 0 || IsIndeterminate;\n\n    public OneClickInstallViewModel(\n        ISettingsManager settingsManager,\n        IPackageFactory packageFactory,\n        IPrerequisiteHelper prerequisiteHelper,\n        ILogger<OneClickInstallViewModel> logger,\n        IPyRunner pyRunner,\n        INavigationService<MainWindowViewModel> navigationService\n    )\n    {\n        this.settingsManager = settingsManager;\n        this.packageFactory = packageFactory;\n        this.prerequisiteHelper = prerequisiteHelper;\n        this.logger = logger;\n        this.pyRunner = pyRunner;\n        this.navigationService = navigationService;\n\n        HeaderText = Resources.Text_WelcomeToStabilityMatrix;\n        SubHeaderText = Resources.Text_OneClickInstaller_SubHeader;\n        ShowInstallButton = true;\n\n        var filteredPackages = this\n            .packageFactory.GetAllAvailablePackages()\n            .Where(p => p is { OfferInOneClickInstaller: true, IsCompatible: true })\n            .ToList();\n\n        AllPackages = new ObservableCollection<BasePackage>(\n            filteredPackages.Any() ? filteredPackages : this.packageFactory.GetAllAvailablePackages()\n        );\n        SelectedPackage = AllPackages[0];\n    }\n\n    [RelayCommand]\n    private async Task Install()\n    {\n        ShowInstallButton = false;\n        await DoInstall();\n        ShowInstallButton = true;\n    }\n\n    [RelayCommand]\n    private Task ToggleAdvancedMode()\n    {\n        EventManager.Instance.OnOneClickInstallFinished(true);\n        return Task.CompletedTask;\n    }\n\n    [RelayCommand]\n    private async Task InstallComfyForInference()\n    {\n        var comfyPackage = AllPackages.FirstOrDefault(x => x is ComfyUI);\n        if (comfyPackage != null)\n        {\n            SelectedPackage = comfyPackage;\n            isInferenceInstall = true;\n            await InstallCommand.ExecuteAsync(null);\n        }\n    }\n\n    private async Task DoInstall()\n    {\n        var steps = new List<IPackageStep>\n        {\n            new SetPackageInstallingStep(settingsManager, SelectedPackage.Name),\n            new SetupPrerequisitesStep(\n                prerequisiteHelper,\n                SelectedPackage,\n                PyInstallationManager.Python_3_10_17\n            ),\n        };\n\n        // get latest version & download & install\n        var installLocation = Path.Combine(settingsManager.LibraryDir, \"Packages\", SelectedPackage.Name);\n        if (Directory.Exists(installLocation))\n        {\n            var installPath = new DirectoryPath(installLocation);\n            await installPath.DeleteVerboseAsync(logger);\n        }\n\n        var downloadVersion = await SelectedPackage.GetLatestVersion();\n        var installedVersion = new InstalledPackageVersion { IsPrerelease = false };\n\n        if (SelectedPackage.ShouldIgnoreReleases)\n        {\n            installedVersion.InstalledBranch = downloadVersion.BranchName;\n            installedVersion.InstalledCommitSha = downloadVersion.CommitHash;\n        }\n        else\n        {\n            installedVersion.InstalledReleaseVersion = downloadVersion.VersionTag;\n        }\n\n        var torchVersion = SelectedPackage.GetRecommendedTorchVersion();\n        var recommendedSharedFolderMethod = SelectedPackage.RecommendedSharedFolderMethod;\n        var recommendedPython = SelectedPackage.RecommendedPythonVersion;\n\n        var downloadStep = new DownloadPackageVersionStep(\n            SelectedPackage,\n            installLocation,\n            new DownloadPackageOptions() { VersionOptions = downloadVersion }\n        );\n        steps.Add(downloadStep);\n\n        var installedPackage = new InstalledPackage\n        {\n            DisplayName = SelectedPackage.DisplayName,\n            LibraryPath = Path.Combine(\"Packages\", SelectedPackage.Name),\n            Id = Guid.NewGuid(),\n            PackageName = SelectedPackage.Name,\n            Version = installedVersion,\n            LaunchCommand = SelectedPackage.LaunchCommand,\n            LastUpdateCheck = DateTimeOffset.Now,\n            PreferredTorchIndex = torchVersion,\n            PreferredSharedFolderMethod = recommendedSharedFolderMethod,\n            PythonVersion = recommendedPython.StringValue,\n        };\n\n        var installStep = new InstallPackageStep(\n            SelectedPackage,\n            installLocation,\n            installedPackage,\n            new InstallPackageOptions\n            {\n                SharedFolderMethod = recommendedSharedFolderMethod,\n                VersionOptions = downloadVersion,\n                PythonOptions = { TorchIndex = torchVersion, PythonVersion = recommendedPython },\n            }\n        );\n        steps.Add(installStep);\n\n        var setupModelFoldersStep = new SetupModelFoldersStep(\n            SelectedPackage,\n            recommendedSharedFolderMethod,\n            installLocation\n        );\n        steps.Add(setupModelFoldersStep);\n\n        var addInstalledPackageStep = new AddInstalledPackageStep(settingsManager, installedPackage);\n        steps.Add(addInstalledPackageStep);\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            HideCloseButton = true,\n            ModificationCompleteMessage = $\"{SelectedPackage.DisplayName} installed successfully\",\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n        await runner.ExecuteSteps(steps);\n\n        EventManager.Instance.OnInstalledPackagesChanged();\n        HeaderText = $\"{SelectedPackage.DisplayName} installed successfully\";\n        for (var i = 3; i > 0; i--)\n        {\n            SubHeaderText = $\"{Resources.Text_ProceedingToLaunchPage} ({i}s)\";\n            await Task.Delay(1000);\n        }\n\n        // should close dialog\n        EventManager.Instance.OnOneClickInstallFinished(false);\n        if (isInferenceInstall)\n        {\n            navigationService.NavigateTo<InferenceViewModel>();\n        }\n    }\n\n    partial void OnShowIncompatiblePackagesChanged(bool value)\n    {\n        var filteredPackages = packageFactory\n            .GetAllAvailablePackages()\n            .Where(p => p.OfferInOneClickInstaller && (ShowIncompatiblePackages || p.IsCompatible))\n            .ToList();\n\n        AllPackages = new ObservableCollection<BasePackage>(\n            filteredPackages.Any() ? filteredPackages : packageFactory.GetAllAvailablePackages()\n        );\n        SelectedPackage = AllPackages[0];\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenArtWorkflowViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.ComponentModel;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.OpenArt;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(OpenArtWorkflowDialog))]\n[ManagedService]\n[RegisterTransient<OpenArtWorkflowViewModel>]\npublic partial class OpenArtWorkflowViewModel(\n    ISettingsManager settingsManager,\n    IPackageFactory packageFactory\n) : ContentDialogViewModelBase\n{\n    public required OpenArtSearchResult Workflow { get; init; }\n\n    [ObservableProperty]\n    private ObservableCollection<OpenArtCustomNode> customNodes = [];\n\n    [ObservableProperty]\n    private string prunedDescription = string.Empty;\n\n    [ObservableProperty]\n    private bool installRequiredNodes = true;\n\n    [ObservableProperty]\n    private InstalledPackage? selectedPackage;\n\n    public PackagePair? SelectedPackagePair =>\n        SelectedPackage is { } package ? packageFactory.GetPackagePair(package) : null;\n\n    public List<InstalledPackage> AvailablePackages =>\n        settingsManager\n            .Settings.InstalledPackages.Where(package => package.PackageName is \"ComfyUI\" or \"ComfyUI-Zluda\")\n            .ToList();\n\n    public List<PackageExtension> MissingNodes { get; } = [];\n\n    public override async Task OnLoadedAsync()\n    {\n        if (Design.IsDesignMode)\n            return;\n\n        if (settingsManager.Settings.PreferredWorkflowPackage is { } preferredPackage)\n        {\n            SelectedPackage = preferredPackage;\n        }\n        else\n        {\n            SelectedPackage = AvailablePackages.FirstOrDefault();\n        }\n\n        if (SelectedPackage == null)\n        {\n            InstallRequiredNodes = false;\n        }\n\n        CustomNodes = new ObservableCollection<OpenArtCustomNode>(\n            await ParseNodes(Workflow.NodesIndex.ToList())\n        );\n        PrunedDescription = Utilities.RemoveHtml(Workflow.Description);\n    }\n\n    partial void OnSelectedPackageChanged(InstalledPackage? oldValue, InstalledPackage? newValue)\n    {\n        if (oldValue is null)\n            return;\n\n        settingsManager.Transaction(settings =>\n        {\n            settings.PreferredWorkflowPackage = newValue;\n        });\n\n        OnLoadedAsync().SafeFireAndForget();\n    }\n\n    [Localizable(false)]\n    private async Task<List<OpenArtCustomNode>> ParseNodes(List<string> nodes)\n    {\n        var indexOfFirstDot = nodes.IndexOf(\".\");\n        if (indexOfFirstDot != -1)\n        {\n            nodes = nodes[(indexOfFirstDot + 1)..];\n        }\n\n        var installedNodesNames = new HashSet<string>();\n        var nameToManifestNodes = new Dictionary<string, PackageExtension>();\n        var addedMissingNodes = new HashSet<string>();\n\n        var packagePair = SelectedPackagePair;\n\n        if (packagePair?.BasePackage.ExtensionManager is { } extensionManager)\n        {\n            var installedNodes = (\n                await extensionManager.GetInstalledExtensionsLiteAsync(packagePair.InstalledPackage)\n            ).ToList();\n\n            var manifestExtensionsMap = await extensionManager.GetManifestExtensionsMapAsync(\n                extensionManager.GetManifests(packagePair.InstalledPackage)\n            );\n\n            // Add manifestExtensions definition to installedNodes if matching git repository url\n            installedNodes = installedNodes\n                .Select(installedNode =>\n                {\n                    if (\n                        installedNode.GitRepositoryUrl is not null\n                        && manifestExtensionsMap.TryGetValue(\n                            installedNode.GitRepositoryUrl,\n                            out var manifestExtension\n                        )\n                    )\n                    {\n                        installedNode = installedNode with { Definition = manifestExtension };\n                    }\n\n                    return installedNode;\n                })\n                .ToList();\n\n            // There may be duplicate titles, deduplicate by using the first one\n            nameToManifestNodes = manifestExtensionsMap\n                .GroupBy(x => x.Value.Title)\n                .ToDictionary(x => x.Key, x => x.First().Value);\n\n            installedNodesNames = installedNodes.Select(x => x.Title).ToHashSet();\n        }\n\n        var sections = new List<OpenArtCustomNode>();\n        OpenArtCustomNode? currentSection = null;\n\n        foreach (var node in nodes)\n        {\n            if (node is \".\" or \",\")\n            {\n                currentSection = null; // End of the current section\n                continue;\n            }\n\n            if (currentSection == null)\n            {\n                currentSection = new OpenArtCustomNode\n                {\n                    Title = node,\n                    IsInstalled = installedNodesNames.Contains(node),\n                };\n\n                // Add missing nodes to the list (deduplicate by title)\n                if (\n                    !currentSection.IsInstalled\n                    && addedMissingNodes.Add(node)\n                    && nameToManifestNodes.TryGetValue(node, out var manifestNode)\n                )\n                {\n                    MissingNodes.Add(manifestNode);\n                }\n\n                sections.Add(currentSection);\n            }\n            else\n            {\n                currentSection.Children.Add(node);\n            }\n        }\n\n        if (sections.FirstOrDefault(x => x.Title == \"ComfyUI\") != null)\n        {\n            sections = sections.Where(x => x.Title != \"ComfyUI\").ToList();\n        }\n\n        return sections;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/OpenModelDbModelDetailsViewModel.cs",
    "content": "﻿using System.Collections.ObjectModel;\nusing System.ComponentModel.DataAnnotations;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Platform.Storage;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Api.OpenModelsDb;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(OpenModelDbModelDetailsDialog))]\n[ManagedService]\n[RegisterTransient<OpenModelDbModelDetailsViewModel>]\npublic partial class OpenModelDbModelDetailsViewModel(\n    OpenModelDbManager openModelDbManager,\n    IModelIndexService modelIndexService,\n    IModelImportService modelImportService,\n    INotificationService notificationService,\n    ISettingsManager settingsManager,\n    IServiceManager<ViewModelBase> dialogFactory\n) : ContentDialogViewModelBase\n{\n    public class ModelResourceViewModel(IModelIndexService modelIndexService)\n    {\n        public required OpenModelDbResource Resource { get; init; }\n\n        public string DisplayName => $\"{Resource.Platform} (.{Resource.Type} file)\";\n\n        public bool IsInstalled =>\n            modelIndexService.FindBySha256Async(Resource.Sha256).GetAwaiter().GetResult().Any();\n    }\n\n    [Required]\n    public OpenModelDbKeyedModel? Model { get; set; }\n\n    public IEnumerable<Uri> ImageUris =>\n        Model?.Images?.SelectImageAbsoluteUris().Any() ?? false\n            ? Model?.Images?.SelectImageAbsoluteUris() ?? [Assets.NoImage]\n            : [Assets.NoImage];\n\n    public IEnumerable<ModelResourceViewModel> Resources =>\n        Model\n            ?.Resources\n            ?.Select(resource => new ModelResourceViewModel(modelIndexService) { Resource = resource }) ?? [];\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(CanImport))]\n    private ModelResourceViewModel? selectedResource;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsCustomSelected))]\n    [NotifyPropertyChangedFor(nameof(ShowEmptyPathWarning))]\n    private string selectedInstallLocation = string.Empty;\n\n    [ObservableProperty]\n    private ObservableCollection<string> availableInstallLocations = [];\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ShowEmptyPathWarning))]\n    private string customInstallLocation = string.Empty;\n\n    public bool IsCustomSelected => SelectedInstallLocation == \"Custom...\";\n    public bool ShowEmptyPathWarning => IsCustomSelected && string.IsNullOrWhiteSpace(CustomInstallLocation);\n    public bool CanImport => SelectedResource is { IsInstalled: false };\n\n    public override void OnLoaded()\n    {\n        if (Design.IsDesignMode || !settingsManager.IsLibraryDirSet)\n            return;\n\n        LoadInstallLocations();\n    }\n\n    [RelayCommand(CanExecute = nameof(CanImport))]\n    private async Task ImportAsync(ModelResourceViewModel? resourceVm)\n    {\n        if (resourceVm?.Resource is null || Model is null)\n            return;\n\n        if (\n            Model.GetSharedFolderType() is not { } sharedFolderType\n            || sharedFolderType is SharedFolderType.Unknown\n        )\n        {\n            notificationService.ShowPersistent(\n                \"Failed to import model\",\n                $\"Model Architecture '{Model.Architecture}' not supported\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        var downloadFolder = new DirectoryPath(\n            settingsManager.ModelsDirectory,\n            sharedFolderType.GetStringValue()\n        ).ToString();\n\n        var useCustomLocation =\n            !string.IsNullOrWhiteSpace(CustomInstallLocation) && Directory.Exists(CustomInstallLocation);\n\n        if (useCustomLocation)\n        {\n            var customFolder = new DirectoryPath(CustomInstallLocation);\n            customFolder.Create();\n\n            downloadFolder = customFolder.ToString();\n        }\n\n        if (!string.IsNullOrWhiteSpace(SelectedInstallLocation) && SelectedInstallLocation != \"Custom...\")\n        {\n            var selectedFolder = SelectedInstallLocation\n                .Replace(\"Models\", string.Empty)\n                .Replace(Path.DirectorySeparatorChar.ToString(), string.Empty);\n            downloadFolder = new DirectoryPath(settingsManager.ModelsDirectory, selectedFolder);\n        }\n\n        await modelImportService.DoOpenModelDbImport(\n            Model,\n            resourceVm.Resource,\n            downloadFolder,\n            download => download.ContextAction = new ModelPostDownloadContextAction()\n        );\n\n        OnPrimaryButtonClick();\n    }\n\n    [RelayCommand]\n    private async Task DeleteModel(ModelResourceViewModel? resourceVm)\n    {\n        if (SelectedResource == null)\n            return;\n\n        var fileToDelete = SelectedResource;\n\n        var hash = fileToDelete.Resource.Sha256;\n        if (string.IsNullOrWhiteSpace(hash))\n        {\n            notificationService.Show(\n                \"Error deleting file\",\n                \"Could not delete model, hash is missing.\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        var matchingModels = (await modelIndexService.FindBySha256Async(hash)).ToList();\n\n        if (matchingModels.Count == 0)\n        {\n            await modelIndexService.RefreshIndex();\n            matchingModels = (await modelIndexService.FindBySha256Async(hash)).ToList();\n\n            if (matchingModels.Count == 0)\n            {\n                notificationService.Show(\n                    \"Error deleting file\",\n                    \"Could not delete model, model not found in index.\",\n                    NotificationType.Error\n                );\n                return;\n            }\n        }\n\n        var confirmDeleteVm = dialogFactory.Get<ConfirmDeleteDialogViewModel>();\n        var pathsToDelete = new List<string>();\n        foreach (var localModel in matchingModels)\n        {\n            var checkpointPath = new FilePath(localModel.GetFullPath(settingsManager.ModelsDirectory));\n            var previewPath = localModel.GetPreviewImageFullPath(settingsManager.ModelsDirectory);\n            var cmInfoPath = checkpointPath.ToString().Replace(checkpointPath.Extension, \".cm-info.json\");\n\n            pathsToDelete.Add(checkpointPath);\n            pathsToDelete.Add(previewPath);\n            pathsToDelete.Add(cmInfoPath);\n        }\n\n        confirmDeleteVm.PathsToDelete = pathsToDelete;\n\n        if (await confirmDeleteVm.GetDialog().ShowAsync() != ContentDialogResult.Primary)\n            return;\n\n        try\n        {\n            await confirmDeleteVm.ExecuteCurrentDeleteOperationAsync(failFast: true);\n        }\n        catch (Exception e)\n        {\n            notificationService.ShowPersistent(\"Error deleting folder\", e.Message, NotificationType.Error);\n            return;\n        }\n    }\n\n    partial void OnSelectedInstallLocationChanged(string? value)\n    {\n        if (value?.Equals(\"Custom...\", StringComparison.OrdinalIgnoreCase) is true)\n        {\n            Dispatcher.UIThread.InvokeAsync(SelectCustomFolder);\n        }\n        else\n        {\n            CustomInstallLocation = string.Empty;\n        }\n    }\n\n    public override BetterContentDialog GetDialog()\n    {\n        var dialog = base.GetDialog();\n        dialog.IsFooterVisible = false;\n        dialog.CloseOnClickOutside = true;\n        dialog.FullSizeDesired = true;\n        dialog.ContentMargin = new Thickness(8);\n        return dialog;\n    }\n\n    public async Task SelectCustomFolder()\n    {\n        var files = await App.StorageProvider.OpenFolderPickerAsync(\n            new FolderPickerOpenOptions\n            {\n                Title = \"Select Download Folder\",\n                AllowMultiple = false,\n                SuggestedStartLocation = await App.StorageProvider.TryGetFolderFromPathAsync(\n                    Path.Combine(\n                        settingsManager.ModelsDirectory,\n                        Model.GetSharedFolderType().GetStringValue()\n                    )\n                )\n            }\n        );\n\n        if (files.FirstOrDefault()?.TryGetLocalPath() is { } path)\n        {\n            CustomInstallLocation = path;\n        }\n    }\n\n    private void LoadInstallLocations()\n    {\n        var installLocations = new ObservableCollection<string>();\n\n        var rootModelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory);\n\n        var downloadDirectory = new DirectoryPath(\n            rootModelsDirectory,\n            Model.GetSharedFolderType().GetStringValue()\n        );\n\n        if (!downloadDirectory.ToString().EndsWith(\"Unknown\"))\n        {\n            installLocations.Add(downloadDirectory.ToString().Replace(rootModelsDirectory, \"Models\"));\n            foreach (\n                var directory in downloadDirectory.EnumerateDirectories(\n                    \"*\",\n                    EnumerationOptionConstants.AllDirectories\n                )\n            )\n            {\n                installLocations.Add(directory.ToString().Replace(rootModelsDirectory, \"Models\"));\n            }\n        }\n\n        installLocations.Add(\"Custom...\");\n\n        AvailableInstallLocations = installLocations;\n        SelectedInstallLocation = installLocations.FirstOrDefault();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Collections.ObjectModel;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(PackageImportDialog))]\n[ManagedService]\n[RegisterTransient<PackageImportViewModel>]\npublic partial class PackageImportViewModel(\n    IPackageFactory packageFactory,\n    ISettingsManager settingsManager,\n    IPyInstallationManager pyInstallationManager\n) : ContentDialogViewModelBase\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private bool venvDetected = false;\n\n    [ObservableProperty]\n    private DirectoryPath? packagePath;\n\n    [ObservableProperty]\n    private BasePackage? selectedBasePackage;\n\n    public IReadOnlyList<BasePackage> AvailablePackages => [.. packageFactory.GetAllAvailablePackages()];\n\n    [ObservableProperty]\n    private PackageVersion? selectedVersion;\n\n    [ObservableProperty]\n    private ObservableCollection<GitCommit>? availableCommits;\n\n    [ObservableProperty]\n    private ObservableCollection<PackageVersion>? availableVersions;\n\n    [ObservableProperty]\n    private GitCommit? selectedCommit;\n\n    [ObservableProperty]\n    private bool canSelectBasePackage = true;\n\n    [ObservableProperty]\n    private string? customCommitSha;\n\n    [ObservableProperty]\n    private bool showCustomCommitSha;\n\n    [ObservableProperty]\n    public partial bool ShowPythonVersionSelection { get; set; } = true;\n\n    // Version types (release or commit)\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ReleaseLabelText), nameof(IsReleaseMode), nameof(SelectedVersion))]\n    private PackageVersionType selectedVersionType = PackageVersionType.Commit;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsReleaseModeAvailable))]\n    private PackageVersionType availableVersionTypes =\n        PackageVersionType.GithubRelease | PackageVersionType.Commit;\n\n    [ObservableProperty]\n    public partial ObservableCollection<UvPythonInfo> AvailablePythonVersions { get; set; } = [];\n\n    [ObservableProperty]\n    public partial UvPythonInfo? SelectedPythonVersion { get; set; }\n\n    public string ReleaseLabelText => IsReleaseMode ? \"Version\" : \"Branch\";\n    public bool IsReleaseMode\n    {\n        get => SelectedVersionType == PackageVersionType.GithubRelease;\n        set => SelectedVersionType = value ? PackageVersionType.GithubRelease : PackageVersionType.Commit;\n    }\n\n    public bool IsReleaseModeAvailable => AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease);\n\n    public override async Task OnLoadedAsync()\n    {\n        SelectedBasePackage ??= AvailablePackages[0];\n\n        if (Design.IsDesignMode)\n            return;\n        // Populate available versions\n        try\n        {\n            var versionOptions = await SelectedBasePackage.GetAllVersionOptions();\n            if (IsReleaseMode)\n            {\n                AvailableVersions = new ObservableCollection<PackageVersion>(\n                    versionOptions.AvailableVersions\n                );\n                if (!AvailableVersions.Any())\n                    return;\n\n                SelectedVersion = AvailableVersions[0];\n            }\n            else\n            {\n                AvailableVersions = new ObservableCollection<PackageVersion>(\n                    versionOptions.AvailableBranches\n                );\n                UpdateSelectedVersionToLatestMain();\n            }\n\n            var pythonVersions = await pyInstallationManager.GetAllAvailablePythonsAsync();\n            AvailablePythonVersions = new ObservableCollection<UvPythonInfo>(pythonVersions);\n\n            if (\n                PackagePath is not null\n                && Utilities.TryGetPyVenvVersion(PackagePath.FullPath, out var venvPyVersion)\n            )\n            {\n                var matchingVenvPy = AvailablePythonVersions.FirstOrDefault(x => x.Version == venvPyVersion);\n                if (matchingVenvPy != default)\n                {\n                    SelectedPythonVersion = matchingVenvPy;\n                    venvDetected = true;\n                }\n            }\n\n            SelectedPythonVersion ??= GetRecommendedPyVersion() ?? AvailablePythonVersions.LastOrDefault();\n        }\n        catch (Exception e)\n        {\n            Logger.Warn(\"Error getting versions: {Exception}\", e.ToString());\n        }\n    }\n\n    private static string GetDisplayVersion(string version, string? branch)\n    {\n        return branch == null ? version : $\"{branch}@{version[..7]}\";\n    }\n\n    // When available version types change, reset selected version type if not compatible\n    partial void OnAvailableVersionTypesChanged(PackageVersionType value)\n    {\n        if (!value.HasFlag(SelectedVersionType))\n        {\n            SelectedVersionType = value;\n        }\n    }\n\n    // When changing branch / release modes, refresh\n    // ReSharper disable once UnusedParameterInPartialMethod\n    partial void OnSelectedVersionTypeChanged(PackageVersionType value) =>\n        OnSelectedBasePackageChanged(SelectedBasePackage);\n\n    partial void OnSelectedVersionChanged(PackageVersion? value)\n    {\n        if (value == null || IsReleaseMode)\n            return;\n\n        if (SelectedBasePackage is BaseGitPackage gitPackage)\n        {\n            Dispatcher\n                .UIThread.InvokeAsync(async () =>\n                {\n                    var commits = (await gitPackage.GetAllCommits(value.TagName))?.ToList();\n                    if (commits is null || commits.Count == 0)\n                        return;\n\n                    commits = [.. commits, new GitCommit { Sha = \"Custom...\" }];\n\n                    AvailableCommits = new ObservableCollection<GitCommit>(commits);\n                    SelectedCommit = AvailableCommits[0];\n                })\n                .SafeFireAndForget();\n        }\n    }\n\n    partial void OnSelectedCommitChanged(GitCommit? value)\n    {\n        ShowCustomCommitSha = value is { Sha: \"Custom...\" };\n    }\n\n    partial void OnSelectedBasePackageChanged(BasePackage? value)\n    {\n        if (value is null || SelectedBasePackage is null)\n        {\n            AvailableVersions?.Clear();\n            AvailableCommits?.Clear();\n            return;\n        }\n\n        AvailableVersions?.Clear();\n        AvailableCommits?.Clear();\n\n        AvailableVersionTypes = SelectedBasePackage.AvailableVersionTypes;\n\n        if (Design.IsDesignMode)\n            return;\n\n        Dispatcher\n            .UIThread.InvokeAsync(async () =>\n            {\n                Logger.Debug($\"Release mode: {IsReleaseMode}\");\n                var versionOptions = await value.GetAllVersionOptions();\n\n                AvailableVersions = IsReleaseMode\n                    ? new ObservableCollection<PackageVersion>(versionOptions.AvailableVersions)\n                    : new ObservableCollection<PackageVersion>(versionOptions.AvailableBranches);\n\n                Logger.Debug($\"Available versions: {string.Join(\", \", AvailableVersions)}\");\n                SelectedVersion = AvailableVersions[0];\n\n                if (!IsReleaseMode)\n                {\n                    var commits = (await value.GetAllCommits(SelectedVersion.TagName))?.ToList();\n                    if (commits is null || commits.Count == 0)\n                        return;\n\n                    commits = [.. commits, new GitCommit { Sha = \"Custom...\" }];\n\n                    AvailableCommits = new ObservableCollection<GitCommit>(commits);\n                    SelectedCommit = AvailableCommits[0];\n                    UpdateSelectedVersionToLatestMain();\n                }\n\n                if (!venvDetected)\n                {\n                    SelectedPythonVersion =\n                        GetRecommendedPyVersion() ?? AvailablePythonVersions.FirstOrDefault();\n                }\n            })\n            .SafeFireAndForget();\n    }\n\n    private void UpdateSelectedVersionToLatestMain()\n    {\n        if (AvailableVersions is null)\n        {\n            SelectedVersion = null;\n        }\n        else\n        {\n            // First try to find master\n            var version = AvailableVersions.FirstOrDefault(x => x.TagName == \"master\");\n            // If not found, try main\n            version ??= AvailableVersions.FirstOrDefault(x => x.TagName == \"main\");\n\n            // If still not found, just use the first one\n            version ??= AvailableVersions[0];\n\n            SelectedVersion = version;\n        }\n    }\n\n    public async Task AddPackageWithCurrentInputs()\n    {\n        if (SelectedBasePackage is null || PackagePath is null)\n            return;\n\n        var version = new InstalledPackageVersion();\n        if (IsReleaseMode)\n        {\n            version.InstalledReleaseVersion =\n                SelectedVersion?.TagName ?? throw new NullReferenceException(\"Selected version is null\");\n        }\n        else\n        {\n            version.InstalledBranch =\n                SelectedVersion?.TagName ?? throw new NullReferenceException(\"Selected version is null\");\n            version.InstalledCommitSha =\n                SelectedCommit?.Sha ?? throw new NullReferenceException(\"Selected commit is null\");\n        }\n\n        var torchVersion = SelectedBasePackage.GetRecommendedTorchVersion();\n        var sharedFolderRecommendation = SelectedBasePackage.RecommendedSharedFolderMethod;\n        var package = new InstalledPackage\n        {\n            Id = Guid.NewGuid(),\n            DisplayName = PackagePath.Name,\n            PackageName = SelectedBasePackage.Name,\n            LibraryPath = Path.Combine(\"Packages\", PackagePath.Name),\n            Version = version,\n            LaunchCommand = SelectedBasePackage.LaunchCommand,\n            LastUpdateCheck = DateTimeOffset.Now,\n            PreferredTorchIndex = torchVersion,\n            PreferredSharedFolderMethod = sharedFolderRecommendation,\n            PythonVersion =\n                SelectedPythonVersion?.Version.StringValue\n                ?? PyInstallationManager.Python_3_10_17.StringValue,\n        };\n\n        // Recreate venv if it's a BaseGitPackage\n        if (SelectedBasePackage is BaseGitPackage { UsesVenv: true } gitPackage)\n        {\n            Logger.Info(\n                \"Recreating venv for imported package {Name} ({PackageName})\",\n                package.DisplayName,\n                package.PackageName\n            );\n            await gitPackage.SetupVenv(\n                PackagePath,\n                forceRecreate: true,\n                pythonVersion: PyVersion.Parse(package.PythonVersion),\n                onConsoleOutput: output =>\n                {\n                    Logger.Debug(\"SetupVenv output: {Output}\", output.Text);\n                }\n            );\n        }\n\n        // Reconfigure shared links\n        Logger.Info(\n            \"Configuring shared links for imported package {Name} ({PackageName})\",\n            package.DisplayName,\n            package.PackageName\n        );\n        var recommendedSharedFolderMethod = SelectedBasePackage.RecommendedSharedFolderMethod;\n        await SelectedBasePackage.UpdateModelFolders(PackagePath, recommendedSharedFolderMethod);\n\n        settingsManager.Transaction(s => s.InstalledPackages.Add(package));\n    }\n\n    private UvPythonInfo? GetRecommendedPyVersion() =>\n        AvailablePythonVersions.LastOrDefault(x =>\n            x.Version.Major.Equals(SelectedBasePackage?.RecommendedPythonVersion.Major)\n            && x.Version.Minor.Equals(SelectedBasePackage?.RecommendedPythonVersion.Minor)\n        );\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/PropertyGridViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.ComponentModel;\nusing Avalonia;\nusing Avalonia.PropertyGrid.ViewModels;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing OneOf;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(PropertyGridDialog))]\n[ManagedService]\n[RegisterTransient<PropertyGridViewModel>]\npublic partial class PropertyGridViewModel : ContentDialogViewModelBase\n{\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(SelectedObjectItemsSource))]\n    private OneOf<INotifyPropertyChanged, IEnumerable<INotifyPropertyChanged>>? selectedObject;\n\n    public IEnumerable<INotifyPropertyChanged>? SelectedObjectItemsSource =>\n        SelectedObject?.Match(single => [single], multiple => multiple);\n\n    [ObservableProperty]\n    private PropertyGridShowStyle showStyle = PropertyGridShowStyle.Alphabetic;\n\n    [ObservableProperty]\n    private IReadOnlyList<string>? excludeCategories;\n\n    [ObservableProperty]\n    private IReadOnlyList<string>? includeCategories;\n\n    /// <inheritdoc />\n    public override BetterContentDialog GetDialog()\n    {\n        var dialog = base.GetDialog();\n\n        dialog.Padding = new Thickness(0);\n        dialog.CloseOnClickOutside = true;\n        dialog.CloseButtonText = Resources.Action_Close;\n\n        return dialog;\n    }\n\n    /// <summary>\n    /// Like <see cref=\"GetDialog\"/>, but with a primary save button.\n    /// </summary>\n    public BetterContentDialog GetSaveDialog()\n    {\n        var dialog = base.GetDialog();\n\n        dialog.Padding = new Thickness(0);\n        dialog.CloseOnClickOutside = true;\n        dialog.CloseButtonText = Resources.Action_Close;\n        dialog.PrimaryButtonText = Resources.Action_Save;\n        dialog.DefaultButton = ContentDialogButton.Primary;\n\n        return dialog;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackageSpecifiersViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Linq;\nusing Avalonia.Collections;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[RegisterTransient<PythonPackageSpecifiersViewModel>]\n[ManagedService]\n[View(typeof(PythonPackageSpecifiersDialog))]\npublic partial class PythonPackageSpecifiersViewModel : ContentDialogViewModelBase\n{\n    [ObservableProperty]\n    private string? title = Resources.Label_PythonDependenciesOverride_Title;\n\n    [ObservableProperty]\n    private string? description = Resources.Label_PythonDependenciesOverride_Description;\n\n    [ObservableProperty]\n    private string? helpLinkLabel = Resources.Label_DependencySpecifiers;\n\n    [ObservableProperty]\n    private Uri? helpLinkUri =\n        new(\"https://packaging.python.org/en/latest/specifications/dependency-specifiers\");\n\n    protected ObservableCollection<PythonPackageSpecifiersItem> Specifiers { get; } = [];\n\n    public DataGridCollectionView SpecifiersView { get; }\n\n    public PythonPackageSpecifiersViewModel()\n    {\n        SpecifiersView = new DataGridCollectionView(Specifiers);\n    }\n\n    public void LoadSpecifiers(IEnumerable<PipPackageSpecifierOverride> specifiers)\n    {\n        Specifiers.Clear();\n        Specifiers.AddRange(specifiers.Select(PythonPackageSpecifiersItem.FromSpecifier));\n    }\n\n    public IEnumerable<PipPackageSpecifierOverride> GetSpecifiers()\n    {\n        return Specifiers.Select(row => row.ToSpecifier());\n    }\n\n    public override BetterContentDialog GetDialog()\n    {\n        var dialog = base.GetDialog();\n        dialog.PrimaryButtonText = Resources.Action_Save;\n        dialog.DefaultButton = ContentDialogButton.Primary;\n        dialog.CloseButtonText = Resources.Action_Cancel;\n        dialog.MaxDialogWidth = 800;\n        dialog.FullSizeDesired = true;\n        return dialog;\n    }\n\n    [RelayCommand]\n    private void AddRow()\n    {\n        Specifiers.Add(\n            PythonPackageSpecifiersItem.FromSpecifier(\n                new PipPackageSpecifierOverride\n                {\n                    Action = PipPackageSpecifierOverrideAction.Update,\n                    Constraint = \"==\"\n                }\n            )\n        );\n    }\n\n    [RelayCommand]\n    private void RemoveSelectedRow(int selectedIndex)\n    {\n        try\n        {\n            Specifiers.RemoveAt(selectedIndex);\n        }\n        catch (ArgumentOutOfRangeException)\n        {\n            Debug.WriteLine($\"RemoveSelectedRow: Index {selectedIndex} out of range\");\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackagesItemViewModel.cs",
    "content": "﻿using Avalonia.Controls;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Semver;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\npublic partial class PythonPackagesItemViewModel(ISettingsManager settingsManager) : ViewModelBase\n{\n    [ObservableProperty]\n    private PipPackageInfo package;\n\n    [ObservableProperty]\n    private string? selectedVersion;\n\n    [ObservableProperty]\n    private IReadOnlyList<string>? availableVersions;\n\n    [ObservableProperty]\n    private PipShowResult? pipShowResult;\n\n    [ObservableProperty]\n    private bool isLoading;\n\n    /// <summary>\n    /// True if selected version is newer than the installed version\n    /// </summary>\n    [ObservableProperty]\n    private bool canUpgrade;\n\n    /// <summary>\n    /// True if selected version is older than the installed version\n    /// </summary>\n    [ObservableProperty]\n    private bool canDowngrade;\n\n    partial void OnSelectedVersionChanged(string? value)\n    {\n        if (\n            value is null\n            || Package.Version == value\n            || !SemVersion.TryParse(Package.Version, out var currentSemver)\n            || !SemVersion.TryParse(value, out var selectedSemver)\n        )\n        {\n            var compare = string.CompareOrdinal(value, Package.Version);\n            CanUpgrade = compare > 0;\n            CanDowngrade = compare < 0;\n            return;\n        }\n\n        var precedence = selectedSemver.ComparePrecedenceTo(currentSemver);\n\n        CanUpgrade = precedence > 0;\n        CanDowngrade = precedence < 0;\n    }\n\n    /// <summary>\n    /// Return the known index URL for a package, currently this is torch, torchvision and torchaudio\n    /// </summary>\n    public static string? GetKnownIndexUrl(string packageName, string version)\n    {\n        var torchPackages = new[] { \"torch\", \"torchvision\", \"torchaudio\" };\n        if (torchPackages.Contains(packageName) && version.Contains('+'))\n        {\n            // Get the metadata for the current version (everything after the +)\n            var indexName = version.Split('+', 2).Last();\n\n            var indexUrl = $\"https://download.pytorch.org/whl/{indexName}\";\n            return indexUrl;\n        }\n\n        return null;\n    }\n\n    /// <summary>\n    /// Loads the pip show result if not already loaded\n    /// </summary>\n    public async Task LoadExtraInfo(DirectoryPath venvPath, PyBaseInstall pyBaseInstall)\n    {\n        if (PipShowResult is not null)\n        {\n            return;\n        }\n\n        IsLoading = true;\n\n        try\n        {\n            if (Design.IsDesignMode)\n            {\n                await LoadExtraInfoDesignMode();\n            }\n            else\n            {\n                await using var venvRunner = await pyBaseInstall.CreateVenvRunnerAsync(\n                    venvPath,\n                    workingDirectory: venvPath.Parent,\n                    environmentVariables: settingsManager.Settings.EnvironmentVariables\n                );\n\n                PipShowResult = await venvRunner.PipShow(Package.Name);\n\n                // Attempt to get known index url\n                var indexUrl = GetKnownIndexUrl(Package.Name, Package.Version);\n\n                if (await venvRunner.PipIndex(Package.Name, indexUrl) is { } pipIndexResult)\n                {\n                    AvailableVersions = pipIndexResult.AvailableVersions;\n                    SelectedVersion = Package.Version;\n                }\n            }\n        }\n        catch (ProcessException)\n        {\n            // Ignore\n        }\n        finally\n        {\n            IsLoading = false;\n        }\n    }\n\n    private async Task LoadExtraInfoDesignMode()\n    {\n        await using var _ = new MinimumDelay(200, 300);\n\n        PipShowResult = new PipShowResult { Name = Package.Name, Version = Package.Version };\n        AvailableVersions = new[] { Package.Version, \"1.2.0\", \"1.1.0\", \"1.0.0\" };\n        SelectedVersion = Package.Version;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/PythonPackagesViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing AutoCtor;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(PythonPackagesDialog))]\n[ManagedService]\n[RegisterTransient<PythonPackagesViewModel>]\n[AutoConstruct]\npublic partial class PythonPackagesViewModel : ContentDialogViewModelBase\n{\n    private readonly ILogger<PythonPackagesViewModel> logger;\n    private readonly ISettingsManager settingsManager;\n    private readonly IPyInstallationManager pyInstallationManager;\n    private readonly IPrerequisiteHelper prerequisiteHelper;\n    private PyBaseInstall? pyBaseInstall;\n\n    public DirectoryPath? VenvPath { get; set; }\n\n    public PyVersion? PythonVersion { get; set; }\n\n    [ObservableProperty]\n    private bool isLoading;\n\n    private readonly SourceCache<PipPackageInfo, string> packageSource = new(p => p.Name);\n\n    public IObservableCollection<PythonPackagesItemViewModel> Packages { get; } =\n        new ObservableCollectionExtended<PythonPackagesItemViewModel>();\n\n    [ObservableProperty]\n    private PythonPackagesItemViewModel? selectedPackage;\n\n    [ObservableProperty]\n    private string searchQuery = string.Empty;\n\n    [AutoPostConstruct]\n    private void PostConstruct()\n    {\n        var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchQuery)\n            .Throttle(TimeSpan.FromMilliseconds(100))\n            .DistinctUntilChanged()\n            .ObserveOn(SynchronizationContext.Current!)\n            .Select(value =>\n            {\n                if (string.IsNullOrWhiteSpace(value.Value))\n                {\n                    return (static _ => true);\n                }\n\n                return (Func<PipPackageInfo, bool>)(\n                    p => p.Name.Contains(value.Value, StringComparison.OrdinalIgnoreCase)\n                );\n            });\n\n        packageSource\n            .Connect()\n            .DeferUntilLoaded()\n            .Filter(searchPredicate)\n            .Transform(p => new PythonPackagesItemViewModel(settingsManager) { Package = p })\n            .SortBy(vm => vm.Package.Name)\n            .ObserveOn(SynchronizationContext.Current!)\n            .Bind(Packages)\n            .Subscribe();\n    }\n\n    private async Task Refresh()\n    {\n        if (VenvPath is null)\n            return;\n\n        IsLoading = true;\n\n        try\n        {\n            if (Design.IsDesignMode)\n            {\n                await Task.Delay(250);\n            }\n            else\n            {\n                pyBaseInstall ??= new PyBaseInstall(\n                    await pyInstallationManager.GetInstallationAsync(\n                        PythonVersion ?? PyInstallationManager.Python_3_10_11\n                    )\n                );\n\n                await using var venvRunner = await pyBaseInstall.CreateVenvRunnerAsync(\n                    VenvPath,\n                    workingDirectory: VenvPath.Parent,\n                    environmentVariables: settingsManager.Settings.EnvironmentVariables\n                );\n\n                var packages = await venvRunner.PipList();\n\n                Dispatcher.UIThread.Post(() =>\n                {\n                    var currentName = SelectedPackage?.Package.Name;\n                    SelectedPackage = null;\n\n                    packageSource.EditDiff(packages);\n                    SelectedPackage = Packages.FirstOrDefault(p => p.Package.Name == currentName);\n                });\n            }\n        }\n        finally\n        {\n            IsLoading = false;\n        }\n    }\n\n    [RelayCommand]\n    private async Task RefreshBackground()\n    {\n        if (VenvPath is null)\n            return;\n\n        pyBaseInstall ??= new PyBaseInstall(\n            await pyInstallationManager.GetInstallationAsync(\n                PythonVersion ?? PyInstallationManager.Python_3_10_11\n            )\n        );\n        await using var venvRunner = await pyBaseInstall.CreateVenvRunnerAsync(\n            VenvPath,\n            workingDirectory: VenvPath.Parent,\n            environmentVariables: settingsManager.Settings.EnvironmentVariables\n        );\n\n        var packages = await venvRunner.PipList();\n\n        Dispatcher.UIThread.Post(() =>\n        {\n            // Backup selected package\n            var currentPackageName = SelectedPackage?.Package.Name;\n            SelectedPackage = null;\n\n            packageSource.EditDiff(packages);\n\n            // Restore selected package\n            SelectedPackage = Packages.FirstOrDefault(p => p.Package.Name == currentPackageName);\n        });\n    }\n\n    /// <summary>\n    /// Load the selected package's show info if not already loaded\n    /// </summary>\n    partial void OnSelectedPackageChanged(PythonPackagesItemViewModel? value)\n    {\n        if (value is null)\n        {\n            return;\n        }\n\n        if (value.PipShowResult is null)\n        {\n            value.LoadExtraInfo(VenvPath!, pyBaseInstall!).SafeFireAndForget();\n        }\n    }\n\n    /// <inheritdoc />\n    public override async Task OnLoadedAsync()\n    {\n        if (Design.IsDesignMode)\n            return;\n\n        await prerequisiteHelper.InstallUvIfNecessary();\n        await Refresh();\n    }\n\n    public void AddPackages(params PipPackageInfo[] packages)\n    {\n        packageSource.AddOrUpdate(packages);\n    }\n\n    [RelayCommand]\n    private Task ModifySelectedPackage(PythonPackagesItemViewModel? item)\n    {\n        return item?.SelectedVersion != null\n            ? UpgradePackageVersion(\n                item.Package.Name,\n                item.SelectedVersion,\n                PythonPackagesItemViewModel.GetKnownIndexUrl(item.Package.Name, item.SelectedVersion),\n                isDowngrade: item.CanDowngrade\n            )\n            : Task.CompletedTask;\n    }\n\n    private async Task UpgradePackageVersion(\n        string packageName,\n        string version,\n        string? extraIndexUrl = null,\n        bool isDowngrade = false\n    )\n    {\n        if (VenvPath is null || SelectedPackage?.Package is not { } package)\n            return;\n\n        // Confirmation dialog\n        var dialog = DialogHelper.CreateMarkdownDialog(\n            isDowngrade\n                ? $\"Downgrade **{package.Name}** to **{version}**?\"\n                : $\"Upgrade **{package.Name}** to **{version}**?\",\n            Resources.Label_ConfirmQuestion\n        );\n\n        dialog.PrimaryButtonText = isDowngrade ? Resources.Action_Downgrade : Resources.Action_Upgrade;\n        dialog.IsPrimaryButtonEnabled = true;\n        dialog.DefaultButton = ContentDialogButton.Primary;\n        dialog.CloseButtonText = Resources.Action_Cancel;\n\n        if (await dialog.ShowAsync() is not ContentDialogResult.Primary)\n        {\n            return;\n        }\n\n        var args = new ProcessArgsBuilder(\"install\", $\"{packageName}=={version}\");\n\n        if (extraIndexUrl != null)\n        {\n            args = args.AddArgs(\"--extra-index-url\", extraIndexUrl);\n        }\n\n        var steps = new List<IPackageStep>\n        {\n            new PipStep\n            {\n                VenvDirectory = VenvPath,\n                WorkingDirectory = VenvPath.Parent,\n                Args = args,\n                BaseInstall = pyBaseInstall,\n            },\n        };\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            ModificationCompleteMessage = isDowngrade\n                ? $\"Downgraded Python Package '{packageName}' to {version}\"\n                : $\"Upgraded Python Package '{packageName}' to {version}\",\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n        await runner.ExecuteSteps(steps);\n\n        // Refresh\n        RefreshBackground().SafeFireAndForget();\n    }\n\n    [RelayCommand]\n    private async Task InstallPackage()\n    {\n        if (VenvPath is null)\n            return;\n\n        // Dialog\n        var fields = new TextBoxField[]\n        {\n            new() { Label = \"Package Name\", InnerLeftText = \"pip install\" },\n        };\n\n        var dialog = DialogHelper.CreateTextEntryDialog(\"Install Package\", \"\", fields);\n        var result = await dialog.ShowAsync();\n\n        if (result != ContentDialogResult.Primary || fields[0].Text is not { } packageArgs)\n        {\n            return;\n        }\n\n        var steps = new List<IPackageStep>\n        {\n            new PipStep\n            {\n                VenvDirectory = VenvPath,\n                WorkingDirectory = VenvPath.Parent,\n                Args = new ProcessArgs(packageArgs).Prepend(\"install\"),\n                BaseInstall = pyBaseInstall,\n                EnvironmentVariables = settingsManager.Settings.EnvironmentVariables,\n            },\n        };\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            ModificationCompleteMessage = $\"Installed Python Package '{packageArgs}'\",\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n        await runner.ExecuteSteps(steps);\n\n        // Refresh\n        RefreshBackground().SafeFireAndForget();\n    }\n\n    [RelayCommand]\n    private async Task UninstallSelectedPackage()\n    {\n        if (VenvPath is null || SelectedPackage?.Package is not { } package)\n            return;\n\n        // Confirmation dialog\n        var dialog = DialogHelper.CreateMarkdownDialog(\n            $\"This will uninstall the package '{package.Name}'\",\n            Resources.Label_ConfirmQuestion\n        );\n        dialog.PrimaryButtonText = Resources.Action_Uninstall;\n        dialog.IsPrimaryButtonEnabled = true;\n        dialog.DefaultButton = ContentDialogButton.Primary;\n        dialog.CloseButtonText = Resources.Action_Cancel;\n\n        if (await dialog.ShowAsync() is not ContentDialogResult.Primary)\n        {\n            return;\n        }\n\n        var steps = new List<IPackageStep>\n        {\n            new PipStep\n            {\n                VenvDirectory = VenvPath,\n                WorkingDirectory = VenvPath.Parent,\n                Args = new[] { \"uninstall\", \"--yes\", package.Name },\n                BaseInstall = pyBaseInstall,\n            },\n        };\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            ModificationCompleteMessage = $\"Uninstalled Python Package '{package.Name}'\",\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n        await runner.ExecuteSteps(steps);\n\n        // Refresh\n        RefreshBackground().SafeFireAndForget();\n    }\n\n    public override BetterContentDialog GetDialog()\n    {\n        return new BetterContentDialog\n        {\n            CloseOnClickOutside = true,\n            MinDialogWidth = 800,\n            MaxDialogWidth = 1500,\n            FullSizeDesired = true,\n            ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled,\n            Title = Resources.Label_PythonPackages,\n            Content = new PythonPackagesDialog { DataContext = this },\n            CloseButtonText = Resources.Action_Close,\n            DefaultButton = ContentDialogButton.Close,\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelItemViewModel.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Models.Api;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\npublic partial class RecommendedModelItemViewModel : ViewModelBase\n{\n    [ObservableProperty]\n    private bool isSelected;\n\n    [ObservableProperty]\n    private string author;\n\n    [ObservableProperty]\n    private CivitModelVersion modelVersion;\n\n    [ObservableProperty]\n    private CivitModel civitModel;\n\n    public Uri ThumbnailUrl =>\n        ModelVersion.Images?.FirstOrDefault(x => x.Type == \"image\")?.Url == null\n            ? Assets.NoImage\n            : new Uri(ModelVersion.Images.First(x => x.Type == \"image\").Url);\n\n    [RelayCommand]\n    public void ToggleSelection() => IsSelected = !IsSelected;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Avalonia.Controls;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing Refit;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Api.LykosAuthApi;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[RegisterTransient<RecommendedModelsViewModel>]\n[ManagedService]\npublic partial class RecommendedModelsViewModel : ContentDialogViewModelBase\n{\n    private readonly ILogger<RecommendedModelsViewModel> logger;\n    private readonly IRecommendedModelsApi lykosApi;\n    private readonly ICivitApi civitApi;\n    private readonly ILiteDbContext liteDbContext;\n    private readonly ISettingsManager settingsManager;\n    private readonly INotificationService notificationService;\n    private readonly ITrackedDownloadService trackedDownloadService;\n    private readonly IDownloadService downloadService;\n    private readonly IModelImportService modelImportService;\n\n    // Single source cache for all models\n    public SourceCache<RecommendedModelItemViewModel, int> AllRecommendedModelsCache { get; } =\n        new(p => p.ModelVersion.Id);\n\n    // Single observable collection bound to the cache\n    public IObservableCollection<RecommendedModelItemViewModel> RecommendedModels { get; } =\n        new ObservableCollectionExtended<RecommendedModelItemViewModel>();\n\n    [ObservableProperty]\n    private bool isLoading;\n\n    public RecommendedModelsViewModel(\n        ILogger<RecommendedModelsViewModel> logger,\n        IRecommendedModelsApi lykosApi,\n        ICivitApi civitApi,\n        ILiteDbContext liteDbContext,\n        ISettingsManager settingsManager,\n        INotificationService notificationService,\n        ITrackedDownloadService trackedDownloadService,\n        IDownloadService downloadService,\n        IModelImportService modelImportService\n    )\n    {\n        this.logger = logger;\n        this.lykosApi = lykosApi;\n        this.civitApi = civitApi;\n        this.liteDbContext = liteDbContext;\n        this.settingsManager = settingsManager;\n        this.notificationService = notificationService;\n        this.trackedDownloadService = trackedDownloadService;\n        this.downloadService = downloadService;\n        this.modelImportService = modelImportService;\n\n        // Bind the single collection to the cache\n        AllRecommendedModelsCache\n            .Connect()\n            .DeferUntilLoaded()\n            .Bind(RecommendedModels)\n            .ObserveOn(SynchronizationContext.Current!) // Use Current! if nullability context allows\n            .Subscribe();\n    }\n\n    public override async Task OnLoadedAsync()\n    {\n        if (Design.IsDesignMode)\n            return;\n\n        IsLoading = true;\n        AllRecommendedModelsCache.Clear(); // Clear cache before loading\n\n        try\n        {\n            // Call the V2 endpoint\n            var recommendedModelsResponse = await lykosApi.GetRecommendedModels();\n\n            var allModels = recommendedModelsResponse\n                .RecommendedModelsByCategory.SelectMany(kvp => kvp.Value) // Flatten the dictionary values (lists of models)\n                .DistinctBy(m => m.Id) // Ensure models appearing in multiple categories are only added once\n                .Select(model =>\n                {\n                    // Find the first non-Turbo/Lightning version, or default to the first version if none match\n                    var suitableVersion =\n                        model.ModelVersions?.FirstOrDefault(\n                            x =>\n                                !x.BaseModel.Contains(\"Turbo\", StringComparison.OrdinalIgnoreCase)\n                                && !x.BaseModel.Contains(\"Lightning\", StringComparison.OrdinalIgnoreCase)\n                                && x.Files != null\n                                && x.Files.Any(f => f.Type == CivitFileType.Model) // Ensure there's a model file\n                        )\n                        ?? model.ModelVersions?.FirstOrDefault(\n                            x => x.Files != null && x.Files.Any(f => f.Type == CivitFileType.Model)\n                        );\n\n                    if (suitableVersion == null)\n                    {\n                        logger.LogWarning(\n                            \"Model {ModelName} (ID: {ModelId}) has no suitable model version file.\",\n                            model.Name,\n                            model.Id\n                        );\n                        return null; // Skip this model if no suitable version found\n                    }\n\n                    return new RecommendedModelItemViewModel\n                    {\n                        ModelVersion = suitableVersion,\n                        Author = $\"by {model.Creator?.Username}\",\n                        CivitModel = model\n                    };\n                })\n                .Where(vm => vm != null); // Filter out nulls (models skipped due to no suitable version)\n\n            AllRecommendedModelsCache.AddOrUpdate(allModels);\n        }\n        catch (ApiException apiEx)\n        {\n            logger.LogError(\n                apiEx,\n                \"API Error fetching recommended models V2. Status: {StatusCode}\",\n                apiEx.StatusCode\n            );\n            notificationService.Show(\n                \"Failed to get recommended models\",\n                $\"Could not reach the server. Please try again later. Error: {apiEx.StatusCode}\"\n            );\n            OnCloseButtonClick();\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Failed to get recommended models V2\");\n            notificationService.Show(\n                \"Failed to get recommended models\",\n                \"An unexpected error occurred. Please try again later or check the Model Browser tab.\"\n            );\n            OnCloseButtonClick();\n        }\n        finally\n        {\n            IsLoading = false;\n        }\n    }\n\n    [RelayCommand]\n    private async Task DoImport()\n    {\n        var selectedModels = RecommendedModels.Where(x => x.IsSelected).ToList(); // Use the single list\n\n        if (!selectedModels.Any())\n        {\n            notificationService.Show(\"No Models Selected\", \"Please select at least one model to import.\");\n            return;\n        }\n\n        IsLoading = true; // Optionally show loading indicator during import\n\n        int successCount = 0;\n        int failCount = 0;\n\n        foreach (var model in selectedModels)\n        {\n            // Get latest version file that is a Model type and marked primary, or fallback to first model file\n            var modelFile =\n                model.ModelVersion.Files?.FirstOrDefault(\n                    f => f is { Type: CivitFileType.Model, IsPrimary: true }\n                ) ?? model.ModelVersion.Files?.FirstOrDefault(f => f.Type == CivitFileType.Model);\n\n            if (modelFile is null)\n            {\n                logger.LogWarning(\n                    \"Skipping import for {ModelName}: No suitable model file found in version {VersionId}.\",\n                    model.CivitModel.Name,\n                    model.ModelVersion.Id\n                );\n                failCount++;\n                continue; // Skip if no suitable file\n            }\n\n            try\n            {\n                var rootModelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory);\n                var downloadDirectory = rootModelsDirectory.JoinDir(\n                    model.CivitModel.Type.ConvertTo<SharedFolderType>().GetStringValue()\n                );\n\n                await modelImportService.DoImport(\n                    model.CivitModel,\n                    downloadDirectory,\n                    model.ModelVersion,\n                    modelFile\n                );\n                successCount++;\n                model.IsSelected = false; // De-select after successful import start\n            }\n            catch (Exception ex)\n            {\n                logger.LogError(ex, \"Failed to initiate import for model {ModelName}\", model.CivitModel.Name);\n                failCount++;\n                // Consider notifying the user about the specific failure\n                notificationService.Show(\n                    \"Import Failed\",\n                    $\"Could not start import for {model.CivitModel.Name}.\"\n                );\n            }\n        }\n\n        IsLoading = false; // Hide loading indicator\n\n        if (failCount == 0 && successCount > 0)\n        {\n            notificationService.Show(\n                \"Import Started\",\n                $\"{successCount} model(s) added to the download queue.\"\n            );\n            // Optionally close the dialog after successful import initiation\n            // OnCloseButtonClick();\n        }\n        else if (successCount > 0)\n        {\n            notificationService.Show(\n                \"Import Partially Started\",\n                $\"{successCount} model(s) added to queue. {failCount} failed to start.\"\n            );\n        }\n        else if (failCount > 0)\n        {\n            notificationService.Show(\n                \"Import Failed\",\n                $\"Could not start import for {failCount} selected model(s).\"\n            );\n        }\n        // else: No models were actually selected or processed, already handled.\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/SafetensorMetadataViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(SafetensorMetadataDialog))]\n[ManagedService]\n[RegisterSingleton<SafetensorMetadataViewModel>]\npublic partial class SafetensorMetadataViewModel : ContentDialogViewModelBase\n{\n    [ObservableProperty]\n    private string? modelName;\n\n    [ObservableProperty]\n    private SafetensorMetadata? metadata;\n\n    [RelayCommand]\n    public void CopyTagToClipboard(string tag)\n    {\n        App.Clipboard?.SetTextAsync(tag);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Linq;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Platform.Storage;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(SelectDataDirectoryDialog))]\n[ManagedService]\n[RegisterTransient<SelectDataDirectoryViewModel>]\npublic partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    public static string DefaultInstallLocation =>\n        Compat.IsLinux\n            ? Path.Combine(\n                Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),\n                \"StabilityMatrix\"\n            )\n            : Compat.AppDataHome;\n\n    private readonly ISettingsManager settingsManager;\n\n    private const string ValidExistingDirectoryText = \"Valid existing data directory found\";\n    private const string InvalidDirectoryText = \"Directory must be empty or have a valid settings.json file\";\n    private const string NotEnoughFreeSpaceText = \"Not enough free space on the selected drive\";\n    private const string FatWarningText = \"FAT32 / exFAT drives are not supported at this time\";\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsInTempFolder))]\n    private string dataDirectory = DefaultInstallLocation;\n\n    [ObservableProperty]\n    private bool isPortableMode;\n\n    [ObservableProperty]\n    private string directoryStatusText = string.Empty;\n\n    [ObservableProperty]\n    private bool isStatusBadgeVisible;\n\n    [ObservableProperty]\n    private bool isDirectoryValid;\n\n    [ObservableProperty]\n    private bool showFatWarning;\n\n    public bool IsInTempFolder =>\n        Compat\n            .AppCurrentDir.ToString()\n            .StartsWith(\n                Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar),\n                StringComparison.OrdinalIgnoreCase\n            );\n\n    public RefreshBadgeViewModel ValidatorRefreshBadge { get; } =\n        new()\n        {\n            State = ProgressState.Inactive,\n            SuccessToolTipText = ValidExistingDirectoryText,\n            FailToolTipText = InvalidDirectoryText,\n        };\n\n    public SelectDataDirectoryViewModel(ISettingsManager settingsManager)\n    {\n        this.settingsManager = settingsManager;\n        ValidatorRefreshBadge.RefreshFunc = ValidateDataDirectory;\n    }\n\n    public override void OnLoaded()\n    {\n        ValidatorRefreshBadge.RefreshCommand.ExecuteAsync(null).SafeFireAndForget();\n        IsPortableMode = true;\n    }\n\n    // Revalidate on data directory change\n    partial void OnDataDirectoryChanged(string value)\n    {\n        ValidatorRefreshBadge.RefreshCommand.ExecuteAsync(null).SafeFireAndForget();\n    }\n\n    private async Task<bool> ValidateDataDirectory()\n    {\n        await using var delay = new MinimumDelay(100, 200);\n\n        ShowFatWarning = IsDriveFat(DataDirectory);\n\n        if (IsInTempFolder)\n            return false;\n\n        // Doesn't exist, this is fine as a new install, hide badge\n        if (!Directory.Exists(DataDirectory))\n        {\n            IsStatusBadgeVisible = false;\n            IsDirectoryValid = true;\n            return true;\n        }\n        // Otherwise check that a settings.json exists\n        var settingsPath = Path.Combine(DataDirectory, \"settings.json\");\n\n        // settings.json exists: Try deserializing it\n        if (File.Exists(settingsPath))\n        {\n            try\n            {\n                var jsonText = await File.ReadAllTextAsync(settingsPath);\n                var _ = JsonSerializer.Deserialize<Core.Models.Settings.Settings>(\n                    jsonText,\n                    new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } }\n                );\n                // If successful, show existing badge\n                IsStatusBadgeVisible = true;\n                IsDirectoryValid = true;\n                DirectoryStatusText = ValidExistingDirectoryText;\n                return true;\n            }\n            catch (Exception e)\n            {\n                Logger.Info(\"Failed to deserialize settings.json: {Msg}\", e.Message);\n                // If not, show error badge, and set directory to invalid to prevent continuing\n                IsStatusBadgeVisible = true;\n                IsDirectoryValid = false;\n                DirectoryStatusText = InvalidDirectoryText;\n                return false;\n            }\n        }\n\n        // No settings.json\n\n        // Check if the directory is %APPDATA%\\StabilityMatrix: hide badge and set directory valid\n        if (DataDirectory == DefaultInstallLocation)\n        {\n            IsStatusBadgeVisible = false;\n            IsDirectoryValid = true;\n            return true;\n        }\n\n        // Check if the directory is empty: hide badge and set directory to valid\n        var isEmpty = !Directory.EnumerateFileSystemEntries(DataDirectory).Any();\n        if (isEmpty)\n        {\n            IsStatusBadgeVisible = false;\n            IsDirectoryValid = true;\n            return true;\n        }\n\n        // Not empty and not appdata: show error badge, and set directory to invalid\n        IsStatusBadgeVisible = true;\n        IsDirectoryValid = false;\n        DirectoryStatusText = InvalidDirectoryText;\n        return false;\n    }\n\n    private bool CanPickFolder => App.StorageProvider.CanPickFolder;\n\n    [RelayCommand(CanExecute = nameof(CanPickFolder))]\n    private async Task ShowFolderBrowserDialog()\n    {\n        var provider = App.StorageProvider;\n        var result = await provider.OpenFolderPickerAsync(\n            new FolderPickerOpenOptions { Title = \"Select Data Folder\", AllowMultiple = false }\n        );\n\n        if (result.Count != 1)\n            return;\n\n        DataDirectory = result[0].Path.LocalPath;\n    }\n\n    partial void OnIsPortableModeChanged(bool value)\n    {\n        DataDirectory = value ? Compat.AppCurrentDir + \"Data\" : DefaultInstallLocation;\n    }\n\n    private bool IsDriveFat(string path)\n    {\n        try\n        {\n            var drive = new DriveInfo(Path.GetPathRoot(path));\n            return drive.DriveFormat.Contains(\"FAT\", StringComparison.OrdinalIgnoreCase);\n        }\n        catch (Exception e)\n        {\n            Logger.Warn(e, \"Error checking drive FATness\");\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs",
    "content": "﻿using System.Collections.ObjectModel;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Platform.Storage;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Services;\nusing Size = StabilityMatrix.Core.Helper.Size;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[ManagedService]\n[RegisterTransient<SelectModelVersionViewModel>]\npublic partial class SelectModelVersionViewModel(\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IModelIndexService modelIndexService,\n    INotificationService notificationService\n) : ContentDialogViewModelBase\n{\n    private readonly IDownloadService downloadService = downloadService;\n\n    public required ContentDialog Dialog { get; set; }\n\n    public required string Description { get; set; }\n    public new required string Title { get; set; }\n    public required CivitModel CivitModel { get; set; }\n\n    [ObservableProperty]\n    public IReadOnlyList<ModelVersionViewModel> versions = [];\n\n    [ObservableProperty]\n    private Bitmap? previewImage;\n\n    [ObservableProperty]\n    private ModelVersionViewModel? selectedVersionViewModel;\n\n    [ObservableProperty]\n    private CivitFileViewModel? selectedFile;\n\n    [ObservableProperty]\n    private bool isImportEnabled;\n\n    [ObservableProperty]\n    private ObservableCollection<ImageSource> imageUrls = new();\n\n    [ObservableProperty]\n    private bool canGoToNextImage;\n\n    [ObservableProperty]\n    private bool canGoToPreviousImage;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(DisplayedPageNumber))]\n    private int selectedImageIndex;\n\n    [ObservableProperty]\n    private string importTooltip = string.Empty;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsCustomSelected))]\n    [NotifyPropertyChangedFor(nameof(ShowEmptyPathWarning))]\n    private string selectedInstallLocation = string.Empty;\n\n    [ObservableProperty]\n    private ObservableCollection<string> availableInstallLocations = [];\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ShowEmptyPathWarning))]\n    private string customInstallLocation = string.Empty;\n\n    public bool IsCustomSelected => SelectedInstallLocation == \"Custom...\";\n    public bool ShowEmptyPathWarning => IsCustomSelected && string.IsNullOrWhiteSpace(CustomInstallLocation);\n\n    public int DisplayedPageNumber => SelectedImageIndex + 1;\n\n    public override void OnLoaded()\n    {\n        SelectedVersionViewModel = Versions[0];\n        CanGoToNextImage = true;\n\n        // LoadInstallLocations() is called within OnSelectedFileChanged, which is triggered by OnSelectedVersionViewModelChanged.\n        // However, to apply preferences correctly, we need AvailableInstallLocations populated first.\n        // It might be better to ensure LoadInstallLocations is called before trying to apply preferences.\n        // For now, we rely on the chain: OnLoaded -> sets SelectedVersionViewModel -> OnSelectedVersionViewModelChanged -> sets SelectedFile -> OnSelectedFileChanged -> LoadInstallLocations\n        // Then, we apply preferences if available.\n    }\n\n    partial void OnSelectedVersionViewModelChanged(ModelVersionViewModel? value)\n    {\n        var nsfwEnabled = settingsManager.Settings.ModelBrowserNsfwEnabled;\n        var allImages = value\n            ?.ModelVersion?.Images?.Where(img => img.Type == \"image\" && (nsfwEnabled || img.NsfwLevel <= 1))\n            ?.Select(x => new ImageSource(x.Url))\n            .ToList();\n\n        if (allImages == null || !allImages.Any())\n        {\n            // allImages = new List<ImageSource> { new(Assets.NoImage) };\n            allImages = [];\n            CanGoToNextImage = false;\n        }\n        else\n        {\n            CanGoToNextImage = allImages.Count > 1;\n        }\n\n        Dispatcher.UIThread.Post(() =>\n        {\n            CanGoToPreviousImage = false;\n            // SelectedFile = SelectedVersionViewModel?.CivitFileViewModels.FirstOrDefault();\n            ImageUrls = new ObservableCollection<ImageSource>(allImages);\n            SelectedImageIndex = 0;\n\n            // Apply saved preferences after SelectedFile change has potentially called LoadInstallLocations\n            // It's crucial that LoadInstallLocations runs before this to populate AvailableInstallLocations\n            // and set an initial SelectedInstallLocation.\n            ApplySavedDownloadPreference();\n        });\n    }\n\n    partial void OnSelectedFileChanged(CivitFileViewModel? value)\n    {\n        if (value is { IsInstalled: true }) { }\n\n        var canImport = true;\n        if (settingsManager.IsLibraryDirSet)\n        {\n            LoadInstallLocations();\n\n            var fileSizeBytes = value?.CivitFile.SizeKb * 1024;\n            var freeSizeBytes =\n                SystemInfo.GetDiskFreeSpaceBytes(settingsManager.ModelsDirectory) ?? long.MaxValue;\n            canImport = fileSizeBytes < freeSizeBytes;\n            ImportTooltip = canImport\n                ? \"Free space after download: \"\n                    + (\n                        freeSizeBytes < long.MaxValue\n                            ? Size.FormatBytes(Convert.ToUInt64(freeSizeBytes - fileSizeBytes))\n                            : \"Unknown\"\n                    )\n                : $\"Not enough space on disk. Need {Size.FormatBytes(Convert.ToUInt64(fileSizeBytes))} but only have {Size.FormatBytes(Convert.ToUInt64(freeSizeBytes))}\";\n        }\n        else\n        {\n            ImportTooltip = \"Please set the library directory in settings\";\n        }\n\n        IsImportEnabled = value?.CivitFile != null && canImport && !ShowEmptyPathWarning;\n    }\n\n    partial void OnSelectedInstallLocationChanged(string? value)\n    {\n        if (value?.Equals(\"Custom...\", StringComparison.OrdinalIgnoreCase) is true)\n        {\n            // Only invoke the folder picker if a custom location isn't already set (e.g., by loading preferences).\n            // If the user manually selects \"Custom...\" and CustomInstallLocation was previously cleared (due to a non-custom selection),\n            // then string.IsNullOrWhiteSpace(this.CustomInstallLocation) will be true, and the dialog will show.\n            if (string.IsNullOrWhiteSpace(this.CustomInstallLocation))\n            {\n                Dispatcher.UIThread.InvokeAsync(SelectCustomFolder);\n            }\n        }\n        else\n        {\n            // If a non-custom location is selected, clear any existing custom path.\n            CustomInstallLocation = string.Empty;\n        }\n\n        IsImportEnabled = !ShowEmptyPathWarning;\n    }\n\n    partial void OnCustomInstallLocationChanged(string value)\n    {\n        IsImportEnabled = !ShowEmptyPathWarning;\n    }\n\n    public void Cancel()\n    {\n        Dialog.Hide(ContentDialogResult.Secondary);\n    }\n\n    public void Import()\n    {\n        SaveCurrentDownloadPreference();\n        Dialog.Hide(ContentDialogResult.Primary);\n    }\n\n    public async Task Delete()\n    {\n        if (SelectedFile == null)\n            return;\n\n        var fileToDelete = SelectedFile;\n        var originalSelectedVersionVm = SelectedVersionViewModel;\n\n        var hash = fileToDelete.CivitFile.Hashes.BLAKE3;\n        if (string.IsNullOrWhiteSpace(hash))\n        {\n            notificationService.Show(\n                \"Error deleting file\",\n                \"Could not delete model, hash is missing.\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        var matchingModels = (await modelIndexService.FindByHashAsync(hash)).ToList();\n\n        if (matchingModels.Count == 0)\n        {\n            await modelIndexService.RefreshIndex();\n            matchingModels = (await modelIndexService.FindByHashAsync(hash)).ToList();\n\n            if (matchingModels.Count == 0)\n            {\n                notificationService.Show(\n                    \"Error deleting file\",\n                    \"Could not delete model, model not found in index.\",\n                    NotificationType.Error\n                );\n                return;\n            }\n        }\n\n        var dialog = new BetterContentDialog\n        {\n            Title = Resources.Label_AreYouSure,\n            MaxDialogWidth = 750,\n            MaxDialogHeight = 850,\n            PrimaryButtonText = Resources.Action_Yes,\n            IsPrimaryButtonEnabled = true,\n            IsSecondaryButtonEnabled = false,\n            CloseButtonText = Resources.Action_Cancel,\n            DefaultButton = ContentDialogButton.Close,\n            Content =\n                $\"The following files:\\n{string.Join('\\n', matchingModels.Select(x => $\"- {x.FileName}\"))}\\n\"\n                + \"and all associated metadata files will be deleted. Are you sure?\",\n        };\n\n        var result = await dialog.ShowAsync();\n        if (result == ContentDialogResult.Primary)\n        {\n            foreach (var localModel in matchingModels)\n            {\n                var checkpointPath = new FilePath(localModel.GetFullPath(settingsManager.ModelsDirectory));\n                if (File.Exists(checkpointPath))\n                {\n                    File.Delete(checkpointPath);\n                }\n\n                var previewPath = localModel.GetPreviewImageFullPath(settingsManager.ModelsDirectory);\n                if (File.Exists(previewPath))\n                {\n                    File.Delete(previewPath);\n                }\n\n                var cmInfoPath = checkpointPath.ToString().Replace(checkpointPath.Extension, \".cm-info.json\");\n                if (File.Exists(cmInfoPath))\n                {\n                    File.Delete(cmInfoPath);\n                }\n\n                await modelIndexService.RemoveModelAsync(localModel);\n            }\n            fileToDelete.IsInstalled = false;\n            originalSelectedVersionVm?.RefreshInstallStatus();\n        }\n    }\n\n    public void PreviousImage()\n    {\n        if (SelectedImageIndex > 0)\n            SelectedImageIndex--;\n        CanGoToPreviousImage = SelectedImageIndex > 0;\n        CanGoToNextImage = SelectedImageIndex < ImageUrls.Count - 1;\n    }\n\n    public void NextImage()\n    {\n        if (SelectedImageIndex < ImageUrls.Count - 1)\n            SelectedImageIndex++;\n        CanGoToPreviousImage = SelectedImageIndex > 0;\n        CanGoToNextImage = SelectedImageIndex < ImageUrls.Count - 1;\n    }\n\n    public async Task SelectCustomFolder()\n    {\n        var files = await App.StorageProvider.OpenFolderPickerAsync(\n            new FolderPickerOpenOptions\n            {\n                Title = \"Select Download Folder\",\n                AllowMultiple = false,\n                SuggestedStartLocation = await App.StorageProvider.TryGetFolderFromPathAsync(\n                    Path.Combine(\n                        settingsManager.ModelsDirectory,\n                        CivitModel.Type.ConvertTo<SharedFolderType>().GetStringValue()\n                    )\n                ),\n            }\n        );\n\n        if (files.FirstOrDefault()?.TryGetLocalPath() is { } path)\n        {\n            CustomInstallLocation = path;\n            // Potentially save preference here if selection is considered final upon folder picking for custom.\n            // However, saving on Import() is more robust as it's the explicit confirmation.\n        }\n    }\n\n    private void LoadInstallLocations()\n    {\n        var installLocations = new ObservableCollection<string>();\n\n        var rootModelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory);\n\n        var downloadDirectory = GetSharedFolderPath(\n            rootModelsDirectory,\n            SelectedFile?.CivitFile,\n            CivitModel.Type,\n            CivitModel.BaseModelType\n        );\n\n        if (!downloadDirectory.ToString().EndsWith(\"Unknown\"))\n        {\n            installLocations.Add(\n                Path.Combine(\"Models\", Path.GetRelativePath(rootModelsDirectory, downloadDirectory))\n            );\n            foreach (\n                var directory in downloadDirectory.EnumerateDirectories(\n                    \"*\",\n                    EnumerationOptionConstants.AllDirectories\n                )\n            )\n            {\n                installLocations.Add(\n                    Path.Combine(\"Models\", Path.GetRelativePath(rootModelsDirectory, directory))\n                );\n            }\n        }\n\n        if (downloadDirectory.ToString().EndsWith(SharedFolderType.DiffusionModels.GetStringValue()))\n        {\n            // also add StableDiffusion in case we have an AIO version\n            var stableDiffusionDirectory = rootModelsDirectory.JoinDir(\n                SharedFolderType.StableDiffusion.GetStringValue()\n            );\n            installLocations.Add(\n                Path.Combine(\"Models\", Path.GetRelativePath(rootModelsDirectory, stableDiffusionDirectory))\n            );\n        }\n\n        installLocations.Add(\"Custom...\");\n\n        AvailableInstallLocations = installLocations;\n        SelectedInstallLocation = installLocations.FirstOrDefault();\n    }\n\n    private static DirectoryPath GetSharedFolderPath(\n        DirectoryPath rootModelsDirectory,\n        CivitFile? civitFile,\n        CivitModelType modelType,\n        string? baseModelType\n    )\n    {\n        if (civitFile?.Type is CivitFileType.VAE)\n        {\n            return rootModelsDirectory.JoinDir(SharedFolderType.VAE.GetStringValue());\n        }\n\n        if (\n            modelType is CivitModelType.Checkpoint\n            && (\n                baseModelType == CivitBaseModelType.Flux1D.GetStringValue()\n                || baseModelType == CivitBaseModelType.Flux1S.GetStringValue()\n                || baseModelType == CivitBaseModelType.WanVideo.GetStringValue()\n                || baseModelType == CivitBaseModelType.HunyuanVideo.GetStringValue()\n                || civitFile?.Metadata.Format == CivitModelFormat.GGUF\n            )\n        )\n        {\n            return rootModelsDirectory.JoinDir(SharedFolderType.DiffusionModels.GetStringValue());\n        }\n\n        return rootModelsDirectory.JoinDir(modelType.ConvertTo<SharedFolderType>().GetStringValue());\n    }\n\n    private void ApplySavedDownloadPreference()\n    {\n        if (CivitModel.Type == null || !settingsManager.IsLibraryDirSet)\n            return;\n\n        var modelTypeKey = CivitModel.Type.ToString();\n        if (\n            settingsManager.Settings.ModelTypeDownloadPreferences.TryGetValue(\n                modelTypeKey,\n                out var preference\n            )\n        )\n        {\n            if (\n                preference.SelectedInstallLocation == \"Custom...\"\n                && !string.IsNullOrWhiteSpace(preference.CustomInstallLocation)\n            )\n            {\n                // Ensure \"Custom...\" is an option or add it if necessary, though LoadInstallLocations should handle it.\n                if (AvailableInstallLocations.Contains(\"Custom...\"))\n                {\n                    CustomInstallLocation = preference.CustomInstallLocation ?? string.Empty;\n                    SelectedInstallLocation = \"Custom...\";\n                }\n            }\n            // If the saved SelectedInstallLocation is a custom path directly (legacy or direct set)\n            // and it's not in AvailableInstallLocations, but CustomInstallLocation is set from preference.\n            else if (\n                !string.IsNullOrWhiteSpace(preference.CustomInstallLocation)\n                && preference.SelectedInstallLocation == preference.CustomInstallLocation\n            )\n            {\n                if (AvailableInstallLocations.Contains(\"Custom...\"))\n                {\n                    CustomInstallLocation = preference.CustomInstallLocation ?? string.Empty;\n                    SelectedInstallLocation = \"Custom...\";\n                }\n            }\n            else if (\n                preference.SelectedInstallLocation != null\n                && AvailableInstallLocations.Contains(preference.SelectedInstallLocation)\n            )\n            {\n                SelectedInstallLocation = preference.SelectedInstallLocation;\n            }\n        }\n    }\n\n    private void SaveCurrentDownloadPreference()\n    {\n        if (\n            CivitModel?.Type == null\n            || !settingsManager.IsLibraryDirSet\n            || string.IsNullOrEmpty(SelectedInstallLocation)\n        )\n            return;\n\n        var modelTypeKey = CivitModel.Type.ToString();\n        var preference = new LastDownloadLocationInfo\n        {\n            SelectedInstallLocation = SelectedInstallLocation,\n            CustomInstallLocation = IsCustomSelected ? CustomInstallLocation : null,\n        };\n\n        settingsManager.Transaction(s =>\n        {\n            s.ModelTypeDownloadPreferences[modelTypeKey] = preference;\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/SponsorshipPromptViewModel.cs",
    "content": "﻿using System.ComponentModel;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Media.Animation;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Settings;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Lykos;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(SponsorshipPromptDialog))]\n[ManagedService]\n[RegisterTransient<SponsorshipPromptViewModel>]\npublic partial class SponsorshipPromptViewModel(\n    INavigationService<MainWindowViewModel> navigationService,\n    INavigationService<SettingsViewModel> settingsNavService\n) : TaskDialogViewModelBase\n{\n    [Localizable(false)]\n    [ObservableProperty]\n    private string titleEmoji = \"\\u2764\\ufe0f\";\n\n    [ObservableProperty]\n    private string title = Resources.Sponsorship_Title;\n\n    [ObservableProperty]\n    private string existingSupporterPreamble = Resources.Sponsorship_ExistingSupporterPreamble;\n\n    [ObservableProperty]\n    private string? featureText;\n\n    [ObservableProperty]\n    private bool isPatreonConnected;\n\n    [ObservableProperty]\n    private bool isExistingSupporter;\n\n    public void Initialize(\n        LykosAccountStatusUpdateEventArgs status,\n        string featureName,\n        string? requiredTier = null\n    )\n    {\n        IsPatreonConnected = status.IsPatreonConnected;\n        IsExistingSupporter = status.IsActiveSupporter;\n\n        if (string.IsNullOrEmpty(requiredTier))\n        {\n            FeatureText = string.Format(Resources.Sponsorship_ReqAnyTier, featureName);\n        }\n        else\n        {\n            FeatureText = string.Format(Resources.Sponsorship_ReqSpecificTier, featureName, requiredTier);\n        }\n    }\n\n    [RelayCommand]\n    private async Task NavigateToAccountSettings()\n    {\n        CloseDialog(TaskDialogStandardResult.Close);\n        navigationService.NavigateTo<SettingsViewModel>(new SuppressNavigationTransitionInfo());\n        await Task.Delay(100);\n        settingsNavService.NavigateTo<AccountSettingsViewModel>(new SuppressNavigationTransitionInfo());\n    }\n\n    [Localizable(false)]\n    [RelayCommand]\n    private static void OpenSupportUrl()\n    {\n        ProcessRunner.OpenUrl(\"https://www.patreon.com/join/StabilityMatrix\");\n    }\n\n    public override TaskDialog GetDialog()\n    {\n        var dialog = base.GetDialog();\n        dialog.Buttons =\n        [\n            new TaskDialogCommand\n            {\n                Text = Resources.Action_ViewSupportOptions,\n                IsDefault = true,\n                Command = OpenSupportUrlCommand\n            },\n            new TaskDialogButton\n            {\n                Text = Resources.Action_MaybeLater,\n                DialogResult = TaskDialogStandardResult.Close\n            }\n        ];\n        return dialog;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Dialogs/UpdateViewModel.cs",
    "content": "﻿using System.Text.RegularExpressions;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing Semver;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Models.Update;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Core.Updater;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\n[View(typeof(UpdateDialog))]\n[ManagedService]\n[RegisterSingleton<UpdateViewModel>]\npublic partial class UpdateViewModel : ContentDialogViewModelBase\n{\n    private readonly ILogger<UpdateViewModel> logger;\n    private readonly ISettingsManager settingsManager;\n    private readonly IHttpClientFactory httpClientFactory;\n    private readonly IUpdateHelper updateHelper;\n\n    private bool isLoaded;\n\n    [ObservableProperty]\n    private bool isUpdateAvailable;\n\n    [ObservableProperty]\n    private UpdateInfo? updateInfo;\n\n    [ObservableProperty]\n    private string? releaseNotes;\n\n    [ObservableProperty]\n    private string? updateText;\n\n    [ObservableProperty]\n    private int progressValue;\n\n    [ObservableProperty]\n    private bool isProgressIndeterminate;\n\n    [ObservableProperty]\n    private bool showProgressBar;\n\n    [ObservableProperty]\n    private string? currentVersionText;\n\n    [ObservableProperty]\n    private string? newVersionText;\n\n    [GeneratedRegex(\n        @\"(##\\s*(v[0-9]+\\.[0-9]+\\.[0-9]+(?:-(?:[0-9A-Za-z-.]+))?)((?:\\n|.)+?))(?=(##\\s*v[0-9]+\\.[0-9]+\\.[0-9]+)|\\z)\"\n    )]\n    private static partial Regex RegexChangelog();\n\n    public UpdateViewModel(\n        ILogger<UpdateViewModel> logger,\n        ISettingsManager settingsManager,\n        IHttpClientFactory httpClientFactory,\n        IUpdateHelper updateHelper\n    )\n    {\n        this.logger = logger;\n        this.settingsManager = settingsManager;\n        this.httpClientFactory = httpClientFactory;\n        this.updateHelper = updateHelper;\n\n        EventManager.Instance.UpdateAvailable += (_, info) =>\n        {\n            IsUpdateAvailable = true;\n            UpdateInfo = info;\n        };\n    }\n\n    public async Task Preload()\n    {\n        if (UpdateInfo is null)\n            return;\n\n        ReleaseNotes = await GetReleaseNotes(UpdateInfo.Changelog.ToString());\n    }\n\n    partial void OnUpdateInfoChanged(UpdateInfo? value)\n    {\n        CurrentVersionText = $\"v{Compat.AppVersion.ToDisplayString()}\";\n        NewVersionText = $\"v{value?.Version.ToDisplayString()}\";\n    }\n\n    public override async Task OnLoadedAsync()\n    {\n        if (!isLoaded)\n        {\n            await Preload();\n        }\n    }\n\n    /// <inheritdoc />\n    public override void OnUnloaded()\n    {\n        base.OnUnloaded();\n        isLoaded = false;\n    }\n\n    [RelayCommand]\n    private async Task InstallUpdate()\n    {\n        if (UpdateInfo == null)\n        {\n            return;\n        }\n\n        ShowProgressBar = true;\n        IsProgressIndeterminate = true;\n        UpdateText = string.Format(Resources.TextTemplate_UpdatingPackage, Resources.Label_StabilityMatrix);\n\n        try\n        {\n            await updateHelper.DownloadUpdate(\n                UpdateInfo,\n                new Progress<ProgressReport>(report =>\n                {\n                    ProgressValue = Convert.ToInt32(report.Percentage);\n                    IsProgressIndeterminate = report.IsIndeterminate;\n                })\n            );\n        }\n        catch (Exception e)\n        {\n            logger.LogWarning(e, \"Failed to download update\");\n\n            var dialog = DialogHelper.CreateMarkdownDialog(\n                $\"{e.GetType().Name}: {e.Message}\",\n                Resources.Label_UnexpectedErrorOccurred\n            );\n\n            await dialog.ShowAsync();\n            return;\n        }\n\n        // On unix, we need to set the executable bit\n        if (Compat.IsUnix)\n        {\n            File.SetUnixFileMode(\n                UpdateHelper.ExecutablePath.FullPath,\n                // 0755\n                UnixFileMode.UserRead\n                    | UnixFileMode.UserWrite\n                    | UnixFileMode.UserExecute\n                    | UnixFileMode.GroupRead\n                    | UnixFileMode.GroupExecute\n                    | UnixFileMode.OtherRead\n                    | UnixFileMode.OtherExecute\n            );\n        }\n\n        // Handle Linux AppImage update\n        if (Compat.IsLinux && Environment.GetEnvironmentVariable(\"APPIMAGE\") is { } appImage)\n        {\n            try\n            {\n                var updateScriptPath = UpdateHelper.UpdateFolder.JoinFile(\"update_script.sh\").FullPath;\n                var newAppImage = UpdateHelper.ExecutablePath.FullPath;\n\n                var scriptContent = \"\"\"\n#!/bin/bash\nset -e\n\n# Wait for the process to exit\nwhile kill -0 \"$PID\" 2>/dev/null; do\n    sleep 0.5\ndone\n\n# Move the new AppImage over the old one\nmv -f \"$NEW_APPIMAGE\" \"$OLD_APPIMAGE\"\nchmod +x \"$OLD_APPIMAGE\"\n\n# Launch the new AppImage detached\n\"$OLD_APPIMAGE\" > /dev/null 2>&1 &\ndisown\n\"\"\";\n                await File.WriteAllTextAsync(updateScriptPath, scriptContent);\n\n                File.SetUnixFileMode(\n                    updateScriptPath,\n                    UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute\n                );\n\n                var startInfo = new System.Diagnostics.ProcessStartInfo\n                {\n                    FileName = \"/usr/bin/env\",\n                    Arguments = $\"bash \\\"{updateScriptPath}\\\"\",\n                    UseShellExecute = false,\n                    CreateNoWindow = true,\n                };\n\n                startInfo.EnvironmentVariables[\"PID\"] = Environment.ProcessId.ToString();\n                startInfo.EnvironmentVariables[\"NEW_APPIMAGE\"] = newAppImage;\n                startInfo.EnvironmentVariables[\"OLD_APPIMAGE\"] = appImage;\n\n                System.Diagnostics.Process.Start(startInfo);\n\n                App.Shutdown();\n                return;\n            }\n            catch (Exception ex)\n            {\n                logger.LogError(ex, \"Failed to execute AppImage update script\");\n\n                var dialog = DialogHelper.CreateMarkdownDialog(\n                    \"AppImage update script failed. \\nCould not replace old AppImage with new version. Please check directory permissions. \\nFalling back to standard update process. User intervention required: \\nAfter program closes, \\nplease move the new AppImage extracted in the '.StabilityMatrixUpdate' hidden directory to the old AppImage overwriting it. \\n\\nClose this dialog to continue with standard update process.\",\n                    Resources.Label_UnexpectedErrorOccurred\n                );\n\n                await dialog.ShowAsync();\n            }\n        }\n\n        // Set current version for update messages\n        settingsManager.Transaction(\n            s => s.UpdatingFromVersion = Compat.AppVersion,\n            ignoreMissingLibraryDir: true\n        );\n\n        UpdateText = \"Getting a few things ready...\";\n        await using (new MinimumDelay(500, 1000))\n        {\n            await Task.Run(() =>\n            {\n                var args = new[] { \"--wait-for-exit-pid\", $\"{Environment.ProcessId}\" };\n\n                if (Program.Args.NoSentry)\n                {\n                    args = args.Append(\"--no-sentry\").ToArray();\n                }\n\n                ProcessRunner.StartApp(UpdateHelper.ExecutablePath.FullPath, args);\n            });\n        }\n\n        UpdateText = \"Update complete. Restarting Stability Matrix in 3 seconds...\";\n        await Task.Delay(1000);\n        UpdateText = \"Update complete. Restarting Stability Matrix in 2 seconds...\";\n        await Task.Delay(1000);\n        UpdateText = \"Update complete. Restarting Stability Matrix in 1 second...\";\n        await Task.Delay(1000);\n        UpdateText = \"Update complete. Restarting Stability Matrix...\";\n\n        App.Shutdown();\n    }\n\n    internal async Task<string> GetReleaseNotes(string changelogUrl)\n    {\n        using var client = httpClientFactory.CreateClient();\n\n        try\n        {\n            var response = await client.GetAsync(changelogUrl);\n            if (response.IsSuccessStatusCode)\n            {\n                var changelog = await response.Content.ReadAsStringAsync();\n\n                // Formatting for new changelog format\n                // https://keepachangelog.com/en/1.1.0/\n                if (changelogUrl.EndsWith(\".md\", StringComparison.OrdinalIgnoreCase))\n                {\n                    return FormatChangelog(\n                            changelog,\n                            Compat.AppVersion,\n                            settingsManager.Settings.PreferredUpdateChannel\n                        ) ?? \"## Unable to format release notes\";\n                }\n\n                return changelog;\n            }\n\n            return \"## Unable to load release notes\";\n        }\n        catch (HttpRequestException e)\n        {\n            return $\"## Unable to fetch release notes ({e.StatusCode})\\n\\n[{changelogUrl}]({changelogUrl})\";\n        }\n        catch (TaskCanceledException) { }\n\n        return $\"## Unable to fetch release notes\\n\\n[{changelogUrl}]({changelogUrl})\";\n    }\n\n    /// <summary>\n    /// Formats changelog markdown including up to the current version\n    /// </summary>\n    /// <param name=\"markdown\">Markdown to format</param>\n    /// <param name=\"currentVersion\">Versions equal or below this are excluded</param>\n    /// <param name=\"maxChannel\">Maximum channel level to include</param>\n    internal static string? FormatChangelog(\n        string markdown,\n        SemVersion currentVersion,\n        UpdateChannel maxChannel = UpdateChannel.Stable\n    )\n    {\n        var pattern = RegexChangelog();\n\n        var results = pattern\n            .Matches(markdown)\n            .Select(m => new\n            {\n                Block = m.Groups[1].Value.Trim(),\n                Version = SemVersion.TryParse(\n                    m.Groups[2].Value.Trim(),\n                    SemVersionStyles.AllowV,\n                    out var version\n                )\n                    ? version\n                    : null,\n                Content = m.Groups[3].Value.Trim(),\n            })\n            .Where(x => x.Version is not null)\n            .ToList();\n\n        // Join all blocks until and excluding the current version\n        // If we're on a pre-release, include the current release\n        var currentVersionBlock = results.FindIndex(x => x.Version == currentVersion.WithoutMetadata());\n\n        // For mismatching build metadata, add one\n        if (\n            currentVersionBlock != -1\n            && results[currentVersionBlock].Version?.Metadata != currentVersion.Metadata\n        )\n        {\n            currentVersionBlock++;\n        }\n\n        // Support for previous pre-release without changelogs\n        if (currentVersionBlock == -1)\n        {\n            currentVersionBlock = results.FindIndex(x =>\n                x.Version == currentVersion.WithoutPrereleaseOrMetadata()\n            );\n\n            // Add 1 if found to include the current release\n            if (currentVersionBlock != -1)\n            {\n                currentVersionBlock++;\n            }\n        }\n\n        // Still not found, just include all\n        if (currentVersionBlock == -1)\n        {\n            currentVersionBlock = results.Count;\n        }\n\n        // Filter out pre-releases\n        var blocks = results\n            .Take(currentVersionBlock)\n            .Where(x =>\n                x.Version!.PrereleaseIdentifiers.Count == 0\n                || x.Version.PrereleaseIdentifiers[0].Value switch\n                {\n                    \"pre\" when maxChannel >= UpdateChannel.Preview => true,\n                    \"dev\" when maxChannel >= UpdateChannel.Development => true,\n                    _ => false,\n                }\n            )\n            .Select(x => x.Block);\n\n        return string.Join(Environment.NewLine + Environment.NewLine, blocks);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Styles;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(FirstLaunchSetupWindow))]\n[ManagedService]\n[RegisterSingleton<FirstLaunchSetupViewModel>]\npublic partial class FirstLaunchSetupViewModel : DisposableViewModelBase\n{\n    [ObservableProperty]\n    private bool eulaAccepted;\n\n    [ObservableProperty]\n    private string gpuInfoText = string.Empty;\n\n    [ObservableProperty]\n    private RefreshBadgeViewModel checkHardwareBadge =\n        new()\n        {\n            WorkingToolTipText = Resources.Label_CheckingHardware,\n            SuccessToolTipText = Resources.Label_EverythingLooksGood,\n            FailToolTipText = Resources.Label_NvidiaGpuRecommended,\n            FailColorBrush = ThemeColors.ThemeYellow,\n        };\n\n    [ObservableProperty]\n    private bool selectDifferentGpu;\n\n    [ObservableProperty]\n    private ObservableCollection<GpuInfo> gpuInfoCollection = [];\n\n    [ObservableProperty]\n    private GpuInfo? selectedGpu;\n\n    public string YouCanChangeThis =>\n        string.Format(\n            Resources.TextTemplate_YouCanChangeThisBehavior,\n            \"Settings > System Settings > Default GPU\"\n        );\n\n    public FirstLaunchSetupViewModel(ISettingsManager settingsManager)\n    {\n        CheckHardwareBadge.RefreshFunc = SetGpuInfo;\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.SelectedGpu,\n                settings => settings.PreferredGpu,\n                true\n            )\n        );\n    }\n\n    private async Task<bool> SetGpuInfo()\n    {\n        GpuInfo[] gpuInfo;\n\n        await using (new MinimumDelay(800, 1200))\n        {\n            // Query GPU info\n            gpuInfo = await Task.Run(() => HardwareHelper.IterGpuInfo().ToArray());\n            GpuInfoCollection = new ObservableCollection<GpuInfo>(gpuInfo);\n        }\n\n        // First Nvidia GPU\n        var activeGpu = gpuInfo.FirstOrDefault(\n            gpu => gpu.Name?.Contains(\"nvidia\", StringComparison.InvariantCultureIgnoreCase) ?? false\n        );\n        var isNvidia = activeGpu is not null;\n\n        // Otherwise first GPU\n        activeGpu ??= gpuInfo.FirstOrDefault();\n\n        SelectedGpu = activeGpu;\n        GpuInfoText = activeGpu is null\n            ? \"No GPU detected\"\n            : $\"{activeGpu.Name} ({Size.FormatBytes(activeGpu.MemoryBytes)})\";\n\n        // Always succeed for macos arm\n        if (Compat.IsMacOS && Compat.IsArm)\n        {\n            return true;\n        }\n\n        return isNvidia;\n    }\n\n    public override void OnLoaded()\n    {\n        base.OnLoaded();\n        CheckHardwareBadge.RefreshCommand.ExecuteAsync(null).SafeFireAndForget();\n    }\n\n    [RelayCommand]\n    private void ToggleManualGpu()\n    {\n        SelectDifferentGpu = !SelectDifferentGpu;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/HuggingFacePage/CategoryViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing DynamicData;\nusing DynamicData.Binding;\nusing StabilityMatrix.Avalonia.Models.HuggingFace;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.HuggingFacePage;\n\npublic partial class CategoryViewModel : ViewModelBase\n{\n    [ObservableProperty]\n    private IObservableCollection<HuggingfaceItemViewModel> items =\n        new ObservableCollectionExtended<HuggingfaceItemViewModel>();\n\n    public SourceCache<HuggingfaceItem, string> ItemsCache { get; } =\n        new(i => i.RepositoryPath + i.ModelName);\n\n    [ObservableProperty]\n    private string? title;\n\n    [ObservableProperty]\n    private bool isChecked;\n\n    [ObservableProperty]\n    private int numSelected;\n\n    public CategoryViewModel(IEnumerable<HuggingfaceItem> items, string modelsDir)\n    {\n        ItemsCache\n            .Connect()\n            .DeferUntilLoaded()\n            .Transform(i => new HuggingfaceItemViewModel { Item = i, ModelsDir = modelsDir })\n            .Bind(Items)\n            .WhenPropertyChanged(p => p.IsSelected)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(_ => NumSelected = Items.Count(i => i.IsSelected));\n\n        ItemsCache.EditDiff(items, (a, b) => a.RepositoryPath == b.RepositoryPath);\n    }\n\n    partial void OnIsCheckedChanged(bool value)\n    {\n        if (Items is null)\n            return;\n\n        foreach (var item in Items)\n        {\n            if (item.Exists)\n                continue;\n\n            item.IsSelected = value;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/HuggingFacePage/HuggingfaceItemViewModel.cs",
    "content": "﻿using System.IO;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing StabilityMatrix.Avalonia.Models.HuggingFace;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.HuggingFacePage;\n\npublic partial class HuggingfaceItemViewModel : ViewModelBase\n{\n    [ObservableProperty]\n    private HuggingfaceItem item;\n\n    [ObservableProperty]\n    private bool isSelected;\n\n    public string LicenseUrl =>\n        $\"https://huggingface.co/{Item.RepositoryPath}/blob/main/{Item.LicensePath ?? \"README.md\"}\";\n    public string RepoUrl => $\"https://huggingface.co/{Item.RepositoryPath}\";\n\n    public required string? ModelsDir { get; init; }\n\n    public bool Exists =>\n        ModelsDir != null\n        && File.Exists(\n            Path.Combine(\n                ModelsDir,\n                Item.ModelCategory.ConvertTo<SharedFolderType>().ToString(),\n                Item.Subfolder ?? string.Empty,\n                Path.GetFileName(Item.Files[0]).Equals(\"ae.safetensors\")\n                && Item.ModelName.Equals(\"HiDream I1 VAE\")\n                    ? \"hidream_vae.safetensors\"\n                    : Path.GetFileName(Item.Files[0])\n            )\n        );\n\n    [RelayCommand]\n    private void ToggleSelected()\n    {\n        IsSelected = !IsSelected;\n    }\n\n    public void NotifyExistsChanged()\n    {\n        OnPropertyChanged(nameof(Exists));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/IDropTarget.cs",
    "content": "﻿using Avalonia.Input;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\npublic interface IDropTarget\n{\n    void DragOver(object? sender, DragEventArgs e);\n    void Drop(object? sender, DragEventArgs e);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/BatchSizeCardViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(BatchSizeCard))]\n[ManagedService]\n[RegisterTransient<BatchSizeCardViewModel>]\npublic partial class BatchSizeCardViewModel : LoadableViewModelBase, IComfyStep\n{\n    [NotifyDataErrorInfo]\n    [ObservableProperty]\n    [Range(1, 1024)]\n    private int batchSize = 1;\n\n    [NotifyDataErrorInfo]\n    [ObservableProperty]\n    [Range(1, int.MaxValue)]\n    private int batchCount = 1;\n\n    [NotifyDataErrorInfo]\n    [ObservableProperty]\n    [Required]\n    private bool isBatchIndexEnabled;\n\n    [NotifyDataErrorInfo]\n    [ObservableProperty]\n    [Range(1, 1024)]\n    private int batchIndex = 1;\n\n    /// <summary>\n    /// Sets batch size to connections.\n    /// Provides:\n    /// <list type=\"number\">\n    /// <item><see cref=\"ComfyNodeBuilder.NodeBuilderConnections.BatchSize\"/></item>\n    /// <item><see cref=\"ComfyNodeBuilder.NodeBuilderConnections.BatchIndex\"/></item>\n    /// </list>\n    /// </summary>\n    public void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        e.Builder.Connections.BatchSize = BatchSize;\n        e.Builder.Connections.BatchIndex = IsBatchIndexEnabled ? BatchIndex : null;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/CfzCudnnToggleCardViewModel.cs",
    "content": "using CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(CfzCudnnToggleCard))]\n[ManagedService]\n[RegisterTransient<CfzCudnnToggleCardViewModel>]\npublic partial class CfzCudnnToggleCardViewModel : LoadableViewModelBase\n{\n    public const string ModuleKey = \"CfzCudnnToggle\";\n\n    [ObservableProperty]\n    private bool disableCudnn = true;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/ControlNetCardViewModel.cs",
    "content": "﻿using System;\nusing System.ComponentModel.DataAnnotations;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData.Binding;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(ControlNetCard))]\n[ManagedService]\n[RegisterTransient<ControlNetCardViewModel>]\npublic partial class ControlNetCardViewModel : LoadableViewModelBase\n{\n    public const string ModuleKey = \"ControlNet\";\n\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n\n    [ObservableProperty]\n    [Required]\n    private HybridModelFile? selectedModel;\n\n    [ObservableProperty]\n    [Required]\n    private ComfyAuxPreprocessor? selectedPreprocessor;\n\n    [ObservableProperty]\n    [Required]\n    [Range(0, 2048)]\n    private int width;\n\n    [ObservableProperty]\n    [Required]\n    [Range(0, 2048)]\n    private int height;\n\n    [ObservableProperty]\n    [Required]\n    [Range(0d, 10d)]\n    private double strength = 1.0;\n\n    [ObservableProperty]\n    [Required]\n    [Range(0d, 1d)]\n    private double startPercent;\n\n    [ObservableProperty]\n    [Required]\n    [Range(0d, 1d)]\n    private double endPercent = 1.0;\n\n    public SelectImageCardViewModel SelectImageCardViewModel { get; }\n\n    public IInferenceClientManager ClientManager { get; }\n\n    public ControlNetCardViewModel(\n        IInferenceClientManager clientManager,\n        IServiceManager<ViewModelBase> vmFactory\n    )\n    {\n        this.vmFactory = vmFactory;\n\n        ClientManager = clientManager;\n        SelectImageCardViewModel = vmFactory.Get<SelectImageCardViewModel>();\n\n        // Update our width and height when the image changes\n        SelectImageCardViewModel\n            .WhenPropertyChanged(card => card.CurrentBitmapSize)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(propertyValue =>\n            {\n                if (!propertyValue.Value.IsEmpty)\n                {\n                    Width = propertyValue.Value.Width;\n                    Height = propertyValue.Value.Height;\n                }\n            });\n    }\n\n    [RelayCommand]\n    private async Task PreviewPreprocessor(ComfyAuxPreprocessor? preprocessor)\n    {\n        if (\n            preprocessor is null\n            || SelectImageCardViewModel.ImageSource is not { } imageSource\n            || SelectImageCardViewModel.IsImageFileNotFound\n        )\n            return;\n\n        var args = new InferenceQueueCustomPromptEventArgs();\n\n        var images = SelectImageCardViewModel.GetInputImages();\n\n        await ClientManager.UploadInputImageAsync(imageSource);\n\n        var image = args.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.LoadImage\n            {\n                Name = args.Nodes.GetUniqueName(\"Preprocessor_LoadImage\"),\n                Image =\n                    SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached(\"Inference\")\n                    ?? throw new ValidationException(\"No ImageSource\")\n            }\n        ).Output1;\n\n        var aioPreprocessor = args.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.AIOPreprocessor\n            {\n                Name = args.Nodes.GetUniqueName(\"Preprocessor\"),\n                Image = image,\n                Preprocessor = preprocessor.ToString(),\n                // AIO wants the lower of the two resolutions. who knows why.\n                // also why can't we put in the low/high thresholds?\n                // Or any of the other parameters for the other preprocessors?\n                Resolution = Math.Min(Width, Height)\n            }\n        );\n\n        args.Builder.Connections.OutputNodes.Add(\n            args.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.PreviewImage\n                {\n                    Name = args.Nodes.GetUniqueName(\"Preprocessor_OutputImage\"),\n                    Images = aioPreprocessor.Output\n                }\n            )\n        );\n\n        // Queue\n        Dispatcher.UIThread.Post(() => EventManager.Instance.OnInferenceQueueCustomPrompt(args));\n\n        // We don't know when it's done so wait a bit?\n        await Task.Delay(1000);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/DiscreteModelSamplingCardViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(DiscreteModelSamplingCard))]\n[ManagedService]\n[RegisterTransient<DiscreteModelSamplingCardViewModel>]\npublic partial class DiscreteModelSamplingCardViewModel : LoadableViewModelBase\n{\n    public const string ModuleKey = \"DiscreteModelSampling\";\n    public List<string> SamplingMethods => [\"eps\", \"v_prediction\", \"lcm\", \"x0\"];\n\n    [ObservableProperty]\n    private bool isZsnrEnabled;\n\n    [ObservableProperty]\n    private string selectedSamplingMethod = \"eps\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/ExtraNetworkCardViewModel.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Reactive;\nusing System.Reactive.Linq;\nusing System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Services;\n\n#pragma warning disable CS0657 // Not a valid attribute location for this declaration\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(ExtraNetworkCard))]\n[ManagedService]\n[RegisterTransient<ExtraNetworkCardViewModel>]\npublic partial class ExtraNetworkCardViewModel : DisposableLoadableViewModelBase\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly ModelCompatChecker modelCompatChecker = new();\n\n    public const string ModuleKey = \"ExtraNetwork\";\n\n    /// <summary>\n    /// Whether user can toggle model weight visibility\n    /// </summary>\n    [JsonIgnore]\n    public bool IsModelWeightToggleEnabled { get; set; }\n\n    /// <summary>\n    /// Whether user can toggle clip weight visibility\n    /// </summary>\n    [JsonIgnore]\n    public bool IsClipWeightToggleEnabled { get; set; }\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(TriggerWords), nameof(ShowTriggerWords))]\n    private HybridModelFile? selectedModel;\n\n    [ObservableProperty]\n    private bool isModelWeightEnabled;\n\n    [ObservableProperty]\n    [property: Category(\"Settings\")]\n    [property: DisplayName(\"CLIP Strength Adjustment\")]\n    private bool isClipWeightEnabled;\n\n    [ObservableProperty]\n    private double modelWeight = 1.0;\n\n    [ObservableProperty]\n    private double clipWeight = 1.0;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedBaseModel;\n\n    /// <inheritdoc/>\n    public ExtraNetworkCardViewModel(IInferenceClientManager clientManager, ISettingsManager settingsManager)\n    {\n        this.settingsManager = settingsManager;\n        ClientManager = clientManager;\n\n        // Observable signal when SelectedBaseModel changes\n        var baseModelChangedSignal = this.WhenPropertyChanged(vm => vm.SelectedBaseModel)\n            .Throttle(TimeSpan.FromMilliseconds(50))\n            .Select(_ => Unit.Default);\n\n        // Observable signal when the FilterExtraNetworksByBaseModel setting changes\n        var settingChangedSignal = settingsManager\n            .ObservePropertyChanged(s => s.FilterExtraNetworksByBaseModel)\n            .Select(_ => Unit.Default);\n\n        // Combine signals\n        var reapplyFilterSignal = Observable\n            .Merge([baseModelChangedSignal, settingChangedSignal])\n            // StartWith ensures the filter is applied at least once initially\n            .StartWith(Unit.Default);\n\n        var filterPredicate = reapplyFilterSignal\n            .ObserveOn(SynchronizationContext.Current!)\n            .Select(_ =>\n            {\n                if (!settingsManager.Settings.FilterExtraNetworksByBaseModel)\n                    return (Func<HybridModelFile, bool>)(_ => true);\n\n                return (Func<HybridModelFile, bool>)FilterCompatibleLoras;\n            });\n\n        AddDisposable(\n            ClientManager\n                .LoraModelsChangeSet.DeferUntilLoaded()\n                .Filter(filterPredicate)\n                .SortAndBind(\n                    LoraModels,\n                    SortExpressionComparer<HybridModelFile>\n                        .Ascending(f => f.Type)\n                        .ThenByAscending(f => f.SortKey)\n                )\n                .ObserveOn(SynchronizationContext.Current!)\n                .Subscribe()\n        );\n    }\n\n    public IObservableCollection<HybridModelFile> LoraModels { get; } =\n        new ObservableCollectionExtended<HybridModelFile>();\n\n    public string TriggerWords =>\n        SelectedModel?.Local?.ConnectedModelInfo?.TrainedWordsString ?? string.Empty;\n    public bool ShowTriggerWords => !string.IsNullOrWhiteSpace(TriggerWords);\n\n    public IInferenceClientManager ClientManager { get; }\n\n    /// <inheritdoc />\n    public override JsonObject SaveStateToJsonObject()\n    {\n        return SerializeModel(\n            new ExtraNetworkCardModel\n            {\n                SelectedModelName = SelectedModel?.RelativePath,\n                IsModelWeightEnabled = IsModelWeightEnabled,\n                IsClipWeightEnabled = IsClipWeightEnabled,\n                ModelWeight = ModelWeight,\n                ClipWeight = ClipWeight,\n            }\n        );\n    }\n\n    /// <inheritdoc />\n    public override void LoadStateFromJsonObject(JsonObject state)\n    {\n        var model = DeserializeModel<ExtraNetworkCardModel>(state);\n\n        SelectedModel = model.SelectedModelName is null\n            ? null\n            : ClientManager.LoraModels.FirstOrDefault(x => x.RelativePath == model.SelectedModelName);\n\n        IsModelWeightEnabled = model.IsModelWeightEnabled;\n        IsClipWeightEnabled = model.IsClipWeightEnabled;\n        ModelWeight = model.ModelWeight;\n        ClipWeight = model.ClipWeight;\n    }\n\n    [RelayCommand]\n    private void CopyTriggerWords()\n    {\n        if (!ShowTriggerWords)\n            return;\n\n        App.Clipboard.SetTextAsync(TriggerWords);\n    }\n\n    private bool FilterCompatibleLoras(HybridModelFile? lora)\n    {\n        if (!settingsManager.Settings.FilterExtraNetworksByBaseModel)\n            return true;\n\n        return modelCompatChecker.IsLoraCompatibleWithBaseModel(lora, SelectedBaseModel) ?? true;\n    }\n\n    internal class ExtraNetworkCardModel\n    {\n        public string? SelectedModelName { get; init; }\n        public bool IsModelWeightEnabled { get; init; }\n        public bool IsClipWeightEnabled { get; init; }\n        public double ModelWeight { get; init; }\n        public double ClipWeight { get; init; }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/FaceDetailerViewModel.cs",
    "content": "﻿using System.Collections.ObjectModel;\nusing System.ComponentModel;\nusing System.Text.Json.Serialization;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(FaceDetailerCard))]\n[ManagedService]\n[RegisterTransient<FaceDetailerViewModel>]\npublic partial class FaceDetailerViewModel : LoadableViewModelBase\n{\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n    public const string ModuleKey = \"FaceDetailer\";\n\n    [ObservableProperty]\n    private bool guideSizeFor = true;\n\n    [ObservableProperty]\n    private int guideSize = 256;\n\n    [ObservableProperty]\n    private int maxSize = 768;\n\n    [ObservableProperty]\n    [property: Category(\"Settings\"), DisplayName(\"Step Count Selection\")]\n    private bool isStepsEnabled;\n\n    [ObservableProperty]\n    private int steps = 20;\n\n    [ObservableProperty]\n    [property: Category(\"Settings\"), DisplayName(\"CFG Scale Selection\")]\n    private bool isCfgScaleEnabled;\n\n    [ObservableProperty]\n    private double cfg = 8;\n\n    [ObservableProperty]\n    [property: Category(\"Settings\"), DisplayName(\"Sampler Selection\")]\n    private bool isSamplerSelectionEnabled;\n\n    [ObservableProperty]\n    private ComfySampler? sampler = ComfySampler.Euler;\n\n    [ObservableProperty]\n    [property: Category(\"Settings\"), DisplayName(\"Scheduler Selection\")]\n    private bool isSchedulerSelectionEnabled;\n\n    [ObservableProperty]\n    private ComfyScheduler? scheduler = ComfyScheduler.Normal;\n\n    [ObservableProperty]\n    private double denoise = 0.5d;\n\n    [ObservableProperty]\n    private int feather = 5;\n\n    [ObservableProperty]\n    private bool noiseMask = true;\n\n    [ObservableProperty]\n    private bool forceInpaint = false;\n\n    [ObservableProperty]\n    private double bboxThreshold = 0.5d;\n\n    [ObservableProperty]\n    private int bboxDilation = 10;\n\n    [ObservableProperty]\n    private int bboxCropFactor = 3;\n\n    [ObservableProperty]\n    private string samDetectionHint = \"center-1\";\n\n    [ObservableProperty]\n    private int samDilation = 0;\n\n    [ObservableProperty]\n    private double samThreshold = 0.93d;\n\n    [ObservableProperty]\n    private int samBboxExpansion = 0;\n\n    [ObservableProperty]\n    private double samMaskHintThreshold = 0.7d;\n\n    [ObservableProperty]\n    private string samMaskHintUseNegative = \"False\";\n\n    [ObservableProperty]\n    private int dropSize = 10;\n\n    [ObservableProperty]\n    private int cycle = 1;\n\n    [ObservableProperty]\n    private HybridModelFile? bboxModel;\n\n    [ObservableProperty]\n    private HybridModelFile? segmModel;\n\n    [ObservableProperty]\n    private HybridModelFile? samModel;\n\n    [ObservableProperty]\n    private bool showSamModelSelector = true;\n\n    [ObservableProperty]\n    private bool useSeparatePrompt;\n\n    [ObservableProperty]\n    private string positivePrompt = string.Empty;\n\n    [ObservableProperty]\n    private string negativePrompt = string.Empty;\n\n    [ObservableProperty]\n    [property: Category(\"Settings\"), DisplayName(\"Inherit Seed\")]\n    private bool inheritSeed = true;\n\n    [ObservableProperty]\n    public partial bool UseTiledEncode { get; set; }\n\n    [ObservableProperty]\n    public partial bool UseTiledDecode { get; set; }\n\n    public IReadOnlyList<ComfyScheduler> AvailableSchedulers => ComfyScheduler.FaceDetailerDefaults;\n\n    /// <inheritdoc/>\n    public FaceDetailerViewModel(\n        IInferenceClientManager clientManager,\n        IServiceManager<ViewModelBase> vmFactory\n    )\n    {\n        this.vmFactory = vmFactory;\n        ClientManager = clientManager;\n        SeedCardViewModel = vmFactory.Get<SeedCardViewModel>();\n        SeedCardViewModel.GenerateNewSeed();\n        PromptCardViewModel = vmFactory.Get<PromptCardViewModel>();\n        WildcardViewModel = vmFactory.Get<PromptCardViewModel>(vm =>\n        {\n            vm.IsNegativePromptEnabled = false;\n            vm.IsStackCardEnabled = false;\n        });\n    }\n\n    [JsonPropertyName(\"DetailerSeed\")]\n    public SeedCardViewModel SeedCardViewModel { get; }\n\n    [JsonPropertyName(\"DetailerPrompt\")]\n    public PromptCardViewModel PromptCardViewModel { get; }\n\n    [JsonPropertyName(\"DetailerWildcard\")]\n    public PromptCardViewModel WildcardViewModel { get; }\n\n    public ObservableCollection<string> SamDetectionHints { get; set; } =\n        [\n            \"center-1\",\n            \"horizontal-2\",\n            \"vertical-2\",\n            \"rect-4\",\n            \"diamond-4\",\n            \"mask-area\",\n            \"mask-points\",\n            \"mask-point-bbox\",\n            \"none\",\n        ];\n\n    public ObservableCollection<string> SamMaskHintUseNegatives { get; set; } = [\"False\", \"Small\", \"Outter\"];\n\n    public IInferenceClientManager ClientManager { get; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/FreeUCardViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(FreeUCard))]\n[ManagedService]\n[RegisterTransient<FreeUCardViewModel>]\npublic partial class FreeUCardViewModel : LoadableViewModelBase\n{\n    public const string ModuleKey = \"FreeU\";\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(0D, 10D)]\n    private double b1 = 1.5;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(0D, 10D)]\n    private double b2 = 1.6;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(0D, 10D)]\n    private double s1 = 0.9;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(0D, 10D)]\n    private double s2 = 0.2;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/IImageGalleryComponent.cs",
    "content": "﻿using System.Linq;\nusing Avalonia.Threading;\nusing StabilityMatrix.Avalonia.Models;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\npublic interface IImageGalleryComponent\n{\n    ImageGalleryCardViewModel ImageGalleryCardViewModel { get; }\n\n    /// <summary>\n    /// Clears existing images and loads new ones\n    /// </summary>\n    public void LoadImagesToGallery(params ImageSource[] imageSources)\n    {\n        Dispatcher.UIThread.Post(() =>\n        {\n            ImageGalleryCardViewModel.SelectedImage = null;\n            ImageGalleryCardViewModel.SelectedImageIndex = -1;\n\n            ImageGalleryCardViewModel.ImageSources.Clear();\n\n            foreach (var imageSource in imageSources)\n            {\n                ImageGalleryCardViewModel.ImageSources.Add(imageSource);\n            }\n\n            ImageGalleryCardViewModel.SelectedImageIndex = imageSources.Length > 0 ? 0 : -1;\n            ImageGalleryCardViewModel.SelectedImage = imageSources.FirstOrDefault();\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardItemViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Models.Database;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\npublic partial class ImageFolderCardItemViewModel : ViewModelBase\n{\n    [ObservableProperty]\n    private LocalImageFile? localImageFile;\n\n    [ObservableProperty]\n    private string? imagePath;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing AsyncImageLoader;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Platform.Storage;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing FuzzySharp;\nusing FuzzySharp.PreProcess;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing SkiaSharp;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Native;\nusing SortDirection = DynamicData.Binding.SortDirection;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(ImageFolderCard))]\n[ManagedService]\n[RegisterTransient<ImageFolderCardViewModel>]\npublic partial class ImageFolderCardViewModel : DisposableViewModelBase\n{\n    private readonly ILogger<ImageFolderCardViewModel> logger;\n    private readonly IImageIndexService imageIndexService;\n    private readonly ISettingsManager settingsManager;\n    private readonly INotificationService notificationService;\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n\n    [ObservableProperty]\n    private string? searchQuery;\n\n    [ObservableProperty]\n    private Size imageSize = new(150, 190);\n\n    /// <summary>\n    /// Collection of local image files\n    /// </summary>\n    public IObservableCollection<LocalImageFile> LocalImages { get; } =\n        new ObservableCollectionExtended<LocalImageFile>();\n\n    public ImageFolderCardViewModel(\n        ILogger<ImageFolderCardViewModel> logger,\n        IImageIndexService imageIndexService,\n        ISettingsManager settingsManager,\n        INotificationService notificationService,\n        IServiceManager<ViewModelBase> vmFactory\n    )\n    {\n        this.logger = logger;\n        this.imageIndexService = imageIndexService;\n        this.settingsManager = settingsManager;\n        this.notificationService = notificationService;\n        this.vmFactory = vmFactory;\n\n        var searcher = new ImageSearcher();\n\n        // Observable predicate from SearchQuery changes\n        var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchQuery)\n            .Throttle(TimeSpan.FromMilliseconds(50))!\n            .Select(property => searcher.GetPredicate(property.Value))\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        imageIndexService\n            .InferenceImages.ItemsSource.Connect()\n            .DeferUntilLoaded()\n            .Filter(searchPredicate)\n            .SortBy(file => file.LastModifiedAt, SortDirection.Descending)\n            .Bind(LocalImages)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.ImageSize,\n                settings => settings.InferenceImageSize,\n                delay: TimeSpan.FromMilliseconds(250)\n            )\n        );\n    }\n\n    private static bool SearchPredicate(LocalImageFile file, string? query)\n    {\n        if (\n            string.IsNullOrWhiteSpace(query)\n            || file.FileName.Contains(query, StringComparison.OrdinalIgnoreCase)\n        )\n        {\n            return true;\n        }\n\n        // File name\n        var filenameScore = Fuzz.WeightedRatio(query, file.FileName, PreprocessMode.Full);\n        if (filenameScore > 80)\n        {\n            return true;\n        }\n\n        // Generation params\n        if (file.GenerationParameters is { } parameters)\n        {\n            if (\n                parameters.Seed.ToString().StartsWith(query, StringComparison.OrdinalIgnoreCase)\n                || parameters.Sampler is { } sampler\n                    && sampler.StartsWith(query, StringComparison.OrdinalIgnoreCase)\n                || parameters.ModelName is { } modelName\n                    && modelName.StartsWith(query, StringComparison.OrdinalIgnoreCase)\n            )\n            {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /// <inheritdoc />\n    public override async Task OnLoadedAsync()\n    {\n        await base.OnLoadedAsync();\n        ImageSize = settingsManager.Settings.InferenceImageSize;\n        imageIndexService.RefreshIndexForAllCollections().SafeFireAndForget();\n    }\n\n    /// <summary>\n    /// Gets the image path if it exists, returns null.\n    /// If the image path is resolved but the file doesn't exist, it will be removed from the index.\n    /// </summary>\n    private FilePath? GetImagePathIfExists(LocalImageFile item)\n    {\n        var imageFile = new FilePath(item.AbsolutePath);\n\n        if (!imageFile.Exists)\n        {\n            // Remove from index\n            imageIndexService.InferenceImages.Remove(item);\n\n            // Invalidate cache\n            if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)\n            {\n                loader.RemoveAllNamesFromCache(imageFile.Name);\n            }\n\n            return null;\n        }\n\n        return imageFile;\n    }\n\n    /// <summary>\n    /// Handles image clicks to show preview\n    /// </summary>\n    [RelayCommand]\n    private async Task OnImageClick(LocalImageFile item)\n    {\n        if (GetImagePathIfExists(item) is not { } imageFile)\n        {\n            return;\n        }\n\n        var currentIndex = LocalImages.IndexOf(item);\n\n        var image = new ImageSource(imageFile);\n\n        // Preload\n        await image.GetBitmapAsync();\n\n        var vm = vmFactory.Get<ImageViewerViewModel>();\n        vm.ImageSource = image;\n        vm.LocalImageFile = item;\n\n        using var onNext = Observable\n            .FromEventPattern<DirectionalNavigationEventArgs>(\n                vm,\n                nameof(ImageViewerViewModel.NavigationRequested)\n            )\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(ctx =>\n            {\n                Dispatcher\n                    .UIThread.InvokeAsync(async () =>\n                    {\n                        var sender = (ImageViewerViewModel)ctx.Sender!;\n                        var newIndex = currentIndex + (ctx.EventArgs.IsNext ? 1 : -1);\n\n                        if (newIndex >= 0 && newIndex < LocalImages.Count)\n                        {\n                            var newImage = LocalImages[newIndex];\n                            var newImageSource = new ImageSource(newImage.AbsolutePath);\n\n                            // Preload\n                            await newImageSource.GetBitmapAsync();\n                            await newImageSource.GetOrRefreshTemplateKeyAsync();\n\n                            // var oldImageSource = sender.ImageSource;\n\n                            sender.ImageSource = newImageSource;\n                            sender.LocalImageFile = newImage;\n\n                            // oldImageSource?.Dispose();\n\n                            currentIndex = newIndex;\n                        }\n                    })\n                    .SafeFireAndForget();\n            });\n\n        await vm.GetDialog().ShowAsync();\n    }\n\n    /// <summary>\n    /// Handles clicks to the image delete button\n    /// </summary>\n    [RelayCommand]\n    private async Task OnImageDelete(LocalImageFile? item)\n    {\n        if (item is null || GetImagePathIfExists(item) is not { } imageFile)\n        {\n            return;\n        }\n\n        // Delete the file\n        var isRecycle =\n            settingsManager.Settings.IsInferenceImageBrowserUseRecycleBinForDelete\n            && NativeFileOperations.IsRecycleBinAvailable;\n\n        var result = isRecycle\n            ? await notificationService.TryAsync(\n                NativeFileOperations.RecycleBin!.MoveFileToRecycleBinAsync(imageFile)\n            )\n            : await notificationService.TryAsync(imageFile.DeleteAsync());\n\n        if (result.IsSuccessful)\n        {\n            // Remove from index\n            imageIndexService.InferenceImages.Remove(item);\n\n            // Invalidate cache\n            if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)\n            {\n                loader.RemoveAllNamesFromCache(imageFile.Name);\n            }\n        }\n        else\n        {\n            logger.LogWarning(result.Exception, \"Failed to delete image\");\n        }\n    }\n\n    /// <summary>\n    /// Handles clicks to the image delete button\n    /// </summary>\n    [RelayCommand]\n    private async Task OnImageCopy(LocalImageFile? item)\n    {\n        if (item is null || GetImagePathIfExists(item) is not { } imageFile)\n        {\n            return;\n        }\n\n        var clipboard = App.Clipboard;\n\n        await clipboard.SetFileDataObjectAsync(imageFile.FullPath);\n    }\n\n    /// <summary>\n    /// Handles clicks to the image open-in-explorer button\n    /// </summary>\n    [RelayCommand]\n    private async Task OnImageOpen(LocalImageFile? item)\n    {\n        if (item is null || GetImagePathIfExists(item) is not { } imageFile)\n        {\n            return;\n        }\n\n        await ProcessRunner.OpenFileBrowser(imageFile);\n    }\n\n    /// <summary>\n    /// Handles clicks to the image export button\n    /// </summary>\n    private async Task ImageExportImpl(\n        LocalImageFile? item,\n        SKEncodedImageFormat format,\n        bool includeMetadata = false\n    )\n    {\n        if (item is null || GetImagePathIfExists(item) is not { } sourceFile)\n        {\n            return;\n        }\n\n        var formatName = format.ToString();\n\n        var storageFile = await App.StorageProvider.SaveFilePickerAsync(\n            new FilePickerSaveOptions\n            {\n                Title = \"Export Image\",\n                ShowOverwritePrompt = true,\n                SuggestedFileName = item.FileNameWithoutExtension,\n                DefaultExtension = formatName.ToLowerInvariant(),\n                FileTypeChoices = new FilePickerFileType[]\n                {\n                    new(formatName)\n                    {\n                        Patterns = new[] { $\"*.{formatName.ToLowerInvariant()}\" },\n                        MimeTypes = new[] { $\"image/{formatName.ToLowerInvariant()}\" },\n                    },\n                },\n            }\n        );\n\n        if (storageFile?.TryGetLocalPath() is not { } targetPath)\n        {\n            return;\n        }\n\n        var targetFile = new FilePath(targetPath);\n\n        try\n        {\n            if (format is SKEncodedImageFormat.Png)\n            {\n                // For include metadata, just copy the file\n                if (includeMetadata)\n                {\n                    await sourceFile.CopyToAsync(targetFile, true);\n                }\n                else\n                {\n                    // Otherwise read and strip the metadata\n                    var imageBytes = await sourceFile.ReadAllBytesAsync();\n\n                    imageBytes = PngDataHelper.RemoveMetadata(imageBytes);\n\n                    await targetFile.WriteAllBytesAsync(imageBytes);\n                }\n            }\n            else\n            {\n                await Task.Run(() =>\n                {\n                    using var fs = sourceFile.Info.OpenRead();\n                    var image = SKImage.FromEncodedData(fs);\n                    fs.Dispose();\n\n                    using var targetStream = targetFile.Info.OpenWrite();\n                    image.Encode(format, 100).SaveTo(targetStream);\n                });\n            }\n        }\n        catch (IOException e)\n        {\n            logger.LogWarning(e, \"Failed to export image\");\n            notificationService.ShowPersistent(\"Failed to export image\", e.Message, NotificationType.Error);\n            return;\n        }\n\n        notificationService.Show(\"Image Exported\", $\"Saved to {targetPath}\", NotificationType.Success);\n    }\n\n    [RelayCommand]\n    private async Task CopySeedToClipboard(LocalImageFile? item)\n    {\n        if (item?.GenerationParameters is null || App.Clipboard is null)\n        {\n            return;\n        }\n\n        await App.Clipboard.SetTextAsync(item.GenerationParameters.Seed.ToString());\n    }\n\n    [RelayCommand]\n    private async Task CopyPromptToClipboard(LocalImageFile? item)\n    {\n        if (item?.GenerationParameters is null || App.Clipboard is null)\n        {\n            return;\n        }\n\n        await App.Clipboard.SetTextAsync(item.GenerationParameters.PositivePrompt);\n    }\n\n    [RelayCommand]\n    private async Task CopyNegativePromptToClipboard(LocalImageFile? item)\n    {\n        if (item?.GenerationParameters is null || App.Clipboard is null)\n        {\n            return;\n        }\n\n        await App.Clipboard.SetTextAsync(item.GenerationParameters.NegativePrompt);\n    }\n\n    [RelayCommand]\n    private async Task CopyModelNameToClipboard(LocalImageFile? item)\n    {\n        if (item?.GenerationParameters is null || App.Clipboard is null)\n        {\n            return;\n        }\n\n        await App.Clipboard.SetTextAsync(item.GenerationParameters.ModelName);\n    }\n\n    [RelayCommand]\n    private async Task CopyModelHashToClipboard(LocalImageFile? item)\n    {\n        if (item?.GenerationParameters is null || App.Clipboard is null)\n        {\n            return;\n        }\n\n        await App.Clipboard.SetTextAsync(item.GenerationParameters.ModelHash);\n    }\n\n    [RelayCommand]\n    private Task OnImageExportPng(LocalImageFile? item) => ImageExportImpl(item, SKEncodedImageFormat.Png);\n\n    [RelayCommand]\n    private Task OnImageExportPngWithMetadata(LocalImageFile? item) =>\n        ImageExportImpl(item, SKEncodedImageFormat.Png, true);\n\n    [RelayCommand]\n    private Task OnImageExportJpeg(LocalImageFile? item) => ImageExportImpl(item, SKEncodedImageFormat.Jpeg);\n\n    [RelayCommand]\n    private Task OnImageExportWebp(LocalImageFile? item) => ImageExportImpl(item, SKEncodedImageFormat.Webp);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Specialized;\nusing System.IO;\nusing System.Threading.Tasks;\nusing Avalonia.Collections;\nusing Avalonia.Media;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(ImageGalleryCard))]\n[ManagedService]\n[RegisterTransient<ImageGalleryCardViewModel>]\npublic partial class ImageGalleryCardViewModel : ViewModelBase\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n\n    [ObservableProperty]\n    private bool isPreviewOverlayEnabled;\n\n    [ObservableProperty]\n    private Bitmap? previewImage;\n\n    [ObservableProperty]\n    private AvaloniaList<ImageSource> imageSources = new();\n\n    [ObservableProperty]\n    private ImageSource? selectedImage;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(CanNavigateBack), nameof(CanNavigateForward))]\n    private int selectedImageIndex;\n\n    [ObservableProperty]\n    private bool isPixelGridEnabled;\n\n    public bool HasMultipleImages => ImageSources.Count > 1;\n\n    public bool CanNavigateBack => SelectedImageIndex > 0;\n    public bool CanNavigateForward => SelectedImageIndex < ImageSources.Count - 1;\n\n    public ImageGalleryCardViewModel(\n        IServiceManager<ViewModelBase> vmFactory,\n        ISettingsManager settingsManager\n    )\n    {\n        this.vmFactory = vmFactory;\n\n        IsPixelGridEnabled = settingsManager.Settings.IsImageViewerPixelGridEnabled;\n\n        settingsManager.RegisterPropertyChangedHandler(\n            s => s.IsImageViewerPixelGridEnabled,\n            newValue =>\n            {\n                IsPixelGridEnabled = newValue;\n            }\n        );\n\n        ImageSources.CollectionChanged += OnImageSourcesItemsChanged;\n    }\n\n    public void SetPreviewImage(byte[] imageBytes)\n    {\n        Dispatcher.UIThread.Post(() =>\n        {\n            using var stream = new MemoryStream(imageBytes);\n\n            var bitmap = new Bitmap(stream);\n\n            var currentImage = PreviewImage;\n\n            PreviewImage = bitmap;\n            IsPreviewOverlayEnabled = true;\n\n            // currentImage?.Dispose();\n        });\n    }\n\n    private void OnImageSourcesItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)\n    {\n        if (sender is AvaloniaList<ImageSource> sources)\n        {\n            if (\n                e.Action\n                is NotifyCollectionChangedAction.Add\n                    or NotifyCollectionChangedAction.Remove\n                    or NotifyCollectionChangedAction.Reset\n            )\n            {\n                if (sources.Count == 0)\n                {\n                    SelectedImageIndex = -1;\n                }\n                else if (SelectedImageIndex == -1)\n                {\n                    SelectedImageIndex = 0;\n                }\n                // Clamp the selected index to the new range\n                else\n                {\n                    SelectedImageIndex = Math.Clamp(SelectedImageIndex, 0, sources.Count - 1);\n                }\n                OnPropertyChanged(nameof(CanNavigateBack));\n                OnPropertyChanged(nameof(CanNavigateForward));\n                OnPropertyChanged(nameof(HasMultipleImages));\n            }\n        }\n    }\n\n    [RelayCommand]\n    // ReSharper disable once UnusedMember.Local\n    private async Task FlyoutCopy(IImage? image)\n    {\n        if (image is null)\n        {\n            Logger.Trace(\"FlyoutCopy: image is null\");\n            return;\n        }\n\n        Logger.Trace($\"FlyoutCopy is copying bitmap...\");\n\n        await Task.Run(() =>\n        {\n            if (Compat.IsWindows)\n            {\n                WindowsClipboard.SetBitmap((Bitmap)image);\n            }\n        });\n    }\n\n    [RelayCommand]\n    // ReSharper disable once UnusedMember.Local\n    private async Task FlyoutPreview(IImage? image)\n    {\n        if (image is null)\n        {\n            Logger.Trace(\"FlyoutPreview: image is null\");\n            return;\n        }\n\n        Logger.Trace($\"FlyoutPreview opening...\");\n\n        var viewerVm = vmFactory.Get<ImageViewerViewModel>();\n        viewerVm.ImageSource = new ImageSource((Bitmap)image);\n\n        var dialog = new BetterContentDialog { Content = new ImageViewerDialog { DataContext = viewerVm } };\n\n        await dialog.ShowAsync();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/InferenceFluxTextToImageViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\nusing StabilityMatrix.Avalonia.Views.Inference;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(InferenceTextToImageView), IsPersistent = true)]\n[ManagedService]\n[RegisterScoped<InferenceFluxTextToImageViewModel>]\npublic class InferenceFluxTextToImageViewModel : InferenceGenerationViewModelBase, IParametersLoadableState\n{\n    private readonly INotificationService notificationService;\n    private readonly IModelIndexService modelIndexService;\n\n    [JsonIgnore]\n    public StackCardViewModel StackCardViewModel { get; }\n\n    [JsonPropertyName(\"Modules\")]\n    public StackEditableCardViewModel ModulesCardViewModel { get; }\n\n    [JsonPropertyName(\"Model\")]\n    public UnetModelCardViewModel ModelCardViewModel { get; }\n\n    [JsonPropertyName(\"Sampler\")]\n    public SamplerCardViewModel SamplerCardViewModel { get; }\n\n    [JsonPropertyName(\"Prompt\")]\n    public PromptCardViewModel PromptCardViewModel { get; }\n\n    [JsonPropertyName(\"BatchSize\")]\n    public BatchSizeCardViewModel BatchSizeCardViewModel { get; }\n\n    [JsonPropertyName(\"Seed\")]\n    public SeedCardViewModel SeedCardViewModel { get; }\n\n    public InferenceFluxTextToImageViewModel(\n        IServiceManager<ViewModelBase> vmFactory,\n        IInferenceClientManager inferenceClientManager,\n        INotificationService notificationService,\n        ISettingsManager settingsManager,\n        RunningPackageService runningPackageService\n    )\n        : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService)\n    {\n        this.notificationService = notificationService;\n        this.modelIndexService = modelIndexService;\n\n        SeedCardViewModel = vmFactory.Get<SeedCardViewModel>();\n        SeedCardViewModel.GenerateNewSeed();\n\n        ModelCardViewModel = vmFactory.Get<UnetModelCardViewModel>();\n\n        SamplerCardViewModel = vmFactory.Get<SamplerCardViewModel>(samplerCard =>\n        {\n            samplerCard.IsDimensionsEnabled = true;\n            samplerCard.IsCfgScaleEnabled = true;\n            samplerCard.IsSamplerSelectionEnabled = true;\n            samplerCard.IsSchedulerSelectionEnabled = true;\n            samplerCard.DenoiseStrength = 1.0d;\n            samplerCard.EnableAddons = false;\n            samplerCard.SelectedSampler = ComfySampler.Euler;\n            samplerCard.SelectedScheduler = ComfyScheduler.Simple;\n            samplerCard.CfgScale = 3.5d;\n            samplerCard.Width = 1024;\n            samplerCard.Height = 1024;\n        });\n\n        PromptCardViewModel = vmFactory.Get<PromptCardViewModel>(promptCard =>\n        {\n            promptCard.IsNegativePromptEnabled = false;\n        });\n\n        BatchSizeCardViewModel = vmFactory.Get<BatchSizeCardViewModel>();\n\n        ModulesCardViewModel = vmFactory.Get<StackEditableCardViewModel>(modulesCard =>\n        {\n            modulesCard.AvailableModules = new[]\n            {\n                typeof(CfzCudnnToggleModule),\n                typeof(FaceDetailerModule),\n                typeof(FluxHiresFixModule),\n                typeof(SaveImageModule),\n                typeof(UpscalerModule),\n            };\n            modulesCard.DefaultModules = new[] { typeof(FluxHiresFixModule), typeof(UpscalerModule) };\n            modulesCard.InitializeDefaults();\n        });\n\n        StackCardViewModel = vmFactory.Get<StackCardViewModel>();\n        StackCardViewModel.AddCards(\n            ModelCardViewModel,\n            SamplerCardViewModel,\n            ModulesCardViewModel,\n            SeedCardViewModel,\n            BatchSizeCardViewModel\n        );\n    }\n\n    protected override void BuildPrompt(BuildPromptEventArgs args)\n    {\n        base.BuildPrompt(args);\n\n        var builder = args.Builder;\n\n        builder.Connections.Seed = args.SeedOverride switch\n        {\n            { } seed => Convert.ToUInt64(seed),\n            _ => Convert.ToUInt64(SeedCardViewModel.Seed),\n        };\n\n        var applyArgs = args.ToModuleApplyStepEventArgs();\n        BatchSizeCardViewModel.ApplyStep(applyArgs);\n        ModelCardViewModel.ApplyStep(applyArgs);\n\n        builder.SetupEmptyLatentSource(\n            SamplerCardViewModel.Width,\n            SamplerCardViewModel.Height,\n            BatchSizeCardViewModel.BatchSize,\n            BatchSizeCardViewModel.BatchIndex,\n            latentType: LatentType.Sd3\n        );\n\n        PromptCardViewModel.ApplyStep(applyArgs);\n\n        // Do custom Sampler setup\n        SamplerCardViewModel.ApplyStepsInitialCustomSampler(applyArgs, true);\n\n        // Apply steps from our modules\n        foreach (var module in ModulesCardViewModel.Cards.Cast<ModuleBase>())\n        {\n            module.ApplyStep(applyArgs);\n        }\n\n        applyArgs.InvokeAllPreOutputActions();\n\n        builder.SetupOutputImage();\n    }\n\n    /// <inheritdoc />\n    protected override async Task GenerateImageImpl(\n        GenerateOverrides overrides,\n        CancellationToken cancellationToken\n    )\n    {\n        // Validate the prompts\n        if (!await PromptCardViewModel.ValidatePrompts())\n            return;\n\n        if (!await ModelCardViewModel.ValidateModel())\n            return;\n\n        foreach (var module in ModulesCardViewModel.Cards.OfType<ModuleBase>())\n        {\n            if (!module.IsEnabled)\n                continue;\n\n            if (module is not IValidatableModule validatableModule)\n                continue;\n\n            if (!await validatableModule.Validate())\n            {\n                return;\n            }\n        }\n\n        if (!await CheckClientConnectedWithPrompt() || !ClientManager.IsConnected)\n            return;\n\n        // If enabled, randomize the seed\n        var seedCard = StackCardViewModel.GetCard<SeedCardViewModel>();\n        if (overrides is not { UseCurrentSeed: true } && seedCard.IsRandomizeEnabled)\n        {\n            seedCard.GenerateNewSeed();\n        }\n\n        var batches = BatchSizeCardViewModel.BatchCount;\n\n        var batchArgs = new List<ImageGenerationEventArgs>();\n\n        for (var i = 0; i < batches; i++)\n        {\n            var seed = seedCard.Seed + i;\n\n            var buildPromptArgs = new BuildPromptEventArgs { Overrides = overrides, SeedOverride = seed };\n            BuildPrompt(buildPromptArgs);\n\n            // update seed in project for batches\n            var inferenceProject = InferenceProjectDocument.FromLoadable(this);\n            if (inferenceProject.State?[\"Seed\"]?[\"Seed\"] is not null)\n            {\n                inferenceProject = inferenceProject.WithState(x => x[\"Seed\"][\"Seed\"] = seed);\n            }\n\n            var generationArgs = new ImageGenerationEventArgs\n            {\n                Client = ClientManager.Client,\n                Nodes = buildPromptArgs.Builder.ToNodeDictionary(),\n                OutputNodeNames = buildPromptArgs.Builder.Connections.OutputNodeNames.ToArray(),\n                Parameters = SaveStateToParameters(new GenerationParameters()),\n                Project = inferenceProject,\n                FilesToTransfer = buildPromptArgs.FilesToTransfer,\n                BatchIndex = i,\n                // Only clear output images on the first batch\n                ClearOutputImages = i == 0,\n            };\n\n            batchArgs.Add(generationArgs);\n        }\n\n        // Run batches\n        foreach (var args in batchArgs)\n        {\n            await RunGeneration(args, cancellationToken);\n        }\n    }\n\n    public void LoadStateFromParameters(GenerationParameters parameters)\n    {\n        PromptCardViewModel.LoadStateFromParameters(parameters);\n        SamplerCardViewModel.LoadStateFromParameters(parameters);\n        ModelCardViewModel.LoadStateFromParameters(parameters);\n\n        SeedCardViewModel.Seed = Convert.ToInt64(parameters.Seed);\n\n        if (Math.Abs(SamplerCardViewModel.DenoiseStrength - 1.0d) > 0.01d)\n        {\n            SamplerCardViewModel.DenoiseStrength = 1.0d;\n        }\n    }\n\n    /// <inheritdoc />\n    public GenerationParameters SaveStateToParameters(GenerationParameters parameters)\n    {\n        parameters = PromptCardViewModel.SaveStateToParameters(parameters);\n        parameters = SamplerCardViewModel.SaveStateToParameters(parameters);\n        parameters = ModelCardViewModel.SaveStateToParameters(parameters);\n\n        parameters.Seed = (ulong)SeedCardViewModel.Seed;\n\n        return parameters;\n    }\n\n    // Deserialization overrides\n    public override void LoadStateFromJsonObject(JsonObject state, int version)\n    {\n        // For v2 and below, do migration\n        if (version <= 2)\n        {\n            ModulesCardViewModel.Clear();\n\n            // Add by default the original cards as steps - HiresFix, Upscaler\n            ModulesCardViewModel.AddModule<HiresFixModule>(module =>\n            {\n                module.IsEnabled = state.GetPropertyValueOrDefault<bool>(\"IsHiresFixEnabled\");\n\n                if (state.TryGetPropertyValue(\"HiresSampler\", out var hiresSamplerState))\n                {\n                    module\n                        .GetCard<SamplerCardViewModel>()\n                        .LoadStateFromJsonObject(hiresSamplerState!.AsObject());\n                }\n\n                if (state.TryGetPropertyValue(\"HiresUpscaler\", out var hiresUpscalerState))\n                {\n                    module\n                        .GetCard<UpscalerCardViewModel>()\n                        .LoadStateFromJsonObject(hiresUpscalerState!.AsObject());\n                }\n            });\n\n            ModulesCardViewModel.AddModule<UpscalerModule>(module =>\n            {\n                module.IsEnabled = state.GetPropertyValueOrDefault<bool>(\"IsUpscaleEnabled\");\n\n                if (state.TryGetPropertyValue(\"Upscaler\", out var upscalerState))\n                {\n                    module\n                        .GetCard<UpscalerCardViewModel>()\n                        .LoadStateFromJsonObject(upscalerState!.AsObject());\n                }\n            });\n\n            // Add FreeU to sampler\n            SamplerCardViewModel.ModulesCardViewModel.AddModule<FreeUModule>(module =>\n            {\n                module.IsEnabled = state.GetPropertyValueOrDefault<bool>(\"IsFreeUEnabled\");\n            });\n        }\n\n        base.LoadStateFromJsonObject(state);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToImageViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text.Json.Serialization;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\nusing StabilityMatrix.Avalonia.Views.Inference;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Inference;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(InferenceImageToImageView), IsPersistent = true)]\n[RegisterScoped<InferenceImageToImageViewModel>, ManagedService]\npublic class InferenceImageToImageViewModel : InferenceTextToImageViewModel\n{\n    [JsonPropertyName(\"SelectImage\")]\n    public SelectImageCardViewModel SelectImageCardViewModel { get; }\n\n    /// <inheritdoc />\n    public InferenceImageToImageViewModel(\n        IServiceManager<ViewModelBase> vmFactory,\n        IInferenceClientManager inferenceClientManager,\n        INotificationService notificationService,\n        ISettingsManager settingsManager,\n        IModelIndexService modelIndexService,\n        RunningPackageService runningPackageService,\n        TabContext tabContext\n    )\n        : base(\n            notificationService,\n            inferenceClientManager,\n            settingsManager,\n            vmFactory,\n            modelIndexService,\n            runningPackageService,\n            tabContext\n        )\n    {\n        SelectImageCardViewModel = vmFactory.Get<SelectImageCardViewModel>(vm =>\n        {\n            vm.IsMaskEditorEnabled = true;\n        });\n\n        SamplerCardViewModel.IsDenoiseStrengthEnabled = true;\n    }\n\n    /// <inheritdoc />\n    protected override void BuildPrompt(BuildPromptEventArgs args)\n    {\n        var builder = args.Builder;\n\n        // Setup constants\n        builder.Connections.Seed = args.SeedOverride switch\n        {\n            { } seed => Convert.ToUInt64(seed),\n            _ => Convert.ToUInt64(SeedCardViewModel.Seed)\n        };\n\n        var applyArgs = args.ToModuleApplyStepEventArgs();\n\n        BatchSizeCardViewModel.ApplyStep(applyArgs);\n\n        // Load models\n        ModelCardViewModel.ApplyStep(applyArgs);\n\n        // Setup image latent source\n        SelectImageCardViewModel.ApplyStep(applyArgs);\n\n        // Prompts and loras\n        PromptCardViewModel.ApplyStep(applyArgs);\n\n        // Setup Sampler and Refiner if enabled\n        var isUnetLoader =\n            ModelCardViewModel.SelectedModelLoader is ModelLoader.Unet || ModelCardViewModel.IsGguf;\n        if (isUnetLoader)\n        {\n            SamplerCardViewModel.ApplyStepsInitialCustomSampler(applyArgs, true);\n        }\n        else if (SamplerCardViewModel.SelectedScheduler?.Name is \"align_your_steps\")\n        {\n            SamplerCardViewModel.ApplyStepsInitialCustomSampler(applyArgs, false);\n        }\n        else\n        {\n            SamplerCardViewModel.ApplyStep(applyArgs);\n        }\n\n        // Apply module steps\n        foreach (var module in ModulesCardViewModel.Cards.OfType<ModuleBase>())\n        {\n            module.ApplyStep(applyArgs);\n        }\n\n        applyArgs.InvokeAllPreOutputActions();\n\n        builder.SetupOutputImage();\n    }\n\n    /// <inheritdoc />\n    protected override IEnumerable<ImageSource> GetInputImages()\n    {\n        var mainImages = SelectImageCardViewModel.GetInputImages();\n\n        var samplerImages = SamplerCardViewModel\n            .ModulesCardViewModel.Cards.OfType<IInputImageProvider>()\n            .SelectMany(m => m.GetInputImages());\n\n        var moduleImages = ModulesCardViewModel\n            .Cards.OfType<IInputImageProvider>()\n            .SelectMany(m => m.GetInputImages());\n\n        return mainImages.Concat(samplerImages).Concat(moduleImages);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageToVideoViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.Linq;\nusing System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Video;\nusing StabilityMatrix.Avalonia.Views.Inference;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(InferenceImageToVideoView), persistent: true)]\n[ManagedService]\n[RegisterScoped<InferenceImageToVideoViewModel>]\npublic partial class InferenceImageToVideoViewModel\n    : InferenceGenerationViewModelBase,\n        IParametersLoadableState\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private readonly INotificationService notificationService;\n    private readonly IModelIndexService modelIndexService;\n\n    [JsonIgnore]\n    public StackCardViewModel StackCardViewModel { get; }\n\n    [JsonPropertyName(\"Model\")]\n    public ImgToVidModelCardViewModel ModelCardViewModel { get; }\n\n    [JsonPropertyName(\"Sampler\")]\n    public SamplerCardViewModel SamplerCardViewModel { get; }\n\n    [JsonPropertyName(\"BatchSize\")]\n    public BatchSizeCardViewModel BatchSizeCardViewModel { get; }\n\n    [JsonPropertyName(\"Seed\")]\n    public SeedCardViewModel SeedCardViewModel { get; }\n\n    [JsonPropertyName(\"ImageLoader\")]\n    public SelectImageCardViewModel SelectImageCardViewModel { get; }\n\n    [JsonPropertyName(\"Conditioning\")]\n    public SvdImgToVidConditioningViewModel SvdImgToVidConditioningViewModel { get; }\n\n    [JsonPropertyName(\"VideoOutput\")]\n    public VideoOutputSettingsCardViewModel VideoOutputSettingsCardViewModel { get; }\n\n    public InferenceImageToVideoViewModel(\n        INotificationService notificationService,\n        IInferenceClientManager inferenceClientManager,\n        ISettingsManager settingsManager,\n        IServiceManager<ViewModelBase> vmFactory,\n        IModelIndexService modelIndexService,\n        RunningPackageService runningPackageService\n    )\n        : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService)\n    {\n        this.notificationService = notificationService;\n        this.modelIndexService = modelIndexService;\n\n        // Get sub view models from service manager\n\n        SeedCardViewModel = vmFactory.Get<SeedCardViewModel>();\n        SeedCardViewModel.GenerateNewSeed();\n\n        ModelCardViewModel = vmFactory.Get<ImgToVidModelCardViewModel>(\n            vm => vm.EnableModelLoaderSelection = false\n        );\n\n        SamplerCardViewModel = vmFactory.Get<SamplerCardViewModel>(samplerCard =>\n        {\n            samplerCard.IsDimensionsEnabled = true;\n            samplerCard.IsCfgScaleEnabled = true;\n            samplerCard.IsSamplerSelectionEnabled = true;\n            samplerCard.IsSchedulerSelectionEnabled = true;\n            samplerCard.CfgScale = 2.5d;\n            samplerCard.SelectedSampler = ComfySampler.Euler;\n            samplerCard.SelectedScheduler = ComfyScheduler.Karras;\n            samplerCard.IsDenoiseStrengthEnabled = true;\n            samplerCard.DenoiseStrength = 1.0f;\n        });\n\n        BatchSizeCardViewModel = vmFactory.Get<BatchSizeCardViewModel>();\n\n        SelectImageCardViewModel = vmFactory.Get<SelectImageCardViewModel>();\n        SvdImgToVidConditioningViewModel = vmFactory.Get<SvdImgToVidConditioningViewModel>();\n        VideoOutputSettingsCardViewModel = vmFactory.Get<VideoOutputSettingsCardViewModel>();\n\n        StackCardViewModel = vmFactory.Get<StackCardViewModel>();\n        StackCardViewModel.AddCards(\n            ModelCardViewModel,\n            SvdImgToVidConditioningViewModel,\n            SamplerCardViewModel,\n            SeedCardViewModel,\n            VideoOutputSettingsCardViewModel,\n            BatchSizeCardViewModel\n        );\n    }\n\n    /// <inheritdoc />\n    protected override void BuildPrompt(BuildPromptEventArgs args)\n    {\n        base.BuildPrompt(args);\n\n        var builder = args.Builder;\n\n        builder.Connections.Seed = args.SeedOverride switch\n        {\n            { } seed => Convert.ToUInt64(seed),\n            _ => Convert.ToUInt64(SeedCardViewModel.Seed)\n        };\n\n        // Load models\n        ModelCardViewModel.ApplyStep(args);\n\n        // Setup latent from image\n        var imageLoad = builder.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.LoadImage\n            {\n                Name = builder.Nodes.GetUniqueName(\"ControlNet_LoadImage\"),\n                Image =\n                    SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached(\"Inference\")\n                    ?? throw new ValidationException()\n            }\n        );\n        builder.Connections.Primary = imageLoad.Output1;\n        builder.Connections.PrimarySize = SelectImageCardViewModel.CurrentBitmapSize;\n\n        // Setup img2vid stuff\n        // Set width & height from SamplerCard\n        SvdImgToVidConditioningViewModel.Width = SamplerCardViewModel.Width;\n        SvdImgToVidConditioningViewModel.Height = SamplerCardViewModel.Height;\n        SvdImgToVidConditioningViewModel.ApplyStep(args);\n\n        // Setup Sampler and Refiner if enabled\n        SamplerCardViewModel.ApplyStep(args);\n\n        // Animated webp output\n        VideoOutputSettingsCardViewModel.ApplyStep(args);\n    }\n\n    /// <inheritdoc />\n    protected override IEnumerable<ImageSource> GetInputImages()\n    {\n        if (SelectImageCardViewModel.ImageSource is { } image)\n        {\n            yield return image;\n        }\n    }\n\n    /// <inheritdoc />\n    protected override async Task GenerateImageImpl(\n        GenerateOverrides overrides,\n        CancellationToken cancellationToken\n    )\n    {\n        if (!await CheckClientConnectedWithPrompt() || !ClientManager.IsConnected)\n        {\n            return;\n        }\n\n        if (!await ModelCardViewModel.ValidateModel())\n            return;\n\n        // If enabled, randomize the seed\n        var seedCard = StackCardViewModel.GetCard<SeedCardViewModel>();\n        if (overrides is not { UseCurrentSeed: true } && seedCard.IsRandomizeEnabled)\n        {\n            seedCard.GenerateNewSeed();\n        }\n\n        var batches = BatchSizeCardViewModel.BatchCount;\n\n        var batchArgs = new List<ImageGenerationEventArgs>();\n\n        for (var i = 0; i < batches; i++)\n        {\n            var seed = seedCard.Seed + i;\n\n            var buildPromptArgs = new BuildPromptEventArgs { Overrides = overrides, SeedOverride = seed };\n            BuildPrompt(buildPromptArgs);\n\n            // update seed in project for batches\n            var inferenceProject = InferenceProjectDocument.FromLoadable(this);\n            if (inferenceProject.State?[\"Seed\"]?[\"Seed\"] is not null)\n            {\n                inferenceProject = inferenceProject.WithState(x => x[\"Seed\"][\"Seed\"] = seed);\n            }\n\n            var generationArgs = new ImageGenerationEventArgs\n            {\n                Client = ClientManager.Client,\n                Nodes = buildPromptArgs.Builder.ToNodeDictionary(),\n                OutputNodeNames = buildPromptArgs.Builder.Connections.OutputNodeNames.ToArray(),\n                Parameters = SaveStateToParameters(new GenerationParameters()) with\n                {\n                    Seed = Convert.ToUInt64(seed)\n                },\n                Project = inferenceProject,\n                FilesToTransfer = buildPromptArgs.FilesToTransfer,\n                BatchIndex = i,\n                // Only clear output images on the first batch\n                ClearOutputImages = i == 0\n            };\n\n            batchArgs.Add(generationArgs);\n        }\n\n        // Run batches\n        foreach (var args in batchArgs)\n        {\n            await RunGeneration(args, cancellationToken);\n        }\n    }\n\n    /// <inheritdoc />\n    public void LoadStateFromParameters(GenerationParameters parameters)\n    {\n        SamplerCardViewModel.LoadStateFromParameters(parameters);\n        ModelCardViewModel.LoadStateFromParameters(parameters);\n        SvdImgToVidConditioningViewModel.LoadStateFromParameters(parameters);\n        VideoOutputSettingsCardViewModel.LoadStateFromParameters(parameters);\n\n        SeedCardViewModel.Seed = Convert.ToInt64(parameters.Seed);\n    }\n\n    /// <inheritdoc />\n    public GenerationParameters SaveStateToParameters(GenerationParameters parameters)\n    {\n        parameters = SamplerCardViewModel.SaveStateToParameters(parameters);\n        parameters = ModelCardViewModel.SaveStateToParameters(parameters);\n        parameters = SvdImgToVidConditioningViewModel.SaveStateToParameters(parameters);\n        parameters = VideoOutputSettingsCardViewModel.SaveStateToParameters(parameters);\n\n        parameters.Seed = (ulong)SeedCardViewModel.Seed;\n\n        return parameters;\n    }\n\n    // Migration for v2 deserialization\n    public override void LoadStateFromJsonObject(JsonObject state, int version)\n    {\n        if (version > 2)\n        {\n            LoadStateFromJsonObject(state);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/InferenceImageUpscaleViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Linq;\nusing System.Text.Json.Serialization;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Inference;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Services;\n\n#pragma warning disable CS0657 // Not a valid attribute location for this declaration\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(InferenceImageUpscaleView), persistent: true)]\n[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]\n[ManagedService]\n[RegisterScoped<InferenceImageUpscaleViewModel>]\npublic class InferenceImageUpscaleViewModel : InferenceGenerationViewModelBase\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private readonly INotificationService notificationService;\n\n    [JsonIgnore]\n    public StackCardViewModel StackCardViewModel { get; }\n\n    [JsonPropertyName(\"Upscaler\")]\n    public UpscalerCardViewModel UpscalerCardViewModel { get; }\n\n    [JsonPropertyName(\"Sharpen\")]\n    public SharpenCardViewModel SharpenCardViewModel { get; }\n\n    [JsonPropertyName(\"SelectImage\")]\n    public SelectImageCardViewModel SelectImageCardViewModel { get; }\n\n    public bool IsUpscaleEnabled\n    {\n        get => StackCardViewModel.GetCard<StackExpanderViewModel>().IsEnabled;\n        set => StackCardViewModel.GetCard<StackExpanderViewModel>().IsEnabled = value;\n    }\n\n    public bool IsSharpenEnabled\n    {\n        get => StackCardViewModel.GetCard<StackExpanderViewModel>(1).IsEnabled;\n        set => StackCardViewModel.GetCard<StackExpanderViewModel>(1).IsEnabled = value;\n    }\n\n    public InferenceImageUpscaleViewModel(\n        INotificationService notificationService,\n        IInferenceClientManager inferenceClientManager,\n        ISettingsManager settingsManager,\n        IServiceManager<ViewModelBase> vmFactory,\n        RunningPackageService runningPackageService\n    )\n        : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService)\n    {\n        this.notificationService = notificationService;\n\n        UpscalerCardViewModel = vmFactory.Get<UpscalerCardViewModel>();\n        SharpenCardViewModel = vmFactory.Get<SharpenCardViewModel>();\n        SelectImageCardViewModel = vmFactory.Get<SelectImageCardViewModel>();\n\n        StackCardViewModel = vmFactory.Get<StackCardViewModel>();\n        StackCardViewModel.AddCards(\n            vmFactory.Get<StackExpanderViewModel>(stackExpander =>\n            {\n                stackExpander.Title = \"Upscale\";\n                stackExpander.AddCards(UpscalerCardViewModel);\n            }),\n            vmFactory.Get<StackExpanderViewModel>(stackExpander =>\n            {\n                stackExpander.Title = \"Sharpen\";\n                stackExpander.AddCards(SharpenCardViewModel);\n            })\n        );\n    }\n\n    /// <inheritdoc />\n    protected override IEnumerable<ImageSource> GetInputImages()\n    {\n        if (SelectImageCardViewModel.ImageSource is { } imageSource)\n        {\n            yield return imageSource;\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void BuildPrompt(BuildPromptEventArgs args)\n    {\n        base.BuildPrompt(args);\n\n        var builder = args.Builder;\n        var nodes = builder.Nodes;\n\n        // Setup image source\n        SelectImageCardViewModel.ApplyStep(args);\n\n        // If upscale is enabled, add another upscale group\n        if (IsUpscaleEnabled)\n        {\n            var upscaleSize = builder.Connections.PrimarySize.WithScale(UpscalerCardViewModel.Scale);\n\n            // Build group\n            builder.Connections.Primary = builder\n                .Group_UpscaleToImage(\n                    \"Upscale\",\n                    builder.GetPrimaryAsImage(),\n                    UpscalerCardViewModel.SelectedUpscaler!.Value,\n                    upscaleSize.Width,\n                    upscaleSize.Height\n                )\n                .Output;\n        }\n\n        // If sharpen is enabled, add another sharpen group\n        if (IsSharpenEnabled)\n        {\n            builder.Connections.Primary = nodes\n                .AddTypedNode(\n                    new ComfyNodeBuilder.ImageSharpen\n                    {\n                        Name = \"Sharpen\",\n                        Image = builder.GetPrimaryAsImage(),\n                        SharpenRadius = SharpenCardViewModel.SharpenRadius,\n                        Sigma = SharpenCardViewModel.Sigma,\n                        Alpha = SharpenCardViewModel.Alpha\n                    }\n                )\n                .Output;\n        }\n\n        builder.SetupOutputImage();\n    }\n\n    /// <inheritdoc />\n    protected override async Task GenerateImageImpl(\n        GenerateOverrides overrides,\n        CancellationToken cancellationToken\n    )\n    {\n        if (!ClientManager.IsConnected)\n        {\n            notificationService.Show(\"Client not connected\", \"Please connect first\");\n            return;\n        }\n\n        if (SelectImageCardViewModel.ImageSource?.LocalFile?.FullPath is not { } path)\n        {\n            notificationService.Show(\"No image selected\", \"Please select an image first\");\n            return;\n        }\n\n        foreach (var image in GetInputImages())\n        {\n            await ClientManager.UploadInputImageAsync(image, cancellationToken);\n        }\n\n        var buildPromptArgs = new BuildPromptEventArgs { Overrides = overrides };\n        BuildPrompt(buildPromptArgs);\n\n        var generationArgs = new ImageGenerationEventArgs\n        {\n            Client = ClientManager.Client,\n            Nodes = buildPromptArgs.Builder.ToNodeDictionary(),\n            OutputNodeNames = buildPromptArgs.Builder.Connections.OutputNodeNames.ToArray(),\n            Parameters = new GenerationParameters\n            {\n                ModelName = UpscalerCardViewModel.SelectedUpscaler?.Name,\n            },\n            Project = InferenceProjectDocument.FromLoadable(this)\n        };\n\n        await RunGeneration(generationArgs, cancellationToken);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing DynamicData.Binding;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.Inference;\nusing StabilityMatrix.Core.Services;\nusing InferenceTextToImageView = StabilityMatrix.Avalonia.Views.Inference.InferenceTextToImageView;\n\n#pragma warning disable CS0657 // Not a valid attribute location for this declaration\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(InferenceTextToImageView), IsPersistent = true)]\n[ManagedService]\n[RegisterScoped<InferenceTextToImageViewModel>]\npublic class InferenceTextToImageViewModel : InferenceGenerationViewModelBase, IParametersLoadableState\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private readonly INotificationService notificationService;\n    private readonly IModelIndexService modelIndexService;\n    private readonly TabContext tabContext;\n\n    [JsonIgnore]\n    public StackCardViewModel StackCardViewModel { get; }\n\n    [JsonPropertyName(\"Modules\")]\n    public StackEditableCardViewModel ModulesCardViewModel { get; }\n\n    [JsonPropertyName(\"Model\")]\n    public ModelCardViewModel ModelCardViewModel { get; }\n\n    [JsonPropertyName(\"Sampler\")]\n    public SamplerCardViewModel SamplerCardViewModel { get; }\n\n    [JsonPropertyName(\"Prompt\")]\n    public PromptCardViewModel PromptCardViewModel { get; }\n\n    [JsonPropertyName(\"BatchSize\")]\n    public BatchSizeCardViewModel BatchSizeCardViewModel { get; }\n\n    [JsonPropertyName(\"Seed\")]\n    public SeedCardViewModel SeedCardViewModel { get; }\n\n    public InferenceTextToImageViewModel(\n        INotificationService notificationService,\n        IInferenceClientManager inferenceClientManager,\n        ISettingsManager settingsManager,\n        IServiceManager<ViewModelBase> vmFactory,\n        IModelIndexService modelIndexService,\n        RunningPackageService runningPackageService,\n        TabContext tabContext\n    )\n        : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService)\n    {\n        this.notificationService = notificationService;\n        this.modelIndexService = modelIndexService;\n        this.tabContext = tabContext;\n\n        // Get sub view models from service manager\n\n        SeedCardViewModel = vmFactory.Get<SeedCardViewModel>();\n        SeedCardViewModel.GenerateNewSeed();\n\n        ModelCardViewModel = vmFactory.Get<ModelCardViewModel>();\n\n        // When the model changes in the ModelCardViewModel, we'll have access to it in the TabContext\n\n        SamplerCardViewModel = vmFactory.Get<SamplerCardViewModel>(samplerCard =>\n        {\n            samplerCard.IsDimensionsEnabled = true;\n            samplerCard.IsCfgScaleEnabled = true;\n            samplerCard.IsSamplerSelectionEnabled = true;\n            samplerCard.IsSchedulerSelectionEnabled = true;\n            samplerCard.DenoiseStrength = 1.0d;\n        });\n\n        PromptCardViewModel = AddDisposable(vmFactory.Get<PromptCardViewModel>());\n\n        BatchSizeCardViewModel = vmFactory.Get<BatchSizeCardViewModel>();\n\n        ModulesCardViewModel = vmFactory.Get<StackEditableCardViewModel>(modulesCard =>\n        {\n            modulesCard.AvailableModules = new[]\n            {\n                typeof(CfzCudnnToggleModule),\n                typeof(FaceDetailerModule),\n                typeof(HiresFixModule),\n                typeof(SaveImageModule),\n                typeof(UpscalerModule),\n            };\n            modulesCard.DefaultModules = new[] { typeof(HiresFixModule), typeof(UpscalerModule) };\n            modulesCard.InitializeDefaults();\n        });\n\n        StackCardViewModel = vmFactory.Get<StackCardViewModel>();\n        StackCardViewModel.AddCards(\n            ModelCardViewModel,\n            SamplerCardViewModel,\n            ModulesCardViewModel,\n            SeedCardViewModel,\n            BatchSizeCardViewModel\n        );\n\n        // When refiner is provided in model card, enable for sampler\n        AddDisposable(\n            ModelCardViewModel\n                .WhenPropertyChanged(x => x.IsRefinerSelectionEnabled)\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe(e =>\n                {\n                    SamplerCardViewModel.IsRefinerStepsEnabled =\n                        e.Sender is { IsRefinerSelectionEnabled: true, SelectedRefiner: not null };\n                })\n        );\n\n        AddDisposable(\n            SamplerCardViewModel\n                .WhenPropertyChanged(x => x.SelectedScheduler)\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe(next =>\n                {\n                    var isAlignYourSteps = next.Value is { Name: \"align_your_steps\" };\n                    ModelCardViewModel.ShowRefinerOption = !isAlignYourSteps;\n\n                    if (isAlignYourSteps)\n                    {\n                        ModelCardViewModel.IsRefinerSelectionEnabled = false;\n                    }\n                })\n        );\n    }\n\n    /// <inheritdoc />\n    protected override void BuildPrompt(BuildPromptEventArgs args)\n    {\n        base.BuildPrompt(args);\n\n        var builder = args.Builder;\n\n        // Load constants\n        builder.Connections.Seed = args.SeedOverride switch\n        {\n            { } seed => Convert.ToUInt64(seed),\n            _ => Convert.ToUInt64(SeedCardViewModel.Seed),\n        };\n\n        var applyArgs = args.ToModuleApplyStepEventArgs();\n\n        BatchSizeCardViewModel.ApplyStep(applyArgs);\n\n        // Load models\n        ModelCardViewModel.ApplyStep(applyArgs);\n\n        var isUnetLoader = ModelCardViewModel.SelectedModelLoader is ModelLoader.Unet;\n        var useSd3Latent =\n            SamplerCardViewModel.ModulesCardViewModel.IsModuleEnabled<FluxGuidanceModule>() || isUnetLoader;\n        var usePlasmaNoise = SamplerCardViewModel.ModulesCardViewModel.IsModuleEnabled<PlasmaNoiseModule>();\n\n        if (useSd3Latent)\n        {\n            builder.SetupEmptyLatentSource(\n                SamplerCardViewModel.Width,\n                SamplerCardViewModel.Height,\n                BatchSizeCardViewModel.BatchSize,\n                BatchSizeCardViewModel.IsBatchIndexEnabled ? BatchSizeCardViewModel.BatchIndex : null,\n                latentType: LatentType.Sd3\n            );\n        }\n        else if (usePlasmaNoise)\n        {\n            var plasmaVm = SamplerCardViewModel\n                .ModulesCardViewModel.GetCard<PlasmaNoiseModule>()\n                .GetCard<PlasmaNoiseCardViewModel>();\n            builder.SetupPlasmaLatentSource(\n                SamplerCardViewModel.Width,\n                SamplerCardViewModel.Height,\n                builder.Connections.Seed,\n                plasmaVm.SelectedNoiseType,\n                plasmaVm.ValueMin,\n                plasmaVm.ValueMax,\n                plasmaVm.RedMin,\n                plasmaVm.RedMax,\n                plasmaVm.GreenMin,\n                plasmaVm.GreenMax,\n                plasmaVm.BlueMin,\n                plasmaVm.BlueMax,\n                plasmaVm.PlasmaTurbulence\n            );\n        }\n        else\n        {\n            // Setup empty latent\n            builder.SetupEmptyLatentSource(\n                SamplerCardViewModel.Width,\n                SamplerCardViewModel.Height,\n                BatchSizeCardViewModel.BatchSize,\n                BatchSizeCardViewModel.IsBatchIndexEnabled ? BatchSizeCardViewModel.BatchIndex : null\n            );\n        }\n\n        // Prompts and loras\n        PromptCardViewModel.ApplyStep(applyArgs);\n\n        // Setup Sampler and Refiner if enabled\n        if (isUnetLoader)\n        {\n            SamplerCardViewModel.ApplyStepsInitialCustomSampler(applyArgs, true);\n        }\n        else if (SamplerCardViewModel.SelectedScheduler?.Name is \"align_your_steps\")\n        {\n            SamplerCardViewModel.ApplyStepsInitialCustomSampler(applyArgs, false);\n        }\n        else\n        {\n            SamplerCardViewModel.ApplyStep(applyArgs);\n        }\n\n        // Hires fix if enabled\n        foreach (var module in ModulesCardViewModel.Cards.OfType<ModuleBase>())\n        {\n            module.ApplyStep(applyArgs);\n        }\n\n        applyArgs.InvokeAllPreOutputActions();\n\n        builder.SetupOutputImage();\n    }\n\n    /// <inheritdoc />\n    protected override IEnumerable<ImageSource> GetInputImages()\n    {\n        var samplerImages = SamplerCardViewModel\n            .ModulesCardViewModel.Cards.OfType<IInputImageProvider>()\n            .SelectMany(m => m.GetInputImages());\n\n        var moduleImages = ModulesCardViewModel\n            .Cards.OfType<IInputImageProvider>()\n            .SelectMany(m => m.GetInputImages());\n\n        return samplerImages.Concat(moduleImages);\n    }\n\n    /// <inheritdoc />\n    protected override async Task GenerateImageImpl(\n        GenerateOverrides overrides,\n        CancellationToken cancellationToken\n    )\n    {\n        // Validate the prompts\n        if (!await PromptCardViewModel.ValidatePrompts())\n            return;\n\n        if (!await ModelCardViewModel.ValidateModel())\n            return;\n\n        foreach (var module in ModulesCardViewModel.Cards.OfType<ModuleBase>())\n        {\n            if (!module.IsEnabled)\n                continue;\n\n            if (module is not IValidatableModule validatableModule)\n                continue;\n\n            if (!await validatableModule.Validate())\n            {\n                return;\n            }\n        }\n\n        if (!await CheckClientConnectedWithPrompt() || !ClientManager.IsConnected)\n            return;\n\n        // If enabled, randomize the seed\n        var seedCard = StackCardViewModel.GetCard<SeedCardViewModel>();\n        if (overrides is not { UseCurrentSeed: true } && seedCard.IsRandomizeEnabled)\n        {\n            seedCard.GenerateNewSeed();\n        }\n\n        var batches = BatchSizeCardViewModel.BatchCount;\n\n        var batchArgs = new List<ImageGenerationEventArgs>();\n\n        for (var i = 0; i < batches; i++)\n        {\n            var seed = seedCard.Seed + i;\n\n            var buildPromptArgs = new BuildPromptEventArgs { Overrides = overrides, SeedOverride = seed };\n            BuildPrompt(buildPromptArgs);\n\n            // update seed in project for batches\n            var inferenceProject = InferenceProjectDocument.FromLoadable(this);\n            if (inferenceProject.State?[\"Seed\"]?[\"Seed\"] is not null)\n            {\n                inferenceProject = inferenceProject.WithState(x => x[\"Seed\"][\"Seed\"] = seed);\n            }\n\n            var generationArgs = new ImageGenerationEventArgs\n            {\n                Client = ClientManager.Client,\n                Nodes = buildPromptArgs.Builder.ToNodeDictionary(),\n                OutputNodeNames = buildPromptArgs.Builder.Connections.OutputNodeNames.ToArray(),\n                Parameters = SaveStateToParameters(new GenerationParameters()) with\n                {\n                    Seed = Convert.ToUInt64(seed),\n                },\n                Project = inferenceProject,\n                FilesToTransfer = buildPromptArgs.FilesToTransfer,\n                BatchIndex = i,\n                // Only clear output images on the first batch\n                ClearOutputImages = i == 0,\n            };\n\n            batchArgs.Add(generationArgs);\n        }\n\n        // Run batches\n        foreach (var args in batchArgs)\n        {\n            await RunGeneration(args, cancellationToken);\n        }\n    }\n\n    /// <inheritdoc />\n    public void LoadStateFromParameters(GenerationParameters parameters)\n    {\n        PromptCardViewModel.LoadStateFromParameters(parameters);\n        SamplerCardViewModel.LoadStateFromParameters(parameters);\n        ModelCardViewModel.LoadStateFromParameters(parameters);\n\n        SeedCardViewModel.Seed = Convert.ToInt64(parameters.Seed);\n\n        if (Math.Abs(SamplerCardViewModel.DenoiseStrength - 1.0d) > 0.01d)\n        {\n            SamplerCardViewModel.DenoiseStrength = 1.0d;\n        }\n    }\n\n    /// <inheritdoc />\n    public GenerationParameters SaveStateToParameters(GenerationParameters parameters)\n    {\n        parameters = PromptCardViewModel.SaveStateToParameters(parameters);\n        parameters = SamplerCardViewModel.SaveStateToParameters(parameters);\n        parameters = ModelCardViewModel.SaveStateToParameters(parameters);\n\n        parameters.Seed = (ulong)SeedCardViewModel.Seed;\n\n        return parameters;\n    }\n\n    // Deserialization overrides\n    public override void LoadStateFromJsonObject(JsonObject state, int version)\n    {\n        // For v2 and below, do migration\n        if (version <= 2)\n        {\n            ModulesCardViewModel.Clear();\n\n            // Add by default the original cards as steps - HiresFix, Upscaler\n            ModulesCardViewModel.AddModule<HiresFixModule>(module =>\n            {\n                module.IsEnabled = state.GetPropertyValueOrDefault<bool>(\"IsHiresFixEnabled\");\n\n                if (state.TryGetPropertyValue(\"HiresSampler\", out var hiresSamplerState))\n                {\n                    module\n                        .GetCard<SamplerCardViewModel>()\n                        .LoadStateFromJsonObject(hiresSamplerState!.AsObject());\n                }\n\n                if (state.TryGetPropertyValue(\"HiresUpscaler\", out var hiresUpscalerState))\n                {\n                    module\n                        .GetCard<UpscalerCardViewModel>()\n                        .LoadStateFromJsonObject(hiresUpscalerState!.AsObject());\n                }\n            });\n\n            ModulesCardViewModel.AddModule<UpscalerModule>(module =>\n            {\n                module.IsEnabled = state.GetPropertyValueOrDefault<bool>(\"IsUpscaleEnabled\");\n\n                if (state.TryGetPropertyValue(\"Upscaler\", out var upscalerState))\n                {\n                    module\n                        .GetCard<UpscalerCardViewModel>()\n                        .LoadStateFromJsonObject(upscalerState!.AsObject());\n                }\n            });\n\n            // Add FreeU to sampler\n            SamplerCardViewModel.ModulesCardViewModel.AddModule<FreeUModule>(module =>\n            {\n                module.IsEnabled = state.GetPropertyValueOrDefault<bool>(\"IsFreeUEnabled\");\n            });\n        }\n\n        base.LoadStateFromJsonObject(state);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/InferenceWanImageToVideoViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing System.Text.Json.Serialization;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Inference;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(InferenceWanImageToVideoView), IsPersistent = true)]\n[RegisterScoped<InferenceWanImageToVideoViewModel>, ManagedService]\npublic class InferenceWanImageToVideoViewModel : InferenceWanTextToVideoViewModel\n{\n    public InferenceWanImageToVideoViewModel(\n        IServiceManager<ViewModelBase> vmFactory,\n        IInferenceClientManager inferenceClientManager,\n        INotificationService notificationService,\n        ISettingsManager settingsManager,\n        RunningPackageService runningPackageService\n    )\n        : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService)\n    {\n        SelectImageCardViewModel = vmFactory.Get<SelectImageCardViewModel>();\n\n        SamplerCardViewModel.IsDenoiseStrengthEnabled = true;\n        SamplerCardViewModel.Width = 512;\n        SamplerCardViewModel.Height = 512;\n\n        ModelCardViewModel.IsClipVisionEnabled = true;\n    }\n\n    [JsonPropertyName(\"ImageLoader\")]\n    public SelectImageCardViewModel SelectImageCardViewModel { get; }\n\n    /// <inheritdoc />\n    protected override void BuildPrompt(BuildPromptEventArgs args)\n    {\n        var applyArgs = args.ToModuleApplyStepEventArgs();\n        var builder = args.Builder;\n\n        builder.Connections.Seed = args.SeedOverride switch\n        {\n            { } seed => Convert.ToUInt64(seed),\n            _ => Convert.ToUInt64(SeedCardViewModel.Seed),\n        };\n\n        // Load models\n        ModelCardViewModel.ApplyStep(applyArgs);\n\n        // Setup latent from image\n        var imageLoad = builder.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.LoadImage\n            {\n                Name = builder.Nodes.GetUniqueName(\"ControlNet_LoadImage\"),\n                Image =\n                    SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached(\"Inference\")\n                    ?? throw new ValidationException(),\n            }\n        );\n        builder.Connections.Primary = imageLoad.Output1;\n        builder.Connections.PrimarySize = SelectImageCardViewModel.CurrentBitmapSize;\n\n        BatchSizeCardViewModel.ApplyStep(applyArgs);\n\n        SelectImageCardViewModel.ApplyStep(applyArgs);\n\n        PromptCardViewModel.ApplyStep(applyArgs);\n\n        SamplerCardViewModel.ApplyStep(applyArgs);\n\n        applyArgs.InvokeAllPreOutputActions();\n\n        // Animated webp output\n        VideoOutputSettingsCardViewModel.ApplyStep(applyArgs);\n    }\n\n    /// <inheritdoc />\n    protected override IEnumerable<ImageSource> GetInputImages()\n    {\n        if (SelectImageCardViewModel.ImageSource is { } image)\n        {\n            yield return image;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/InferenceWanTextToVideoViewModel.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Video;\nusing StabilityMatrix.Avalonia.Views.Inference;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(InferenceWanTextToVideoView), IsPersistent = true)]\n[RegisterScoped<InferenceWanTextToVideoViewModel>, ManagedService]\npublic class InferenceWanTextToVideoViewModel : InferenceGenerationViewModelBase, IParametersLoadableState\n{\n    [JsonIgnore]\n    public StackCardViewModel StackCardViewModel { get; }\n\n    [JsonPropertyName(\"Model\")]\n    public WanModelCardViewModel ModelCardViewModel { get; }\n\n    [JsonPropertyName(\"Sampler\")]\n    public SamplerCardViewModel SamplerCardViewModel { get; }\n\n    [JsonPropertyName(\"BatchSize\")]\n    public BatchSizeCardViewModel BatchSizeCardViewModel { get; }\n\n    [JsonPropertyName(\"Seed\")]\n    public SeedCardViewModel SeedCardViewModel { get; }\n\n    [JsonPropertyName(\"Prompt\")]\n    public PromptCardViewModel PromptCardViewModel { get; }\n\n    [JsonPropertyName(\"VideoOutput\")]\n    public VideoOutputSettingsCardViewModel VideoOutputSettingsCardViewModel { get; }\n\n    public InferenceWanTextToVideoViewModel(\n        IServiceManager<ViewModelBase> vmFactory,\n        IInferenceClientManager inferenceClientManager,\n        INotificationService notificationService,\n        ISettingsManager settingsManager,\n        RunningPackageService runningPackageService\n    )\n        : base(vmFactory, inferenceClientManager, notificationService, settingsManager, runningPackageService)\n    {\n        SeedCardViewModel = vmFactory.Get<SeedCardViewModel>();\n        SeedCardViewModel.GenerateNewSeed();\n\n        ModelCardViewModel = vmFactory.Get<WanModelCardViewModel>();\n\n        SamplerCardViewModel = vmFactory.Get<WanSamplerCardViewModel>(samplerCard =>\n        {\n            samplerCard.IsDimensionsEnabled = true;\n            samplerCard.IsCfgScaleEnabled = true;\n            samplerCard.IsSamplerSelectionEnabled = true;\n            samplerCard.IsSchedulerSelectionEnabled = true;\n            samplerCard.DenoiseStrength = 1.0d;\n            samplerCard.EnableAddons = true;\n            samplerCard.IsLengthEnabled = true;\n            samplerCard.Width = 832;\n            samplerCard.Height = 480;\n            samplerCard.Length = 33;\n        });\n\n        PromptCardViewModel = AddDisposable(vmFactory.Get<PromptCardViewModel>());\n\n        BatchSizeCardViewModel = vmFactory.Get<BatchSizeCardViewModel>();\n\n        VideoOutputSettingsCardViewModel = vmFactory.Get<VideoOutputSettingsCardViewModel>(vm =>\n            vm.Fps = 16.0d\n        );\n\n        StackCardViewModel = vmFactory.Get<StackCardViewModel>();\n        StackCardViewModel.AddCards(\n            ModelCardViewModel,\n            SamplerCardViewModel,\n            SeedCardViewModel,\n            BatchSizeCardViewModel,\n            VideoOutputSettingsCardViewModel\n        );\n    }\n\n    /// <inheritdoc />\n    protected override void BuildPrompt(BuildPromptEventArgs args)\n    {\n        base.BuildPrompt(args);\n\n        var applyArgs = args.ToModuleApplyStepEventArgs();\n        var builder = args.Builder;\n\n        builder.Connections.Seed = args.SeedOverride switch\n        {\n            { } seed => Convert.ToUInt64(seed),\n            _ => Convert.ToUInt64(SeedCardViewModel.Seed),\n        };\n\n        // Load models\n        ModelCardViewModel.ApplyStep(applyArgs);\n\n        builder.SetupEmptyLatentSource(\n            SamplerCardViewModel.Width,\n            SamplerCardViewModel.Height,\n            BatchSizeCardViewModel.BatchSize,\n            BatchSizeCardViewModel.IsBatchIndexEnabled ? BatchSizeCardViewModel.BatchIndex : null,\n            SamplerCardViewModel.Length,\n            LatentType.Hunyuan\n        );\n\n        BatchSizeCardViewModel.ApplyStep(applyArgs);\n\n        PromptCardViewModel.ApplyStep(applyArgs);\n\n        SamplerCardViewModel.ApplyStep(applyArgs);\n\n        applyArgs.InvokeAllPreOutputActions();\n\n        // Animated webp output\n        VideoOutputSettingsCardViewModel.ApplyStep(applyArgs);\n    }\n\n    /// <inheritdoc />\n    protected override async Task GenerateImageImpl(\n        GenerateOverrides overrides,\n        CancellationToken cancellationToken\n    )\n    {\n        if (!await CheckClientConnectedWithPrompt() || !ClientManager.IsConnected)\n        {\n            return;\n        }\n\n        if (!await ModelCardViewModel.ValidateModel())\n            return;\n\n        // If enabled, randomize the seed\n        var seedCard = StackCardViewModel.GetCard<SeedCardViewModel>();\n        if (overrides is not { UseCurrentSeed: true } && seedCard.IsRandomizeEnabled)\n        {\n            seedCard.GenerateNewSeed();\n        }\n\n        var batches = BatchSizeCardViewModel.BatchCount;\n\n        var batchArgs = new List<ImageGenerationEventArgs>();\n\n        for (var i = 0; i < batches; i++)\n        {\n            var seed = seedCard.Seed + i;\n\n            var buildPromptArgs = new BuildPromptEventArgs { Overrides = overrides, SeedOverride = seed };\n            BuildPrompt(buildPromptArgs);\n\n            // update seed in project for batches\n            var inferenceProject = InferenceProjectDocument.FromLoadable(this);\n            if (inferenceProject.State?[\"Seed\"]?[\"Seed\"] is not null)\n            {\n                inferenceProject = inferenceProject.WithState(x => x[\"Seed\"][\"Seed\"] = seed);\n            }\n\n            var generationArgs = new ImageGenerationEventArgs\n            {\n                Client = ClientManager.Client,\n                Nodes = buildPromptArgs.Builder.ToNodeDictionary(),\n                OutputNodeNames = buildPromptArgs.Builder.Connections.OutputNodeNames.ToArray(),\n                Parameters = SaveStateToParameters(new GenerationParameters()) with\n                {\n                    Seed = Convert.ToUInt64(seed),\n                },\n                Project = inferenceProject,\n                FilesToTransfer = buildPromptArgs.FilesToTransfer,\n                BatchIndex = i,\n                // Only clear output images on the first batch\n                ClearOutputImages = i == 0,\n            };\n\n            batchArgs.Add(generationArgs);\n        }\n\n        // Run batches\n        foreach (var args in batchArgs)\n        {\n            await RunGeneration(args, cancellationToken);\n        }\n    }\n\n    /// <inheritdoc />\n    public void LoadStateFromParameters(GenerationParameters parameters)\n    {\n        SamplerCardViewModel.LoadStateFromParameters(parameters);\n        ModelCardViewModel.LoadStateFromParameters(parameters);\n        PromptCardViewModel.LoadStateFromParameters(parameters);\n        VideoOutputSettingsCardViewModel.LoadStateFromParameters(parameters);\n        SeedCardViewModel.Seed = Convert.ToInt64(parameters.Seed);\n    }\n\n    /// <inheritdoc />\n    public GenerationParameters SaveStateToParameters(GenerationParameters parameters)\n    {\n        parameters = SamplerCardViewModel.SaveStateToParameters(parameters);\n        parameters = ModelCardViewModel.SaveStateToParameters(parameters);\n        parameters = PromptCardViewModel.SaveStateToParameters(parameters);\n        parameters = VideoOutputSettingsCardViewModel.SaveStateToParameters(parameters);\n\n        parameters.Seed = (ulong)SeedCardViewModel.Seed;\n\n        return parameters;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/LayerDiffuseCardViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing KGySoft.CoreLibraries;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Inference;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[RegisterTransient<LayerDiffuseCardViewModel>]\n[ManagedService]\n[View(typeof(LayerDiffuseCard))]\npublic partial class LayerDiffuseCardViewModel : LoadableViewModelBase, IComfyStep\n{\n    public const string ModuleKey = \"LayerDiffuse\";\n\n    [ObservableProperty]\n    private LayerDiffuseMode selectedMode = LayerDiffuseMode.None;\n\n    public IEnumerable<LayerDiffuseMode> AvailableModes => Enum<LayerDiffuseMode>.GetValues();\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(-1d, 3d)]\n    private double weight = 1;\n\n    /// <inheritdoc />\n    public void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        if (SelectedMode == LayerDiffuseMode.None)\n            return;\n\n        var sdType = SelectedMode switch\n        {\n            LayerDiffuseMode.GenerateForegroundWithTransparencySD15 => \"SD15\",\n            LayerDiffuseMode.GenerateForegroundWithTransparencySDXL => \"SDXL\",\n            LayerDiffuseMode.None => throw new ArgumentOutOfRangeException(),\n            _ => throw new ArgumentOutOfRangeException()\n        };\n\n        // Choose config based on mode\n        var config = SelectedMode switch\n        {\n            LayerDiffuseMode.GenerateForegroundWithTransparencySD15\n                => \"SD15, Attention Injection, attn_sharing\",\n            LayerDiffuseMode.GenerateForegroundWithTransparencySDXL => \"SDXL, Conv Injection\",\n            LayerDiffuseMode.None => throw new ArgumentOutOfRangeException(),\n            _ => throw new ArgumentOutOfRangeException()\n        };\n\n        foreach (var modelConnections in e.Temp.Models.Values)\n        {\n            var layerDiffuseApply = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.LayeredDiffusionApply\n                {\n                    Name = e.Nodes.GetUniqueName($\"LayerDiffuseApply_{modelConnections.Name}\"),\n                    Model = modelConnections.Model,\n                    Config = config,\n                    Weight = Weight,\n                }\n            );\n\n            modelConnections.Model = layerDiffuseApply.Output;\n        }\n\n        // Add pre output action\n        e.PreOutputActions.Add(applyArgs =>\n        {\n            // Use last latent for decode\n            var latent =\n                applyArgs.Builder.Connections.LastPrimaryLatent\n                ?? throw new InvalidOperationException(\"Connections.LastPrimaryLatent not set\");\n\n            // Convert primary to image if not already\n            var primaryImage = applyArgs.Builder.GetPrimaryAsImage();\n            applyArgs.Builder.Connections.Primary = primaryImage;\n\n            // Add a Layer Diffuse Decode\n            var decode = applyArgs.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.LayeredDiffusionDecodeRgba\n                {\n                    Name = applyArgs.Nodes.GetUniqueName(\"LayerDiffuseDecode\"),\n                    Samples = latent,\n                    Images = primaryImage,\n                    SdVersion = sdType\n                }\n            );\n\n            // Set primary to decode output\n            applyArgs.Builder.Connections.Primary = decode.Output;\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing System.Text.Json.Nodes;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\nusing StabilityMatrix.Core.Models.Inference;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(ModelCard))]\n[ManagedService]\n[RegisterTransient<ModelCardViewModel>]\npublic partial class ModelCardViewModel(\n    IInferenceClientManager clientManager,\n    IServiceManager<ViewModelBase> vmFactory,\n    TabContext tabContext\n) : LoadableViewModelBase, IParametersLoadableState, IComfyStep\n{\n    [ObservableProperty]\n    private HybridModelFile? selectedModel;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsGguf), nameof(ShowPrecisionSelection))]\n    private HybridModelFile? selectedUnetModel;\n\n    [ObservableProperty]\n    private bool isRefinerSelectionEnabled;\n\n    [ObservableProperty]\n    private bool showRefinerOption = true;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedRefiner = HybridModelFile.None;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedVae = HybridModelFile.Default;\n\n    [ObservableProperty]\n    private bool isVaeSelectionEnabled;\n\n    [ObservableProperty]\n    private bool disableSettings;\n\n    [ObservableProperty]\n    private bool isClipSkipEnabled;\n\n    [NotifyDataErrorInfo]\n    [ObservableProperty]\n    [Range(1, 24)]\n    private int clipSkip = 1;\n\n    [ObservableProperty]\n    private bool isExtraNetworksEnabled;\n\n    [ObservableProperty]\n    private bool isModelLoaderSelectionEnabled;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsStandaloneModelLoader))]\n    private ModelLoader selectedModelLoader;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedClip1;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedClip2;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedClip3;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedClip4;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsSd3Clip), nameof(IsHiDreamClip))]\n    private string? selectedClipType;\n\n    [ObservableProperty]\n    private string? selectedDType;\n\n    [ObservableProperty]\n    private bool enableModelLoaderSelection = true;\n\n    [ObservableProperty]\n    private bool isClipModelSelectionEnabled;\n\n    [ObservableProperty]\n    private double shift = 3.0d;\n\n    public List<string> WeightDTypes { get; set; } = [\"default\", \"fp8_e4m3fn\", \"fp8_e5m2\"];\n    public List<string> ClipTypes { get; set; } = [\"flux\", \"sd3\", \"HiDream\"];\n\n    public StackEditableCardViewModel ExtraNetworksStackCardViewModel { get; } =\n        new(vmFactory) { Title = Resources.Label_ExtraNetworks, AvailableModules = [typeof(LoraModule)] };\n\n    public IInferenceClientManager ClientManager { get; } = clientManager;\n\n    public List<ModelLoader> ModelLoaders { get; } =\n        Enum.GetValues<ModelLoader>().Except([ModelLoader.Gguf]).ToList();\n\n    public bool IsStandaloneModelLoader => SelectedModelLoader is ModelLoader.Unet;\n    public bool ShowPrecisionSelection => SelectedModelLoader is ModelLoader.Unet && !IsGguf;\n    public bool IsSd3Clip => SelectedClipType == \"sd3\";\n    public bool IsHiDreamClip => SelectedClipType == \"HiDream\";\n    public bool IsGguf => SelectedUnetModel?.RelativePath.EndsWith(\"gguf\") ?? false;\n\n    protected override void OnInitialLoaded()\n    {\n        base.OnInitialLoaded();\n        ExtraNetworksStackCardViewModel.CardAdded += ExtraNetworksStackCardViewModelOnCardAdded;\n    }\n\n    public override void OnUnloaded()\n    {\n        base.OnUnloaded();\n        ExtraNetworksStackCardViewModel.CardAdded -= ExtraNetworksStackCardViewModelOnCardAdded;\n    }\n\n    private void ExtraNetworksStackCardViewModelOnCardAdded(object? sender, LoadableViewModelBase e)\n    {\n        OnSelectedModelChanged(SelectedModel);\n    }\n\n    [RelayCommand]\n    private static async Task OnConfigClickAsync()\n    {\n        await DialogHelper\n            .CreateMarkdownDialog(\n                \"\"\"\n                You can use a config (.yaml) file to load a model with specific settings.\n\n                Place the config file next to the model file with the same name:\n                ```md\n                Models/\n                    StableDiffusion/\n                        my_model.safetensors\n                        my_model.yaml <-\n                ```\n                \"\"\",\n                \"Using Model Configs\",\n                TextEditorPreset.Console\n            )\n            .ShowAsync();\n    }\n\n    public async Task<bool> ValidateModel()\n    {\n        if (IsStandaloneModelLoader && SelectedUnetModel != null)\n            return true;\n\n        if (!IsStandaloneModelLoader && SelectedModel != null)\n            return true;\n\n        var dialog = DialogHelper.CreateMarkdownDialog(\n            \"Please select a model to continue.\",\n            \"No Model Selected\"\n        );\n        await dialog.ShowAsync();\n        return false;\n    }\n\n    private static ComfyTypedNodeBase<\n        ModelNodeConnection,\n        ClipNodeConnection,\n        VAENodeConnection\n    > GetDefaultModelLoader(ModuleApplyStepEventArgs e, string nodeName, HybridModelFile model)\n    {\n        // Check if config\n        if (model.Local?.ConfigFullPath is { } configPath)\n        {\n            // We'll need to upload the config file to `models/configs` later\n            var uploadConfigPath = e.AddFileTransferToConfigs(configPath);\n\n            return new ComfyNodeBuilder.CheckpointLoader\n            {\n                Name = nodeName,\n                // Only the file name is needed\n                ConfigName = Path.GetFileName(uploadConfigPath),\n                CkptName = model.RelativePath,\n            };\n        }\n\n        // Simple loader if no config\n        return new ComfyNodeBuilder.CheckpointLoaderSimple { Name = nodeName, CkptName = model.RelativePath };\n    }\n\n    /// <inheritdoc />\n    public virtual void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        if (SelectedModelLoader is ModelLoader.Default or ModelLoader.Nf4)\n        {\n            SetupDefaultModelLoader(e);\n        }\n        else // UNET/GGUF UNET workflow\n        {\n            SetupStandaloneModelLoader(e);\n        }\n\n        // Clip skip all models if enabled\n        if (IsClipSkipEnabled)\n        {\n            foreach (var (modelName, model) in e.Builder.Connections.Models)\n            {\n                if (model.Clip is not { } modelClip)\n                    continue;\n\n                var clipSetLastLayer = e.Nodes.AddTypedNode(\n                    new ComfyNodeBuilder.CLIPSetLastLayer\n                    {\n                        Name = $\"CLIP_Skip_{modelName}\",\n                        Clip = modelClip,\n                        // Need to convert to negative indexing from (1 to 24) to (-1 to -24)\n                        StopAtClipLayer = -ClipSkip,\n                    }\n                );\n\n                model.Clip = clipSetLastLayer.Output;\n            }\n        }\n\n        // Load extra networks if enabled\n        if (IsExtraNetworksEnabled)\n        {\n            ExtraNetworksStackCardViewModel.ApplyStep(e);\n        }\n    }\n\n    /// <inheritdoc />\n    public override JsonObject SaveStateToJsonObject()\n    {\n        return SerializeModel(\n            new ModelCardModel\n            {\n                SelectedModelName = IsStandaloneModelLoader\n                    ? SelectedUnetModel?.RelativePath\n                    : SelectedModel?.RelativePath,\n                SelectedVaeName = SelectedVae?.RelativePath,\n                SelectedRefinerName = SelectedRefiner?.RelativePath,\n                ClipSkip = ClipSkip,\n                IsVaeSelectionEnabled = IsVaeSelectionEnabled,\n                IsRefinerSelectionEnabled = IsRefinerSelectionEnabled,\n                IsClipSkipEnabled = IsClipSkipEnabled,\n                IsExtraNetworksEnabled = IsExtraNetworksEnabled,\n                IsModelLoaderSelectionEnabled = IsModelLoaderSelectionEnabled,\n                SelectedClip1Name = SelectedClip1?.RelativePath,\n                SelectedClip2Name = SelectedClip2?.RelativePath,\n                SelectedClip3Name = SelectedClip3?.RelativePath,\n                SelectedClip4Name = SelectedClip4?.RelativePath,\n                SelectedClipType = SelectedClipType,\n                IsClipModelSelectionEnabled = IsClipModelSelectionEnabled,\n                ModelLoader = SelectedModelLoader,\n                ShowRefinerOption = ShowRefinerOption,\n                ExtraNetworks = ExtraNetworksStackCardViewModel.SaveStateToJsonObject(),\n            }\n        );\n    }\n\n    /// <inheritdoc />\n    public override void LoadStateFromJsonObject(JsonObject state)\n    {\n        var model = DeserializeModel<ModelCardModel>(state);\n\n        // uwu 123\n        // :thinknom:\n        // :thinkcode:\n        SelectedModelLoader = model.ModelLoader is ModelLoader.Gguf ? ModelLoader.Unet : model.ModelLoader;\n\n        if (SelectedModelLoader is ModelLoader.Unet)\n        {\n            SelectedUnetModel = model.SelectedModelName is null\n                ? null\n                : ClientManager.UnetModels.FirstOrDefault(x => x.RelativePath == model.SelectedModelName);\n        }\n        else\n        {\n            SelectedModel = model.SelectedModelName is null\n                ? null\n                : ClientManager.Models.FirstOrDefault(x => x.RelativePath == model.SelectedModelName);\n        }\n\n        SelectedVae = model.SelectedVaeName is null\n            ? HybridModelFile.Default\n            : ClientManager.VaeModels.FirstOrDefault(x => x.RelativePath == model.SelectedVaeName);\n\n        SelectedRefiner = model.SelectedRefinerName is null\n            ? HybridModelFile.None\n            : ClientManager.Models.FirstOrDefault(x => x.RelativePath == model.SelectedRefinerName);\n\n        SelectedClip1 = model.SelectedClip1Name is null\n            ? HybridModelFile.None\n            : ClientManager.ClipModels.FirstOrDefault(x => x.RelativePath == model.SelectedClip1Name);\n\n        SelectedClip2 = model.SelectedClip2Name is null\n            ? HybridModelFile.None\n            : ClientManager.ClipModels.FirstOrDefault(x => x.RelativePath == model.SelectedClip2Name);\n\n        SelectedClip3 = model.SelectedClip3Name is null\n            ? HybridModelFile.None\n            : ClientManager.ClipModels.FirstOrDefault(x => x.RelativePath == model.SelectedClip3Name);\n\n        SelectedClip4 = model.SelectedClip4Name is null\n            ? HybridModelFile.None\n            : ClientManager.ClipModels.FirstOrDefault(x => x.RelativePath == model.SelectedClip4Name);\n\n        SelectedClipType = model.SelectedClipType;\n\n        ClipSkip = model.ClipSkip;\n\n        IsVaeSelectionEnabled = model.IsVaeSelectionEnabled;\n        IsRefinerSelectionEnabled = model.IsRefinerSelectionEnabled;\n        ShowRefinerOption = model.ShowRefinerOption;\n        IsClipSkipEnabled = model.IsClipSkipEnabled;\n        IsExtraNetworksEnabled = model.IsExtraNetworksEnabled;\n        IsModelLoaderSelectionEnabled = model.IsModelLoaderSelectionEnabled;\n        IsClipModelSelectionEnabled = model.IsClipModelSelectionEnabled;\n\n        if (model.ExtraNetworks is not null)\n        {\n            ExtraNetworksStackCardViewModel.LoadStateFromJsonObject(model.ExtraNetworks);\n        }\n    }\n\n    /// <inheritdoc />\n    public void LoadStateFromParameters(GenerationParameters parameters)\n    {\n        if (parameters.ModelName is not { } paramsModelName)\n            return;\n\n        var currentModels = ClientManager.Models.Concat(ClientManager.UnetModels).ToList();\n        var currentExtraNetworks = ClientManager.LoraModels.ToList();\n\n        HybridModelFile? model;\n\n        // First try hash match\n        if (parameters.ModelHash is not null)\n        {\n            model = currentModels.FirstOrDefault(m =>\n                m.Local?.ConnectedModelInfo?.Hashes.SHA256 is { } sha256\n                && sha256.StartsWith(parameters.ModelHash, StringComparison.InvariantCultureIgnoreCase)\n            );\n        }\n        else if (parameters.ModelVersionId is not null)\n        {\n            model = currentModels.FirstOrDefault(m =>\n                m.Local?.ConnectedModelInfo?.VersionId == parameters.ModelVersionId\n            );\n        }\n        else\n        {\n            // Name matches\n            model = currentModels.FirstOrDefault(m => m.RelativePath.EndsWith(paramsModelName));\n            model ??= currentModels.FirstOrDefault(m => m.ShortDisplayName.StartsWith(paramsModelName));\n        }\n\n        ExtraNetworksStackCardViewModel.Clear();\n\n        if (parameters.ExtraNetworkModelVersionIds is not null)\n        {\n            IsExtraNetworksEnabled = true;\n\n            foreach (var versionId in parameters.ExtraNetworkModelVersionIds)\n            {\n                var module = ExtraNetworksStackCardViewModel.AddModule<LoraModule>();\n                module.GetCard<ExtraNetworkCardViewModel>().SelectedModel =\n                    currentExtraNetworks.FirstOrDefault(m =>\n                        m.Local?.ConnectedModelInfo?.VersionId == versionId\n                    );\n                module.IsEnabled = true;\n            }\n        }\n\n        if (model is null)\n            return;\n\n        if (model.Local?.SharedFolderType is SharedFolderType.DiffusionModels)\n        {\n            SelectedUnetModel = model;\n        }\n        else\n        {\n            SelectedModel = model;\n        }\n    }\n\n    /// <inheritdoc />\n    public GenerationParameters SaveStateToParameters(GenerationParameters parameters)\n    {\n        if (IsStandaloneModelLoader)\n        {\n            return parameters with\n            {\n                ModelName = SelectedUnetModel?.FileName,\n                ModelHash = SelectedUnetModel?.Local?.ConnectedModelInfo?.Hashes.SHA256,\n            };\n        }\n\n        return parameters with\n        {\n            ModelName = SelectedModel?.FileName,\n            ModelHash = SelectedModel?.Local?.ConnectedModelInfo?.Hashes.SHA256,\n        };\n    }\n\n    partial void OnSelectedModelLoaderChanged(ModelLoader value)\n    {\n        if (value is ModelLoader.Unet)\n        {\n            if (!IsVaeSelectionEnabled)\n                IsVaeSelectionEnabled = true;\n\n            if (!IsClipModelSelectionEnabled)\n                IsClipModelSelectionEnabled = true;\n        }\n    }\n\n    partial void OnSelectedModelChanged(HybridModelFile? value)\n    {\n        // Update TabContext with the selected model\n        tabContext.SelectedModel = value;\n\n        if (!IsExtraNetworksEnabled)\n            return;\n\n        foreach (var card in ExtraNetworksStackCardViewModel.Cards)\n        {\n            if (card is not LoraModule loraModule)\n                continue;\n\n            if (loraModule.GetCard<ExtraNetworkCardViewModel>() is not { } cardViewModel)\n                continue;\n\n            cardViewModel.SelectedBaseModel = value;\n        }\n    }\n\n    partial void OnSelectedUnetModelChanged(HybridModelFile? value) => OnSelectedModelChanged(value);\n\n    private void SetupStandaloneModelLoader(ModuleApplyStepEventArgs e)\n    {\n        if (SelectedModelLoader is ModelLoader.Unet && IsGguf)\n        {\n            var checkpointLoader = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.UnetLoaderGGUF\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.UNETLoader)),\n                    UnetName =\n                        SelectedUnetModel?.RelativePath\n                        ?? throw new ValidationException(\"Model not selected\"),\n                }\n            );\n            e.Builder.Connections.Base.Model = checkpointLoader.Output;\n        }\n        else\n        {\n            var checkpointLoader = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.UNETLoader\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.UNETLoader)),\n                    UnetName =\n                        SelectedUnetModel?.RelativePath\n                        ?? throw new ValidationException(\"Model not selected\"),\n                    WeightDtype = SelectedDType ?? \"default\",\n                }\n            );\n            e.Builder.Connections.Base.Model = checkpointLoader.Output;\n        }\n\n        if (SelectedModelLoader is ModelLoader.Unet && IsHiDreamClip)\n        {\n            var modelSamplingSd3 = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.ModelSamplingSD3\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.ModelSamplingSD3)),\n                    Model = e.Builder.Connections.Base.Model,\n                    Shift = Shift,\n                }\n            );\n\n            e.Builder.Connections.Base.Model = modelSamplingSd3.Output;\n        }\n\n        var vaeLoader = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.VAELoader\n            {\n                Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.VAELoader)),\n                VaeName = SelectedVae?.RelativePath ?? throw new ValidationException(\"No VAE Selected\"),\n            }\n        );\n        e.Builder.Connections.Base.VAE = vaeLoader.Output;\n\n        if (SelectedClipType == \"flux\")\n        {\n            // DualCLIPLoader\n            var clipLoader = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.DualCLIPLoader\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.DualCLIPLoader)),\n                    ClipName1 =\n                        SelectedClip1?.RelativePath ?? throw new ValidationException(\"No Clip1 Selected\"),\n                    ClipName2 =\n                        SelectedClip2?.RelativePath ?? throw new ValidationException(\"No Clip2 Selected\"),\n                    Type = SelectedClipType ?? throw new ValidationException(\"No Clip Type Selected\"),\n                }\n            );\n            e.Builder.Connections.Base.Clip = clipLoader.Output;\n        }\n        else\n        {\n            SetupClipLoaders(e);\n        }\n    }\n\n    private void SetupDefaultModelLoader(ModuleApplyStepEventArgs e)\n    {\n        // Load base checkpoint\n        var loaderNode =\n            SelectedModelLoader is ModelLoader.Default\n                ? GetDefaultModelLoader(\n                    e,\n                    \"CheckpointLoader_Base\",\n                    SelectedModel ?? throw new ValidationException(\"Model not selected\")\n                )\n                : new ComfyNodeBuilder.CheckpointLoaderNF4\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.CheckpointLoaderNF4)),\n                    CkptName =\n                        SelectedModel?.RelativePath ?? throw new ValidationException(\"Model not selected\"),\n                };\n\n        var baseLoader = e.Nodes.AddTypedNode(loaderNode);\n\n        e.Builder.Connections.Base.Model = baseLoader.Output1;\n        e.Builder.Connections.Base.VAE = baseLoader.Output3;\n\n        if (IsClipModelSelectionEnabled)\n        {\n            SetupClipLoaders(e);\n        }\n        else\n        {\n            e.Builder.Connections.Base.Clip = baseLoader.Output2;\n        }\n\n        // Load refiner if enabled\n        if (IsRefinerSelectionEnabled && SelectedRefiner is { IsNone: false })\n        {\n            var refinerLoader = e.Nodes.AddTypedNode(\n                GetDefaultModelLoader(\n                    e,\n                    \"CheckpointLoader_Refiner\",\n                    SelectedRefiner ?? throw new ValidationException(\"Refiner Model enabled but not selected\")\n                )\n            );\n\n            e.Builder.Connections.Refiner.Model = refinerLoader.Output1;\n            e.Builder.Connections.Refiner.Clip = refinerLoader.Output2;\n            e.Builder.Connections.Refiner.VAE = refinerLoader.Output3;\n        }\n\n        // Load VAE override if enabled\n        if (IsVaeSelectionEnabled && SelectedVae is { IsNone: false, IsDefault: false })\n        {\n            var vaeLoader = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.VAELoader\n                {\n                    Name = \"VAELoader\",\n                    VaeName =\n                        SelectedVae?.RelativePath\n                        ?? throw new ValidationException(\"VAE enabled but not selected\"),\n                }\n            );\n\n            e.Builder.Connections.PrimaryVAE = vaeLoader.Output;\n        }\n    }\n\n    private void SetupClipLoaders(ModuleApplyStepEventArgs e)\n    {\n        if (\n            SelectedClip4 is { IsNone: false }\n            && SelectedClip3 is { IsNone: false }\n            && SelectedClip2 is { IsNone: false }\n            && SelectedClip1 is { IsNone: false }\n        )\n        {\n            var clipLoader = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.QuadrupleCLIPLoader\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.QuadrupleCLIPLoader)),\n                    ClipName1 =\n                        SelectedClip1?.RelativePath ?? throw new ValidationException(\"No Clip1 Selected\"),\n                    ClipName2 =\n                        SelectedClip2?.RelativePath ?? throw new ValidationException(\"No Clip2 Selected\"),\n                    ClipName3 =\n                        SelectedClip3?.RelativePath ?? throw new ValidationException(\"No Clip3 Selected\"),\n                    ClipName4 =\n                        SelectedClip4?.RelativePath ?? throw new ValidationException(\"No Clip4 Selected\"),\n                }\n            );\n            e.Builder.Connections.Base.Clip = clipLoader.Output;\n        }\n        else if (\n            SelectedClip3 is { IsNone: false }\n            && SelectedClip2 is { IsNone: false }\n            && SelectedClip1 is { IsNone: false }\n        )\n        {\n            var clipLoader = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.TripleCLIPLoader\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.TripleCLIPLoader)),\n                    ClipName1 =\n                        SelectedClip1?.RelativePath ?? throw new ValidationException(\"No Clip1 Selected\"),\n                    ClipName2 =\n                        SelectedClip2?.RelativePath ?? throw new ValidationException(\"No Clip2 Selected\"),\n                    ClipName3 =\n                        SelectedClip3?.RelativePath ?? throw new ValidationException(\"No Clip3 Selected\"),\n                }\n            );\n            e.Builder.Connections.Base.Clip = clipLoader.Output;\n        }\n        else if (SelectedClip2 is { IsNone: false } && SelectedClip1 is { IsNone: false })\n        {\n            var clipLoader = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.DualCLIPLoader\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.DualCLIPLoader)),\n                    ClipName1 =\n                        SelectedClip1?.RelativePath ?? throw new ValidationException(\"No Clip1 Selected\"),\n                    ClipName2 =\n                        SelectedClip2?.RelativePath ?? throw new ValidationException(\"No Clip2 Selected\"),\n                    Type = SelectedClipType ?? throw new ValidationException(\"No Clip Type Selected\"),\n                }\n            );\n            e.Builder.Connections.Base.Clip = clipLoader.Output;\n        }\n        else if (SelectedClip1 is { IsNone: false })\n        {\n            var clipLoader = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.CLIPLoader()\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.CLIPLoader)),\n                    ClipName =\n                        SelectedClip1?.RelativePath ?? throw new ValidationException(\"No Clip1 Selected\"),\n                    Type = SelectedClipType ?? throw new ValidationException(\"No Clip Type Selected\"),\n                }\n            );\n            e.Builder.Connections.Base.Clip = clipLoader.Output;\n        }\n    }\n\n    internal class ModelCardModel\n    {\n        public string? SelectedModelName { get; init; }\n        public string? SelectedRefinerName { get; init; }\n        public string? SelectedVaeName { get; init; }\n        public string? SelectedClip1Name { get; init; }\n        public string? SelectedClip2Name { get; init; }\n        public string? SelectedClip3Name { get; init; }\n        public string? SelectedClip4Name { get; init; }\n        public string? SelectedClipType { get; init; }\n        public ModelLoader ModelLoader { get; init; }\n        public int ClipSkip { get; init; } = 1;\n\n        public bool IsVaeSelectionEnabled { get; init; }\n        public bool IsRefinerSelectionEnabled { get; init; }\n        public bool IsClipSkipEnabled { get; init; }\n        public bool IsExtraNetworksEnabled { get; init; }\n        public bool IsModelLoaderSelectionEnabled { get; init; }\n        public bool IsClipModelSelectionEnabled { get; init; }\n        public bool ShowRefinerOption { get; init; }\n\n        public JsonObject? ExtraNetworks { get; init; }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/CfzCudnnToggleModule.cs",
    "content": "using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\nusing StabilityMatrix.Core.Models.Inference;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<CfzCudnnToggleModule>]\npublic class CfzCudnnToggleModule : ModuleBase\n{\n    /// <inheritdoc />\n    public CfzCudnnToggleModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"CUDNN Toggle (ComfyUI-Zluda)\";\n        AddCards(vmFactory.Get<CfzCudnnToggleCardViewModel>());\n    }\n\n    /// <summary>\n    /// Applies CUDNN Toggle node between sampler latent output and VAE decode\n    /// This prevents \"GET was unable to find an engine\" errors on AMD cards with Zluda\n    /// </summary>\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        // Get the primary connection (can be latent or image)\n        var primary = e.Builder.Connections.Primary;\n        if (primary == null)\n        {\n            return; // No primary connection to process\n        }\n\n        // Check if primary is a latent (from sampler output)\n        if (primary.IsT0) // T0 is LatentNodeConnection\n        {\n            var card = GetCard<CfzCudnnToggleCardViewModel>();\n            var latentConnection = primary.AsT0;\n\n            // Insert CUDNN toggle node between sampler and VAE decode\n            var cudnnToggleOutput = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.CUDNNToggleAutoPassthrough\n                {\n                    Name = e.Nodes.GetUniqueName(\"CUDNNToggle\"),\n                    Model = null,\n                    Conditioning = null,\n                    Latent = latentConnection, // Pass through the latent from sampler\n                    EnableCudnn = !card.DisableCudnn,\n                    CudnnBenchmark = false,\n                }\n            );\n\n            // Update the primary connection to use the CUDNN toggle latent output (Output3)\n            // This ensures VAE decode receives latent from CUDNN toggle instead of directly from sampler\n            e.Builder.Connections.Primary = cudnnToggleOutput.Output3;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ControlNetModule.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<ControlNetModule>]\npublic class ControlNetModule : ModuleBase\n{\n    /// <inheritdoc />\n    public ControlNetModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"ControlNet\";\n        AddCards(vmFactory.Get<ControlNetCardViewModel>());\n    }\n\n    protected override IEnumerable<ImageSource> GetInputImages()\n    {\n        if (\n            IsEnabled\n            && GetCard<ControlNetCardViewModel>().SelectImageCardViewModel\n                is { ImageSource: { } image, IsImageFileNotFound: false }\n        )\n        {\n            yield return image;\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var card = GetCard<ControlNetCardViewModel>();\n\n        var image = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.LoadImage\n            {\n                Name = e.Nodes.GetUniqueName(\"ControlNet_LoadImage\"),\n                Image =\n                    card.SelectImageCardViewModel.ImageSource?.GetHashGuidFileNameCached(\"Inference\")\n                    ?? throw new ValidationException(\"No ImageSource\")\n            }\n        ).Output1;\n\n        if (card.SelectedPreprocessor is { } preprocessor && preprocessor != ComfyAuxPreprocessor.None)\n        {\n            var aioPreprocessor = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.AIOPreprocessor\n                {\n                    Name = e.Nodes.GetUniqueName(\"ControlNet_Preprocessor\"),\n                    Image = image,\n                    Preprocessor = preprocessor.ToString(),\n                    // Use lower of width and height for resolution\n                    Resolution = Math.Min(card.Width, card.Height)\n                }\n            );\n\n            image = aioPreprocessor.Output;\n        }\n\n        // If ReferenceOnly is selected, use special node\n        if (card.SelectedModel == RemoteModels.ControlNetReferenceOnlyModel)\n        {\n            // We need to rescale image to be the current primary size if it's not already\n            var originalPrimary = e.Temp.Primary!.Unwrap();\n            var originalPrimarySize = e.Builder.Connections.PrimarySize;\n\n            if (card.SelectImageCardViewModel.CurrentBitmapSize != originalPrimarySize)\n            {\n                var scaled = e.Builder.Group_Upscale(\n                    e.Nodes.GetUniqueName(\"ControlNet_Rescale\"),\n                    image,\n                    e.Temp.GetDefaultVAE(),\n                    ComfyUpscaler.NearestExact,\n                    originalPrimarySize.Width,\n                    originalPrimarySize.Height\n                );\n                e.Temp.Primary = scaled;\n            }\n            else\n            {\n                e.Temp.Primary = image;\n            }\n\n            // Set image as new latent source, add reference only node\n            var model = e.Temp.GetRefinerOrBaseModel();\n            var controlNetReferenceOnly = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.ReferenceOnlySimple\n                {\n                    Name = e.Nodes.GetUniqueName(\"ControlNet_ReferenceOnly\"),\n                    Reference = e.Builder.GetPrimaryAsLatent(\n                        e.Temp.Primary,\n                        e.Builder.Connections.GetDefaultVAE()\n                    ),\n                    Model = model\n                }\n            );\n\n            var referenceOnlyModel = controlNetReferenceOnly.Output1;\n\n            // If ControlNet strength is not 1, add Model Merge\n            if (Math.Abs(card.Strength - 1) > 0.01)\n            {\n                var modelBlend = e.Nodes.AddTypedNode(\n                    new ComfyNodeBuilder.ModelMergeSimple\n                    {\n                        Name = e.Nodes.GetUniqueName(\"ControlNet_ReferenceOnly_ModelMerge\"),\n                        Model1 = referenceOnlyModel,\n                        Model2 = e.Temp.GetRefinerOrBaseModel(),\n                        // Where 0 is full reference only, 1 is full original\n                        Ratio = 1 - card.Strength\n                    }\n                );\n\n                referenceOnlyModel = modelBlend.Output;\n            }\n\n            // Set output as new primary and model source\n            if (model == e.Temp.Refiner.Model)\n            {\n                e.Temp.Refiner.Model = referenceOnlyModel;\n            }\n            else\n            {\n                e.Temp.Base.Model = referenceOnlyModel;\n            }\n            e.Temp.Primary = controlNetReferenceOnly.Output2;\n\n            // Indicate that the Primary latent has been temp batched\n            // https://github.com/comfyanonymous/ComfyUI_experiments/issues/11\n\n            e.Temp.IsPrimaryTempBatched = true;\n            // Index 0 is the original image, index 1 is the reference only latent\n            e.Temp.PrimaryTempBatchPickIndex = 1;\n\n            return;\n        }\n\n        var controlNetLoader = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.ControlNetLoader\n            {\n                Name = e.Nodes.GetUniqueName(\"ControlNetLoader\"),\n                ControlNetName =\n                    card.SelectedModel?.RelativePath ?? throw new ValidationException(\"No SelectedModel\"),\n            }\n        );\n\n        var controlNetApply = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.ControlNetApplyAdvanced\n            {\n                Name = e.Nodes.GetUniqueName(\"ControlNetApply\"),\n                Image = image,\n                ControlNet = controlNetLoader.Output,\n                Positive = e.Temp.Base.Conditioning!.Unwrap().Positive,\n                Negative = e.Temp.Base.Conditioning.Negative,\n                Strength = card.Strength,\n                StartPercent = card.StartPercent,\n                EndPercent = card.EndPercent,\n            }\n        );\n\n        e.Temp.Base.Conditioning = (controlNetApply.Output1, controlNetApply.Output2);\n\n        // Refiner if available\n        if (e.Temp.Refiner.Conditioning is not null)\n        {\n            var controlNetRefinerApply = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.ControlNetApplyAdvanced\n                {\n                    Name = e.Nodes.GetUniqueName(\"Refiner_ControlNetApply\"),\n                    Image = image,\n                    ControlNet = controlNetLoader.Output,\n                    Positive = e.Temp.Refiner.Conditioning!.Unwrap().Positive,\n                    Negative = e.Temp.Refiner.Conditioning.Negative,\n                    Strength = card.Strength,\n                    StartPercent = card.StartPercent,\n                    EndPercent = card.EndPercent,\n                }\n            );\n\n            e.Temp.Refiner.Conditioning = (controlNetRefinerApply.Output1, controlNetRefinerApply.Output2);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/DiscreteModelSamplingModule.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<DiscreteModelSamplingModule>]\npublic class DiscreteModelSamplingModule : ModuleBase\n{\n    public DiscreteModelSamplingModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"Discrete Model Sampling\";\n        AddCards(vmFactory.Get<DiscreteModelSamplingCardViewModel>());\n    }\n\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var vm = GetCard<DiscreteModelSamplingCardViewModel>();\n\n        foreach (var modelConnections in e.Builder.Connections.Models.Values)\n        {\n            if (modelConnections.Model is not { } model)\n                continue;\n\n            var modelSamplingDiscrete = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.ModelSamplingDiscrete\n                {\n                    Name = e.Nodes.GetUniqueName(\"ModelSamplingDiscrete\"),\n                    Model = model,\n                    Sampling = vm.SelectedSamplingMethod,\n                    Zsnr = vm.IsZsnrEnabled\n                }\n            );\n\n            modelConnections.Model = modelSamplingDiscrete.Output;\n            e.Temp.Base.Model = modelSamplingDiscrete.Output;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FaceDetailerModule.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<FaceDetailerModule>]\npublic partial class FaceDetailerModule : ModuleBase, IValidatableModule\n{\n    /// <inheritdoc />\n    public override bool IsSettingsEnabled => true;\n\n    /// <inheritdoc />\n    public override IRelayCommand SettingsCommand => OpenSettingsDialogCommand;\n\n    public FaceDetailerModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"Face Detailer\";\n        AddCards(vmFactory.Get<FaceDetailerViewModel>());\n    }\n\n    [RelayCommand]\n    private async Task OpenSettingsDialog()\n    {\n        var gridVm = VmFactory.Get<PropertyGridViewModel>(vm =>\n        {\n            vm.Title = $\"{Title} {Resources.Label_Settings}\";\n            vm.SelectedObject = Cards.ToArray();\n            vm.IncludeCategories = [\"Settings\"];\n        });\n\n        await gridVm.GetDialog().ShowAsync();\n    }\n\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var faceDetailerCard = GetCard<FaceDetailerViewModel>();\n        if (faceDetailerCard is { InheritSeed: false, SeedCardViewModel.IsRandomizeEnabled: true })\n        {\n            faceDetailerCard.SeedCardViewModel.GenerateNewSeed();\n        }\n\n        var bboxLoader = new ComfyNodeBuilder.UltralyticsDetectorProvider\n        {\n            Name = e.Builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.UltralyticsDetectorProvider)),\n            ModelName =\n                GetModelName(faceDetailerCard.BboxModel) ?? throw new ArgumentException(\"No BboxModel\"),\n        };\n\n        var samplerName =\n            (\n                faceDetailerCard.IsSamplerSelectionEnabled\n                    ? faceDetailerCard.Sampler?.Name\n                    : e.Builder.Connections.PrimarySampler?.Name\n            ) ?? throw new ArgumentException(\"No PrimarySampler\");\n\n        var schedulerName =\n            (\n                faceDetailerCard.IsSchedulerSelectionEnabled\n                    ? faceDetailerCard.Scheduler?.Name\n                    : e.Builder.Connections.PrimaryScheduler?.Name\n            ) ?? throw new ArgumentException(\"No PrimaryScheduler\");\n\n        if (schedulerName == \"align_your_steps\")\n        {\n            if (e.Builder.Connections.PrimaryModelType is null)\n            {\n                throw new ArgumentException(\"No Model Type for AYS\");\n            }\n\n            schedulerName =\n                e.Builder.Connections.PrimaryModelType == \"SDXL\"\n                    ? ComfyScheduler.FaceDetailerAlignYourStepsSDXL.Name\n                    : ComfyScheduler.FaceDetailerAlignYourStepsSD1.Name;\n        }\n\n        var cfg = faceDetailerCard.IsCfgScaleEnabled\n            ? faceDetailerCard.Cfg\n            : e.Builder.Connections.PrimaryCfg ?? throw new ArgumentException(\"No CFG\");\n\n        var steps = faceDetailerCard.IsStepsEnabled\n            ? faceDetailerCard.Steps\n            : e.Builder.Connections.PrimarySteps ?? throw new ArgumentException(\"No Steps\");\n\n        var faceDetailer = new ComfyNodeBuilder.FaceDetailer\n        {\n            Name = e.Builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.FaceDetailer)),\n            GuideSize = faceDetailerCard.GuideSize,\n            GuideSizeFor = faceDetailerCard.GuideSizeFor,\n            MaxSize = faceDetailerCard.MaxSize,\n            Seed = faceDetailerCard.InheritSeed\n                ? e.Builder.Connections.Seed\n                : Convert.ToUInt64(faceDetailerCard.SeedCardViewModel.Seed),\n            Steps = steps,\n            Cfg = cfg,\n            SamplerName = samplerName,\n            Scheduler = schedulerName,\n            Denoise = faceDetailerCard.Denoise,\n            Feather = faceDetailerCard.Feather,\n            NoiseMask = faceDetailerCard.NoiseMask,\n            ForceInpaint = faceDetailerCard.ForceInpaint,\n            BboxThreshold = faceDetailerCard.BboxThreshold,\n            BboxDilation = faceDetailerCard.BboxDilation,\n            BboxCropFactor = faceDetailerCard.BboxCropFactor,\n            SamDetectionHint = faceDetailerCard.SamDetectionHint,\n            SamDilation = faceDetailerCard.SamDilation,\n            SamThreshold = faceDetailerCard.SamThreshold,\n            SamBboxExpansion = faceDetailerCard.SamBboxExpansion,\n            SamMaskHintThreshold = faceDetailerCard.SamMaskHintThreshold,\n            SamMaskHintUseNegative = faceDetailerCard.SamMaskHintUseNegative,\n            DropSize = faceDetailerCard.DropSize,\n            Cycle = faceDetailerCard.Cycle,\n            Image = e.Builder.GetPrimaryAsImage(),\n            Model = e.Builder.Connections.GetRefinerOrBaseModel(),\n            Clip = e.Builder.Connections.Base.Clip ?? throw new ArgumentException(\"No BaseClip\"),\n            Vae = e.Builder.Connections.GetDefaultVAE(),\n            Positive = GetPositiveConditioning(faceDetailerCard, e),\n            Negative = GetNegativeConditioning(faceDetailerCard, e),\n            BboxDetector = e.Nodes.AddTypedNode(bboxLoader).Output1,\n            Wildcard = faceDetailerCard.WildcardViewModel.GetPrompt().ProcessedText ?? string.Empty,\n            TiledDecode = faceDetailerCard.UseTiledDecode,\n            TiledEncode = faceDetailerCard.UseTiledEncode,\n        };\n\n        var segmModelName = GetModelName(faceDetailerCard.SegmModel);\n        if (!string.IsNullOrWhiteSpace(segmModelName))\n        {\n            var segmLoader = new ComfyNodeBuilder.UltralyticsDetectorProvider\n            {\n                Name = e.Builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.UltralyticsDetectorProvider)),\n                ModelName = segmModelName,\n            };\n            faceDetailer.SegmDetectorOpt = e.Nodes.AddTypedNode(segmLoader).Output2;\n        }\n\n        var samModelName = GetModelName(faceDetailerCard.SamModel);\n        if (!string.IsNullOrWhiteSpace(samModelName))\n        {\n            var samLoader = new ComfyNodeBuilder.SamLoader\n            {\n                Name = e.Builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.SamLoader)),\n                ModelName = samModelName,\n                DeviceMode = \"AUTO\",\n            };\n            faceDetailer.SamModelOpt = e.Nodes.AddTypedNode(samLoader).Output;\n        }\n\n        e.Builder.Connections.Primary = e.Nodes.AddTypedNode(faceDetailer).Output;\n    }\n\n    private string? GetModelName(HybridModelFile? model) =>\n        model switch\n        {\n            null => null,\n            { FileName: \"@none\" } => null,\n            { RemoteName: \"@none\" } => null,\n            { Local: not null } => model.RelativePath.NormalizePathSeparators(),\n            { RemoteName: not null } => model.RemoteName,\n            _ => null,\n        };\n\n    private ConditioningNodeConnection GetPositiveConditioning(\n        FaceDetailerViewModel viewModel,\n        ModuleApplyStepEventArgs e\n    )\n    {\n        if (!viewModel.UseSeparatePrompt)\n        {\n            return e.Builder.Connections.GetRefinerOrBaseConditioning().Positive;\n        }\n\n        var prompt = viewModel.PromptCardViewModel.GetPrompt();\n        prompt.Process();\n        var positiveClip = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.CLIPTextEncode\n            {\n                Name = e.Builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.CLIPTextEncode)),\n                Clip = e.Builder.Connections.Base.Clip!,\n                Text = prompt.ProcessedText,\n            }\n        );\n\n        return positiveClip.Output;\n    }\n\n    private ConditioningNodeConnection GetNegativeConditioning(\n        FaceDetailerViewModel viewModel,\n        ModuleApplyStepEventArgs e\n    )\n    {\n        if (!viewModel.UseSeparatePrompt)\n        {\n            return e.Builder.Connections.GetRefinerOrBaseConditioning().Negative;\n        }\n\n        var prompt = viewModel.PromptCardViewModel.GetNegativePrompt();\n        prompt.Process();\n        var negativeClip = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.CLIPTextEncode\n            {\n                Name = e.Builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.CLIPTextEncode)),\n                Clip = e.Builder.Connections.Base.Clip!,\n                Text = prompt.ProcessedText,\n            }\n        );\n\n        return negativeClip.Output;\n    }\n\n    public async Task<bool> Validate()\n    {\n        var faceDetailerCard = GetCard<FaceDetailerViewModel>();\n        if (!string.IsNullOrWhiteSpace(GetModelName(faceDetailerCard.BboxModel)))\n            return true;\n\n        var dialog = DialogHelper.CreateMarkdownDialog(\n            \"Please select a BBox Model to continue.\",\n            \"No Model Selected\"\n        );\n        await dialog.ShowAsync();\n        return false;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxGuidanceModule.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<FluxGuidanceModule>]\npublic class FluxGuidanceModule : ModuleBase\n{\n    public FluxGuidanceModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"Use Flux Guidance\";\n    }\n\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e) { }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FluxHiresFixModule.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<FluxHiresFixModule>]\npublic class FluxHiresFixModule(IServiceManager<ViewModelBase> vmFactory) : HiresFixModule(vmFactory)\n{\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var builder = e.Builder;\n\n        var upscaleCard = GetCard<UpscalerCardViewModel>();\n        var samplerCard = GetCard<SamplerCardViewModel>();\n\n        // Get new latent size\n        var hiresSize = builder.Connections.PrimarySize.WithScale(upscaleCard.Scale);\n\n        // Select between latent upscale and normal upscale based on the upscale method\n        var selectedUpscaler = upscaleCard.SelectedUpscaler!.Value;\n\n        // If upscaler selected, upscale latent image first\n        if (selectedUpscaler.Type != ComfyUpscalerType.None)\n        {\n            builder.Connections.Primary = builder.Group_Upscale(\n                builder.Nodes.GetUniqueName(\"HiresFix\"),\n                builder.Connections.Primary.Unwrap(),\n                builder.Connections.GetDefaultVAE(),\n                selectedUpscaler,\n                hiresSize.Width,\n                hiresSize.Height\n            );\n        }\n\n        // If we need to inherit primary sampler addons, use their temp args\n        if (samplerCard.InheritPrimarySamplerAddons)\n        {\n            e.Temp = e.Builder.Connections.BaseSamplerTemporaryArgs ?? e.CreateTempFromBuilder();\n        }\n        else\n        {\n            // otherwise just use new ones\n            e.Temp = e.CreateTempFromBuilder();\n        }\n\n        var hiresSampler = builder.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.SamplerCustomAdvanced\n            {\n                Name = builder.Nodes.GetUniqueName(\"HiresFix_Sampler\"),\n                Guider = builder.Connections.PrimaryGuider,\n                Noise = builder.Connections.PrimaryNoise,\n                Sampler = builder.Connections.PrimarySamplerNode,\n                Sigmas = builder.Connections.PrimarySigmas,\n                LatentImage = builder.GetPrimaryAsLatent()\n            }\n        );\n\n        // Set as primary\n        builder.Connections.Primary = hiresSampler.Output1;\n        builder.Connections.PrimarySize = hiresSize;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/FreeUModule.cs",
    "content": "﻿using System.Linq;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<FreeUModule>]\npublic class FreeUModule : ModuleBase\n{\n    /// <inheritdoc />\n    public FreeUModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"FreeU\";\n        AddCards(vmFactory.Get<FreeUCardViewModel>());\n    }\n\n    /// <summary>\n    /// Applies FreeU to the Model property\n    /// </summary>\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var card = GetCard<FreeUCardViewModel>();\n\n        // Currently applies to all models\n        // TODO: Add option to apply to either base or refiner\n\n        foreach (var modelConnections in e.Builder.Connections.Models.Values.Where(m => m.Model is not null))\n        {\n            var freeUOutput = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.FreeU\n                {\n                    Name = e.Nodes.GetUniqueName($\"FreeU_{modelConnections.Name}\"),\n                    Model = modelConnections.Model!,\n                    B1 = card.B1,\n                    B2 = card.B2,\n                    S1 = card.S1,\n                    S2 = card.S2\n                }\n            ).Output;\n\n            modelConnections.Model = freeUOutput;\n            e.Temp.Base.Model = freeUOutput;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/HiresFixModule.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<HiresFixModule>]\npublic partial class HiresFixModule : ModuleBase\n{\n    /// <inheritdoc />\n    public override bool IsSettingsEnabled => true;\n\n    /// <inheritdoc />\n    public override IRelayCommand SettingsCommand => OpenSettingsDialogCommand;\n\n    /// <inheritdoc />\n    public HiresFixModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"HiresFix\";\n        AddCards(\n            vmFactory.Get<UpscalerCardViewModel>(),\n            vmFactory.Get<SamplerCardViewModel>(vmSampler =>\n            {\n                vmSampler.IsDenoiseStrengthEnabled = true;\n            })\n        );\n    }\n\n    [RelayCommand]\n    private async Task OpenSettingsDialog()\n    {\n        var gridVm = VmFactory.Get<PropertyGridViewModel>(vm =>\n        {\n            vm.Title = $\"{Title} {Resources.Label_Settings}\";\n            vm.SelectedObject = Cards.ToArray();\n            vm.IncludeCategories = [\"Settings\"];\n        });\n\n        await gridVm.GetDialog().ShowAsync();\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var builder = e.Builder;\n\n        var upscaleCard = GetCard<UpscalerCardViewModel>();\n        var samplerCard = GetCard<SamplerCardViewModel>();\n\n        // Get new latent size\n        var hiresSize = builder.Connections.PrimarySize.WithScale(upscaleCard.Scale);\n\n        // Select between latent upscale and normal upscale based on the upscale method\n        var selectedUpscaler = upscaleCard.SelectedUpscaler!.Value;\n\n        // If upscaler selected, upscale latent image first\n        if (selectedUpscaler.Type != ComfyUpscalerType.None)\n        {\n            builder.Connections.Primary = builder.Group_Upscale(\n                builder.Nodes.GetUniqueName(\"HiresFix\"),\n                builder.Connections.Primary.Unwrap(),\n                builder.Connections.GetDefaultVAE(),\n                selectedUpscaler,\n                hiresSize.Width,\n                hiresSize.Height\n            );\n        }\n\n        // If we need to inherit primary sampler addons, use their temp args\n        if (samplerCard.InheritPrimarySamplerAddons)\n        {\n            e.Temp = e.Builder.Connections.BaseSamplerTemporaryArgs ?? e.CreateTempFromBuilder();\n        }\n        else\n        {\n            // otherwise just use new ones\n            e.Temp = e.CreateTempFromBuilder();\n        }\n\n        var samplerName =\n            (\n                samplerCard.IsSamplerSelectionEnabled\n                    ? samplerCard.SelectedSampler?.Name\n                    : e.Builder.Connections.PrimarySampler?.Name\n            ) ?? throw new ArgumentException(\"No PrimarySampler\");\n\n        var schedulerName =\n            (\n                samplerCard.IsSchedulerSelectionEnabled\n                    ? samplerCard.SelectedScheduler?.Name\n                    : e.Builder.Connections.PrimaryScheduler?.Name\n            ) ?? throw new ArgumentException(\"No PrimaryScheduler\");\n\n        var cfg = samplerCard.IsCfgScaleEnabled\n            ? samplerCard.CfgScale\n            : e.Builder.Connections.PrimaryCfg ?? throw new ArgumentException(\"No CFG\");\n\n        if (schedulerName == \"align_your_steps\")\n        {\n            var samplerSelect = builder.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.KSamplerSelect\n                {\n                    Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.KSamplerSelect)),\n                    SamplerName = samplerName\n                }\n            );\n\n            var alignYourStepsScheduler = builder.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.AlignYourStepsScheduler\n                {\n                    Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.AlignYourStepsScheduler)),\n                    ModelType = samplerCard.SelectedModelType,\n                    Denoise = samplerCard.DenoiseStrength,\n                    Steps = samplerCard.Steps\n                }\n            );\n\n            var hiresCustomSampler = builder.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.SamplerCustom\n                {\n                    Name = builder.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.SamplerCustom)),\n                    Model = builder.Connections.GetRefinerOrBaseModel(),\n                    AddNoise = true,\n                    NoiseSeed = builder.Connections.Seed,\n                    Cfg = cfg,\n                    Sampler = samplerSelect.Output,\n                    Sigmas = alignYourStepsScheduler.Output,\n                    Positive = e.Temp.GetRefinerOrBaseConditioning().Positive,\n                    Negative = e.Temp.GetRefinerOrBaseConditioning().Negative,\n                    LatentImage = builder.GetPrimaryAsLatent(),\n                }\n            );\n\n            builder.Connections.Primary = hiresCustomSampler.Output1;\n        }\n        else\n        {\n            var hiresSampler = builder.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.KSampler\n                {\n                    Name = builder.Nodes.GetUniqueName(\"HiresFix_Sampler\"),\n                    Model = builder.Connections.GetRefinerOrBaseModel(),\n                    Seed = builder.Connections.Seed,\n                    Steps = samplerCard.Steps,\n                    Cfg = cfg,\n                    SamplerName = samplerName ?? throw new ArgumentException(\"No PrimarySampler\"),\n                    Scheduler = schedulerName ?? throw new ArgumentException(\"No PrimaryScheduler\"),\n                    Positive = e.Temp.GetRefinerOrBaseConditioning().Positive,\n                    Negative = e.Temp.GetRefinerOrBaseConditioning().Negative,\n                    LatentImage = builder.GetPrimaryAsLatent(),\n                    Denoise = samplerCard.DenoiseStrength\n                }\n            );\n\n            // Set as primary\n            builder.Connections.Primary = hiresSampler.Output;\n        }\n\n        builder.Connections.PrimarySize = hiresSize;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LayerDiffuseModule.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<LayerDiffuseModule>]\npublic class LayerDiffuseModule : ModuleBase\n{\n    /// <inheritdoc />\n    public LayerDiffuseModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"Layer Diffuse\";\n        AddCards(vmFactory.Get<LayerDiffuseCardViewModel>());\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var card = GetCard<LayerDiffuseCardViewModel>();\n        card.ApplyStep(e);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/LoraModule.cs",
    "content": "﻿using System.Linq;\nusing System.Reactive.Linq;\nusing System.Threading.Tasks;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData.Binding;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<LoraModule>]\npublic partial class LoraModule : ModuleBase\n{\n    /// <inheritdoc />\n    public override bool IsSettingsEnabled => true;\n\n    /// <inheritdoc />\n    public override IRelayCommand SettingsCommand => OpenSettingsDialogCommand;\n\n    public LoraModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"Lora\";\n\n        var extraNetworksVm = vmFactory.Get<ExtraNetworkCardViewModel>(card =>\n        {\n            card.IsModelWeightEnabled = true;\n            // Disable clip weight by default, but allow user to enable it\n            card.IsClipWeightToggleEnabled = true;\n            card.IsClipWeightEnabled = false;\n        });\n\n        AddCards(extraNetworksVm);\n\n        AddDisposable(\n            extraNetworksVm\n                .WhenPropertyChanged(vm => vm.SelectedModel)\n                .Throttle(TimeSpan.FromMilliseconds(50))\n                .Subscribe(next =>\n                {\n                    var model = next.Value;\n                    if (model is null)\n                    {\n                        Title = Resources.Label_ExtraNetworks;\n                        return;\n                    }\n\n                    if (model.Local?.HasConnectedModel ?? false)\n                    {\n                        Title = model.Local.ConnectedModelInfo.ModelName;\n                    }\n                    else\n                    {\n                        Title = model.ShortDisplayName;\n                    }\n                })\n        );\n    }\n\n    [RelayCommand]\n    private async Task OpenSettingsDialog()\n    {\n        var gridVm = VmFactory.Get<PropertyGridViewModel>(vm =>\n        {\n            vm.Title = $\"{Title} {Resources.Label_Settings}\";\n            vm.SelectedObject = Cards.ToArray();\n            vm.IncludeCategories = [\"Settings\"];\n        });\n\n        await gridVm.GetDialog().ShowAsync();\n    }\n\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var card = GetCard<ExtraNetworkCardViewModel>();\n\n        // Skip if no lora model\n        if (card.SelectedModel is not { } selectedLoraModel)\n            return;\n\n        // Add lora conditioning to all models\n        foreach (var modelConnections in e.Builder.Connections.Models.Values)\n        {\n            if (modelConnections.Model is not { } model || modelConnections.Clip is not { } clip)\n                continue;\n\n            var loraLoader = e.Nodes.AddNamedNode(\n                ComfyNodeBuilder.LoraLoader(\n                    e.Nodes.GetUniqueName($\"Loras_{modelConnections.Name}\"),\n                    model,\n                    clip,\n                    selectedLoraModel.RelativePath,\n                    card.ModelWeight,\n                    card.ClipWeight\n                )\n            );\n\n            // Replace current model and clip with lora loaded model and clip\n            modelConnections.Model = loraLoader.Output1;\n            modelConnections.Clip = loraLoader.Output2;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/ModuleBase.cs",
    "content": "﻿using System.Collections.Generic;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\npublic abstract class ModuleBase : StackExpanderViewModel, IComfyStep, IInputImageProvider\n{\n    protected readonly IServiceManager<ViewModelBase> VmFactory;\n\n    /// <inheritdoc />\n    protected ModuleBase(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        VmFactory = vmFactory;\n    }\n\n    /// <inheritdoc />\n    public void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        if (e.IsEnabledOverrides.TryGetValue(GetType(), out var isEnabledOverride))\n        {\n            if (isEnabledOverride)\n            {\n                OnApplyStep(e);\n            }\n\n            return;\n        }\n\n        if (!IsEnabled)\n        {\n            return;\n        }\n\n        OnApplyStep(e);\n    }\n\n    protected abstract void OnApplyStep(ModuleApplyStepEventArgs e);\n\n    /// <inheritdoc />\n    IEnumerable<ImageSource> IInputImageProvider.GetInputImages() => GetInputImages();\n\n    protected virtual IEnumerable<ImageSource> GetInputImages()\n    {\n        yield break;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/NRSModule.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<NRSModule>]\npublic class NRSModule : ModuleBase\n{\n    /// <inheritdoc />\n    public NRSModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"Negative Rejection Steering (NRS)\";\n        AddCards(vmFactory.Get<NrsCardViewModel>());\n    }\n\n    /// <summary>\n    /// Applies NRS to the Model property\n    /// </summary>\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var card = GetCard<NrsCardViewModel>();\n\n        // Currently applies to all models\n        // TODO: Add option to apply to either base or refiner\n\n        foreach (var modelConnections in e.Builder.Connections.Models.Values.Where(m => m.Model is not null))\n        {\n            var nrsOutput = e\n                .Nodes.AddTypedNode(\n                    new ComfyNodeBuilder.NRS\n                    {\n                        Name = e.Nodes.GetUniqueName($\"NRS_{modelConnections.Name}\"),\n                        Model = modelConnections.Model!,\n                        Skew = card.Skew,\n                        Stretch = card.Stretch,\n                        Squash = card.Squash,\n                    }\n                )\n                .Output;\n\n            modelConnections.Model = nrsOutput;\n            e.Temp.Base.Model = nrsOutput;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PlasmaNoiseModule.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<PlasmaNoiseModule>]\npublic class PlasmaNoiseModule : ModuleBase\n{\n    public PlasmaNoiseModule(ServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"Plasma Noise\";\n        AddCards(vmFactory.Get<PlasmaNoiseCardViewModel>());\n    }\n\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        // handled elsewhere\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/PromptExpansionModule.cs",
    "content": "﻿using System;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<PromptExpansionModule>]\npublic class PromptExpansionModule : ModuleBase\n{\n    public PromptExpansionModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"Prompt Expansion\";\n        AddCards(vmFactory.Get<PromptExpansionCardViewModel>());\n    }\n\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var promptExpansionCard = GetCard<PromptExpansionCardViewModel>();\n\n        var model =\n            promptExpansionCard.SelectedModel\n            ?? throw new InvalidOperationException($\"{Title}: Model not selected\");\n\n        e.Builder.Connections.PositivePrompt = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.PromptExpansion\n            {\n                Name = e.Nodes.GetUniqueName(\"PromptExpansion_Positive\"),\n                ModelName = model.RelativePath,\n                Text = e.Builder.Connections.PositivePrompt,\n                Seed = e.Builder.Connections.Seed,\n                LogPrompt = promptExpansionCard.IsLogOutputEnabled\n            }\n        ).Output;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/RescaleCfgModule.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<RescaleCfgModule>]\npublic class RescaleCfgModule : ModuleBase\n{\n    public RescaleCfgModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"CFG Rescale\";\n        AddCards(vmFactory.Get<RescaleCfgCardViewModel>());\n    }\n\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var vm = GetCard<RescaleCfgCardViewModel>();\n\n        foreach (var modelConnections in e.Builder.Connections.Models.Values)\n        {\n            if (modelConnections.Model is not { } model)\n                continue;\n\n            var rescaleCfg = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.RescaleCFG\n                {\n                    Name = e.Nodes.GetUniqueName(\"RescaleCFG\"),\n                    Model = model,\n                    Multiplier = vm.Multiplier\n                }\n            );\n\n            modelConnections.Model = rescaleCfg.Output;\n\n            switch (modelConnections.Name)\n            {\n                case \"Base\":\n                    e.Temp.Base.Model = rescaleCfg.Output;\n                    break;\n                case \"Refiner\":\n                    e.Temp.Refiner.Model = rescaleCfg.Output;\n                    break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/SaveImageModule.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<SaveImageModule>]\npublic class SaveImageModule : ModuleBase\n{\n    /// <inheritdoc />\n    public SaveImageModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = Resources.Label_SaveIntermediateImage;\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var preview = e.Builder.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.PreviewImage\n            {\n                Name = e.Builder.Nodes.GetUniqueName(\"SaveIntermediateImage\"),\n                Images = e.Builder.GetPrimaryAsImage()\n            }\n        );\n\n        e.Builder.Connections.OutputNodes.Add(preview);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/TiledVAEModule.cs",
    "content": "using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<TiledVAEModule>]\npublic class TiledVAEModule : ModuleBase\n{\n    public TiledVAEModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"Tiled VAE Decode\";\n        AddCards(vmFactory.Get<TiledVAECardViewModel>());\n    }\n\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var card = GetCard<TiledVAECardViewModel>();\n\n        // Register a pre-output action that replaces standard VAE decode with tiled decode\n        e.PreOutputActions.Add(args =>\n        {\n            var builder = args.Builder;\n\n            // Only apply if primary is in latent space\n            if (builder.Connections.Primary?.IsT0 != true)\n                return;\n\n            var latent = builder.Connections.Primary.AsT0;\n            var vae = builder.Connections.GetDefaultVAE();\n\n            // Use tiled VAE decode instead of standard decode\n            var tiledDecode = builder.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.TiledVAEDecode\n                {\n                    Name = builder.Nodes.GetUniqueName(\"TiledVAEDecode\"),\n                    Samples = latent,\n                    Vae = vae,\n                    TileSize = card.TileSize,\n                    Overlap = card.Overlap,\n                    TemporalSize = card.TemporalSize,\n                    TemporalOverlap = card.TemporalOverlap\n                }\n            );\n\n            // Update primary connection to the decoded image\n            builder.Connections.Primary = tiledDecode.Output;\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Modules/UpscalerModule.cs",
    "content": "﻿using System;\nusing System.ComponentModel.DataAnnotations;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\n\n[ManagedService]\n[RegisterTransient<UpscalerModule>]\npublic class UpscalerModule : ModuleBase\n{\n    /// <inheritdoc />\n    public UpscalerModule(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        Title = \"Upscaler\";\n        AddCards(vmFactory.Get<UpscalerCardViewModel>());\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var card = GetCard<UpscalerCardViewModel>();\n\n        // Skip if scale is close to 1\n        if (Math.Abs(card.Scale - 1) < 0.005)\n        {\n            return;\n        }\n\n        var upscaleSize = e.Builder.Connections.PrimarySize.WithScale(card.Scale);\n\n        var upscaleResult = e.Builder.Group_Upscale(\n            e.Builder.Nodes.GetUniqueName(\"PostUpscale\"),\n            e.Builder.Connections.Primary ?? throw new ArgumentException(\"No Primary\"),\n            e.Builder.Connections.GetDefaultVAE(),\n            card.SelectedUpscaler ?? throw new ValidationException(\"Upscaler is required\"),\n            upscaleSize.Width,\n            upscaleSize.Height\n        );\n\n        e.Builder.Connections.Primary = upscaleResult;\n        e.Builder.Connections.PrimarySize = upscaleSize;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/NrsCardViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(NrsCard))]\n[ManagedService]\n[RegisterTransient<NrsCardViewModel>]\npublic partial class NrsCardViewModel : LoadableViewModelBase\n{\n    public const string ModuleKey = \"NRS\";\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(-30.0d, 30.0d)]\n    public partial double Skew { get; set; } = 4;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(-30.0d, 30.0d)]\n    public partial double Stretch { get; set; } = 2;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(0d, 1d)]\n    public partial double Squash { get; set; } = 0;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/PlasmaNoiseCardViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(PlasmaNoiseCard))]\n[ManagedService]\n[RegisterTransient<PlasmaNoiseCardViewModel>]\npublic partial class PlasmaNoiseCardViewModel : LoadableViewModelBase\n{\n    public const string ModuleKey = \"PlasmaNoise\";\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ShowPlasmaTurbulence))]\n    private NoiseType selectedNoiseType = NoiseType.Plasma;\n\n    [ObservableProperty]\n    private double plasmaTurbulence = 2.75;\n\n    [ObservableProperty]\n    private int valueMin = -1;\n\n    [ObservableProperty]\n    private int valueMax = -1;\n\n    [ObservableProperty]\n    private bool isPerChannelClampingEnabled;\n\n    [ObservableProperty]\n    private bool isPlasmaSamplerEnabled;\n\n    [ObservableProperty]\n    private int redMin = -1;\n\n    [ObservableProperty]\n    private int redMax = -1;\n\n    [ObservableProperty]\n    private int greenMin = -1;\n\n    [ObservableProperty]\n    private int greenMax = -1;\n\n    [ObservableProperty]\n    private int blueMin = -1;\n\n    [ObservableProperty]\n    private int blueMax = -1;\n\n    [ObservableProperty]\n    private double plasmaSamplerLatentNoise = 0.05;\n\n    public List<NoiseType> NoiseTypes => Enum.GetValues<NoiseType>().ToList();\n    public bool ShowPlasmaTurbulence => SelectedNoiseType == NoiseType.Plasma;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs",
    "content": "﻿using System.Net;\nusing System.Text;\nusing System.Text.Json;\nusing System.Text.Json.Nodes;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls.Notifications;\nusing AvaloniaEdit;\nusing AvaloniaEdit.Document;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Media.Animation;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing OpenIddict.Client;\nusing Refit;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\nusing StabilityMatrix.Avalonia.ViewModels.Settings;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Api.PromptGenApi;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Api.Lykos;\nusing StabilityMatrix.Core.Models.PromptSyntax;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\nusing YamlDotNet.Core.Tokens;\nusing Prompt = StabilityMatrix.Avalonia.Models.Inference.Prompt;\nusing TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(PromptCard))]\n[ManagedService]\n[RegisterTransient<PromptCardViewModel>]\npublic partial class PromptCardViewModel\n    : DisposableLoadableViewModelBase,\n        IParametersLoadableState,\n        IComfyStep\n{\n    private readonly IModelIndexService modelIndexService;\n    private readonly ISettingsManager settingsManager;\n    private readonly TabContext tabContext;\n    private readonly IPromptGenApi promptGenApi;\n    private readonly INotificationService notificationService;\n    private readonly ILogger<PromptCardViewModel> logger;\n    private readonly IAccountsService accountsService;\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n\n    /// <summary>\n    /// Cache of prompt text to tokenized Prompt\n    /// </summary>\n    private LRUCache<string, Prompt> PromptCache { get; } = new(4);\n\n    public ICompletionProvider CompletionProvider { get; }\n    public ITokenizerProvider TokenizerProvider { get; }\n    public SharedState SharedState { get; }\n\n    public TextDocument PromptDocument { get; } = new();\n    public TextDocument NegativePromptDocument { get; } = new();\n\n    public StackEditableCardViewModel ModulesCardViewModel { get; }\n\n    [ObservableProperty]\n    private bool isAutoCompletionEnabled;\n\n    [ObservableProperty]\n    private bool isHelpButtonTeachingTipOpen;\n\n    [ObservableProperty]\n    private bool isPromptAmplifyTeachingTipOpen;\n\n    [ObservableProperty]\n    private bool isNegativePromptEnabled = true;\n\n    [ObservableProperty]\n    private bool isThinkingEnabled;\n\n    [ObservableProperty]\n    private bool isFocused;\n\n    [ObservableProperty]\n    private bool isBalanced = true;\n\n    [ObservableProperty]\n    private bool isImaginative;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ShowLowTokenWarning), nameof(LowTokenWarningText))]\n    private int tokensRemaining = -1;\n\n    [ObservableProperty]\n    private bool isFlyoutOpen;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ShowLowTokenWarning))]\n    private int lowTokenThreshold = 25;\n\n    [ObservableProperty]\n    public partial bool IsStackCardEnabled { get; set; } = true;\n\n    public bool ShowLowTokenWarning => TokensRemaining <= LowTokenThreshold && TokensRemaining >= 0;\n\n    public string LowTokenWarningText =>\n        $\"{TokensRemaining} amplification{(TokensRemaining == 1 ? \"\" : \"s\")} remaining (resets in {Utilities.GetNumDaysTilBeginningOfNextMonth()} days)\";\n\n    /// <inheritdoc />\n    public PromptCardViewModel(\n        ICompletionProvider completionProvider,\n        ITokenizerProvider tokenizerProvider,\n        ISettingsManager settingsManager,\n        IModelIndexService modelIndexService,\n        IServiceManager<ViewModelBase> vmFactory,\n        IPromptGenApi promptGenApi,\n        INotificationService notificationService,\n        ILogger<PromptCardViewModel> logger,\n        IAccountsService accountsService,\n        SharedState sharedState,\n        TabContext tabContext\n    )\n    {\n        this.modelIndexService = modelIndexService;\n        this.settingsManager = settingsManager;\n        this.tabContext = tabContext;\n        this.promptGenApi = promptGenApi;\n        this.notificationService = notificationService;\n        this.logger = logger;\n        this.accountsService = accountsService;\n        this.vmFactory = vmFactory;\n        CompletionProvider = completionProvider;\n        TokenizerProvider = tokenizerProvider;\n        SharedState = sharedState;\n\n        // Subscribe to tab context state changes\n        tabContext.StateChanged += OnTabContextStateChanged;\n\n        ModulesCardViewModel = vmFactory.Get<StackEditableCardViewModel>(vm =>\n        {\n            vm.Title = \"Styles\";\n            vm.AvailableModules = [typeof(PromptExpansionModule)];\n        });\n\n        AddDisposable(\n            settingsManager.RelayPropertyFor(\n                this,\n                vm => vm.IsAutoCompletionEnabled,\n                settings => settings.IsPromptCompletionEnabled,\n                true\n            )\n        );\n    }\n\n    private void OnTabContextStateChanged(object? sender, TabContext.TabStateChangedEventArgs e)\n    {\n        if (e.PropertyName == nameof(TabContext.SelectedModel))\n        {\n            // Handle selected model change\n            // Could use this to update prompt suggestions based on the model\n        }\n    }\n\n    public override void OnUnloaded()\n    {\n        base.OnUnloaded();\n\n        // Unsubscribe from events when view model is unloaded\n        tabContext.StateChanged -= OnTabContextStateChanged;\n    }\n\n    partial void OnIsHelpButtonTeachingTipOpenChanging(bool oldValue, bool newValue)\n    {\n        // If the teaching tip is being closed, save the setting\n        if (oldValue && !newValue)\n        {\n            settingsManager.Transaction(settings =>\n            {\n                settings.SeenTeachingTips.Add(TeachingTip.InferencePromptHelpButtonTip);\n            });\n\n            if (!settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.InferencePromptAmplifyTip))\n            {\n                IsPromptAmplifyTeachingTipOpen = true;\n            }\n        }\n    }\n\n    partial void OnIsPromptAmplifyTeachingTipOpenChanging(bool oldValue, bool newValue)\n    {\n        if (oldValue && !newValue)\n        {\n            settingsManager.Transaction(settings =>\n            {\n                settings.SeenTeachingTips.Add(TeachingTip.InferencePromptAmplifyTip);\n            });\n        }\n    }\n\n    /// <inheritdoc />\n    public override async Task OnLoadedAsync()\n    {\n        await base.OnLoadedAsync();\n\n        // Show teaching tip for help button if not seen\n        if (!settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.InferencePromptHelpButtonTip))\n        {\n            IsHelpButtonTeachingTipOpen = true;\n        }\n\n        if (\n            !IsHelpButtonTeachingTipOpen\n            && !settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.InferencePromptAmplifyTip)\n        )\n        {\n            IsPromptAmplifyTeachingTipOpen = true;\n        }\n    }\n\n    protected override Task OnInitialLoadedAsync()\n    {\n        Task.Run(async () =>\n            {\n                try\n                {\n                    var isLoggedIn = await accountsService.HasStoredLykosAccountAsync();\n                    if (!isLoggedIn)\n                    {\n                        return;\n                    }\n\n                    SetTokenThreshold();\n                }\n                catch (Exception ex)\n                {\n                    logger.LogError(ex, \"Error refreshing account data\");\n                }\n\n                try\n                {\n                    var result = await promptGenApi.AccountMeTokens();\n                    TokensRemaining = result.Available;\n                }\n                catch (ApiException e)\n                {\n                    if (\n                        e.StatusCode != HttpStatusCode.Unauthorized\n                        && e.StatusCode != HttpStatusCode.NotFound\n                    )\n                    {\n                        notificationService.Show(\n                            \"Error retrieving prompt amplifier data\",\n                            e.Message,\n                            NotificationType.Error\n                        );\n                        return;\n                    }\n\n                    TokensRemaining = -1;\n                }\n            })\n            .SafeFireAndForget(onException: ex => logger.LogError(ex, \"Error getting prompt amplifier data\"));\n\n        return Task.CompletedTask;\n    }\n\n    private void SetTokenThreshold()\n    {\n        if (accountsService.LykosStatus is not { User: not null } status)\n            return;\n\n        if (status.User.Roles.Count is 1 && status.User.Roles.Contains(LykosRole.Basic.ToString()))\n        {\n            LowTokenThreshold = 25;\n        }\n    }\n\n    /// <summary>\n    /// Applies the prompt step.\n    /// Requires:\n    /// <list type=\"number\">\n    /// <item><see cref=\"ComfyNodeBuilder.NodeBuilderConnections.Base\"/> - Model, Clip</item>\n    /// </list>\n    /// Provides:\n    /// <list type=\"number\">\n    /// <item><see cref=\"ComfyNodeBuilder.NodeBuilderConnections.Base\"/> - Conditioning</item>\n    /// </list>\n    /// </summary>\n    public void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        // Load prompts\n        var positivePrompt = GetPrompt();\n        positivePrompt.Process();\n        e.Builder.Connections.PositivePrompt = positivePrompt.ProcessedText;\n\n        var negativePrompt = GetNegativePrompt();\n        negativePrompt.Process();\n        e.Builder.Connections.NegativePrompt = negativePrompt.ProcessedText;\n\n        // Apply modules / styles that may modify the prompt\n        ModulesCardViewModel.ApplyStep(e);\n\n        foreach (var modelConnections in e.Builder.Connections.Models.Values)\n        {\n            if (modelConnections.Model is not { } model || modelConnections.Clip is not { } clip)\n                continue;\n\n            // If need to load loras, add a group\n            if (positivePrompt.ExtraNetworks.Count > 0)\n            {\n                var loras = positivePrompt.GetExtraNetworksAsLocalModels(modelIndexService).ToList();\n\n                // Add group to load loras onto model and clip in series\n                var lorasGroup = e.Builder.Group_LoraLoadMany(\n                    $\"Loras_{modelConnections.Name}\",\n                    model,\n                    clip,\n                    loras\n                );\n\n                // Set last outputs as model and clip\n                modelConnections.Model = lorasGroup.Output1;\n                modelConnections.Clip = lorasGroup.Output2;\n            }\n\n            // Clips\n            var positiveClip = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.CLIPTextEncode\n                {\n                    Name = $\"PositiveCLIP_{modelConnections.Name}\",\n                    Clip = e.Builder.Connections.Base.Clip!,\n                    Text = e.Builder.Connections.PositivePrompt,\n                }\n            );\n            var negativeClip = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.CLIPTextEncode\n                {\n                    Name = $\"NegativeCLIP_{modelConnections.Name}\",\n                    Clip = e.Builder.Connections.Base.Clip!,\n                    Text = e.Builder.Connections.NegativePrompt,\n                }\n            );\n\n            // Set conditioning from Clips\n            modelConnections.Conditioning = (positiveClip.Output, negativeClip.Output);\n        }\n    }\n\n    /// <summary>\n    /// Gets the tokenized Prompt for given text and caches it\n    /// </summary>\n    private Prompt GetOrCachePrompt(string text)\n    {\n        // Try get from cache\n        if (PromptCache.Get(text, out var cachedPrompt))\n        {\n            return cachedPrompt!;\n        }\n        var prompt = Prompt.FromRawText(text, TokenizerProvider);\n        PromptCache.Add(text, prompt);\n        return prompt;\n    }\n\n    /// <summary>\n    /// Processes current positive prompt text into a Prompt object\n    /// </summary>\n    public Prompt GetPrompt() => GetOrCachePrompt(PromptDocument.Text);\n\n    /// <summary>\n    /// Processes current negative prompt text into a Prompt object\n    /// </summary>\n    public Prompt GetNegativePrompt() => GetOrCachePrompt(NegativePromptDocument.Text);\n\n    /// <summary>\n    /// Validates both prompts, shows an error dialog if invalid\n    /// </summary>\n    public async Task<bool> ValidatePrompts()\n    {\n        var promptText = PromptDocument.Text;\n        var negPromptText = NegativePromptDocument.Text;\n\n        try\n        {\n            var prompt = GetOrCachePrompt(promptText);\n            prompt.Process(processWildcards: false);\n            prompt.ValidateExtraNetworks(modelIndexService);\n        }\n        catch (PromptError e)\n        {\n            var dialog = DialogHelper.CreatePromptErrorDialog(e, promptText, modelIndexService);\n            await dialog.ShowAsync();\n            return false;\n        }\n\n        try\n        {\n            var negPrompt = GetOrCachePrompt(negPromptText);\n            negPrompt.Process();\n        }\n        catch (PromptError e)\n        {\n            var dialog = DialogHelper.CreatePromptErrorDialog(e, negPromptText, modelIndexService);\n            await dialog.ShowAsync();\n            return false;\n        }\n\n        return true;\n    }\n\n    [RelayCommand]\n    private async Task ShowHelpDialog()\n    {\n        var md = $$\"\"\"\n            ## {{Resources.Label_Emphasis}}\n            You can also use (`Ctrl+Up`/`Ctrl+Down`) in the editor to adjust the \n            weight emphasis of the token under the caret or the currently selected text.\n            ```prompt\n            (keyword)\n            (keyword:1.0)\n            ```\n\n            ## {{Resources.Label_Deemphasis}}\n            ```prompt\n            [keyword]\n            ```\n\n            ## {{Resources.Label_EmbeddingsOrTextualInversion}}\n            They may be used in either the positive or negative prompts. \n            Essentially they are text presets, so the position where you place them \n            could make a difference. \n            ```prompt\n            <embedding:model>\n            <embedding:model:1.0>\n            ```\n\n            ## {{Resources.Label_NetworksLoraOrLycoris}}\n            Unlike embeddings, network tags do not get tokenized to the model, \n            so the position in the prompt where you place them does not matter.\n            ```prompt\n            <lora:model>\n            <lora:model:1.0>\n            <lyco:model>\n            <lyco:model:1.0>\n            ```\n\n            ## {{Resources.Label_Comments}}\n            Inline comments can be marked by a hashtag ' # '. \n            All text after a ' # ' on a line will be disregarded during generation.\n            ```prompt\n            # comments\n            a red cat # also comments\n            detailed\n            ```\n\n            ## {{Resources.Label_Wildcards}}\n            Wildcards can be used to select a random value from a list of options.\n            ```prompt\n            {red|green|blue} cat\n            ```\n            In this example, a color will be randomly chosen at the start of each generation. \n            The final output could be \"red cat\", \"green cat\", or \"blue cat\".\n\n            You can also use networks and embeddings in wildcards. For example:\n            ```prompt\n            {<lora:model:1>|<embedding:model>} cat\n            ```\n            \"\"\";\n\n        var dialog = DialogHelper.CreateMarkdownDialog(md, \"Prompt Syntax\", TextEditorPreset.Prompt);\n        dialog.MinDialogWidth = 800;\n        dialog.MaxDialogHeight = 1000;\n        dialog.MaxDialogWidth = 1000;\n        await dialog.ShowAsync();\n    }\n\n    [RelayCommand]\n    private async Task DebugShowTokens()\n    {\n        var prompt = GetPrompt();\n\n        try\n        {\n            prompt.Process();\n        }\n        catch (PromptError e)\n        {\n            await DialogHelper.CreatePromptErrorDialog(e, prompt.RawText, modelIndexService).ShowAsync();\n            return;\n        }\n\n        var tokens = prompt.TokenizeResult.Tokens;\n\n        var builder = new StringBuilder();\n\n        try\n        {\n            var astBuilder = new PromptSyntaxBuilder(prompt.TokenizeResult, prompt.RawText);\n            var ast = astBuilder.BuildAST();\n            builder.AppendLine(\"## AST:\");\n            builder.AppendLine(\"```csharp\");\n            builder.AppendLine(ast.ToDebugString());\n            builder.AppendLine(\"```\");\n        }\n        catch (PromptError e)\n        {\n            builder.AppendLine($\"## AST (Error)\");\n            builder.AppendLine($\"({e.GetType().Name} - {e.Message})\");\n            builder.AppendLine(\"```csharp\");\n            builder.AppendLine(e.StackTrace);\n            builder.AppendLine(\"```\");\n            throw;\n        }\n\n        builder.AppendLine($\"## Tokens ({tokens.Length}):\");\n        builder.AppendLine(\"```csharp\");\n        builder.AppendLine(prompt.GetDebugText());\n        builder.AppendLine(\"```\");\n\n        try\n        {\n            if (prompt.ExtraNetworks is { } networks)\n            {\n                builder.AppendLine($\"## Networks ({networks.Count}):\");\n                builder.AppendLine(\"```csharp\");\n                builder.AppendLine(\n                    JsonSerializer.Serialize(networks, new JsonSerializerOptions() { WriteIndented = true })\n                );\n                builder.AppendLine(\"```\");\n            }\n\n            builder.AppendLine(\"## Formatted for server:\");\n            builder.AppendLine(\"```csharp\");\n            builder.AppendLine(prompt.ProcessedText);\n            builder.AppendLine(\"```\");\n        }\n        catch (PromptError e)\n        {\n            builder.AppendLine($\"##{e.GetType().Name} - {e.Message}\");\n            builder.AppendLine(\"```csharp\");\n            builder.AppendLine(e.StackTrace);\n            builder.AppendLine(\"```\");\n            throw;\n        }\n\n        var dialog = DialogHelper.CreateMarkdownDialog(builder.ToString(), \"Prompt Tokens\");\n        dialog.MinDialogWidth = 800;\n        dialog.MaxDialogHeight = 1000;\n        dialog.MaxDialogWidth = 1000;\n        await dialog.ShowAsync();\n    }\n\n    [RelayCommand]\n    private void EditorCopy(TextEditor? textEditor)\n    {\n        textEditor?.Copy();\n    }\n\n    [RelayCommand]\n    private void EditorPaste(TextEditor? textEditor)\n    {\n        textEditor?.Paste();\n    }\n\n    [RelayCommand]\n    private void EditorCut(TextEditor? textEditor)\n    {\n        textEditor?.Cut();\n    }\n\n    [RelayCommand]\n    private async Task AmplifyPrompt()\n    {\n        if (!settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.PromptAmplifyDisclaimer))\n        {\n            var dialog = DialogHelper.CreateMarkdownDialog(Resources.PromptAmplifier_Disclaimer);\n            dialog.PrimaryButtonText = \"Continue\";\n            dialog.CloseButtonText = \"Back\";\n            dialog.IsPrimaryButtonEnabled = true;\n            dialog.DefaultButton = ContentDialogButton.Primary;\n\n            var result = await dialog.ShowAsync();\n            if (result == ContentDialogResult.Primary)\n            {\n                settingsManager.Transaction(settings =>\n                {\n                    settings.SeenTeachingTips.Add(TeachingTip.PromptAmplifyDisclaimer);\n                });\n            }\n            else\n            {\n                return;\n            }\n        }\n\n        var valid = await ValidatePrompts();\n        if (!valid)\n            return;\n\n        var prompt = GetPrompt();\n        if (string.IsNullOrWhiteSpace(prompt.RawText))\n        {\n            notificationService.Show(\"Prompt Amplifier Error\", \"Prompt is empty\", NotificationType.Error);\n            return;\n        }\n\n        var negativePrompt = GetNegativePrompt();\n        var selectedModel = tabContext.SelectedModel;\n        var modelTags = selectedModel?.Local?.ConnectedModelInfo?.BaseModel?.ToLower() switch\n        {\n            { } baseModel when baseModel.Contains(\"flux\") => new List<ModelTags> { ModelTags.Flux },\n            { } baseModel when baseModel.Contains(\"sdxl\") => [ModelTags.Sdxl],\n            \"pony\" => [ModelTags.Pony],\n            \"noobai\" => [ModelTags.Illustrious],\n            \"illustrious\" => [ModelTags.Illustrious],\n            _ => [],\n        };\n        var mode =\n            IsFocused ? PromptExpansionRequestMode.Focused\n            : IsImaginative ? PromptExpansionRequestMode.Imaginative\n            : PromptExpansionRequestMode.Balanced;\n        try\n        {\n            var expandedPrompt = await promptGenApi.ExpandPrompt(\n                new PromptExpansionRequest\n                {\n                    Prompt = new PromptToEnhance\n                    {\n                        PositivePrompt = prompt.ProcessedText ?? prompt.RawText,\n                        NegativePrompt = negativePrompt.ProcessedText ?? negativePrompt.RawText,\n                        Model = selectedModel?.Local?.DisplayModelName,\n                    },\n                    Model = IsThinkingEnabled ? \"PromptV1ThinkingDev\" : \"PromptV1Dev\",\n                    Mode = mode,\n                    ModelTags = modelTags,\n                }\n            );\n\n            PromptDocument.Text = expandedPrompt.Response.PositivePrompt;\n            NegativePromptDocument.Text = expandedPrompt.Response.NegativePrompt;\n\n            TokensRemaining = expandedPrompt.AvailableTokens;\n        }\n        catch (ApiException e)\n        {\n            logger.LogError(e, \"Error amplifying prompt\");\n            switch (e.StatusCode)\n            {\n                case HttpStatusCode.PaymentRequired:\n                {\n                    var dialog = DialogHelper.CreateMarkdownDialog(\n                        $\"You have no Prompt Amplifier usage left this month. Usage resets on the 1st of each month. ({Utilities.GetNumDaysTilBeginningOfNextMonth()} days left)\",\n                        \"Rate Limit Reached\"\n                    );\n                    dialog.PrimaryButtonText = \"Upgrade\";\n                    dialog.PrimaryButtonCommand = new RelayCommand(() =>\n                        ProcessRunner.OpenUrl(\"https://patreon.com/join/StabilityMatrix\")\n                    );\n                    dialog.IsPrimaryButtonEnabled = true;\n                    dialog.DefaultButton = ContentDialogButton.Primary;\n\n                    await dialog.ShowAsync();\n                    break;\n                }\n                case HttpStatusCode.BadRequest:\n                    notificationService.Show(\n                        \"Moderation Error\",\n                        \"Your prompt was flagged by the moderation system. Please try again with a different prompt.\",\n                        NotificationType.Error\n                    );\n                    break;\n                case HttpStatusCode.Unauthorized:\n                    if (await ShowLoginDialog())\n                    {\n                        await AmplifyPrompt();\n                    }\n                    else\n                    {\n                        notificationService.Show(\n                            \"Prompt Amplifier Error\",\n                            \"You need to be logged in to use this feature.\",\n                            NotificationType.Error\n                        );\n                    }\n                    break;\n                default:\n                    notificationService.Show(\n                        \"Prompt Amplifier Error\",\n                        \"There was an error processing your request.\",\n                        NotificationType.Error\n                    );\n                    break;\n            }\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Error amplifying prompt\");\n            notificationService.Show(\"Prompt Amplifier Error\", e.Message, NotificationType.Error);\n        }\n    }\n\n    [RelayCommand]\n    private Task ShowAmplifierDisclaimer() =>\n        DialogHelper.CreateMarkdownDialog(Resources.PromptAmplifier_Disclaimer).ShowAsync();\n\n    partial void OnIsBalancedChanged(bool value)\n    {\n        switch (value)\n        {\n            case false when !IsFocused && !IsImaginative:\n                IsBalanced = true;\n                return;\n            case false:\n                return;\n            default:\n                IsFocused = false;\n                IsImaginative = false;\n                break;\n        }\n    }\n\n    partial void OnIsFocusedChanged(bool value)\n    {\n        if (!value)\n            return;\n\n        IsBalanced = false;\n        IsImaginative = false;\n    }\n\n    partial void OnIsImaginativeChanged(bool value)\n    {\n        if (!value)\n            return;\n\n        IsBalanced = false;\n        IsFocused = false;\n    }\n\n    /// <inheritdoc />\n    public override JsonObject SaveStateToJsonObject()\n    {\n        return SerializeModel(\n            new PromptCardModel\n            {\n                Prompt = PromptDocument.Text,\n                NegativePrompt = NegativePromptDocument.Text,\n                ModulesCardState = ModulesCardViewModel.SaveStateToJsonObject(),\n            }\n        );\n    }\n\n    /// <inheritdoc />\n    public override void LoadStateFromJsonObject(JsonObject state)\n    {\n        var model = DeserializeModel<PromptCardModel>(state);\n\n        PromptDocument.Text = model.Prompt ?? \"\";\n        NegativePromptDocument.Text = model.NegativePrompt ?? \"\";\n\n        if (model.ModulesCardState is not null)\n        {\n            ModulesCardViewModel.LoadStateFromJsonObject(model.ModulesCardState);\n        }\n    }\n\n    /// <inheritdoc />\n    public void LoadStateFromParameters(GenerationParameters parameters)\n    {\n        PromptDocument.Text = parameters.PositivePrompt ?? \"\";\n        NegativePromptDocument.Text = parameters.NegativePrompt ?? \"\";\n    }\n\n    /// <inheritdoc />\n    public GenerationParameters SaveStateToParameters(GenerationParameters parameters)\n    {\n        return parameters with\n        {\n            PositivePrompt = PromptDocument.Text,\n            NegativePrompt = NegativePromptDocument.Text,\n        };\n    }\n\n    private async Task<bool> ShowLoginDialog()\n    {\n        var dialog = DialogHelper.CreateTaskDialog(\n            \"Lykos Account Required\",\n            \"You need to be logged in to use this feature. Please log in to your Lykos account.\"\n        );\n\n        dialog.Buttons =\n        [\n            new TaskDialogButton(Resources.Action_Login, TaskDialogStandardResult.OK),\n            TaskDialogButton.CloseButton,\n        ];\n\n        if (await dialog.ShowAsync(true) is not TaskDialogStandardResult.OK)\n            return false;\n\n        var vm = vmFactory.Get<OAuthDeviceAuthViewModel>();\n        vm.ChallengeRequest = new OpenIddictClientModels.DeviceChallengeRequest\n        {\n            ProviderName = OpenIdClientConstants.LykosAccount.ProviderName,\n        };\n        await vm.ShowDialogAsync();\n\n        if (vm.AuthenticationResult is not { } result)\n            return false;\n\n        await accountsService.LykosAccountV2LoginAsync(\n            new LykosAccountV2Tokens(result.AccessToken, result.RefreshToken, result.IdentityToken)\n        );\n\n        var tokens = await promptGenApi.AccountMeTokens();\n        TokensRemaining = tokens.Available;\n        SetTokenThreshold();\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/PromptExpansionCardViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(PromptExpansionCard))]\n[ManagedService]\n[RegisterTransient<PromptExpansionCardViewModel>]\npublic partial class PromptExpansionCardViewModel(IInferenceClientManager clientManager)\n    : LoadableViewModelBase\n{\n    public const string ModuleKey = \"PromptExpansion\";\n\n    public IInferenceClientManager ClientManager { get; } = clientManager;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedModel;\n\n    [ObservableProperty]\n    private bool isLogOutputEnabled = true;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/RescaleCfgCardViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(RescaleCfgCard))]\n[ManagedService]\n[RegisterTransient<RescaleCfgCardViewModel>]\npublic partial class RescaleCfgCardViewModel : LoadableViewModelBase\n{\n    public const string ModuleKey = \"RescaleCFG\";\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(0d, 1d)]\n    private double multiplier = 0.7d;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs",
    "content": "﻿using System.ComponentModel;\nusing System.ComponentModel.DataAnnotations;\nusing System.Text.Json.Serialization;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\nusing StabilityMatrix.Core.Services;\nusing Size = System.Drawing.Size;\n\n#pragma warning disable CS0657 // Not a valid attribute location for this declaration\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(SamplerCard))]\n[ManagedService]\n[RegisterTransient<SamplerCardViewModel>]\npublic partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLoadableState, IComfyStep\n{\n    private ISettingsManager settingsManager;\n    private readonly TabContext tabContext;\n\n    public const string ModuleKey = \"Sampler\";\n\n    [ObservableProperty]\n    private bool isRefinerStepsEnabled;\n\n    [ObservableProperty]\n    private int steps = 20;\n\n    [ObservableProperty]\n    private int refinerSteps = 10;\n\n    [ObservableProperty]\n    private bool isDenoiseStrengthEnabled;\n\n    /// <summary>\n    /// Temporary enable for denoise strength, used for SDTurbo.\n    /// Denoise will be enabled if either this or <see cref=\"IsDenoiseStrengthEnabled\"/> is true.\n    /// </summary>\n    public bool IsDenoiseStrengthTempEnabled => SelectedScheduler == ComfyScheduler.SDTurbo;\n\n    [ObservableProperty]\n    private double denoiseStrength = 0.7f;\n\n    [ObservableProperty]\n    [property: Category(\"Settings\")]\n    [property: DisplayName(\"CFG Scale Selection\")]\n    private bool isCfgScaleEnabled;\n\n    [ObservableProperty]\n    private double cfgScale = 5;\n\n    [ObservableProperty]\n    private bool isDimensionsEnabled;\n\n    [ObservableProperty]\n    private int width = 1024;\n\n    [ObservableProperty]\n    private int height = 1024;\n\n    [ObservableProperty]\n    [property: Category(\"Settings\")]\n    [property: DisplayName(\"Sampler Selection\")]\n    private bool isSamplerSelectionEnabled;\n\n    [ObservableProperty]\n    [Required]\n    private ComfySampler? selectedSampler = ComfySampler.EulerAncestral;\n\n    [ObservableProperty]\n    [property: Category(\"Settings\")]\n    [property: DisplayName(\"Scheduler Selection\")]\n    private bool isSchedulerSelectionEnabled;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsDenoiseStrengthTempEnabled), nameof(IsModelTypeSelectionEnabled))]\n    [Required]\n    private ComfyScheduler? selectedScheduler = ComfyScheduler.Normal;\n\n    [ObservableProperty]\n    [property: Category(\"Settings\")]\n    [property: DisplayName(\"Inherit Primary Sampler Addons\")]\n    private bool inheritPrimarySamplerAddons = true;\n\n    [ObservableProperty]\n    private bool enableAddons = true;\n\n    [ObservableProperty]\n    private string selectedModelType = \"SDXL\";\n\n    [ObservableProperty]\n    private bool isLengthEnabled;\n\n    [ObservableProperty]\n    private int length;\n\n    [ObservableProperty]\n    public partial List<string> AvailableResolutions { get; set; }\n\n    [ObservableProperty]\n    public partial Dictionary<string, List<string>> GroupedResolutionsByAspectRatio { get; set; } = new();\n\n    [ObservableProperty]\n    public partial int DimensionStepChange { get; set; }\n\n    [JsonPropertyName(\"Modules\")]\n    public StackEditableCardViewModel ModulesCardViewModel { get; }\n\n    [JsonIgnore]\n    public bool IsModelTypeSelectionEnabled => SelectedScheduler?.Name == ComfyScheduler.AlignYourSteps.Name;\n\n    [JsonIgnore]\n    public List<string> ModelTypes => [\"SD1\", \"SDXL\"];\n\n    [JsonIgnore]\n    public IInferenceClientManager ClientManager { get; }\n\n    private int TotalSteps => Steps + RefinerSteps;\n\n    public SamplerCardViewModel(\n        IInferenceClientManager clientManager,\n        IServiceManager<ViewModelBase> vmFactory,\n        ISettingsManager settingsManager,\n        TabContext tabContext\n    )\n    {\n        this.settingsManager = settingsManager;\n        this.tabContext = tabContext;\n        ClientManager = clientManager;\n        ModulesCardViewModel = vmFactory.Get<StackEditableCardViewModel>(modulesCard =>\n        {\n            modulesCard.Title = Resources.Label_Addons;\n            modulesCard.AvailableModules =\n            [\n                typeof(FreeUModule),\n                typeof(ControlNetModule),\n                typeof(LayerDiffuseModule),\n                typeof(FluxGuidanceModule),\n                typeof(DiscreteModelSamplingModule),\n                typeof(RescaleCfgModule),\n                typeof(PlasmaNoiseModule),\n                typeof(NRSModule),\n                typeof(TiledVAEModule),\n            ];\n        });\n    }\n\n    public override void OnLoaded()\n    {\n        base.OnLoaded();\n        DimensionStepChange = settingsManager.Settings.InferenceDimensionStepChange;\n        AvailableResolutions = settingsManager.Settings.SavedInferenceDimensions.ToList();\n        LoadAvailableResolutions();\n\n        tabContext.StateChanged += TabContextOnStateChanged;\n    }\n\n    public override void OnUnloaded()\n    {\n        base.OnUnloaded();\n        tabContext.StateChanged -= TabContextOnStateChanged;\n    }\n\n    private void TabContextOnStateChanged(object? sender, TabContext.TabStateChangedEventArgs e)\n    {\n        if (e.PropertyName != nameof(tabContext.SelectedModel))\n            return;\n\n        if (tabContext.SelectedModel?.Local?.ConnectedModelInfo?.InferenceDefaults is not { } defaults)\n            return;\n\n        Width = defaults.Width;\n        Height = defaults.Height;\n        Steps = defaults.Steps;\n        CfgScale = defaults.CfgScale;\n        SelectedSampler = defaults.Sampler;\n        SelectedScheduler = defaults.Scheduler;\n    }\n\n    [RelayCommand]\n    private void SwapDimensions()\n    {\n        (Width, Height) = (Height, Width);\n    }\n\n    [RelayCommand]\n    private void SetResolution(string resolution)\n    {\n        if (string.IsNullOrWhiteSpace(resolution))\n        {\n            return;\n        }\n\n        // split on 'x' or 'X'\n        var parts = resolution\n            .ToLowerInvariant()\n            .Split('x', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);\n\n        if (parts.Length != 2 || !int.TryParse(parts[0], out var w) || !int.TryParse(parts[1], out var h))\n        {\n            return;\n        }\n\n        Width = w;\n        Height = h;\n    }\n\n    [RelayCommand]\n    private void SaveDimensionsToFavorites()\n    {\n        var dimension = $\"{Width}x{Height}\";\n        var dimensionWithSpace = $\"{Width} x {Height}\";\n        // Check if already exists\n        if (\n            settingsManager.Settings.SavedInferenceDimensions.Any(d =>\n                d == dimension || d == dimensionWithSpace\n            )\n        )\n        {\n            return;\n        }\n\n        // Add to favorites\n        settingsManager.Transaction(s => s.SavedInferenceDimensions.Add(dimensionWithSpace));\n\n        var orientation =\n            Width > Height ? \"Landscape\"\n            : Width < Height ? \"Portrait\"\n            : \"Square\";\n        GroupedResolutionsByAspectRatio[orientation].Add(dimensionWithSpace);\n    }\n\n    /// <inheritdoc />\n    public virtual void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        // Resample the current primary if size does not match the selected size\n        if (\n            e.Builder.Connections.PrimarySize.Width != Width\n            || e.Builder.Connections.PrimarySize.Height != Height\n        )\n        {\n            e.Builder.Connections.Primary = e.Builder.Group_Upscale(\n                e.Nodes.GetUniqueName(\"Sampler_ScalePrimary\"),\n                e.Builder.Connections.Primary ?? throw new ArgumentException(\"No Primary\"),\n                e.Builder.Connections.GetDefaultVAE(),\n                ComfyUpscaler.NearestExact,\n                Width,\n                Height\n            );\n\n            e.Builder.Connections.PrimarySize = new Size(Width, Height);\n        }\n\n        // Provide temp values\n        e.Temp = e.CreateTempFromBuilder();\n\n        // Apply steps from our addons\n        ApplyAddonSteps(e);\n\n        // If \"Sampler\" is not yet a node, do initial setup\n        // otherwise do hires setup\n\n        if (!e.Nodes.ContainsKey(\"Sampler\"))\n        {\n            ApplyStepsInitialSampler(e);\n\n            // Save temp\n            e.Builder.Connections.BaseSamplerTemporaryArgs = e.Temp;\n        }\n        else\n        {\n            // Hires does its own sampling so just throw I guess\n            throw new InvalidOperationException(\n                \"Sampler ApplyStep was called when Sampler node already exists\"\n            );\n        }\n    }\n\n    public void ApplyStepsInitialCustomSampler(ModuleApplyStepEventArgs e, bool useFluxGuidance)\n    {\n        // Provide temp values\n        e.Temp = e.CreateTempFromBuilder();\n\n        // Apply steps from our addons\n        ApplyAddonSteps(e);\n\n        // Get primary as latent using vae\n        var primaryLatent = e.Builder.GetPrimaryAsLatent(\n            e.Temp.Primary!.Unwrap(),\n            e.Builder.Connections.GetDefaultVAE()\n        );\n\n        // Set primary sampler and scheduler\n        var primarySampler = SelectedSampler ?? throw new ValidationException(\"Sampler not selected\");\n        e.Builder.Connections.PrimarySampler = primarySampler;\n\n        var primaryScheduler = SelectedScheduler ?? throw new ValidationException(\"Scheduler not selected\");\n        e.Builder.Connections.PrimaryScheduler = primaryScheduler;\n\n        // for later inheritance if needed\n        e.Builder.Connections.PrimaryCfg = CfgScale;\n        e.Builder.Connections.PrimarySteps = Steps;\n        e.Builder.Connections.PrimaryModelType = SelectedModelType;\n\n        // KSamplerSelect\n        var kSamplerSelect = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.KSamplerSelect\n            {\n                Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.KSamplerSelect)),\n                SamplerName = e.Builder.Connections.PrimarySampler?.Name!,\n            }\n        );\n\n        e.Builder.Connections.PrimarySamplerNode = kSamplerSelect.Output;\n\n        // Scheduler/Sigmas\n        if (e.Builder.Connections.PrimaryScheduler?.Name is \"align_your_steps\")\n        {\n            var alignYourSteps = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.AlignYourStepsScheduler\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.AlignYourStepsScheduler)),\n                    ModelType = SelectedModelType,\n                    Steps = Steps,\n                    Denoise = IsDenoiseStrengthEnabled ? DenoiseStrength : 1.0d,\n                }\n            );\n\n            e.Builder.Connections.PrimarySigmas = alignYourSteps.Output;\n        }\n        else\n        {\n            var basicScheduler = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.BasicScheduler\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.BasicScheduler)),\n                    Model = e.Temp.Base.Model.Unwrap(),\n                    Scheduler = e.Builder.Connections.PrimaryScheduler?.Name!,\n                    Denoise = IsDenoiseStrengthEnabled ? DenoiseStrength : 1.0d,\n                    Steps = Steps,\n                }\n            );\n\n            e.Builder.Connections.PrimarySigmas = basicScheduler.Output;\n        }\n\n        // Noise\n        var randomNoise = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.RandomNoise\n            {\n                Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.RandomNoise)),\n                NoiseSeed = e.Builder.Connections.Seed,\n            }\n        );\n\n        e.Builder.Connections.PrimaryNoise = randomNoise.Output;\n\n        if (useFluxGuidance)\n        {\n            // Guidance\n            var fluxGuidance = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.FluxGuidance\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.FluxGuidance)),\n                    Conditioning = e.Builder.Connections.GetRefinerOrBaseConditioning().Positive,\n                    Guidance = CfgScale,\n                }\n            );\n\n            e.Builder.Connections.Base.Conditioning = new ConditioningConnections(\n                fluxGuidance.Output,\n                e.Builder.Connections.GetRefinerOrBaseConditioning().Negative\n            );\n\n            // Guider\n            var basicGuider = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.BasicGuider\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.BasicGuider)),\n                    Model = e.Builder.Connections.Base.Model.Unwrap(),\n                    Conditioning = e.Builder.Connections.GetRefinerOrBaseConditioning().Positive,\n                }\n            );\n\n            e.Builder.Connections.PrimaryGuider = basicGuider.Output;\n        }\n        else\n        {\n            e.Builder.Connections.Base.Conditioning = e.Builder.Connections.GetRefinerOrBaseConditioning();\n\n            var cfgGuider = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.CFGGuider\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.CFGGuider)),\n                    Model = e.Temp.Base.Model.Unwrap(),\n                    Positive = e.Builder.Connections.Base.Conditioning.Positive,\n                    Negative = e.Builder.Connections.Base.Conditioning.Negative,\n                    Cfg = CfgScale,\n                }\n            );\n\n            e.Builder.Connections.PrimaryGuider = cfgGuider.Output;\n        }\n\n        // SamplerCustomAdvanced\n        var sampler = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.SamplerCustomAdvanced\n            {\n                Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.SamplerCustomAdvanced)),\n                Guider = e.Builder.Connections.PrimaryGuider,\n                Noise = e.Builder.Connections.PrimaryNoise,\n                Sampler = e.Builder.Connections.PrimarySamplerNode,\n                Sigmas = e.Builder.Connections.PrimarySigmas,\n                LatentImage = primaryLatent,\n            }\n        );\n\n        e.Builder.Connections.Primary = sampler.Output1;\n\n        e.Builder.Connections.BaseSamplerTemporaryArgs = e.Temp;\n    }\n\n    private void ApplyStepsInitialSampler(ModuleApplyStepEventArgs e)\n    {\n        // Get primary as latent using vae\n        var primaryLatent = e.Builder.GetPrimaryAsLatent(\n            e.Temp.Primary!.Unwrap(),\n            e.Builder.Connections.GetDefaultVAE()\n        );\n\n        // Set primary sampler and scheduler\n        var primarySampler = SelectedSampler ?? throw new ValidationException(\"Sampler not selected\");\n        e.Builder.Connections.PrimarySampler = primarySampler;\n\n        var primaryScheduler = SelectedScheduler ?? throw new ValidationException(\"Scheduler not selected\");\n        e.Builder.Connections.PrimaryScheduler = primaryScheduler;\n\n        // for later inheritance if needed\n        e.Builder.Connections.PrimaryCfg = CfgScale;\n        e.Builder.Connections.PrimarySteps = Steps;\n        e.Builder.Connections.PrimaryModelType = SelectedModelType;\n\n        // Use Temp Conditioning that may be modified by addons\n        var conditioning = e.Temp.Base.Conditioning.Unwrap();\n        var refinerConditioning = e.Temp.Refiner.Conditioning;\n\n        var useFluxGuidance = ModulesCardViewModel.IsModuleEnabled<FluxGuidanceModule>();\n\n        var isPlasmaEnabled = ModulesCardViewModel.IsModuleEnabled<PlasmaNoiseModule>();\n        var usePlasmaSampler = false;\n\n        if (isPlasmaEnabled)\n        {\n            var plasmaViewModel = ModulesCardViewModel\n                .GetCard<PlasmaNoiseModule>()\n                .GetCard<PlasmaNoiseCardViewModel>();\n\n            usePlasmaSampler = plasmaViewModel.IsPlasmaSamplerEnabled;\n        }\n\n        if (useFluxGuidance)\n        {\n            // Flux guidance\n            var fluxGuidance = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.FluxGuidance\n                {\n                    Name = e.Nodes.GetUniqueName(\"FluxGuidance\"),\n                    Conditioning = conditioning.Positive,\n                    Guidance = CfgScale,\n                }\n            );\n\n            conditioning = conditioning with { Positive = fluxGuidance.Output };\n        }\n\n        // Use custom sampler if SDTurbo scheduler is selected\n        if (e.Builder.Connections.PrimaryScheduler == ComfyScheduler.SDTurbo)\n        {\n            // Error if using refiner\n            if (e.Builder.Connections.Refiner.Model is not null)\n            {\n                throw new ValidationException(\"SDTurbo Scheduler cannot be used with Refiner Model\");\n            }\n\n            var kSamplerSelect = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.KSamplerSelect\n                {\n                    Name = \"KSamplerSelect\",\n                    SamplerName = e.Builder.Connections.PrimarySampler?.Name!,\n                }\n            );\n\n            var turboScheduler = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.SDTurboScheduler\n                {\n                    Name = \"SDTurboScheduler\",\n                    Model = e.Builder.Connections.Base.Model.Unwrap(),\n                    Steps = Steps,\n                    Denoise = DenoiseStrength,\n                }\n            );\n\n            var sampler = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.SamplerCustom\n                {\n                    Name = \"Sampler\",\n                    Model = e.Builder.Connections.Base.Model,\n                    AddNoise = true,\n                    NoiseSeed = e.Builder.Connections.Seed,\n                    Cfg = useFluxGuidance ? 1.0d : CfgScale,\n                    Positive = conditioning.Positive,\n                    Negative = conditioning.Negative,\n                    Sampler = kSamplerSelect.Output,\n                    Sigmas = turboScheduler.Output,\n                    LatentImage = primaryLatent,\n                }\n            );\n\n            e.Builder.Connections.Primary = sampler.Output1;\n        }\n        else if (usePlasmaSampler)\n        {\n            var plasmaViewModel = ModulesCardViewModel\n                .GetCard<PlasmaNoiseModule>()\n                .GetCard<PlasmaNoiseCardViewModel>();\n\n            var sampler = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.PlasmaSampler\n                {\n                    Name = \"PlasmaSampler\",\n                    Model = e.Temp.Base.Model!.Unwrap(),\n                    NoiseSeed = e.Builder.Connections.Seed,\n                    SamplerName = primarySampler.Name,\n                    Scheduler = primaryScheduler.Name,\n                    Steps = Steps,\n                    Cfg = useFluxGuidance ? 1.0d : CfgScale,\n                    Positive = conditioning.Positive,\n                    Negative = conditioning.Negative,\n                    LatentImage = primaryLatent,\n                    Denoise = DenoiseStrength,\n                    DistributionType = \"rand\",\n                    LatentNoise = plasmaViewModel.PlasmaSamplerLatentNoise,\n                }\n            );\n\n            e.Builder.Connections.Primary = sampler.Output;\n        }\n        // Use KSampler if no refiner, otherwise need KSamplerAdvanced\n        else if (e.Builder.Connections.Refiner.Model is null)\n        {\n            // No refiner\n            var sampler = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.KSampler\n                {\n                    Name = \"Sampler\",\n                    Model = e.Temp.Base.Model!.Unwrap(),\n                    Seed = e.Builder.Connections.Seed,\n                    SamplerName = primarySampler.Name,\n                    Scheduler = primaryScheduler.Name,\n                    Steps = Steps,\n                    Cfg = useFluxGuidance ? 1.0d : CfgScale,\n                    Positive = conditioning.Positive,\n                    Negative = conditioning.Negative,\n                    LatentImage = primaryLatent,\n                    Denoise = DenoiseStrength,\n                }\n            );\n\n            e.Builder.Connections.Primary = sampler.Output;\n        }\n        else\n        {\n            // Advanced base sampler for refiner\n            var sampler = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.KSamplerAdvanced\n                {\n                    Name = \"Sampler\",\n                    Model = e.Temp.Base.Model!.Unwrap(),\n                    AddNoise = true,\n                    NoiseSeed = e.Builder.Connections.Seed,\n                    Steps = TotalSteps,\n                    Cfg = useFluxGuidance ? 1.0d : CfgScale,\n                    SamplerName = primarySampler.Name,\n                    Scheduler = primaryScheduler.Name,\n                    Positive = conditioning.Positive,\n                    Negative = conditioning.Negative,\n                    LatentImage = primaryLatent,\n                    StartAtStep = 0,\n                    EndAtStep = Steps,\n                    ReturnWithLeftoverNoise = true,\n                }\n            );\n\n            e.Builder.Connections.Primary = sampler.Output;\n        }\n\n        // If temp batched, add a LatentFromBatch to pick the temp batch right after first sampler\n        if (e.Temp.IsPrimaryTempBatched)\n        {\n            e.Builder.Connections.Primary = e\n                .Nodes.AddTypedNode(\n                    new ComfyNodeBuilder.LatentFromBatch\n                    {\n                        Name = e.Nodes.GetUniqueName(\"ControlNet_LatentFromBatch\"),\n                        Samples = e.Builder.GetPrimaryAsLatent(),\n                        BatchIndex = e.Temp.PrimaryTempBatchPickIndex,\n                        // Use max length here as recommended\n                        // https://github.com/comfyanonymous/ComfyUI_experiments/issues/11\n                        Length = 64,\n                    }\n                )\n                .Output;\n        }\n\n        // Refiner\n        if (e.Builder.Connections.Refiner.Model is not null)\n        {\n            // Add refiner sampler\n            e.Builder.Connections.Primary = e\n                .Nodes.AddTypedNode(\n                    new ComfyNodeBuilder.KSamplerAdvanced\n                    {\n                        Name = \"Sampler_Refiner\",\n                        Model = e.Builder.Connections.Refiner.Model,\n                        AddNoise = false,\n                        NoiseSeed = e.Builder.Connections.Seed,\n                        Steps = TotalSteps,\n                        Cfg = CfgScale,\n                        SamplerName = primarySampler.Name,\n                        Scheduler = primaryScheduler.Name,\n                        Positive = refinerConditioning!.Positive,\n                        Negative = refinerConditioning.Negative,\n                        // Connect to previous sampler\n                        LatentImage = e.Builder.GetPrimaryAsLatent(),\n                        StartAtStep = Steps,\n                        EndAtStep = TotalSteps,\n                        ReturnWithLeftoverNoise = false,\n                    }\n                )\n                .Output;\n        }\n    }\n\n    /// <summary>\n    /// Applies each step of our addons\n    /// </summary>\n    /// <param name=\"e\"></param>\n    private void ApplyAddonSteps(ModuleApplyStepEventArgs e)\n    {\n        // Apply steps from our modules\n        foreach (var module in ModulesCardViewModel.Cards.Cast<ModuleBase>())\n        {\n            module.ApplyStep(e);\n        }\n    }\n\n    /// <inheritdoc />\n    public void LoadStateFromParameters(GenerationParameters parameters)\n    {\n        Width = parameters.Width;\n        Height = parameters.Height;\n        Steps = parameters.Steps;\n        CfgScale = parameters.CfgScale;\n\n        if (\n            !string.IsNullOrEmpty(parameters.Sampler)\n            && GenerationParametersConverter.TryGetSamplerScheduler(\n                parameters.Sampler,\n                out var samplerScheduler\n            )\n        )\n        {\n            SelectedSampler = ClientManager.Samplers.FirstOrDefault(s => s == samplerScheduler.Sampler);\n            SelectedScheduler = ClientManager.Schedulers.FirstOrDefault(s => s == samplerScheduler.Scheduler);\n        }\n    }\n\n    /// <inheritdoc />\n    public GenerationParameters SaveStateToParameters(GenerationParameters parameters)\n    {\n        var sampler = GenerationParametersConverter.TryGetParameters(\n            new ComfySamplerScheduler(SelectedSampler ?? default, SelectedScheduler ?? default),\n            out var res\n        )\n            ? res\n            : null;\n\n        if (sampler is null && SelectedSampler is not null && SelectedScheduler is not null)\n        {\n            sampler = $\"{SelectedSampler?.DisplayName} {SelectedScheduler?.DisplayName}\";\n        }\n\n        return parameters with\n        {\n            Width = Width,\n            Height = Height,\n            Steps = Steps,\n            CfgScale = CfgScale,\n            Sampler = sampler,\n        };\n    }\n\n    private void LoadAvailableResolutions()\n    {\n        GroupedResolutionsByAspectRatio.Clear();\n        foreach (var res in AvailableResolutions)\n        {\n            // split on 'x' or 'X'\n            var parts = res.ToLowerInvariant()\n                .Split('x', StringSplitOptions.RemoveEmptyEntries)\n                .Select(p => p.Trim())\n                .ToArray();\n\n            if (parts.Length != 2 || !int.TryParse(parts[0], out var w) || !int.TryParse(parts[1], out var h))\n            {\n                continue;\n            }\n\n            var category = \"Square\";\n            if (w > h)\n            {\n                category = \"Landscape\";\n            }\n            else if (h > w)\n            {\n                category = \"Portrait\";\n            }\n\n            if (!GroupedResolutionsByAspectRatio.TryGetValue(category, out var list))\n            {\n                list = [];\n                GroupedResolutionsByAspectRatio[category] = list;\n            }\n            list.Add(res.Trim());\n        }\n\n        // Sort the resolutions by width and height\n        foreach (var key in GroupedResolutionsByAspectRatio.Keys.ToList())\n        {\n            if (key == \"Portrait\")\n            {\n                GroupedResolutionsByAspectRatio[key] = GroupedResolutionsByAspectRatio[key]\n                    .Order(DimensionStringComparer.Instance)\n                    .ToList();\n            }\n            else\n            {\n                GroupedResolutionsByAspectRatio[key] = GroupedResolutionsByAspectRatio[key]\n                    .OrderDescending(DimensionStringComparer.Instance)\n                    .ToList();\n            }\n        }\n\n        // fire that off just in case\n        OnPropertyChanged(nameof(GroupedResolutionsByAspectRatio));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/SeedCardViewModel.cs",
    "content": "﻿using System;\nusing System.Text.Json.Nodes;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(SeedCard))]\n[ManagedService]\n[RegisterTransient<SeedCardViewModel>]\npublic partial class SeedCardViewModel : LoadableViewModelBase\n{\n    [ObservableProperty, NotifyPropertyChangedFor(nameof(RandomizeButtonToolTip))]\n    private bool isRandomizeEnabled = true;\n\n    [ObservableProperty]\n    private long seed;\n\n    public string RandomizeButtonToolTip =>\n        IsRandomizeEnabled ? \"Randomizing Seed on each run\" : \"Seed is locked\";\n\n    [RelayCommand]\n    public void GenerateNewSeed()\n    {\n        Seed = Random.Shared.NextInt64(0, int.MaxValue);\n    }\n\n    /// <inheritdoc />\n    public override void LoadStateFromJsonObject(JsonObject state)\n    {\n        var model = DeserializeModel<SeedCardModel>(state);\n\n        Seed = model.Seed;\n        IsRandomizeEnabled = model.IsRandomizeEnabled;\n    }\n\n    /// <inheritdoc />\n    public override JsonObject SaveStateToJsonObject()\n    {\n        return SerializeModel(new SeedCardModel { Seed = Seed, IsRandomizeEnabled = IsRandomizeEnabled });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Runtime.CompilerServices;\nusing System.Text.Json.Serialization;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Input;\nusing Avalonia.Platform.Storage;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Database;\nusing Size = System.Drawing.Size;\n#pragma warning disable CS0657 // Not a valid attribute location for this declaration\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(SelectImageCard))]\n[ManagedService]\n[RegisterTransient<SelectImageCardViewModel>]\npublic partial class SelectImageCardViewModel(\n    INotificationService notificationService,\n    IServiceManager<ViewModelBase> vmFactory\n) : LoadableViewModelBase, IDropTarget, IComfyStep, IInputImageProvider\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private static FilePickerFileType SupportedImages { get; } =\n        new(\"Supported Images\")\n        {\n            Patterns = new[] { \"*.png\", \"*.jpg\", \"*.jpeg\" },\n            AppleUniformTypeIdentifiers = new[] { \"public.jpeg\", \"public.png\" },\n            MimeTypes = new[] { \"image/jpeg\", \"image/png\" }\n        };\n\n    private readonly Lazy<MaskEditorViewModel> _lazyMaskEditorViewModel =\n        new(vmFactory.Get<MaskEditorViewModel>);\n\n    /// <summary>\n    /// When true, enables a button to open a mask editor for the image.\n    /// This is not saved or loaded from state.\n    /// </summary>\n    [ObservableProperty]\n    [property: JsonIgnore]\n    [property: MemberNotNull(nameof(MaskEditorViewModel))]\n    private bool isMaskEditorEnabled;\n\n    /// <summary>\n    /// Toggles whether the mask overlay is shown over the image.\n    /// </summary>\n    [ObservableProperty]\n    private bool isMaskOverlayEnabled;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsSelectionAvailable))]\n    [NotifyPropertyChangedFor(nameof(IsImageFileNotFound))]\n    private ImageSource? imageSource;\n\n    [ObservableProperty]\n    [property: JsonIgnore]\n    [NotifyPropertyChangedFor(nameof(IsSelectionAvailable))]\n    private bool isSelectionEnabled = true;\n\n    /// <summary>\n    /// Set by <see cref=\"SelectImageCard\"/> when the image is loaded.\n    /// </summary>\n    [ObservableProperty]\n    private Size currentBitmapSize = Size.Empty;\n\n    /// <summary>\n    /// True if the image file is set but the local file does not exist.\n    /// </summary>\n    [MemberNotNullWhen(true, nameof(NotFoundImagePath))]\n    public bool IsImageFileNotFound => ImageSource?.LocalFile?.Exists == false;\n\n    public bool IsSelectionAvailable => IsSelectionEnabled && ImageSource == null;\n\n    /// <summary>\n    /// Path of the not found image\n    /// </summary>\n    public string? NotFoundImagePath => ImageSource?.LocalFile?.FullPath;\n\n    [JsonInclude]\n    public MaskEditorViewModel? MaskEditorViewModel =>\n        IsMaskEditorEnabled ? _lazyMaskEditorViewModel.Value : null;\n\n    [JsonIgnore]\n    public ImageSource? LastMaskImage { get; private set; }\n\n    /// <inheritdoc />\n    public void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        // With Mask image\n        if (IsMaskEditorEnabled && MaskEditorViewModel.IsMaskEnabled)\n        {\n            MaskEditorViewModel.PaintCanvasViewModel.CanvasSize = CurrentBitmapSize;\n\n            e.Builder.SetupImagePrimarySourceWithMask(\n                ImageSource ?? throw new ValidationException(\"Input Image is required\"),\n                !CurrentBitmapSize.IsEmpty\n                    ? CurrentBitmapSize\n                    : throw new ValidationException(\"CurrentBitmapSize is null\"),\n                MaskEditorViewModel.GetCachedOrNewMaskRenderInverseAlphaImage(),\n                MaskEditorViewModel.PaintCanvasViewModel.CanvasSize,\n                e.Builder.Connections.BatchIndex\n            );\n        }\n        // Normal image only\n        else\n        {\n            e.Builder.SetupImagePrimarySource(\n                ImageSource ?? throw new ValidationException(\"Input Image is required\"),\n                !CurrentBitmapSize.IsEmpty\n                    ? CurrentBitmapSize\n                    : throw new ValidationException(\"CurrentBitmapSize is null\"),\n                e.Builder.Connections.BatchIndex\n            );\n        }\n    }\n\n    /// <inheritdoc />\n    public IEnumerable<ImageSource> GetInputImages()\n    {\n        // Main image\n        if (ImageSource is { } image && !IsImageFileNotFound)\n        {\n            yield return image;\n        }\n\n        // Mask image\n        if (IsMaskEditorEnabled && MaskEditorViewModel.IsMaskEnabled)\n        {\n            using var timer = CodeTimer.StartDebug(\"MaskImage\");\n\n            MaskEditorViewModel.PaintCanvasViewModel.CanvasSize = CurrentBitmapSize;\n\n            var maskImage = MaskEditorViewModel.GetCachedOrNewMaskRenderInverseAlphaImage();\n\n            timer.Dispose();\n\n            yield return maskImage;\n        }\n    }\n\n    partial void OnImageSourceChanged(ImageSource? value)\n    {\n        // Cache the hash for later upload use\n        if (value?.LocalFile is { Exists: true } localFile)\n        {\n            value\n                .GetBlake3HashAsync()\n                .SafeFireAndForget(ex =>\n                {\n                    Logger.Warn(ex, \"Error getting hash for image {Path}\", localFile.Name);\n                    notificationService.ShowPersistent(\n                        $\"Error getting hash for image {localFile.Name}\",\n                        $\"{ex.GetType().Name}: {ex.Message}\"\n                    );\n                });\n        }\n    }\n\n    [RelayCommand]\n    private async Task SelectImageFromFilePickerAsync()\n    {\n        var files = await App.StorageProvider.OpenFilePickerAsync(\n            new FilePickerOpenOptions\n            {\n                FileTypeFilter = [FilePickerFileTypes.ImagePng, FilePickerFileTypes.ImageJpg, SupportedImages]\n            }\n        );\n\n        if (files.FirstOrDefault()?.TryGetLocalPath() is { } path)\n        {\n            Dispatcher.UIThread.Post(() => LoadUserImageSafe(new ImageSource(path)));\n        }\n    }\n\n    [RelayCommand]\n    private async Task OpenEditMaskDialogAsync()\n    {\n        if (!IsMaskEditorEnabled || ImageSource is null)\n        {\n            return;\n        }\n\n        // Make a backup to restore if not saving later\n        var maskEditorStateBackup = MaskEditorViewModel.SaveStateToJsonObject();\n\n        // Set the background image\n        if (await ImageSource.GetBitmapAsync() is not { } currentBitmap)\n        {\n            Logger.Warn(\"GetBitmapAsync returned null for image {Path}\", ImageSource.LocalFile?.FullPath);\n            notificationService.ShowPersistent(\n                \"Error Loading Image\",\n                \"Could not load mask editor for the provided image.\",\n                NotificationType.Error\n            );\n            return;\n        }\n        MaskEditorViewModel.PaintCanvasViewModel.BackgroundImage = currentBitmap.ToSKBitmap();\n\n        if (await MaskEditorViewModel.GetDialog().ShowAsync() == ContentDialogResult.Primary)\n        {\n            MaskEditorViewModel.InvalidateCachedMaskRenderImage();\n        }\n        else\n        {\n            // Restore the backup\n            MaskEditorViewModel.LoadStateFromJsonObject(maskEditorStateBackup);\n        }\n    }\n\n    /// <summary>\n    /// Supports LocalImageFile Context or OS Files\n    /// </summary>\n    public void DragOver(object? sender, DragEventArgs e)\n    {\n        if (\n            e.Data.GetDataFormats().Contains(DataFormats.Files)\n            || e.Data.GetContext<LocalImageFile>() is not null\n        )\n        {\n            e.Handled = true;\n            return;\n        }\n\n        e.DragEffects = DragDropEffects.None;\n    }\n\n    /// <inheritdoc />\n    public void Drop(object? sender, DragEventArgs e)\n    {\n        // 1. Context drop for LocalImageFile\n        if (e.Data.GetContext<LocalImageFile>() is { } imageFile)\n        {\n            e.Handled = true;\n\n            Dispatcher.UIThread.Post(() => LoadUserImageSafe(new ImageSource(imageFile.AbsolutePath)));\n\n            return;\n        }\n        // 2. OS Files\n        if (\n            e.Data.GetFiles() is { } files\n            && files.Select(f => f.TryGetLocalPath()).FirstOrDefault() is { } path\n        )\n        {\n            e.Handled = true;\n\n            Dispatcher.UIThread.Post(() => LoadUserImageSafe(new ImageSource(path)));\n        }\n    }\n\n    /// <summary>\n    /// Calls <see cref=\"LoadUserImage\"/> with notification error handling.\n    /// </summary>\n    private void LoadUserImageSafe(ImageSource image)\n    {\n        try\n        {\n            LoadUserImage(image);\n        }\n        catch (Exception e)\n        {\n            Logger.Warn(e, \"Error loading image\");\n            notificationService.Show(\"Error loading image\", e.Message);\n        }\n    }\n\n    /// <summary>\n    /// Loads the user image from the given ImageSource.\n    /// </summary>\n    /// <param name=\"image\">The ImageSource object representing the user image.</param>\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    private void LoadUserImage(ImageSource image)\n    {\n        var current = ImageSource;\n\n        ImageSource = image;\n\n        // current?.Dispose();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/SharpenCardViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(SharpenCard))]\n[ManagedService]\n[RegisterTransient<SharpenCardViewModel>]\npublic partial class SharpenCardViewModel : LoadableViewModelBase\n{\n    [Range(1, 31)]\n    [ObservableProperty]\n    private int sharpenRadius = 1;\n\n    [Range(0.1, 10)]\n    [ObservableProperty]\n    private double sigma = 1;\n\n    [Range(0, 5)]\n    [ObservableProperty]\n    private double alpha = 1;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/StackCardViewModel.cs",
    "content": "﻿using System.Linq;\nusing System.Text.Json.Nodes;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(StackCard))]\n[ManagedService]\n[RegisterTransient<StackCardViewModel>]\npublic class StackCardViewModel : StackViewModelBase\n{\n    /// <inheritdoc />\n    public StackCardViewModel(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory) { }\n\n    /// <inheritdoc />\n    public override void LoadStateFromJsonObject(JsonObject state)\n    {\n        var model = DeserializeModel<StackCardModel>(state);\n\n        if (model.Cards is null)\n            return;\n\n        foreach (var (i, card) in model.Cards.Enumerate())\n        {\n            // Ignore if more than cards than we have\n            if (i > Cards.Count - 1)\n                break;\n\n            Cards[i].LoadStateFromJsonObject(card);\n        }\n    }\n\n    /// <inheritdoc />\n    public override JsonObject SaveStateToJsonObject()\n    {\n        return SerializeModel(\n            new StackCardModel { Cards = Cards.Select(x => x.SaveStateToJsonObject()).ToList() }\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/StackEditableCardViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text.Json.Serialization;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\nusing StabilityMatrix.Core.Attributes;\n#pragma warning disable CS0657 // Not a valid attribute location for this declaration\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(StackEditableCard))]\n[ManagedService]\n[RegisterTransient<StackEditableCardViewModel>]\npublic partial class StackEditableCardViewModel : StackViewModelBase, IComfyStep\n{\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n\n    [ObservableProperty]\n    [property: JsonIgnore]\n    private string? title = Languages.Resources.Label_Steps;\n\n    [ObservableProperty]\n    [property: JsonIgnore]\n    private bool isEditEnabled;\n\n    /// <summary>\n    /// Available module types for user creation\n    /// </summary>\n    [JsonIgnore]\n    public IReadOnlyList<Type> AvailableModules { get; set; } = Array.Empty<Type>();\n\n    /// <summary>\n    /// Default modules that are used when no modules are loaded\n    /// This is a subset of <see cref=\"AvailableModules\"/>\n    /// </summary>\n    [JsonIgnore]\n    public IReadOnlyList<Type> DefaultModules { get; set; } = Array.Empty<Type>();\n\n    /// <inheritdoc />\n    public StackEditableCardViewModel(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory)\n    {\n        this.vmFactory = vmFactory;\n    }\n\n    /// <summary>\n    /// Populate <see cref=\"StackViewModelBase.Cards\"/> with new instances of <see cref=\"DefaultModules\"/> types\n    /// </summary>\n    public void InitializeDefaults()\n    {\n        foreach (var module in DefaultModules)\n        {\n            AddModule(module);\n        }\n    }\n\n    partial void OnIsEditEnabledChanged(bool value)\n    {\n        // Propagate edit state to children\n        foreach (var module in Cards.OfType<StackExpanderViewModel>())\n        {\n            module.IsEditEnabled = value;\n        }\n    }\n\n    /// <inheritdoc />\n    public void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        foreach (var module in Cards.OfType<IComfyStep>())\n        {\n            module.ApplyStep(e);\n        }\n    }\n\n    /// <inheritdoc />\n    protected override void OnCardAdded(LoadableViewModelBase item)\n    {\n        base.OnCardAdded(item);\n\n        if (item is StackExpanderViewModel module)\n        {\n            // Inherit our edit state\n            module.IsEditEnabled = IsEditEnabled;\n        }\n    }\n\n    public T AddModule<T>()\n        where T : ModuleBase\n    {\n        var card = vmFactory.Get<T>();\n        AddCards(card);\n        return card;\n    }\n\n    public T AddModule<T>(Action<T> initializer)\n        where T : ModuleBase\n    {\n        var card = vmFactory.Get(initializer);\n        AddCards(card);\n        return card;\n    }\n\n    [RelayCommand]\n    private void AddModule(Type type)\n    {\n        if (!type.IsSubclassOf(typeof(ModuleBase)))\n        {\n            throw new ArgumentException($\"Type {type} must be subclass of {nameof(ModuleBase)}\");\n        }\n        var card = vmFactory.Get(type) as LoadableViewModelBase;\n        AddCards(card!);\n    }\n\n    public bool IsModuleEnabled<T>(int index = 0)\n        where T : ModuleBase\n    {\n        var card = Cards.OfType<T>().ElementAtOrDefault(index);\n        return card is { IsEnabled: true };\n    }\n\n    /*/// <inheritdoc />\n    public override void LoadStateFromJsonObject(JsonObject state)\n    {\n        var derivedTypes = ViewModelSerializer.GetDerivedTypes(typeof(LoadableViewModelBase));\n        \n        Clear();\n        \n        var stateArray = state.AsArray();\n\n        foreach (var node in stateArray)\n        {\n            \n        }\n        \n        var cards = ViewModelSerializer.DeserializeJsonObject<List<LoadableViewModelBase>>(state);\n        AddCards(cards!);\n    }\n\n    /// <inheritdoc />\n    public override JsonObject SaveStateToJsonObject()\n    {\n        return ViewModelSerializer.SerializeToJsonObject(Cards.ToList());\n    }*/\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/StackExpanderViewModel.cs",
    "content": "﻿using System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n#pragma warning disable CS0657 // Not a valid attribute location for this declaration\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(StackExpander))]\n[ManagedService]\n[RegisterTransient<StackExpanderViewModel>]\npublic partial class StackExpanderViewModel : StackViewModelBase\n{\n    public const string ModuleKey = \"StackExpander\";\n\n    [ObservableProperty]\n    [property: JsonIgnore]\n    private string? title;\n\n    [ObservableProperty]\n    [property: JsonIgnore]\n    private string? titleExtra;\n\n    [ObservableProperty]\n    private bool isEnabled;\n\n    /// <summary>\n    /// True if parent StackEditableCard is in edit mode (can drag to reorder)\n    /// </summary>\n    [ObservableProperty]\n    [property: JsonIgnore]\n    private bool isEditEnabled;\n\n    /// <summary>\n    /// True to show the settings button, invokes <see cref=\"SettingsCommand\"/> when clicked\n    /// </summary>\n    public virtual bool IsSettingsEnabled { get; set; }\n\n    public virtual IRelayCommand? SettingsCommand { get; set; }\n\n    /// <inheritdoc />\n    public StackExpanderViewModel(IServiceManager<ViewModelBase> vmFactory)\n        : base(vmFactory) { }\n\n    public override void OnContainerIndexChanged(int value)\n    {\n        TitleExtra = $\"{value + 1}.\";\n    }\n\n    /// <inheritdoc />\n    public override void LoadStateFromJsonObject(JsonObject state)\n    {\n        base.LoadStateFromJsonObject(state);\n\n        if (\n            state.TryGetPropertyValue(nameof(IsEnabled), out var isEnabledNode)\n            && isEnabledNode is JsonValue jsonValue\n            && jsonValue.TryGetValue(out bool isEnabledBool)\n        )\n        {\n            IsEnabled = isEnabledBool;\n        }\n    }\n\n    /// <inheritdoc />\n    public override JsonObject SaveStateToJsonObject()\n    {\n        var state = base.SaveStateToJsonObject();\n        state.Add(nameof(IsEnabled), IsEnabled);\n        return state;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/StackViewModelBase.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text.Json.Nodes;\nusing Nito.Disposables.Internals;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\npublic abstract class StackViewModelBase : DisposableLoadableViewModelBase\n{\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n\n    public AdvancedObservableList<LoadableViewModelBase> Cards { get; } = new();\n\n    protected StackViewModelBase(IServiceManager<ViewModelBase> vmFactory)\n    {\n        this.vmFactory = vmFactory;\n\n        Cards.CollectionChanged += (sender, args) =>\n        {\n            if (args.NewItems != null)\n            {\n                var itemIndex = args.NewStartingIndex;\n                foreach (var item in args.NewItems.OfType<StackViewModelBase>())\n                {\n                    item.OnContainerIndexChanged(itemIndex);\n                    itemIndex++;\n                }\n            }\n        };\n    }\n\n    public virtual void OnContainerIndexChanged(int value) { }\n\n    /// <summary>\n    /// Event raised when a card is added\n    /// </summary>\n    public event EventHandler<LoadableViewModelBase>? CardAdded;\n\n    protected virtual void OnCardAdded(LoadableViewModelBase item)\n    {\n        CardAdded?.Invoke(this, item);\n    }\n\n    public void AddCards(params LoadableViewModelBase[] cards)\n    {\n        AddCards((IEnumerable<LoadableViewModelBase>)cards);\n    }\n\n    /// <summary>\n    /// Register new cards\n    /// </summary>\n    public void AddCards(IEnumerable<LoadableViewModelBase> cards)\n    {\n        foreach (var card in cards)\n        {\n            Cards.Add(card);\n            OnCardAdded(card);\n        }\n    }\n\n    /// <summary>\n    /// Registers new cards and returns self\n    /// </summary>\n    public StackViewModelBase WithCards(IEnumerable<LoadableViewModelBase> cards)\n    {\n        AddCards(cards);\n        return this;\n    }\n\n    /// <summary>\n    /// Gets a card by type at specified index\n    /// </summary>\n    public T GetCard<T>(int index = 0)\n        where T : LoadableViewModelBase\n    {\n        return Cards.OfType<T>().ElementAtOrDefault(index)\n            ?? throw new InvalidOperationException(\n                $\"Card of type {typeof(T).Name} at index {index} not found\"\n            );\n    }\n\n    public void Clear()\n    {\n        Cards.Clear();\n    }\n\n    /// <inheritdoc />\n    public override void LoadStateFromJsonObject(JsonObject state)\n    {\n        Clear();\n\n        var derivedTypes = ViewModelSerializer.GetDerivedTypes(typeof(LoadableViewModelBase));\n\n        if (!state.TryGetPropertyValue(\"$values\", out var values) || values is not JsonArray nodesArray)\n        {\n            return;\n        }\n\n        foreach (var node in nodesArray.Select(n => n as JsonObject).WhereNotNull())\n        {\n            // Get $type key\n            if (\n                !node.TryGetPropertyValue(\"$type\", out var typeValue)\n                || typeValue is not JsonValue jsonValue\n                || jsonValue.ToString() is not { } typeKey\n            )\n            {\n                continue;\n            }\n\n            // Get type from key\n            if (!derivedTypes.TryGetValue(typeKey, out var type))\n            {\n                continue;\n            }\n\n            if (vmFactory.Get(type) is not LoadableViewModelBase vm)\n            {\n                continue;\n            }\n\n            vm.LoadStateFromJsonObject(node);\n            AddCards(vm);\n        }\n    }\n\n    /// <inheritdoc />\n    public override JsonObject SaveStateToJsonObject()\n    {\n        var derivedTypeNames = ViewModelSerializer\n            .GetDerivedTypes(typeof(LoadableViewModelBase))\n            .ToDictionary(x => x.Value, x => x.Key);\n\n        var nodes = new JsonArray(\n            Cards\n                .Select(x =>\n                {\n                    var typeKey = derivedTypeNames[x.GetType()];\n                    var node = x.SaveStateToJsonObject();\n                    node.Add(\"$type\", typeKey);\n                    return (JsonNode)node;\n                })\n                .ToArray()\n        );\n\n        return new JsonObject { [\"$values\"] = nodes };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/TiledVAECardViewModel.cs",
    "content": "using System.ComponentModel.DataAnnotations;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(TiledVAECard))]\n[ManagedService]\n[RegisterTransient<TiledVAECardViewModel>]\npublic partial class TiledVAECardViewModel : LoadableViewModelBase\n{\n    public const string ModuleKey = \"TiledVAE\";\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(64, 4096)]\n    private int tileSize = 512;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(0, 4096)]\n    private int overlap = 64;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(8, 4096)]\n    private int temporalSize = 64;\n\n    [ObservableProperty]\n    [NotifyDataErrorInfo]\n    [Required]\n    [Range(4, 4096)]\n    private int temporalOverlap = 8;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/UnetModelCardViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.Linq;\nusing System.Text.Json.Nodes;\nusing System.Threading.Tasks;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(UnetModelCard))]\n[ManagedService]\n[RegisterTransient<UnetModelCardViewModel>]\npublic partial class UnetModelCardViewModel(IInferenceClientManager clientManager)\n    : LoadableViewModelBase,\n        IParametersLoadableState,\n        IComfyStep\n{\n    [ObservableProperty]\n    private HybridModelFile? selectedModel;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedVae;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedClip1;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedClip2;\n\n    [ObservableProperty]\n    private string selectedDType = \"default\";\n\n    public List<string> WeightDTypes { get; set; } = [\"default\", \"fp8_e4m3fn\", \"fp8_e5m2\"];\n\n    public IInferenceClientManager ClientManager { get; } = clientManager;\n\n    public async Task<bool> ValidateModel()\n    {\n        if (SelectedModel != null)\n            return true;\n\n        var dialog = DialogHelper.CreateMarkdownDialog(\n            \"Please select a model to continue.\",\n            \"No Model Selected\"\n        );\n        await dialog.ShowAsync();\n        return false;\n    }\n\n    public void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var checkpointLoader = e.Nodes.AddTypedNode(GetModelLoader(e, SelectedModel!, SelectedDType));\n        e.Builder.Connections.Base.Model = checkpointLoader.Output;\n\n        var vaeLoader = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.VAELoader\n            {\n                Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.VAELoader)),\n                VaeName = SelectedVae?.RelativePath ?? throw new ValidationException(\"No VAE Selected\")\n            }\n        );\n        e.Builder.Connections.Base.VAE = vaeLoader.Output;\n\n        // DualCLIPLoader\n        var clipLoader = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.DualCLIPLoader\n            {\n                Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.DualCLIPLoader)),\n                ClipName1 = SelectedClip1?.RelativePath ?? throw new ValidationException(\"No Clip1 Selected\"),\n                ClipName2 = SelectedClip2?.RelativePath ?? throw new ValidationException(\"No Clip2 Selected\"),\n                Type = \"flux\"\n            }\n        );\n        e.Builder.Connections.Base.Clip = clipLoader.Output;\n    }\n\n    private static ComfyTypedNodeBase<ModelNodeConnection> GetModelLoader(\n        ModuleApplyStepEventArgs e,\n        HybridModelFile model,\n        string selectedDType\n    )\n    {\n        // Simple loader for UNET\n        return new ComfyNodeBuilder.UNETLoader\n        {\n            Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.UNETLoader)),\n            UnetName = model.RelativePath,\n            WeightDtype = selectedDType\n        };\n    }\n\n    /// <inheritdoc />\n    public override JsonObject SaveStateToJsonObject()\n    {\n        return SerializeModel(\n            new UnetModelCardModel\n            {\n                SelectedModelName = SelectedModel?.RelativePath,\n                SelectedVaeName = SelectedVae?.RelativePath,\n                SelectedClip1Name = SelectedClip1?.RelativePath,\n                SelectedClip2Name = SelectedClip2?.RelativePath\n            }\n        );\n    }\n\n    /// <inheritdoc />\n    public override void LoadStateFromJsonObject(JsonObject state)\n    {\n        var model = DeserializeModel<UnetModelCardModel>(state);\n\n        SelectedModel = model.SelectedModelName is null\n            ? null\n            : ClientManager.Models.FirstOrDefault(x => x.RelativePath == model.SelectedModelName);\n\n        SelectedVae = model.SelectedVaeName is null\n            ? HybridModelFile.Default\n            : ClientManager.VaeModels.FirstOrDefault(x => x.RelativePath == model.SelectedVaeName);\n\n        SelectedClip1 = model.SelectedClip1Name is null\n            ? HybridModelFile.None\n            : ClientManager.Models.FirstOrDefault(x => x.RelativePath == model.SelectedClip1Name);\n\n        SelectedClip2 = model.SelectedClip2Name is null\n            ? HybridModelFile.None\n            : ClientManager.Models.FirstOrDefault(x => x.RelativePath == model.SelectedClip2Name);\n    }\n\n    internal class UnetModelCardModel\n    {\n        public string? SelectedModelName { get; set; }\n        public string? SelectedVaeName { get; set; }\n        public string? SelectedClip1Name { get; set; }\n        public string? SelectedClip2Name { get; set; }\n    }\n\n    /// <inheritdoc />\n    public void LoadStateFromParameters(GenerationParameters parameters)\n    {\n        if (parameters.ModelName is not { } paramsModelName)\n            return;\n\n        var currentModels = ClientManager.Models;\n\n        HybridModelFile? model;\n\n        // First try hash match\n        if (parameters.ModelHash is not null)\n        {\n            model = currentModels.FirstOrDefault(\n                m =>\n                    m.Local?.ConnectedModelInfo?.Hashes.SHA256 is { } sha256\n                    && sha256.StartsWith(parameters.ModelHash, StringComparison.InvariantCultureIgnoreCase)\n            );\n        }\n        else\n        {\n            // Name matches\n            model = currentModels.FirstOrDefault(m => m.RelativePath.EndsWith(paramsModelName));\n            model ??= currentModels.FirstOrDefault(m => m.ShortDisplayName.StartsWith(paramsModelName));\n        }\n\n        if (model is not null)\n        {\n            SelectedModel = model;\n        }\n    }\n\n    /// <inheritdoc />\n    public GenerationParameters SaveStateToParameters(GenerationParameters parameters)\n    {\n        return parameters with\n        {\n            ModelName = SelectedModel?.FileName,\n            ModelHash = SelectedModel?.Local?.ConnectedModelInfo?.Hashes.SHA256\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/UpscalerCardViewModel.cs",
    "content": "﻿using System.Text.Json.Nodes;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(UpscalerCard))]\n[ManagedService]\n[RegisterTransient<UpscalerCardViewModel>]\npublic partial class UpscalerCardViewModel : LoadableViewModelBase\n{\n    public const string ModuleKey = \"Upscaler\";\n\n    private readonly INotificationService notificationService;\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n\n    [ObservableProperty]\n    private double scale = 2;\n\n    [ObservableProperty]\n    private ComfyUpscaler? selectedUpscaler = ComfyUpscaler.Defaults[0];\n\n    public IInferenceClientManager ClientManager { get; }\n\n    public UpscalerCardViewModel(\n        IInferenceClientManager clientManager,\n        INotificationService notificationService,\n        IServiceManager<ViewModelBase> vmFactory\n    )\n    {\n        this.notificationService = notificationService;\n        this.vmFactory = vmFactory;\n\n        ClientManager = clientManager;\n    }\n\n    /// <inheritdoc />\n    public override void LoadStateFromJsonObject(JsonObject state)\n    {\n        var model = DeserializeModel<UpscalerCardModel>(state);\n\n        Scale = model.Scale;\n        SelectedUpscaler = model.SelectedUpscaler;\n    }\n\n    /// <inheritdoc />\n    public override JsonObject SaveStateToJsonObject()\n    {\n        return SerializeModel(new UpscalerCardModel { Scale = Scale, SelectedUpscaler = SelectedUpscaler });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Video/ImgToVidModelCardViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Video;\n\n[View(typeof(ModelCard))]\n[ManagedService]\n[RegisterTransient<ImgToVidModelCardViewModel>]\npublic class ImgToVidModelCardViewModel : ModelCardViewModel\n{\n    public ImgToVidModelCardViewModel(\n        IInferenceClientManager clientManager,\n        IServiceManager<ViewModelBase> vmFactory,\n        TabContext tabContext\n    )\n        : base(clientManager, vmFactory, tabContext)\n    {\n        DisableSettings = true;\n    }\n\n    public override void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        var imgToVidLoader = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.ImageOnlyCheckpointLoader\n            {\n                Name = \"ImageOnlyCheckpointLoader\",\n                CkptName = SelectedModel?.RelativePath ?? throw new ValidationException(\"Model not selected\"),\n            }\n        );\n\n        e.Builder.Connections.Base.Model = imgToVidLoader.Output1;\n        e.Builder.Connections.BaseClipVision = imgToVidLoader.Output2;\n        e.Builder.Connections.Base.VAE = imgToVidLoader.Output3;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Video/SvdImgToVidConditioningViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Video;\n\n[View(typeof(VideoGenerationSettingsCard))]\n[ManagedService]\n[RegisterTransient<SvdImgToVidConditioningViewModel>]\npublic partial class SvdImgToVidConditioningViewModel\n    : LoadableViewModelBase,\n        IParametersLoadableState,\n        IComfyStep\n{\n    [ObservableProperty]\n    private int width = 1024;\n\n    [ObservableProperty]\n    private int height = 576;\n\n    [ObservableProperty]\n    private int numFrames = 14;\n\n    [ObservableProperty]\n    private int motionBucketId = 127;\n\n    [ObservableProperty]\n    private int fps = 6;\n\n    [ObservableProperty]\n    private double augmentationLevel;\n\n    [ObservableProperty]\n    private double minCfg = 1.0d;\n\n    public void LoadStateFromParameters(GenerationParameters parameters)\n    {\n        Width = parameters.Width;\n        Height = parameters.Height;\n        NumFrames = parameters.FrameCount;\n        MotionBucketId = parameters.MotionBucketId;\n        Fps = parameters.Fps;\n        AugmentationLevel = parameters.AugmentationLevel;\n        MinCfg = parameters.MinCfg;\n    }\n\n    public GenerationParameters SaveStateToParameters(GenerationParameters parameters)\n    {\n        return parameters with\n        {\n            FrameCount = NumFrames,\n            MotionBucketId = MotionBucketId,\n            Fps = Fps,\n            AugmentationLevel = AugmentationLevel,\n            MinCfg = MinCfg,\n        };\n    }\n\n    public void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        // do VideoLinearCFGGuidance stuff first\n        var cfgGuidanceNode = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.VideoLinearCFGGuidance\n            {\n                Name = e.Nodes.GetUniqueName(\"LinearCfgGuidance\"),\n                Model =\n                    e.Builder.Connections.Base.Model ?? throw new ValidationException(\"Model not selected\"),\n                MinCfg = MinCfg\n            }\n        );\n\n        e.Builder.Connections.Base.Model = cfgGuidanceNode.Output;\n\n        // then do the SVD stuff\n        var svdImgToVidConditioningNode = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.SVD_img2vid_Conditioning\n            {\n                ClipVision = e.Builder.Connections.BaseClipVision!,\n                InitImage = e.Builder.GetPrimaryAsImage(),\n                Vae = e.Builder.Connections.Base.VAE!,\n                Name = e.Nodes.GetUniqueName(\"SvdImgToVidConditioning\"),\n                Width = Width,\n                Height = Height,\n                VideoFrames = NumFrames,\n                MotionBucketId = MotionBucketId,\n                Fps = Fps,\n                AugmentationLevel = AugmentationLevel\n            }\n        );\n\n        e.Builder.Connections.Base.Conditioning = new ConditioningConnections(\n            svdImgToVidConditioningNode.Output1,\n            svdImgToVidConditioningNode.Output2\n        );\n        e.Builder.Connections.Primary = svdImgToVidConditioningNode.Output3;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/Video/VideoOutputSettingsCardViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference.Video;\n\n[View(typeof(VideoOutputSettingsCard))]\n[ManagedService]\n[RegisterTransient<VideoOutputSettingsCardViewModel>]\npublic partial class VideoOutputSettingsCardViewModel\n    : LoadableViewModelBase,\n        IParametersLoadableState,\n        IComfyStep\n{\n    [ObservableProperty]\n    private double fps = 6;\n\n    [ObservableProperty]\n    private bool lossless = true;\n\n    [ObservableProperty]\n    private int quality = 85;\n\n    [ObservableProperty]\n    private VideoOutputMethod selectedMethod = VideoOutputMethod.Default;\n\n    [ObservableProperty]\n    private List<VideoOutputMethod> availableMethods = Enum.GetValues<VideoOutputMethod>().ToList();\n\n    public void LoadStateFromParameters(GenerationParameters parameters)\n    {\n        Fps = parameters.OutputFps;\n        Lossless = parameters.Lossless;\n        Quality = parameters.VideoQuality;\n\n        if (string.IsNullOrWhiteSpace(parameters.VideoOutputMethod))\n            return;\n\n        SelectedMethod = Enum.TryParse<VideoOutputMethod>(parameters.VideoOutputMethod, true, out var method)\n            ? method\n            : VideoOutputMethod.Default;\n    }\n\n    public GenerationParameters SaveStateToParameters(GenerationParameters parameters)\n    {\n        return parameters with\n        {\n            OutputFps = Fps,\n            Lossless = Lossless,\n            VideoQuality = Quality,\n            VideoOutputMethod = SelectedMethod.ToString(),\n        };\n    }\n\n    public void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        if (e.Builder.Connections.Primary is null)\n            throw new ArgumentException(\"No Primary\");\n\n        var image = e.Builder.Connections.Primary.Match(\n            _ =>\n                e.Builder.GetPrimaryAsImage(\n                    e.Builder.Connections.PrimaryVAE\n                        ?? e.Builder.Connections.Refiner.VAE\n                        ?? e.Builder.Connections.Base.VAE\n                        ?? throw new ArgumentException(\"No Primary, Refiner, or Base VAE\")\n                ),\n            image => image\n        );\n\n        var outputStep = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.SaveAnimatedWEBP\n            {\n                Name = e.Nodes.GetUniqueName(\"SaveAnimatedWEBP\"),\n                Images = image,\n                FilenamePrefix = \"InferenceVideo\",\n                Fps = Fps,\n                Lossless = Lossless,\n                Quality = Quality,\n                Method = SelectedMethod.ToString().ToLowerInvariant()\n            }\n        );\n\n        e.Builder.Connections.OutputNodes.Add(outputStep);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/WanModelCardViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(WanModelCard))]\n[ManagedService]\n[RegisterTransient<WanModelCardViewModel>]\npublic partial class WanModelCardViewModel(\n    IInferenceClientManager clientManager,\n    IServiceManager<ViewModelBase> vmFactory\n) : LoadableViewModelBase, IParametersLoadableState, IComfyStep\n{\n    [ObservableProperty]\n    private HybridModelFile? selectedModel;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedClipModel;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedClipVisionModel;\n\n    [ObservableProperty]\n    private HybridModelFile? selectedVae;\n\n    [ObservableProperty]\n    private string? selectedDType = \"fp8_e4m3fn_fast\";\n\n    [ObservableProperty]\n    private bool isClipVisionEnabled;\n\n    [ObservableProperty]\n    private double shift = 8.0d;\n\n    public IInferenceClientManager ClientManager { get; } = clientManager;\n\n    public StackEditableCardViewModel ExtraNetworksStackCardViewModel { get; } =\n        new(vmFactory) { Title = Resources.Label_ExtraNetworks, AvailableModules = [typeof(LoraModule)] };\n\n    public List<string> WeightDTypes { get; set; } = [\"default\", \"fp8_e4m3fn\", \"fp8_e4m3fn_fast\", \"fp8_e5m2\"];\n\n    public async Task<bool> ValidateModel()\n    {\n        if (SelectedModel == null)\n        {\n            var dialog = DialogHelper.CreateMarkdownDialog(\n                \"Please select a model to continue.\",\n                \"No Model Selected\"\n            );\n            await dialog.ShowAsync();\n            return false;\n        }\n\n        if (SelectedVae == null)\n        {\n            var dialog = DialogHelper.CreateMarkdownDialog(\n                \"Please select a VAE model to continue.\",\n                \"No VAE Model Selected\"\n            );\n            await dialog.ShowAsync();\n            return false;\n        }\n\n        if (SelectedClipModel == null)\n        {\n            var dialog = DialogHelper.CreateMarkdownDialog(\n                \"Please select a CLIP model to continue.\",\n                \"No CLIP Model Selected\"\n            );\n            await dialog.ShowAsync();\n            return false;\n        }\n\n        if (IsClipVisionEnabled && SelectedClipVisionModel == null)\n        {\n            var dialog = DialogHelper.CreateMarkdownDialog(\n                \"Please select a CLIP Vision model to continue.\",\n                \"No CLIP Vision Model Selected\"\n            );\n            await dialog.ShowAsync();\n            return false;\n        }\n\n        return true;\n    }\n\n    public void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        ComfyTypedNodeBase<ModelNodeConnection> modelLoader;\n        if (SelectedModel?.RelativePath.EndsWith(\"gguf\", StringComparison.OrdinalIgnoreCase) is true)\n        {\n            modelLoader = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.UnetLoaderGGUF\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.UnetLoaderGGUF)),\n                    UnetName =\n                        SelectedModel?.RelativePath ?? throw new ValidationException(\"Model not selected\")\n                }\n            );\n        }\n        else\n        {\n            modelLoader = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.UNETLoader\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.UNETLoader)),\n                    UnetName =\n                        SelectedModel?.RelativePath ?? throw new ValidationException(\"Model not selected\"),\n                    WeightDtype = SelectedDType ?? \"fp8_e4m3fn_fast\"\n                }\n            );\n        }\n\n        var modelSamplingSd3 = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.ModelSamplingSD3\n            {\n                Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.ModelSamplingSD3)),\n                Model = modelLoader.Output,\n                Shift = Shift\n            }\n        );\n\n        e.Builder.Connections.Base.Model = modelSamplingSd3.Output;\n\n        var clipLoader = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.CLIPLoader\n            {\n                Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.CLIPLoader)),\n                ClipName =\n                    SelectedClipModel?.RelativePath\n                    ?? throw new ValidationException(\"No Clip Model Selected\"),\n                Type = \"wan\"\n            }\n        );\n\n        e.Builder.Connections.Base.Clip = clipLoader.Output;\n\n        var vaeLoader = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.VAELoader\n            {\n                Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.VAELoader)),\n                VaeName = SelectedVae?.RelativePath ?? throw new ValidationException(\"No VAE Selected\")\n            }\n        );\n        e.Builder.Connections.Base.VAE = vaeLoader.Output;\n        e.Builder.Connections.PrimaryVAE = vaeLoader.Output;\n\n        if (ExtraNetworksStackCardViewModel.Cards.OfType<LoraModule>().Any(x => x.IsEnabled))\n        {\n            ExtraNetworksStackCardViewModel.ApplyStep(e);\n        }\n\n        if (!IsClipVisionEnabled)\n            return;\n\n        var clipVisionLoader = e.Nodes.AddTypedNode(\n            new ComfyNodeBuilder.CLIPVisionLoader\n            {\n                Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.CLIPVisionLoader)),\n                ClipName =\n                    SelectedClipVisionModel?.RelativePath\n                    ?? throw new ValidationException(\"No Clip Vision Model Selected\")\n            }\n        );\n\n        e.Builder.Connections.BaseClipVision = clipVisionLoader.Output;\n        e.Builder.Connections.Base.ClipVision = clipVisionLoader.Output;\n    }\n\n    public void LoadStateFromParameters(GenerationParameters parameters)\n    {\n        if (parameters.ModelName is not { } paramsModelName)\n            return;\n\n        var currentModels = ClientManager.UnetModels;\n\n        HybridModelFile? model;\n\n        // First try hash match\n        if (parameters.ModelHash is not null)\n        {\n            model = currentModels.FirstOrDefault(\n                m =>\n                    m.Local?.ConnectedModelInfo?.Hashes.SHA256 is { } sha256\n                    && sha256.StartsWith(parameters.ModelHash, StringComparison.InvariantCultureIgnoreCase)\n            );\n        }\n        else\n        {\n            // Name matches\n            model = currentModels.FirstOrDefault(m => m.RelativePath.EndsWith(paramsModelName));\n            model ??= currentModels.FirstOrDefault(m => m.ShortDisplayName.StartsWith(paramsModelName));\n        }\n\n        if (model is null)\n            return;\n\n        SelectedModel = model;\n    }\n\n    public GenerationParameters SaveStateToParameters(GenerationParameters parameters)\n    {\n        return parameters with\n        {\n            ModelName = SelectedModel?.FileName,\n            ModelHash = SelectedModel?.Local?.ConnectedModelInfo?.Hashes.SHA256\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Inference/WanSamplerCardViewModel.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing System.Linq;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Inference.Modules;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Inference;\n\n[View(typeof(SamplerCard))]\n[ManagedService]\n[RegisterTransient<WanSamplerCardViewModel>]\npublic class WanSamplerCardViewModel : SamplerCardViewModel\n{\n    public WanSamplerCardViewModel(\n        IInferenceClientManager clientManager,\n        IServiceManager<ViewModelBase> vmFactory,\n        ISettingsManager settingsManager,\n        TabContext tabContext\n    )\n        : base(clientManager, vmFactory, settingsManager, tabContext)\n    {\n        EnableAddons = false;\n        IsLengthEnabled = true;\n        SelectedSampler = ComfySampler.UniPC;\n        SelectedScheduler = ComfyScheduler.Simple;\n        Length = 33;\n    }\n\n    public override void ApplyStep(ModuleApplyStepEventArgs e)\n    {\n        if (EnableAddons)\n        {\n            foreach (var module in ModulesCardViewModel.Cards.OfType<ModuleBase>())\n            {\n                module.ApplyStep(e);\n            }\n        }\n\n        // Set primary sampler and scheduler\n        var primarySampler = SelectedSampler ?? throw new ValidationException(\"Sampler not selected\");\n        e.Builder.Connections.PrimarySampler = primarySampler;\n\n        var primaryScheduler = SelectedScheduler ?? throw new ValidationException(\"Scheduler not selected\");\n        e.Builder.Connections.PrimaryScheduler = primaryScheduler;\n\n        // for later inheritance if needed\n        e.Builder.Connections.PrimaryCfg = CfgScale;\n        e.Builder.Connections.PrimarySteps = Steps;\n        e.Builder.Connections.PrimaryModelType = SelectedModelType;\n\n        e.Temp = e.CreateTempFromBuilder();\n\n        var conditioning = e.Temp.Base.Conditioning.Unwrap();\n\n        var isImgToVid = IsDenoiseStrengthEnabled;\n\n        if (isImgToVid)\n        {\n            var clipVisionEncode = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.CLIPVisionEncode\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.CLIPVisionEncode)),\n                    ClipVision =\n                        e.Builder.Connections.BaseClipVision\n                        ?? throw new ValidationException(\"BaseClipVision not set\"),\n                    Image = e.Builder.GetPrimaryAsImage(),\n                    Crop = \"none\",\n                }\n            );\n\n            var wanImageToVideo = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.WanImageToVideo\n                {\n                    Name = e.Nodes.GetUniqueName(nameof(ComfyNodeBuilder.WanImageToVideo)),\n                    Positive = conditioning.Positive,\n                    Negative = conditioning.Negative,\n                    Vae = e.Builder.Connections.GetDefaultVAE(),\n                    ClipVisionOutput = clipVisionEncode.Output,\n                    StartImage = e.Builder.GetPrimaryAsImage(),\n                    Width = Width,\n                    Height = Height,\n                    Length = Length,\n                    BatchSize = e.Builder.Connections.BatchSize,\n                }\n            );\n\n            var kSampler = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.KSampler\n                {\n                    Name = \"Sampler\",\n                    Model = e.Temp.Base.Model!.Unwrap(),\n                    Seed = e.Builder.Connections.Seed,\n                    SamplerName = primarySampler.Name,\n                    Scheduler = primaryScheduler.Name,\n                    Steps = Steps,\n                    Cfg = CfgScale,\n                    Positive = wanImageToVideo.Output1,\n                    Negative = wanImageToVideo.Output2,\n                    LatentImage = wanImageToVideo.Output3,\n                    Denoise = DenoiseStrength,\n                }\n            );\n            e.Builder.Connections.Primary = kSampler.Output;\n        }\n        else\n        {\n            var primaryLatent = e.Builder.GetPrimaryAsLatent(\n                e.Temp.Primary!.Unwrap(),\n                e.Builder.Connections.GetDefaultVAE()\n            );\n\n            var kSampler = e.Nodes.AddTypedNode(\n                new ComfyNodeBuilder.KSampler\n                {\n                    Name = \"Sampler\",\n                    Model = e.Temp.Base.Model!.Unwrap(),\n                    Seed = e.Builder.Connections.Seed,\n                    SamplerName = primarySampler.Name,\n                    Scheduler = primaryScheduler.Name,\n                    Steps = Steps,\n                    Cfg = CfgScale,\n                    Positive = conditioning.Positive,\n                    Negative = conditioning.Negative,\n                    LatentImage = primaryLatent,\n                    Denoise = DenoiseStrength,\n                }\n            );\n            e.Builder.Connections.Primary = kSampler.Output;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/InferenceViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Collections.ObjectModel;\nusing System.Collections.Specialized;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Text.Json;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Platform.Storage;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing FluentIcons.Common;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.DependencyInjection;\nusing NLog;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Inference;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\nusing InferenceTabViewModelBase = StabilityMatrix.Avalonia.ViewModels.Base.InferenceTabViewModelBase;\nusing Path = System.IO.Path;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[Preload]\n[View(typeof(InferencePage))]\n[RegisterSingleton<InferenceViewModel>]\npublic partial class InferenceViewModel : PageViewModelBase, IAsyncDisposable\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private readonly INotificationService notificationService;\n\n    private readonly ISettingsManager settingsManager;\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n    private readonly IModelIndexService modelIndexService;\n    private readonly ILiteDbContext liteDbContext;\n    private readonly RunningPackageService runningPackageService;\n    private Guid? selectedPackageId;\n    private List<IDisposable> scopes = [];\n\n    public override string Title => Resources.Label_Inference;\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.AppGeneric, IconVariant = IconVariant.Filled };\n\n    public RefreshBadgeViewModel ConnectionBadge { get; } =\n        new()\n        {\n            State = ProgressState.Failed,\n            FailToolTipText = \"Not connected\",\n            FailIcon = FluentAvalonia.UI.Controls.Symbol.Refresh,\n            SuccessToolTipText = Resources.Label_Connected,\n        };\n\n    public IInferenceClientManager ClientManager { get; }\n\n    public SharedState SharedState { get; }\n\n    public ObservableCollection<InferenceTabViewModelBase> Tabs { get; } = new();\n\n    [ObservableProperty]\n    private InferenceTabViewModelBase? selectedTab;\n\n    [ObservableProperty]\n    private int selectedTabIndex;\n\n    [ObservableProperty]\n    private bool isWaitingForConnection;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsComfyRunning))]\n    private PackagePair? runningPackage;\n\n    public bool IsComfyRunning => RunningPackage?.BasePackage is ComfyUI;\n\n    private IDisposable? onStartupComplete;\n\n    public InferenceViewModel(\n        IServiceManager<ViewModelBase> vmFactory,\n        INotificationService notificationService,\n        IInferenceClientManager inferenceClientManager,\n        ISettingsManager settingsManager,\n        IModelIndexService modelIndexService,\n        ILiteDbContext liteDbContext,\n        RunningPackageService runningPackageService,\n        SharedState sharedState\n    )\n    {\n        this.vmFactory = vmFactory;\n        this.notificationService = notificationService;\n        this.settingsManager = settingsManager;\n        this.modelIndexService = modelIndexService;\n        this.liteDbContext = liteDbContext;\n        this.runningPackageService = runningPackageService;\n\n        ClientManager = inferenceClientManager;\n        SharedState = sharedState;\n\n        // Keep RunningPackage updated with the current package pair\n        runningPackageService.RunningPackages.CollectionChanged += RunningPackagesOnCollectionChanged;\n\n        // \"Send to Inference\"\n        EventManager.Instance.InferenceProjectRequested += InstanceOnInferenceProjectRequested;\n\n        // Global requests for custom prompt queueing\n        EventManager.Instance.InferenceQueueCustomPrompt += OnInferenceQueueCustomPromptRequested;\n\n        MenuSaveAsCommand.WithConditionalNotificationErrorHandler(notificationService);\n        MenuOpenProjectCommand.WithConditionalNotificationErrorHandler(notificationService);\n    }\n\n    private Task InstanceOnInferenceProjectRequested(\n        object? sender,\n        LocalImageFile imageFile,\n        InferenceProjectType type\n    ) => Dispatcher.UIThread.InvokeAsync(async () => await AddTabFromFileAsync(imageFile, type));\n\n    private void DisconnectFromComfy()\n    {\n        RunningPackage = null;\n\n        // Cancel any pending connection\n        if (ConnectCancelCommand.CanExecute(null))\n        {\n            ConnectCancelCommand.Execute(null);\n        }\n        onStartupComplete?.Dispose();\n        onStartupComplete = null;\n        IsWaitingForConnection = false;\n\n        // Disconnect\n        Logger.Trace(\"On package close - disconnecting\");\n        DisconnectCommand.Execute(null);\n    }\n\n    /// <summary>\n    /// Updates the RunningPackage property when the running package changes.\n    /// Also starts a connection to the backend if a new ComfyUI package is running.\n    /// And disconnects if the package is closed.\n    /// </summary>\n    private void RunningPackagesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)\n    {\n        if (\n            e.NewItems?.OfType<KeyValuePair<Guid, RunningPackageViewModel>>().Select(x => x.Value)\n            is not { } newItems\n        )\n        {\n            if (RunningPackage != null)\n            {\n                DisconnectFromComfy();\n            }\n            return;\n        }\n\n        var comfyViewModel = newItems.FirstOrDefault(\n            vm =>\n                vm.RunningPackage.InstalledPackage.Id == selectedPackageId\n                || vm.RunningPackage.BasePackage is ComfyUI\n        );\n\n        if (comfyViewModel is null && RunningPackage?.BasePackage is ComfyUI)\n        {\n            DisconnectFromComfy();\n        }\n        else if (comfyViewModel != null && RunningPackage == null)\n        {\n            IsWaitingForConnection = true;\n            RunningPackage = comfyViewModel.RunningPackage;\n            onStartupComplete = Observable\n                .FromEventPattern<string>(\n                    comfyViewModel.RunningPackage.BasePackage,\n                    nameof(comfyViewModel.RunningPackage.BasePackage.StartupComplete)\n                )\n                .Take(1)\n                .Subscribe(_ =>\n                {\n                    Dispatcher.UIThread.Post(() =>\n                    {\n                        if (ConnectCommand.CanExecute(null))\n                        {\n                            Logger.Trace(\"On package launch - starting connection\");\n                            ConnectCommand.Execute(null);\n                        }\n\n                        IsWaitingForConnection = false;\n                    });\n                });\n        }\n    }\n\n    private void OnInferenceQueueCustomPromptRequested(object? sender, InferenceQueueCustomPromptEventArgs e)\n    {\n        // Get currently selected tab\n        var currentTab = SelectedTab;\n\n        if (currentTab is InferenceGenerationViewModelBase generationViewModel)\n        {\n            Dispatcher\n                .UIThread.InvokeAsync(async () =>\n                {\n                    await generationViewModel.RunCustomGeneration(e);\n                })\n                .SafeFireAndForget(ex =>\n                {\n                    Logger.Error(ex, \"Failed to queue prompt\");\n\n                    Dispatcher.UIThread.Post(() =>\n                    {\n                        notificationService.ShowPersistent(\n                            \"Failed to queue prompt\",\n                            $\"{ex.GetType().Name}: {ex.Message}\",\n                            NotificationType.Error\n                        );\n                    });\n                });\n        }\n    }\n\n    public override void OnLoaded()\n    {\n        base.OnLoaded();\n\n        modelIndexService.BackgroundRefreshIndex();\n    }\n\n    protected override async Task OnInitialLoadedAsync()\n    {\n        await base.OnInitialLoadedAsync();\n\n        if (Design.IsDesignMode)\n            return;\n\n        // Load any open projects\n        var openProjects = await liteDbContext.InferenceProjects.FindAsync(p => p.IsOpen);\n\n        if (openProjects is not null)\n        {\n            foreach (var project in openProjects.OrderBy(p => p.CurrentTabIndex))\n            {\n                var file = new FilePath(project.FilePath);\n\n                if (!file.Exists)\n                {\n                    // Remove from database\n                    await liteDbContext.InferenceProjects.DeleteAsync(project.Id);\n                }\n\n                try\n                {\n                    if (file.Exists)\n                    {\n                        await AddTabFromFile(project.FilePath);\n                    }\n                }\n                catch (Exception e)\n                {\n                    Logger.Warn(e, \"Failed to open project file {FilePath}\", project.FilePath);\n\n                    notificationService.Show(\n                        \"Failed to open project file\",\n                        $\"[{e.GetType().Name}] {e.Message}\",\n                        NotificationType.Error\n                    );\n\n                    // Set not open\n                    await liteDbContext.InferenceProjects.UpdateAsync(\n                        project with\n                        {\n                            IsOpen = false,\n                            IsSelected = false,\n                            CurrentTabIndex = -1\n                        }\n                    );\n                }\n            }\n        }\n\n        if (Tabs.Count == 0)\n        {\n            AddTab(InferenceProjectType.TextToImage);\n        }\n    }\n\n    /// <summary>\n    /// On exit, sync tab states to database\n    /// </summary>\n    public async ValueTask DisposeAsync()\n    {\n        await SyncTabStatesWithDatabase();\n\n        foreach (var scope in scopes)\n        {\n            scope.Dispose();\n        }\n\n        GC.SuppressFinalize(this);\n    }\n\n    /// <summary>\n    /// Update the database with current tabs\n    /// </summary>\n    private async Task SyncTabStatesWithDatabase()\n    {\n        // Update the database with the current tabs\n        foreach (var (i, tab) in Tabs.ToImmutableArray().Enumerate())\n        {\n            if (tab.ProjectFile is not { } projectFile)\n            {\n                continue;\n            }\n\n            var projectPath = projectFile.ToString();\n\n            var entry = await liteDbContext.InferenceProjects.FindOneAsync(p => p.FilePath == projectPath);\n\n            // Create if not found\n            entry ??= new InferenceProjectEntry { Id = Guid.NewGuid(), FilePath = projectFile.ToString() };\n\n            entry.IsOpen = tab == SelectedTab;\n            entry.CurrentTabIndex = i;\n\n            Logger.Trace(\n                \"SyncTabStatesWithDatabase updated entry for tab '{Title}': {@Entry}\",\n                tab.TabTitle,\n                entry\n            );\n            await liteDbContext.InferenceProjects.UpsertAsync(entry);\n        }\n    }\n\n    /// <summary>\n    /// Update the database with given tab\n    /// </summary>\n    private async Task SyncTabStateWithDatabase(InferenceTabViewModelBase tab)\n    {\n        if (tab.ProjectFile is not { } projectFile)\n        {\n            return;\n        }\n\n        var entry = await liteDbContext.InferenceProjects.FindOneAsync(\n            p => p.FilePath == projectFile.ToString()\n        );\n\n        // Create if not found\n        entry ??= new InferenceProjectEntry { Id = Guid.NewGuid(), FilePath = projectFile.ToString() };\n\n        entry.IsOpen = tab == SelectedTab;\n        entry.CurrentTabIndex = Tabs.IndexOf(tab);\n\n        Logger.Trace(\n            \"SyncTabStatesWithDatabase updated entry for tab '{Title}': {@Entry}\",\n            tab.TabTitle,\n            entry\n        );\n        await liteDbContext.InferenceProjects.UpsertAsync(entry);\n    }\n\n    /// <summary>\n    /// When the + button on the tab control is clicked, add a new tab.\n    /// </summary>\n    [RelayCommand]\n    private void AddTab(InferenceProjectType type)\n    {\n        if (type.ToViewModelType() is not { } vmType)\n        {\n            return;\n        }\n\n        // Create a new scope for this tab\n        var scope = vmFactory.CreateScope();\n\n        // Get the view model using the scope's service provider\n        var tab =\n            scope.ServiceManager.Get(vmType) as InferenceTabViewModelBase\n            ?? throw new NullReferenceException($\"Could not create view model of type {vmType}\");\n\n        Tabs.Add(tab);\n\n        // Set as new selected tab\n        SelectedTabIndex = Tabs.Count - 1;\n\n        // Update the database with the current tab\n        SyncTabStateWithDatabase(tab).SafeFireAndForget();\n    }\n\n    /// <summary>\n    /// When the close button on the tab is clicked, remove the tab.\n    /// </summary>\n    public void OnTabCloseRequested(TabViewTabCloseRequestedEventArgs e)\n    {\n        if (e.Item is not InferenceTabViewModelBase vm)\n        {\n            Logger.Warn(\"Tab close requested for unknown item {@Item}\", e);\n            return;\n        }\n\n        Logger.Trace(\"Closing tab {Title}\", vm.TabTitle);\n\n        // Set the selected tab to the next tab if there is one, then previous, then null\n        lock (Tabs)\n        {\n            var index = Tabs.IndexOf(vm);\n            if (index < Tabs.Count - 1)\n            {\n                SelectedTabIndex = index + 1;\n            }\n            else if (index > 0)\n            {\n                SelectedTabIndex = index - 1;\n            }\n\n            // Remove the tab\n            Tabs.RemoveAt(index);\n\n            // Dispose the scope for this tab\n            if (index < scopes.Count)\n            {\n                scopes[index].Dispose();\n                scopes.RemoveAt(index);\n            }\n        }\n\n        // Update the database with the current tab\n        SyncTabStateWithDatabase(vm).SafeFireAndForget();\n\n        // Dispose the view model\n        vm.Dispose();\n    }\n\n    /// <summary>\n    /// Show the connection help dialog.\n    /// </summary>\n    [RelayCommand]\n    private async Task ShowConnectionHelp()\n    {\n        var vm = vmFactory.Get<InferenceConnectionHelpViewModel>();\n        var result = await vm.CreateDialog().ShowAsync();\n\n        if (result != ContentDialogResult.Primary)\n            return;\n\n        selectedPackageId = vm.SelectedPackage?.Id;\n    }\n\n    /// <summary>\n    /// Connect to the inference server.\n    /// </summary>\n    [RelayCommand(IncludeCancelCommand = true)]\n    private async Task Connect(CancellationToken cancellationToken = default)\n    {\n        if (ClientManager.IsConnected)\n            return;\n\n        if (Design.IsDesignMode)\n        {\n            await ClientManager.ConnectAsync(cancellationToken);\n            return;\n        }\n\n        if (RunningPackage is not null)\n        {\n            var result = await notificationService.TryAsync(\n                ClientManager.ConnectAsync(RunningPackage, cancellationToken),\n                \"Could not connect to backend\"\n            );\n\n            if (result.Exception is { } exception)\n            {\n                Logger.Error(exception, \"Failed to connect to Inference backend\");\n            }\n        }\n    }\n\n    /// <summary>\n    /// Disconnect from the inference server.\n    /// </summary>\n    [RelayCommand]\n    private async Task Disconnect()\n    {\n        if (!ClientManager.IsConnected)\n            return;\n\n        if (Design.IsDesignMode)\n        {\n            await ClientManager.CloseAsync();\n            return;\n        }\n\n        await notificationService.TryAsync(\n            ClientManager.CloseAsync(),\n            \"Could not disconnect from ComfyUI backend\"\n        );\n    }\n\n    /// <summary>\n    /// Menu \"Save As\" command.\n    /// </summary>\n    [RelayCommand(FlowExceptionsToTaskScheduler = true)]\n    private async Task MenuSaveAs()\n    {\n        var currentTab = SelectedTab;\n        if (currentTab == null)\n        {\n            Logger.Warn(\"MenuSaveAs: currentTab is null\");\n            return;\n        }\n\n        // Prompt for save file dialog\n        var provider = App.StorageProvider;\n\n        var projectDir = new DirectoryPath(settingsManager.LibraryDir, \"Projects\");\n        projectDir.Create();\n        var startDir = await provider.TryGetFolderFromPathAsync(projectDir);\n\n        var result = await provider.SaveFilePickerAsync(\n            new FilePickerSaveOptions\n            {\n                Title = \"Save As\",\n                SuggestedFileName = \"Untitled\",\n                FileTypeChoices = new FilePickerFileType[]\n                {\n                    new(\"StabilityMatrix Project\")\n                    {\n                        Patterns = new[] { \"*.smproj\" },\n                        MimeTypes = new[] { \"application/json\" },\n                    }\n                },\n                SuggestedStartLocation = startDir,\n                DefaultExtension = \".smproj\",\n                ShowOverwritePrompt = true,\n            }\n        );\n\n        if (result is null)\n        {\n            Logger.Trace(\"MenuSaveAs: user cancelled\");\n            return;\n        }\n\n        var document = InferenceProjectDocument.FromLoadable(currentTab);\n\n        // Save to file\n        try\n        {\n            await using var stream = await result.OpenWriteAsync();\n            stream.SetLength(0); // Overwrite fully\n\n            await JsonSerializer.SerializeAsync(\n                stream,\n                document,\n                new JsonSerializerOptions { WriteIndented = true }\n            );\n        }\n        catch (Exception e)\n        {\n            notificationService.ShowPersistent(\n                \"Could not save to file\",\n                $\"[{e.GetType().Name}] {e.Message}\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        // Update project file\n        currentTab.ProjectFile = new FilePath(result.TryGetLocalPath()!);\n\n        await SyncTabStatesWithDatabase();\n\n        notificationService.Show(\"Saved\", $\"Saved project to {result.Name}\", NotificationType.Success);\n    }\n\n    /// <summary>\n    /// Menu \"Save Project\" command.\n    /// </summary>\n    [RelayCommand(FlowExceptionsToTaskScheduler = true)]\n    private async Task MenuSave()\n    {\n        if (SelectedTab is not { } currentTab)\n        {\n            Logger.Info(\"MenuSaveProject: currentTab is null\");\n            return;\n        }\n\n        // If the tab has no project file, prompt for save as\n        if (currentTab.ProjectFile is not { } projectFile)\n        {\n            await MenuSaveAs();\n            return;\n        }\n\n        // Otherwise, save to the current project file\n        var document = InferenceProjectDocument.FromLoadable(currentTab);\n\n        // Save to file\n        try\n        {\n            await using var stream = projectFile.Info.OpenWrite();\n            stream.SetLength(0); // Overwrite fully\n\n            await JsonSerializer.SerializeAsync(\n                stream,\n                document,\n                new JsonSerializerOptions { WriteIndented = true }\n            );\n        }\n        catch (Exception e)\n        {\n            notificationService.ShowPersistent(\n                \"Could not save to file\",\n                $\"[{e.GetType().Name}] {e.Message}\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        notificationService.Show(\"Saved\", $\"Saved project to {projectFile.Name}\", NotificationType.Success);\n    }\n\n    [RelayCommand]\n    private void GoForwardTabWithLooping()\n    {\n        if (SelectedTabIndex == Tabs.Count - 1)\n        {\n            SelectedTabIndex = 0;\n        }\n        else\n        {\n            SelectedTabIndex++;\n        }\n    }\n\n    [RelayCommand]\n    private void GoBackwardsTabWithLooping()\n    {\n        if (SelectedTabIndex == 0)\n        {\n            SelectedTabIndex = Tabs.Count - 1;\n        }\n        else\n        {\n            SelectedTabIndex--;\n        }\n    }\n\n    private async Task AddTabFromFile(FilePath file)\n    {\n        await using var stream = file.Info.OpenRead();\n\n        var document = await JsonSerializer.DeserializeAsync<InferenceProjectDocument>(stream);\n        if (document is null)\n        {\n            throw new ApplicationException(\"MenuOpenProject: Deserialize project file returned null\");\n        }\n\n        if (document.State is null)\n        {\n            throw new ApplicationException(\"Project file does not have 'State' key\");\n        }\n\n        document.VerifyVersion();\n\n        if (document.ProjectType.ToViewModelType() is not { } vmType)\n        {\n            throw new InvalidOperationException($\"Unsupported project type: {document.ProjectType}\");\n        }\n\n        // Create a new scope for this tab\n        var scope = vmFactory.CreateScope();\n        scopes.Add(scope);\n\n        // Get the view model using the scope's service provider\n        var vm =\n            scope.ServiceManager.Get(vmType) as InferenceTabViewModelBase\n            ?? throw new NullReferenceException($\"Could not create view model of type {vmType}\");\n\n        vm.LoadStateFromJsonObject(document.State);\n        vm.ProjectFile = file;\n\n        Tabs.Add(vm);\n\n        SelectedTab = vm;\n\n        await SyncTabStatesWithDatabase();\n    }\n\n    private async Task AddTabFromFileAsync(LocalImageFile imageFile, InferenceProjectType projectType)\n    {\n        // Create a new scope for this tab\n        var scope = vmFactory.CreateScope();\n        scopes.Add(scope);\n\n        // Get the appropriate view model from the scope\n        InferenceTabViewModelBase vm = projectType switch\n        {\n            InferenceProjectType.TextToImage => scope.ServiceManager.Get<InferenceTextToImageViewModel>(),\n            InferenceProjectType.ImageToImage => scope.ServiceManager.Get<InferenceImageToImageViewModel>(),\n            InferenceProjectType.ImageToVideo => scope.ServiceManager.Get<InferenceImageToVideoViewModel>(),\n            InferenceProjectType.Upscale => scope.ServiceManager.Get<InferenceImageUpscaleViewModel>(),\n            InferenceProjectType.FluxTextToImage\n                => scope.ServiceManager.Get<InferenceFluxTextToImageViewModel>(),\n        };\n\n        switch (vm)\n        {\n            case InferenceImageToImageViewModel imgToImgVm:\n                imgToImgVm.SelectImageCardViewModel.ImageSource = new ImageSource(imageFile.AbsolutePath);\n                vm.LoadImageMetadata(imageFile.AbsolutePath);\n                break;\n            case InferenceTextToImageViewModel _:\n                vm.LoadImageMetadata(imageFile.AbsolutePath);\n                break;\n            case InferenceImageUpscaleViewModel upscaleVm:\n                upscaleVm.IsUpscaleEnabled = true;\n                upscaleVm.SelectImageCardViewModel.ImageSource = new ImageSource(imageFile.AbsolutePath);\n                break;\n            case InferenceImageToVideoViewModel imgToVidVm:\n                imgToVidVm.SelectImageCardViewModel.ImageSource = new ImageSource(imageFile.AbsolutePath);\n                break;\n            case InferenceFluxTextToImageViewModel _:\n                vm.LoadImageMetadata(imageFile.AbsolutePath);\n                break;\n        }\n\n        Tabs.Add(vm);\n        SelectedTab = vm;\n\n        await SyncTabStatesWithDatabase();\n    }\n\n    /// <summary>\n    /// Menu \"Open Project\" command.\n    /// </summary>\n    [RelayCommand(FlowExceptionsToTaskScheduler = true)]\n    private async Task MenuOpenProject()\n    {\n        // Prompt for open file dialog\n        var provider = App.StorageProvider;\n\n        var projectDir = new DirectoryPath(settingsManager.LibraryDir, \"Projects\");\n        projectDir.Create();\n        var startDir = await provider.TryGetFolderFromPathAsync(projectDir);\n\n        var results = await provider.OpenFilePickerAsync(\n            new FilePickerOpenOptions\n            {\n                Title = \"Open Project File\",\n                FileTypeFilter = new FilePickerFileType[]\n                {\n                    new(\"StabilityMatrix Project\")\n                    {\n                        Patterns = new[] { \"*.smproj\" },\n                        MimeTypes = new[] { \"application/json\" },\n                    }\n                },\n                SuggestedStartLocation = startDir,\n            }\n        );\n\n        if (results.Count == 0)\n        {\n            Logger.Trace(\"MenuOpenProject: No files selected\");\n            return;\n        }\n\n        // Load from file\n        var file = results[0].TryGetLocalPath()!;\n\n        try\n        {\n            await AddTabFromFile(file);\n        }\n        catch (NotSupportedException e)\n        {\n            notificationService.ShowPersistent(\n                $\"Unsupported Project Version\",\n                $\"[{Path.GetFileName(file)}] {e.Message}\",\n                NotificationType.Error\n            );\n        }\n        catch (Exception e)\n        {\n            notificationService.ShowPersistent(\n                $\"Failed to load Project\",\n                $\"[{Path.GetFileName(file)}] {e.Message}\",\n                NotificationType.Error\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs",
    "content": "﻿using System.Reactive.Linq;\nusing System.Text.Json;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Platform.Storage;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Api.OpenArt;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(InstalledWorkflowsPage))]\n[RegisterSingleton<InstalledWorkflowsViewModel>]\npublic partial class InstalledWorkflowsViewModel(\n    ISettingsManager settingsManager,\n    INotificationService notificationService\n) : TabViewModelBase\n{\n    public override string Header => Resources.TabLabel_InstalledWorkflows;\n\n    private readonly SourceCache<OpenArtMetadata, string> workflowsCache =\n        new(x => x.Workflow?.Id ?? Guid.NewGuid().ToString());\n\n    [ObservableProperty]\n    private IObservableCollection<OpenArtMetadata> displayedWorkflows =\n        new ObservableCollectionExtended<OpenArtMetadata>();\n\n    [ObservableProperty]\n    private string searchQuery = string.Empty;\n\n    protected override async Task OnInitialLoadedAsync()\n    {\n        await base.OnInitialLoadedAsync();\n\n        var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchQuery)\n            .Throttle(TimeSpan.FromMilliseconds(100))\n            .DistinctUntilChanged()\n            .Select(_ => (Func<OpenArtMetadata, bool>)FilterWorkflows);\n\n        AddDisposable(\n            workflowsCache\n                .Connect()\n                .DeferUntilLoaded()\n                .Filter(searchPredicate)\n                .SortBy(x => x.Index)\n                .Bind(DisplayedWorkflows)\n                .ObserveOn(SynchronizationContext.Current!)\n                .Subscribe()\n        );\n\n        if (Design.IsDesignMode)\n            return;\n\n        await LoadInstalledWorkflowsAsync();\n        EventManager.Instance.WorkflowInstalled += OnWorkflowInstalled;\n    }\n\n    [RelayCommand]\n    private async Task LoadInstalledWorkflowsAsync()\n    {\n        workflowsCache.Clear();\n\n        if (!Directory.Exists(settingsManager.WorkflowDirectory))\n        {\n            Directory.CreateDirectory(settingsManager.WorkflowDirectory);\n        }\n\n        var count = 0;\n\n        foreach (\n            var workflowPath in Directory.EnumerateFiles(\n                settingsManager.WorkflowDirectory,\n                \"*.json\",\n                EnumerationOptionConstants.AllDirectories\n            )\n        )\n        {\n            try\n            {\n                var json = await File.ReadAllTextAsync(workflowPath);\n                var metadata = JsonSerializer.Deserialize<OpenArtMetadata>(json);\n\n                if (metadata?.Workflow == null)\n                {\n                    metadata = new OpenArtMetadata\n                    {\n                        Workflow = new OpenArtSearchResult\n                        {\n                            Id = Guid.NewGuid().ToString(),\n                            Name = Path.GetFileNameWithoutExtension(workflowPath),\n                        },\n                        Index = count++,\n                    };\n                }\n\n                metadata.FilePath = [await App.StorageProvider.TryGetFileFromPathAsync(workflowPath)];\n                workflowsCache.AddOrUpdate(metadata);\n            }\n            catch (Exception e)\n            {\n                Console.WriteLine(e);\n            }\n        }\n    }\n\n    [RelayCommand]\n    private async Task OpenInExplorer(OpenArtMetadata metadata)\n    {\n        if (metadata.FilePath == null)\n            return;\n\n        var path = metadata.FilePath.FirstOrDefault()?.Path.ToString();\n        if (string.IsNullOrWhiteSpace(path))\n            return;\n\n        await ProcessRunner.OpenFileBrowser(path);\n    }\n\n    [RelayCommand]\n    private void OpenOnOpenArt(OpenArtMetadata metadata)\n    {\n        if (metadata.Workflow == null)\n            return;\n\n        ProcessRunner.OpenUrl($\"https://openart.ai/workflows/{metadata.Workflow.Id}\");\n    }\n\n    [RelayCommand]\n    private async Task DeleteAsync(OpenArtMetadata metadata)\n    {\n        var confirmationDialog = new BetterContentDialog\n        {\n            Title = Resources.Label_AreYouSure,\n            Content = Resources.Label_ActionCannotBeUndone,\n            PrimaryButtonText = Resources.Action_Delete,\n            SecondaryButtonText = Resources.Action_Cancel,\n            DefaultButton = ContentDialogButton.Primary,\n            IsSecondaryButtonEnabled = true,\n        };\n        var dialogResult = await confirmationDialog.ShowAsync();\n        if (dialogResult != ContentDialogResult.Primary)\n            return;\n\n        await using var delay = new MinimumDelay(200, 500);\n\n        var path = metadata?.FilePath?.FirstOrDefault()?.Path.ToString().Replace(\"file:///\", \"\");\n        if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))\n        {\n            await notificationService.TryAsync(\n                Task.Run(() => File.Delete(path)),\n                message: \"Error deleting workflow\"\n            );\n\n            var id = metadata?.Workflow?.Id;\n            if (!string.IsNullOrWhiteSpace(id))\n            {\n                workflowsCache.Remove(id);\n            }\n        }\n\n        notificationService.Show(\n            Resources.Label_WorkflowDeleted,\n            string.Format(Resources.Label_WorkflowDeletedSuccessfully, metadata?.Workflow?.Name)\n        );\n    }\n\n    private bool FilterWorkflows(OpenArtMetadata metadata)\n    {\n        if (string.IsNullOrWhiteSpace(SearchQuery))\n            return true;\n\n        if (metadata.HasMetadata)\n        {\n            return metadata.Workflow.Creator.Name.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase)\n                || metadata.Workflow.Name.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase);\n        }\n\n        return metadata.Workflow?.Name.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) ?? false;\n    }\n\n    private void OnWorkflowInstalled(object? sender, EventArgs e)\n    {\n        LoadInstalledWorkflowsAsync().SafeFireAndForget();\n    }\n\n    protected override void Dispose(bool disposing)\n    {\n        if (disposing)\n        {\n            EventManager.Instance.WorkflowInstalled -= OnWorkflowInstalled;\n        }\n\n        base.Dispose(disposing);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/LaunchPageViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing System.Text.RegularExpressions;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing FluentIcons.Common;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(LaunchPageView))]\npublic partial class LaunchPageViewModel : PageViewModelBase, IDisposable, IAsyncDisposable\n{\n    private readonly ILogger<LaunchPageViewModel> logger;\n    private readonly ISettingsManager settingsManager;\n    private readonly IPyRunner pyRunner;\n    private readonly INotificationService notificationService;\n    private readonly ISharedFolders sharedFolders;\n    private readonly IServiceManager<ViewModelBase> dialogFactory;\n    protected readonly IPackageFactory PackageFactory;\n\n    // Regex to match if input contains a yes/no prompt,\n    // i.e \"Y/n\", \"yes/no\". Case insensitive.\n    // Separated by / or |.\n    [GeneratedRegex(@\"y(/|\\|)n|yes(/|\\|)no\", RegexOptions.IgnoreCase)]\n    private static partial Regex InputYesNoRegex();\n\n    public override string Title => \"Launch\";\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.Rocket, IconVariant = IconVariant.Filled };\n\n    public ConsoleViewModel Console { get; } = new();\n\n    [ObservableProperty]\n    private bool launchButtonVisibility;\n\n    [ObservableProperty]\n    private bool stopButtonVisibility;\n\n    [ObservableProperty]\n    private bool isLaunchTeachingTipsOpen;\n\n    [ObservableProperty]\n    private bool showWebUiButton;\n\n    [\n        ObservableProperty,\n        NotifyPropertyChangedFor(nameof(SelectedBasePackage), nameof(SelectedPackageExtraCommands))\n    ]\n    private InstalledPackage? selectedPackage;\n\n    [ObservableProperty]\n    private ObservableCollection<InstalledPackage> installedPackages = new();\n\n    [ObservableProperty]\n    private PackagePair? runningPackage;\n\n    [ObservableProperty]\n    private bool autoScrollToEnd = true;\n\n    public virtual BasePackage? SelectedBasePackage =>\n        PackageFactory.FindPackageByName(SelectedPackage?.PackageName);\n\n    public IReadOnlyDictionary<string, string> SelectedPackageExtraCommands =>\n        SelectedBasePackage?.ExtraLaunchCommands ?? new Dictionary<string, string>();\n\n    // private bool clearingPackages;\n    private string webUiUrl = string.Empty;\n\n    // Input info-bars\n    [ObservableProperty]\n    private bool showManualInputPrompt;\n\n    [ObservableProperty]\n    private bool showConfirmInputPrompt;\n\n    public LaunchPageViewModel(\n        ILogger<LaunchPageViewModel> logger,\n        ISettingsManager settingsManager,\n        IPackageFactory packageFactory,\n        IPyRunner pyRunner,\n        INotificationService notificationService,\n        ISharedFolders sharedFolders,\n        IServiceManager<ViewModelBase> dialogFactory\n    )\n    {\n        this.logger = logger;\n        this.settingsManager = settingsManager;\n        this.PackageFactory = packageFactory;\n        this.pyRunner = pyRunner;\n        this.notificationService = notificationService;\n        this.sharedFolders = sharedFolders;\n        this.dialogFactory = dialogFactory;\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.SelectedPackage,\n            settings => settings.ActiveInstalledPackage\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.AutoScrollToEnd,\n            settings => settings.AutoScrollLaunchConsoleToEnd\n        );\n\n        EventManager.Instance.PackageLaunchRequested += OnPackageLaunchRequested;\n        EventManager.Instance.OneClickInstallFinished += OnOneClickInstallFinished;\n        EventManager.Instance.InstalledPackagesChanged += OnInstalledPackagesChanged;\n        EventManager.Instance.TeachingTooltipNeeded += OnTeachingTooltipNeeded;\n        // Handler for console input\n        Console.ApcInput += (_, message) =>\n        {\n            if (InputYesNoRegex().IsMatch(message.Data))\n            {\n                ShowConfirmInputPrompt = true;\n            }\n            else\n            {\n                ShowManualInputPrompt = true;\n            }\n        };\n    }\n\n    private void OnTeachingTooltipNeeded(object? sender, EventArgs e) => IsLaunchTeachingTipsOpen = true;\n\n    private void OnInstalledPackagesChanged(object? sender, EventArgs e) => OnLoaded();\n\n    private void OnPackageLaunchRequested(object? sender, Guid e)\n    {\n        if (RunningPackage is not null)\n        {\n            notificationService.Show(\n                \"A package is already running\",\n                \"Please stop the current package before launching another.\",\n                NotificationType.Error\n            );\n            return;\n        }\n\n        SelectedPackage = InstalledPackages.FirstOrDefault(x => x.Id == e);\n        LaunchAsync().SafeFireAndForget();\n    }\n\n    partial void OnAutoScrollToEndChanged(bool value)\n    {\n        if (value)\n        {\n            EventManager.Instance.OnScrollToBottomRequested();\n        }\n    }\n\n    protected override Task OnInitialLoadedAsync()\n    {\n        if (string.IsNullOrWhiteSpace(Program.Args.LaunchPackageName))\n            return base.OnInitialLoadedAsync();\n\n        var package = InstalledPackages.FirstOrDefault(x => x.DisplayName == Program.Args.LaunchPackageName);\n        if (package is not null)\n        {\n            SelectedPackage = package;\n            return LaunchAsync();\n        }\n\n        package = InstalledPackages.FirstOrDefault(x => x.Id.ToString() == Program.Args.LaunchPackageName);\n        if (package is null)\n            return base.OnInitialLoadedAsync();\n\n        SelectedPackage = package;\n        return LaunchAsync();\n    }\n\n    public override void OnLoaded()\n    {\n        // Load installed packages\n        InstalledPackages = new ObservableCollection<InstalledPackage>(\n            settingsManager.Settings.InstalledPackages\n        );\n\n        // Ensure active package either exists or is null\n        if (SelectedPackage?.Id is { } id && InstalledPackages.All(x => x.Id != id))\n        {\n            settingsManager.Transaction(\n                s =>\n                {\n                    s.UpdateActiveInstalledPackage();\n                },\n                ignoreMissingLibraryDir: true\n            );\n        }\n\n        // Load active package\n        SelectedPackage = settingsManager.Settings.ActiveInstalledPackage;\n        AutoScrollToEnd = settingsManager.Settings.AutoScrollLaunchConsoleToEnd;\n\n        base.OnLoaded();\n    }\n\n    [RelayCommand]\n    public async Task LaunchAsync(string? command = null)\n    {\n        await notificationService.TryAsync(LaunchImpl(command));\n    }\n\n    protected virtual async Task LaunchImpl(string? command)\n    {\n        IsLaunchTeachingTipsOpen = false;\n\n        var activeInstall = SelectedPackage;\n        if (activeInstall == null)\n        {\n            // No selected package: error notification\n            notificationService.Show(\n                new Notification(\n                    message: \"You must install and select a package before launching\",\n                    title: \"No package selected\",\n                    type: NotificationType.Error\n                )\n            );\n            return;\n        }\n\n        var activeInstallName = activeInstall.PackageName;\n        var basePackage = string.IsNullOrWhiteSpace(activeInstallName)\n            ? null\n            : PackageFactory.FindPackageByName(activeInstallName);\n\n        if (basePackage == null)\n        {\n            logger.LogWarning(\n                \"During launch, package name '{PackageName}' did not match a definition\",\n                activeInstallName\n            );\n\n            notificationService.Show(\n                new Notification(\n                    \"Package name invalid\",\n                    \"Install package name did not match a definition. Please reinstall and let us know about this issue.\",\n                    NotificationType.Error\n                )\n            );\n            return;\n        }\n\n        // If this is the first launch (LaunchArgs is null),\n        // load and save a launch options dialog vm\n        // so that dynamic initial values are saved.\n        if (activeInstall.LaunchArgs == null)\n        {\n            var definitions = basePackage.LaunchOptions;\n            // Create config cards and save them\n            var cards = LaunchOptionCard\n                .FromDefinitions(definitions, Array.Empty<LaunchOption>())\n                .ToImmutableArray();\n\n            var args = cards.SelectMany(c => c.Options).ToList();\n\n            logger.LogDebug(\n                \"Setting initial launch args: {Args}\",\n                string.Join(\", \", args.Select(o => o.ToArgString()?.ToRepr()))\n            );\n\n            settingsManager.SaveLaunchArgs(activeInstall.Id, args);\n        }\n\n        if (basePackage is not StableSwarm)\n        {\n            await pyRunner.Initialize();\n        }\n\n        // Get path from package\n        var packagePath = new DirectoryPath(settingsManager.LibraryDir, activeInstall.LibraryPath!);\n\n        if (basePackage is not StableSwarm)\n        {\n            // Unpack sitecustomize.py to venv\n            await UnpackSiteCustomize(packagePath.JoinDir(\"venv\"));\n        }\n\n        basePackage.Exited += OnProcessExited;\n        basePackage.StartupComplete += RunningPackageOnStartupComplete;\n\n        // Clear console and start update processing\n        await Console.StopUpdatesAsync();\n        await Console.Clear();\n        Console.StartUpdates();\n\n        // Update shared folder links (in case library paths changed)\n        await basePackage.UpdateModelFolders(\n            packagePath,\n            activeInstall.PreferredSharedFolderMethod ?? basePackage.RecommendedSharedFolderMethod\n        );\n\n        // Load user launch args from settings and convert to string\n        var userArgs = activeInstall.LaunchArgs ?? [];\n        var userArgsString = string.Join(\" \", userArgs.Select(opt => opt.ToArgString()));\n\n        // Join with extras, if any\n        userArgsString = string.Join(\" \", userArgsString, basePackage.ExtraLaunchArguments);\n\n        // Use input command if provided, otherwise use package launch command\n        command ??= basePackage.LaunchCommand;\n\n        // await basePackage.RunPackage(packagePath, command, userArgsString, OnProcessOutputReceived);\n        RunningPackage = new PackagePair(activeInstall, basePackage);\n\n        EventManager.Instance.OnRunningPackageStatusChanged(RunningPackage);\n    }\n\n    // Unpacks sitecustomize.py to the target venv\n    private static async Task UnpackSiteCustomize(DirectoryPath venvPath)\n    {\n        var sitePackages = venvPath.JoinDir(PyVenvRunner.RelativeSitePackagesPath);\n        var file = sitePackages.JoinFile(\"sitecustomize.py\");\n        file.Directory?.Create();\n        await Assets.PyScriptSiteCustomize.ExtractTo(file, true);\n    }\n\n    [RelayCommand]\n    private async Task Config()\n    {\n        var activeInstall = SelectedPackage;\n        var name = activeInstall?.PackageName;\n        if (name == null || activeInstall == null)\n        {\n            logger.LogWarning($\"Selected package is null\");\n            return;\n        }\n\n        var package = PackageFactory.FindPackageByName(name);\n        if (package == null)\n        {\n            logger.LogWarning(\"Package {Name} not found\", name);\n            return;\n        }\n\n        // Check if package supports IArgParsable\n        // Use dynamic parsed args over static\n        /*if (package is IArgParsable parsable)\n        {\n            var rootPath = activeInstall.FullPath!;\n            var moduleName = parsable.RelativeArgsDefinitionScriptPath;\n            var parser = new ArgParser(pyRunner, rootPath, moduleName);\n            definitions = await parser.GetArgsAsync();\n        }*/\n\n        // Open a config page\n        var viewModel = dialogFactory.Get<LaunchOptionsViewModel>();\n        viewModel.Cards = LaunchOptionCard\n            .FromDefinitions(package.LaunchOptions, activeInstall.LaunchArgs ?? [])\n            .ToImmutableArray();\n\n        logger.LogDebug(\"Launching config dialog with cards: {CardsCount}\", viewModel.Cards.Count);\n\n        var dialog = new BetterContentDialog\n        {\n            ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled,\n            IsPrimaryButtonEnabled = true,\n            PrimaryButtonText = Resources.Action_Save,\n            CloseButtonText = Resources.Action_Cancel,\n            FullSizeDesired = true,\n            DefaultButton = ContentDialogButton.Primary,\n            ContentMargin = new Thickness(32, 16),\n            Padding = new Thickness(0, 16),\n            Content = new LaunchOptionsDialog { DataContext = viewModel },\n        };\n\n        var result = await dialog.ShowAsync();\n\n        if (result == ContentDialogResult.Primary)\n        {\n            // Save config\n            var args = viewModel.AsLaunchArgs();\n            settingsManager.SaveLaunchArgs(activeInstall.Id, args);\n        }\n    }\n\n    // Send user input to running package\n    public async Task SendInput(string input)\n    {\n        if (RunningPackage?.BasePackage is BaseGitPackage gitPackage)\n        {\n            var venv = gitPackage.VenvRunner;\n            var process = venv?.Process;\n            if (process is not null)\n            {\n                await process.StandardInput.WriteLineAsync(input);\n            }\n            else\n            {\n                logger.LogWarning(\"Attempted to write input but Process is null\");\n            }\n        }\n    }\n\n    [RelayCommand]\n    private async Task SendConfirmInput(bool value)\n    {\n        // This must be on the UI thread\n        Dispatcher.UIThread.CheckAccess();\n        // Also send input to our own console\n        if (value)\n        {\n            Console.Post(\"y\\n\");\n            await SendInput(\"y\\n\");\n        }\n        else\n        {\n            Console.Post(\"n\\n\");\n            await SendInput(\"n\\n\");\n        }\n\n        ShowConfirmInputPrompt = false;\n    }\n\n    [RelayCommand]\n    private async Task SendManualInput(string input)\n    {\n        // Also send input to our own console\n        Console.PostLine(input);\n        await SendInput(input);\n    }\n\n    public virtual async Task Stop()\n    {\n        if (RunningPackage is null)\n            return;\n        await RunningPackage.BasePackage.WaitForShutdown();\n        RunningPackage = null;\n        ShowWebUiButton = false;\n\n        Console.PostLine($\"{Environment.NewLine}Stopped process at {DateTimeOffset.Now}\");\n    }\n\n    public void OpenWebUi()\n    {\n        if (string.IsNullOrEmpty(webUiUrl))\n            return;\n\n        notificationService.TryAsync(\n            Task.Run(() => ProcessRunner.OpenUrl(webUiUrl)),\n            \"Failed to open URL\",\n            $\"{webUiUrl}\"\n        );\n    }\n\n    private void OnProcessExited(object? sender, int exitCode)\n    {\n        EventManager.Instance.OnRunningPackageStatusChanged(null);\n        Dispatcher\n            .UIThread.InvokeAsync(async () =>\n            {\n                logger.LogTrace(\"Process exited ({Code}) at {Time:g}\", exitCode, DateTimeOffset.Now);\n\n                // Need to wait for streams to finish before detaching handlers\n                if (sender is BaseGitPackage { VenvRunner: not null } package)\n                {\n                    var process = package.VenvRunner.Process;\n                    if (process is not null)\n                    {\n                        // Max 5 seconds\n                        var ct = new CancellationTokenSource(5000).Token;\n                        try\n                        {\n                            await process.WaitUntilOutputEOF(ct);\n                        }\n                        catch (OperationCanceledException e)\n                        {\n                            logger.LogWarning(\"Waiting for process EOF timed out: {Message}\", e.Message);\n                        }\n                    }\n                }\n\n                // Detach handlers\n                if (sender is BasePackage basePackage)\n                {\n                    basePackage.Exited -= OnProcessExited;\n                    basePackage.StartupComplete -= RunningPackageOnStartupComplete;\n                }\n                RunningPackage = null;\n                ShowWebUiButton = false;\n\n                await Console.StopUpdatesAsync();\n\n                // Need to reset cursor in case its in some weird position\n                // from progress bars\n                await Console.ResetWriteCursor();\n                Console.PostLine($\"{Environment.NewLine}Process finished with exit code {exitCode}\");\n            })\n            .SafeFireAndForget();\n    }\n\n    // Callback for processes\n    private void OnProcessOutputReceived(ProcessOutput output)\n    {\n        Console.Post(output);\n\n        if (AutoScrollToEnd)\n        {\n            EventManager.Instance.OnScrollToBottomRequested();\n        }\n    }\n\n    private void OnOneClickInstallFinished(object? sender, bool e)\n    {\n        OnLoaded();\n    }\n\n    private void RunningPackageOnStartupComplete(object? sender, string e)\n    {\n        webUiUrl = e.Replace(\"0.0.0.0\", \"127.0.0.1\");\n        ShowWebUiButton = !string.IsNullOrWhiteSpace(webUiUrl);\n    }\n\n    public void OnMainWindowClosing(WindowClosingEventArgs e)\n    {\n        if (RunningPackage != null)\n        {\n            // Show confirmation\n            if (e.CloseReason is WindowCloseReason.WindowClosing)\n            {\n                e.Cancel = true;\n\n                var dialog = CreateExitConfirmDialog();\n                Dispatcher\n                    .UIThread.InvokeAsync(async () =>\n                    {\n                        if (\n                            (TaskDialogStandardResult)await dialog.ShowAsync(true)\n                            == TaskDialogStandardResult.Yes\n                        )\n                        {\n                            App.Services.GetRequiredService<MainWindow>().Hide();\n                            App.Shutdown();\n                        }\n                    })\n                    .SafeFireAndForget();\n            }\n        }\n    }\n\n    private static TaskDialog CreateExitConfirmDialog()\n    {\n        var dialog = DialogHelper.CreateTaskDialog(\n            \"Confirm Exit\",\n            \"Are you sure you want to exit? This will also close the currently running package.\"\n        );\n\n        dialog.ShowProgressBar = false;\n        dialog.FooterVisibility = TaskDialogFooterVisibility.Never;\n\n        dialog.Buttons = new List<TaskDialogButton>\n        {\n            new(\"Exit\", TaskDialogStandardResult.Yes),\n            TaskDialogButton.CancelButton,\n        };\n        dialog.Buttons[0].IsDefault = true;\n\n        return dialog;\n    }\n\n    protected override void Dispose(bool disposing)\n    {\n        if (disposing)\n        {\n            RunningPackage?.BasePackage.Shutdown();\n            RunningPackage = null;\n\n            Console.Dispose();\n        }\n\n        base.Dispose(disposing);\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (RunningPackage is not null)\n        {\n            await RunningPackage.BasePackage.WaitForShutdown();\n            RunningPackage = null;\n        }\n        await Console.DisposeAsync();\n\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Runtime.InteropServices;\nusing AsyncAwaitBestPractices;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Media.Animation;\nusing NLog;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.ViewModels.Progress;\nusing StabilityMatrix.Avalonia.ViewModels.Settings;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Analytics;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Lykos.Analytics;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Models.Update;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Core.Updater;\nusing TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(MainWindow))]\npublic partial class MainWindowViewModel : ViewModelBase\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private readonly ISettingsManager settingsManager;\n    private readonly IServiceManager<ViewModelBase> dialogFactory;\n    private readonly ITrackedDownloadService trackedDownloadService;\n    private readonly IDiscordRichPresenceService discordRichPresenceService;\n    private readonly IModelIndexService modelIndexService;\n    private readonly Lazy<IModelDownloadLinkHandler> modelDownloadLinkHandler;\n    private readonly INotificationService notificationService;\n    private readonly IAnalyticsHelper analyticsHelper;\n    private readonly IUpdateHelper updateHelper;\n    private readonly ISecretsManager secretsManager;\n    private readonly INavigationService<MainWindowViewModel> navigationService;\n    private readonly INavigationService<SettingsViewModel> settingsNavService;\n    public string Greeting => \"Welcome to Avalonia!\";\n\n    [ObservableProperty]\n    private PageViewModelBase? currentPage;\n\n    [ObservableProperty]\n    private object? selectedCategory;\n\n    [ObservableProperty]\n    public partial bool IsPaneOpen { get; set; }\n\n    [ObservableProperty]\n    private List<PageViewModelBase> pages = new();\n\n    [ObservableProperty]\n    private List<PageViewModelBase> footerPages = new();\n\n    public ProgressManagerViewModel ProgressManagerViewModel { get; init; }\n    public UpdateViewModel UpdateViewModel { get; init; }\n\n    public double PaneWidth =>\n        (Compat.IsWindows ? 0 : 20)\n        + Cultures.Current switch\n        {\n            { Name: \"it-IT\" } => 250,\n            { Name: \"fr-FR\" } => 250,\n            { Name: \"es\" } => 250,\n            { Name: \"ru-RU\" } => 250,\n            { Name: \"tr-TR\" } => 235,\n            { Name: \"de\" } => 250,\n            { Name: \"pt-PT\" } => 300,\n            { Name: \"pt-BR\" } => 260,\n            { Name: \"ko-KR\" } => 235,\n            { Name: \"cs-CZ\" } => 250,\n            _ => 200,\n        };\n\n    public MainWindowViewModel(\n        ISettingsManager settingsManager,\n        IDiscordRichPresenceService discordRichPresenceService,\n        IServiceManager<ViewModelBase> dialogFactory,\n        ITrackedDownloadService trackedDownloadService,\n        IModelIndexService modelIndexService,\n        Lazy<IModelDownloadLinkHandler> modelDownloadLinkHandler,\n        INotificationService notificationService,\n        IAnalyticsHelper analyticsHelper,\n        IUpdateHelper updateHelper,\n        ISecretsManager secretsManager,\n        INavigationService<MainWindowViewModel> navigationService,\n        INavigationService<SettingsViewModel> settingsNavService\n    )\n    {\n        this.settingsManager = settingsManager;\n        this.dialogFactory = dialogFactory;\n        this.discordRichPresenceService = discordRichPresenceService;\n        this.trackedDownloadService = trackedDownloadService;\n        this.modelIndexService = modelIndexService;\n        this.modelDownloadLinkHandler = modelDownloadLinkHandler;\n        this.notificationService = notificationService;\n        this.analyticsHelper = analyticsHelper;\n        this.updateHelper = updateHelper;\n        this.secretsManager = secretsManager;\n        this.navigationService = navigationService;\n        this.settingsNavService = settingsNavService;\n        ProgressManagerViewModel = dialogFactory.Get<ProgressManagerViewModel>();\n        UpdateViewModel = dialogFactory.Get<UpdateViewModel>();\n    }\n\n    public override void OnLoaded()\n    {\n        base.OnLoaded();\n\n        // Set only if null, since this may be called again when content dialogs open\n        CurrentPage ??= Pages.FirstOrDefault();\n        SelectedCategory ??= Pages.FirstOrDefault();\n    }\n\n    protected override async Task OnInitialLoadedAsync()\n    {\n        await base.OnLoadedAsync();\n\n        // Skip if design mode\n        if (Design.IsDesignMode)\n            return;\n\n        if (!await EnsureDataDirectory())\n        {\n            // False if user exited dialog, shutdown app\n            App.Shutdown();\n            return;\n        }\n\n        Task.Run(() => SharedFolders.SetupSharedModelFolders(settingsManager.ModelsDirectory))\n            .SafeFireAndForget(ex =>\n            {\n                Logger.Error(ex, \"Error setting up shared model folders\");\n            });\n\n        try\n        {\n            await modelDownloadLinkHandler.Value.StartListening();\n        }\n        catch (IOException)\n        {\n            var dialog = new BetterContentDialog\n            {\n                Title = Resources.Label_StabilityMatrixAlreadyRunning,\n                Content = Resources.Label_AnotherInstanceAlreadyRunning,\n                IsPrimaryButtonEnabled = true,\n                PrimaryButtonText = Resources.Action_Close,\n                DefaultButton = ContentDialogButton.Primary,\n            };\n            await dialog.ShowAsync();\n            App.Shutdown();\n            return;\n        }\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.IsPaneOpen,\n            settings => settings.IsMainWindowSidebarOpen,\n            true\n        );\n\n        // Initialize Discord Rich Presence (this needs LibraryDir so is set here)\n        discordRichPresenceService.UpdateState();\n\n        // Ensure GPU compute capability is populated before other components check it\n        // This must run early and be awaited to prevent race conditions with package initialization\n        await Task.Run(AddComputeCapabilityIfNecessary);\n\n        // Load in-progress downloads\n        ProgressManagerViewModel.AddDownloads(trackedDownloadService.Downloads);\n\n        // Index checkpoints if we dont have\n        // Task.Run(() => settingsManager.IndexCheckpoints()).SafeFireAndForget();\n\n        // Disable preload for now, might be causing https://github.com/LykosAI/StabilityMatrix/issues/249\n        /*if (!App.IsHeadlessMode)\n        {\n            PreloadPages();\n        }*/\n\n        Program.StartupTimer.Stop();\n        var startupTime = CodeTimer.FormatTime(Program.StartupTimer.Elapsed);\n        Logger.Info($\"App started ({startupTime})\");\n\n        // Show analytics notice if not seen\n        if (\n            settingsManager.Settings.Analytics.LastSeenConsentVersion is null\n            || settingsManager.Settings.Analytics.LastSeenConsentAccepted is null\n        )\n        {\n            var vm = dialogFactory.Get<AnalyticsOptInViewModel>();\n            var result = await vm.GetDialog().ShowAsync();\n\n            settingsManager.Transaction(s =>\n            {\n                s.Analytics.LastSeenConsentVersion = Compat.AppVersion;\n                s.Analytics.LastSeenConsentAccepted = result == ContentDialogResult.Secondary;\n            });\n\n            if (result == ContentDialogResult.Secondary)\n            {\n                settingsManager.Transaction(s => s.Analytics.IsUsageDataEnabled, true);\n            }\n            else if (result is ContentDialogResult.Primary)\n            {\n                settingsManager.Transaction(s => s.Analytics.IsUsageDataEnabled, false);\n            }\n        }\n\n        if (Program.Args.DebugOneClickInstall || settingsManager.Settings.InstalledPackages.Count == 0)\n        {\n            var viewModel = dialogFactory.Get<NewOneClickInstallViewModel>();\n            var dialog = new BetterContentDialog\n            {\n                IsPrimaryButtonEnabled = false,\n                IsSecondaryButtonEnabled = false,\n                IsFooterVisible = false,\n                FullSizeDesired = true,\n                MinDialogHeight = 775,\n                Content = new NewOneClickInstallDialog { DataContext = viewModel },\n            };\n\n            var firstDialogResult = await dialog.ShowAsync(App.TopLevel);\n\n            if (firstDialogResult != ContentDialogResult.Primary)\n            {\n                analyticsHelper.TrackFirstTimeInstallAsync(null, null, true).SafeFireAndForget();\n                return;\n            }\n\n            var recommendedModelsViewModel = dialogFactory.Get<RecommendedModelsViewModel>();\n            dialog = new BetterContentDialog\n            {\n                IsPrimaryButtonEnabled = true,\n                FullSizeDesired = true,\n                MinDialogHeight = 900,\n                PrimaryButtonText = Resources.Action_Download,\n                CloseButtonText = Resources.Action_Close,\n                DefaultButton = ContentDialogButton.Primary,\n                PrimaryButtonCommand = recommendedModelsViewModel.DoImportCommand,\n                Content = new RecommendedModelsDialog { DataContext = recommendedModelsViewModel },\n            };\n\n            await dialog.ShowAsync(App.TopLevel);\n\n            EventManager.Instance.OnRecommendedModelsDialogClosed();\n            EventManager.Instance.OnDownloadsTeachingTipRequested();\n\n            var installedPackageNameMaybe =\n                settingsManager.PackageInstallsInProgress.FirstOrDefault()\n                ?? settingsManager.Settings.InstalledPackages.FirstOrDefault()?.PackageName;\n\n            analyticsHelper\n                .TrackFirstTimeInstallAsync(\n                    installedPackageNameMaybe,\n                    recommendedModelsViewModel\n                        .RecommendedModels.Where(x => x.IsSelected)\n                        .Select(x => x.CivitModel.Name)\n                        .ToList(),\n                    false\n                )\n                .SafeFireAndForget();\n        }\n\n        // Show what's new for updates\n        if (settingsManager.Settings.UpdatingFromVersion is { } updatingFromVersion)\n        {\n            var currentVersion = Compat.AppVersion;\n\n            notificationService.Show(\n                \"Update Successful\",\n                $\"Stability Matrix has been updated from {updatingFromVersion.ToDisplayString()} to {currentVersion.ToDisplayString()}.\"\n            );\n\n            settingsManager.Transaction(s => s.UpdatingFromVersion = null);\n        }\n\n        // Start checking for updates\n        await updateHelper.StartCheckingForUpdates();\n\n        // Periodic launch stats\n        if (\n            settingsManager.Settings.Analytics.IsUsageDataEnabled\n            && (\n                settingsManager.Settings.Analytics.LaunchDataLastSentAt is null\n                || (DateTimeOffset.UtcNow - settingsManager.Settings.Analytics.LaunchDataLastSentAt)\n                    > AnalyticsSettings.DefaultLaunchDataSendInterval\n            )\n        )\n        {\n            analyticsHelper\n                .TrackAsync(\n                    new LaunchAnalyticsRequest\n                    {\n                        Version = Compat.AppVersion.ToString(),\n                        RuntimeIdentifier = RuntimeInformation.RuntimeIdentifier,\n                        OsDescription = RuntimeInformation.OSDescription,\n                    }\n                )\n                .ContinueWith(task =>\n                {\n                    if (!task.IsFaulted)\n                    {\n                        settingsManager.Transaction(s =>\n                            s.Analytics.LaunchDataLastSentAt = DateTimeOffset.UtcNow\n                        );\n                    }\n                })\n                .SafeFireAndForget();\n        }\n\n        // Account migration notice\n        Task.Run(async () =>\n            {\n                if (settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.LykosAccountMigrateTip))\n                {\n                    return;\n                }\n\n                var secrets = await secretsManager.LoadAsync();\n                if (!secrets.HasLegacyLykosAccount() || secrets.LykosAccountV2 is not null)\n                {\n                    return;\n                }\n\n                // Show dialog\n                await Dispatcher.UIThread.InvokeAsync(async () =>\n                {\n                    var dialog = DialogHelper.CreateMarkdownDialog(Resources.Text_LykosAccountUpgradeNotice);\n\n                    dialog.MaxDialogWidth = 600;\n                    dialog.ContentMargin = new Thickness(32, 0);\n                    dialog.PrimaryButtonText = Resources.Action_GoToSettings;\n                    dialog.IsPrimaryButtonEnabled = true;\n                    dialog.CloseButtonText = Resources.Action_Close;\n                    dialog.DefaultButton = ContentDialogButton.Primary;\n\n                    // Show dialog, nav to settings if primary\n                    if (await dialog.ShowAsync() is ContentDialogResult.Primary)\n                    {\n                        navigationService.NavigateTo<SettingsViewModel>(\n                            new SuppressNavigationTransitionInfo()\n                        );\n                        await Task.Delay(100);\n                        settingsNavService.NavigateTo<AccountSettingsViewModel>(\n                            new SuppressNavigationTransitionInfo()\n                        );\n                    }\n                });\n\n                // Mark as seen\n                settingsManager.Transaction(s => s.SeenTeachingTips.Add(TeachingTip.LykosAccountMigrateTip));\n            })\n            .SafeFireAndForget(ex =>\n            {\n                Logger.Error(ex, \"Error during account migration notice check\");\n            });\n\n        await ShowMigrationTipIfNecessaryAsync();\n    }\n\n    private void PreloadPages()\n    {\n        // Preload pages with Preload attribute\n        foreach (\n            var page in Pages\n                .Concat(FooterPages)\n                .Where(p => p.GetType().GetCustomAttributes(typeof(PreloadAttribute), true).Any())\n        )\n        {\n            Dispatcher\n                .UIThread.InvokeAsync(\n                    async () =>\n                    {\n                        var stopwatch = Stopwatch.StartNew();\n\n                        // ReSharper disable once MethodHasAsyncOverload\n                        page.OnLoaded();\n                        await page.OnLoadedAsync();\n\n                        // Get view\n                        new ViewLocator().Build(page);\n\n                        Logger.Trace(\n                            $\"Preloaded page {page.GetType().Name} in {stopwatch.Elapsed.TotalMilliseconds:F1}ms\"\n                        );\n                    },\n                    DispatcherPriority.Background\n                )\n                .ContinueWith(task =>\n                {\n                    if (task.Exception is { } exception)\n                    {\n                        Logger.Error(exception, \"Error preloading page\");\n                        Debug.Fail(exception.Message);\n                    }\n                });\n        }\n    }\n\n    /// <summary>\n    /// Check if the data directory exists, if not, show the select data directory dialog.\n    /// </summary>\n    private async Task<bool> EnsureDataDirectory()\n    {\n        // If we can't find library, show selection dialog\n        var foundInitially = settingsManager.TryFindLibrary();\n        if (!foundInitially)\n        {\n            var result = await ShowSelectDataDirectoryDialog();\n            if (!result)\n                return false;\n        }\n\n        // Try to find library again, should be found now\n        if (!settingsManager.TryFindLibrary())\n        {\n            throw new Exception(\"Could not find library after setting path\");\n        }\n\n        // Tell LaunchPage to load any packages if they selected an existing directory\n        if (!foundInitially)\n        {\n            EventManager.Instance.OnInstalledPackagesChanged();\n        }\n\n        // Check if there are old packages, if so show migration dialog\n        // TODO: Migration dialog\n\n        return true;\n    }\n\n    /// <summary>\n    /// Return true if we should show the update available teaching tip\n    /// </summary>\n    public bool ShouldShowUpdateAvailableTeachingTip([NotNullWhen(true)] UpdateInfo? info)\n    {\n        if (info is null)\n        {\n            return false;\n        }\n\n        // If matching settings seen version, don't show\n        if (info.Version == settingsManager.Settings.LastSeenUpdateVersion)\n        {\n            return false;\n        }\n\n        // Save that we have dismissed this update\n        settingsManager.Transaction(\n            s => s.LastSeenUpdateVersion = info.Version,\n            ignoreMissingLibraryDir: true\n        );\n\n        return true;\n    }\n\n    /// <summary>\n    /// Shows the select data directory dialog.\n    /// </summary>\n    /// <returns>true if path set successfully, false if user exited dialog.</returns>\n    private async Task<bool> ShowSelectDataDirectoryDialog()\n    {\n        var viewModel = dialogFactory.Get<SelectDataDirectoryViewModel>();\n        var dialog = new BetterContentDialog\n        {\n            IsPrimaryButtonEnabled = false,\n            IsSecondaryButtonEnabled = false,\n            IsFooterVisible = false,\n            Content = new SelectDataDirectoryDialog { DataContext = viewModel },\n        };\n\n        var result = await dialog.ShowAsync(App.TopLevel);\n        if (result == ContentDialogResult.Primary)\n        {\n            // 1. For portable mode, call settings.SetPortableMode()\n            if (viewModel.IsPortableMode)\n            {\n                settingsManager.SetPortableMode();\n            }\n            // 2. For custom path, call settings.SetLibraryPath(path)\n            else\n            {\n                settingsManager.SetLibraryPath(viewModel.DataDirectory);\n            }\n            // Indicate success\n            return true;\n        }\n\n        return false;\n    }\n\n    public async Task ShowUpdateDialog()\n    {\n        var viewModel = dialogFactory.Get<UpdateViewModel>();\n        var dialog = new BetterContentDialog\n        {\n            ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled,\n            DefaultButton = ContentDialogButton.Close,\n            IsPrimaryButtonEnabled = false,\n            IsSecondaryButtonEnabled = false,\n            IsFooterVisible = false,\n            Content = new UpdateDialog { DataContext = viewModel },\n        };\n\n        await viewModel.Preload();\n        await dialog.ShowAsync();\n    }\n\n    private async Task ShowMigrationTipIfNecessaryAsync()\n    {\n        if (\n            settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.SharedFolderMigrationTip)\n            || settingsManager.Settings.InstalledPackages.Count == 0\n        )\n        {\n            return;\n        }\n\n        var folderReference = DialogHelper.CreateMarkdownDialog(MarkdownSnippets.SharedFolderMigration);\n        folderReference.CloseButtonText = Resources.Action_OK;\n        await folderReference.ShowAsync();\n\n        settingsManager.Transaction(s => s.SeenTeachingTips.Add(TeachingTip.SharedFolderMigrationTip));\n    }\n\n    private void AddComputeCapabilityIfNecessary()\n    {\n        try\n        {\n            if (settingsManager.Settings.PreferredGpu is not { IsNvidia: true, ComputeCapability: null })\n                return;\n\n            var newGpuInfos = HardwareHelper.IterGpuInfoNvidiaSmi();\n            var matchedGpuInfo = newGpuInfos?.FirstOrDefault(x =>\n                x.Name?.Equals(settingsManager.Settings.PreferredGpu.Name) ?? false\n            );\n\n            if (matchedGpuInfo is null)\n            {\n                return;\n            }\n\n            using var transaction = settingsManager.BeginTransaction();\n            transaction.Settings.PreferredGpu = matchedGpuInfo;\n        }\n        catch (Exception)\n        {\n            // ignored\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls.Notifications;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing Refit;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models.Api.OpenArt;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\nusing Resources = StabilityMatrix.Avalonia.Languages.Resources;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(OpenArtBrowserPage))]\n[RegisterSingleton<OpenArtBrowserViewModel>]\npublic partial class OpenArtBrowserViewModel(\n    IOpenArtApi openArtApi,\n    INotificationService notificationService,\n    ISettingsManager settingsManager,\n    IPackageFactory packageFactory\n) : TabViewModelBase, IInfinitelyScroll\n{\n    private const int PageSize = 20;\n\n    public override string Header => Resources.Label_OpenArtBrowser;\n\n    private readonly SourceCache<OpenArtSearchResult, string> searchResultsCache = new(x => x.Id);\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(PageCount), nameof(CanGoBack), nameof(CanGoForward), nameof(CanGoToEnd))]\n    private OpenArtSearchResponse? latestSearchResponse;\n\n    [ObservableProperty]\n    private IObservableCollection<OpenArtSearchResult> searchResults =\n        new ObservableCollectionExtended<OpenArtSearchResult>();\n\n    [ObservableProperty]\n    private string searchQuery = string.Empty;\n\n    [ObservableProperty]\n    private bool isLoading;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(InternalPageNumber), nameof(CanGoBack))]\n    private int displayedPageNumber = 1;\n\n    public int InternalPageNumber => DisplayedPageNumber - 1;\n\n    public int PageCount =>\n        Math.Max(\n            1,\n            Convert.ToInt32(Math.Ceiling((LatestSearchResponse?.Total ?? 0) / Convert.ToDouble(PageSize)))\n        );\n\n    public bool CanGoBack =>\n        string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor) && InternalPageNumber > 0;\n\n    public bool CanGoForward =>\n        !string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor) || PageCount > InternalPageNumber + 1;\n\n    public bool CanGoToEnd =>\n        string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor) && PageCount > InternalPageNumber + 1;\n\n    public IEnumerable<string> AllSortModes => [\"Trending\", \"Latest\", \"Most Downloaded\", \"Most Liked\"];\n\n    [ObservableProperty]\n    private string? selectedSortMode;\n\n    protected override void OnInitialLoaded()\n    {\n        searchResultsCache\n            .Connect()\n            .DeferUntilLoaded()\n            .Bind(SearchResults)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n        SelectedSortMode = AllSortModes.First();\n        DoSearch().SafeFireAndForget();\n    }\n\n    [RelayCommand]\n    private async Task FirstPage()\n    {\n        DisplayedPageNumber = 1;\n        searchResultsCache.Clear();\n\n        await DoSearch();\n    }\n\n    [RelayCommand]\n    private async Task PreviousPage()\n    {\n        DisplayedPageNumber--;\n        searchResultsCache.Clear();\n\n        await DoSearch(InternalPageNumber);\n    }\n\n    [RelayCommand]\n    private async Task NextPage()\n    {\n        if (string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor))\n        {\n            DisplayedPageNumber++;\n        }\n\n        searchResultsCache.Clear();\n        await DoSearch(InternalPageNumber);\n    }\n\n    [RelayCommand]\n    private async Task LastPage()\n    {\n        if (string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor))\n        {\n            DisplayedPageNumber = PageCount;\n        }\n\n        searchResultsCache.Clear();\n        await DoSearch(PageCount - 1);\n    }\n\n    [Localizable(false)]\n    [RelayCommand]\n    private void OpenModel(OpenArtSearchResult workflow)\n    {\n        ProcessRunner.OpenUrl($\"https://openart.ai/workflows/{workflow.Id}\");\n    }\n\n    [RelayCommand]\n    private async Task SearchButton()\n    {\n        DisplayedPageNumber = 1;\n        LatestSearchResponse = null;\n        searchResultsCache.Clear();\n\n        await DoSearch();\n    }\n\n    [RelayCommand]\n    private async Task OpenWorkflow(OpenArtSearchResult workflow)\n    {\n        var vm = new OpenArtWorkflowViewModel(settingsManager, packageFactory) { Workflow = workflow };\n\n        var dialog = new BetterContentDialog\n        {\n            IsPrimaryButtonEnabled = true,\n            IsSecondaryButtonEnabled = true,\n            PrimaryButtonText = Resources.Action_Import,\n            SecondaryButtonText = Resources.Action_Cancel,\n            DefaultButton = ContentDialogButton.Primary,\n            IsFooterVisible = true,\n            MaxDialogWidth = 750,\n            MaxDialogHeight = 850,\n            CloseOnClickOutside = true,\n            Content = vm\n        };\n\n        var result = await dialog.ShowAsync();\n\n        if (result != ContentDialogResult.Primary)\n            return;\n\n        List<IPackageStep> steps =\n        [\n            new DownloadOpenArtWorkflowStep(openArtApi, vm.Workflow, settingsManager)\n        ];\n\n        // Add install steps if missing nodes and preferred\n        if (\n            vm is\n            {\n                InstallRequiredNodes: true,\n                MissingNodes: { Count: > 0 } missingNodes,\n                SelectedPackage: not null,\n                SelectedPackagePair: not null\n            }\n        )\n        {\n            var extensionManager = vm.SelectedPackagePair.BasePackage.ExtensionManager!;\n\n            steps.AddRange(\n                missingNodes.Select(\n                    extension =>\n                        new InstallExtensionStep(\n                            extensionManager,\n                            vm.SelectedPackagePair.InstalledPackage,\n                            extension\n                        )\n                )\n            );\n        }\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            ModificationCompleteTitle = Resources.Label_WorkflowImported,\n            ModificationCompleteMessage = Resources.Label_FinishedImportingWorkflow\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n\n        await runner.ExecuteSteps(steps);\n\n        notificationService.Show(\n            Resources.Label_WorkflowImported,\n            Resources.Label_WorkflowImportComplete,\n            NotificationType.Success\n        );\n\n        EventManager.Instance.OnWorkflowInstalled();\n    }\n\n    [RelayCommand]\n    private void OpenOnOpenArt(OpenArtSearchResult? workflow)\n    {\n        if (workflow?.Id == null)\n            return;\n\n        ProcessRunner.OpenUrl($\"https://openart.ai/workflows/{workflow.Id}\");\n    }\n\n    private async Task DoSearch(int page = 0)\n    {\n        IsLoading = true;\n\n        try\n        {\n            OpenArtSearchResponse? response = null;\n            if (string.IsNullOrWhiteSpace(SearchQuery))\n            {\n                var request = new OpenArtFeedRequest { Sort = GetSortMode(SelectedSortMode) };\n                if (!string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor))\n                {\n                    request.Cursor = LatestSearchResponse.NextCursor;\n                }\n\n                response = await openArtApi.GetFeedAsync(request);\n            }\n            else\n            {\n                response = await openArtApi.SearchAsync(\n                    new OpenArtSearchRequest\n                    {\n                        Keyword = SearchQuery,\n                        PageSize = PageSize,\n                        CurrentPage = page\n                    }\n                );\n            }\n\n            foreach (var item in response.Items)\n            {\n                searchResultsCache.AddOrUpdate(item);\n            }\n\n            LatestSearchResponse = response;\n        }\n        catch (ApiException e)\n        {\n            notificationService.Show(Resources.Label_ErrorRetrievingWorkflows, e.Message);\n        }\n        finally\n        {\n            IsLoading = false;\n        }\n    }\n\n    partial void OnSelectedSortModeChanged(string? value)\n    {\n        if (value is null || SearchResults.Count == 0)\n            return;\n\n        searchResultsCache.Clear();\n        LatestSearchResponse = null;\n\n        DoSearch().SafeFireAndForget();\n    }\n\n    public async Task LoadNextPageAsync()\n    {\n        if (!CanGoForward)\n            return;\n\n        try\n        {\n            OpenArtSearchResponse? response = null;\n            if (string.IsNullOrWhiteSpace(SearchQuery))\n            {\n                var request = new OpenArtFeedRequest { Sort = GetSortMode(SelectedSortMode) };\n                if (!string.IsNullOrWhiteSpace(LatestSearchResponse?.NextCursor))\n                {\n                    request.Cursor = LatestSearchResponse.NextCursor;\n                }\n\n                response = await openArtApi.GetFeedAsync(request);\n            }\n            else\n            {\n                DisplayedPageNumber++;\n                response = await openArtApi.SearchAsync(\n                    new OpenArtSearchRequest\n                    {\n                        Keyword = SearchQuery,\n                        PageSize = PageSize,\n                        CurrentPage = InternalPageNumber\n                    }\n                );\n            }\n\n            foreach (var item in response.Items)\n            {\n                searchResultsCache.AddOrUpdate(item);\n            }\n\n            LatestSearchResponse = response;\n        }\n        catch (ApiException e)\n        {\n            notificationService.Show(\"Unable to load the next page\", e.Message);\n        }\n    }\n\n    private static string GetSortMode(string? sortMode)\n    {\n        return sortMode switch\n        {\n            \"Trending\" => \"trending\",\n            \"Latest\" => \"latest\",\n            \"Most Downloaded\" => \"most_downloaded\",\n            \"Most Liked\" => \"most_liked\",\n            _ => \"trending\"\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/OutputsPage/OutputImageViewModel.cs",
    "content": "﻿using StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Models.Database;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.OutputsPage;\n\npublic class OutputImageViewModel : SelectableViewModelBase\n{\n    public OutputImageViewModel(LocalImageFile imageFile)\n    {\n        ImageFile = imageFile;\n    }\n\n    public LocalImageFile ImageFile { get; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.IO;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing AsyncImageLoader;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Media;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing DynamicData;\nusing DynamicData.Binding;\nusing FluentAvalonia.UI.Controls;\nusing FluentIcons.Common;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing Nito.Disposables.Internals;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Controls.VendorLabs;\nusing StabilityMatrix.Avalonia.Controls.VendorLabs.Cache;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.ViewModels.OutputsPage;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Inference;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(Views.OutputsPage))]\n[RegisterSingleton<OutputsPageViewModel>]\npublic partial class OutputsPageViewModel : PageViewModelBase\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly IPackageFactory packageFactory;\n    private readonly INotificationService notificationService;\n    private readonly INavigationService<MainWindowViewModel> navigationService;\n    private readonly ILogger<OutputsPageViewModel> logger;\n    private readonly List<CancellationTokenSource> cancellationTokenSources = [];\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n\n    public override string Title => Resources.Label_OutputsPageTitle;\n\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.Grid, IconVariant = IconVariant.Filled };\n\n    public SourceCache<LocalImageFile, string> OutputsCache { get; } = new(file => file.AbsolutePath);\n\n    private SourceCache<TreeViewDirectory, string> categoriesCache = new(category => category.Path);\n\n    public IObservableCollection<OutputImageViewModel> Outputs { get; set; } =\n        new ObservableCollectionExtended<OutputImageViewModel>();\n\n    public IObservableCollection<TreeViewDirectory> Categories { get; set; } =\n        new ObservableCollectionExtended<TreeViewDirectory>();\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(CanShowOutputTypes))]\n    private TreeViewDirectory? selectedCategory;\n\n    [ObservableProperty]\n    private SharedOutputType? selectedOutputType;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(NumImagesSelected))]\n    private int numItemsSelected;\n\n    [ObservableProperty]\n    private string searchQuery;\n\n    [ObservableProperty]\n    private bool isConsolidating;\n\n    [ObservableProperty]\n    private bool isLoading;\n\n    [ObservableProperty]\n    private bool showFolders;\n\n    [ObservableProperty]\n    private bool isChangingCategory;\n\n    [ObservableProperty]\n    private double resizeFactor;\n\n    public bool CanShowOutputTypes => SelectedCategory?.Name?.Equals(\"Shared Output Folder\") ?? false;\n\n    public string NumImagesSelected =>\n        NumItemsSelected == 1\n            ? Resources.Label_OneImageSelected\n            : string.Format(Resources.Label_NumImagesSelected, NumItemsSelected);\n\n    private string[] allowedExtensions = [\".png\", \".webp\", \".jpg\", \".jpeg\", \".gif\"];\n\n    private TreeViewDirectory? lastOutputCategory;\n\n    public OutputsPageViewModel(\n        ISettingsManager settingsManager,\n        IPackageFactory packageFactory,\n        INotificationService notificationService,\n        INavigationService<MainWindowViewModel> navigationService,\n        ILogger<OutputsPageViewModel> logger,\n        IServiceManager<ViewModelBase> vmFactory\n    )\n    {\n        this.settingsManager = settingsManager;\n        this.packageFactory = packageFactory;\n        this.notificationService = notificationService;\n        this.navigationService = navigationService;\n        this.logger = logger;\n        this.vmFactory = vmFactory;\n\n        var searcher = new ImageSearcher();\n\n        // Observable predicate from SearchQuery changes\n        var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchQuery)\n            .Throttle(TimeSpan.FromMilliseconds(100))!\n            .Select(property => searcher.GetPredicate(property.Value))\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        OutputsCache\n            .Connect()\n            .DeferUntilLoaded()\n            .Filter(searchPredicate)\n            .Transform(file => new OutputImageViewModel(file))\n            .Sort(\n                SortExpressionComparer<OutputImageViewModel>\n                    .Descending(vm => vm.ImageFile.CreatedAt)\n                    .ThenByDescending(vm => vm.ImageFile.FileName)\n            )\n            .Bind(Outputs)\n            .WhenPropertyChanged(p => p.IsSelected)\n            .Throttle(TimeSpan.FromMilliseconds(50))\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(_ =>\n            {\n                NumItemsSelected = Outputs.Count(o => o.IsSelected);\n            });\n\n        categoriesCache\n            .Connect()\n            .DeferUntilLoaded()\n            .Bind(Categories)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.ResizeFactor,\n            settings => settings.OutputsPageResizeFactor,\n            true,\n            delay: TimeSpan.FromMilliseconds(250)\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.ShowFolders,\n            settings => settings.IsOutputsTreeViewEnabled,\n            true\n        );\n    }\n\n    protected override void OnInitialLoaded()\n    {\n        if (Design.IsDesignMode)\n            return;\n\n        if (!settingsManager.IsLibraryDirSet)\n            return;\n\n        Directory.CreateDirectory(settingsManager.ImagesDirectory);\n\n        RefreshCategories();\n\n        SelectedCategory ??= Categories.First();\n        SelectedOutputType ??= SharedOutputType.All;\n        SearchQuery = string.Empty;\n        lastOutputCategory = SelectedCategory;\n\n        IsChangingCategory = true;\n\n        var path =\n            CanShowOutputTypes && SelectedOutputType != SharedOutputType.All\n                ? Path.Combine(SelectedCategory.Path, SelectedOutputType.ToString())\n                : SelectedCategory.Path;\n        GetOutputs(path);\n    }\n\n    public override void OnUnloaded()\n    {\n        base.OnUnloaded();\n\n        logger.LogTrace(\"OutputsPageViewModel Unloaded\");\n\n        logger.LogTrace(\"Clearing memory cache\");\n        var items = ImageLoaders.OutputsPageImageCache.ClearMemoryCache();\n\n        logger.LogTrace(\"Cleared {Items} items from memory cache\", items);\n    }\n\n    partial void OnSelectedCategoryChanged(TreeViewDirectory? oldValue, TreeViewDirectory? newValue)\n    {\n        if (oldValue == newValue || oldValue == null || newValue == null)\n            return;\n\n        var path =\n            CanShowOutputTypes && SelectedOutputType != SharedOutputType.All\n                ? Path.Combine(newValue.Path, SelectedOutputType.ToString())\n                : SelectedCategory.Path;\n        GetOutputs(path);\n        lastOutputCategory = newValue;\n    }\n\n    partial void OnSelectedOutputTypeChanged(SharedOutputType? oldValue, SharedOutputType? newValue)\n    {\n        if (oldValue == newValue || oldValue == null || newValue == null)\n            return;\n\n        var path =\n            newValue == SharedOutputType.All\n                ? SelectedCategory?.Path\n                : Path.Combine(SelectedCategory.Path, newValue.ToString());\n        GetOutputs(path);\n    }\n\n    public Task OnImageClick(OutputImageViewModel item)\n    {\n        // Select image if we're in \"select mode\"\n        if (NumItemsSelected > 0)\n        {\n            item.IsSelected = !item.IsSelected;\n        }\n        else\n        {\n            return ShowImageDialog(item);\n        }\n\n        return Task.CompletedTask;\n    }\n\n    public async Task ShowImageDialog(OutputImageViewModel item)\n    {\n        var currentIndex = Outputs.IndexOf(item);\n\n        var image = new ImageSource(new FilePath(item.ImageFile.AbsolutePath));\n\n        // Preload\n        await image.GetBitmapAsync();\n\n        var vm = vmFactory.Get<ImageViewerViewModel>();\n        vm.ImageSource = image;\n        vm.LocalImageFile = item.ImageFile;\n\n        using var onNext = Observable\n            .FromEventPattern<DirectionalNavigationEventArgs>(\n                vm,\n                nameof(ImageViewerViewModel.NavigationRequested)\n            )\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(ctx =>\n            {\n                Dispatcher\n                    .UIThread.InvokeAsync(async () =>\n                    {\n                        var sender = (ImageViewerViewModel)ctx.Sender!;\n                        var newIndex = currentIndex + (ctx.EventArgs.IsNext ? 1 : -1);\n\n                        if (newIndex >= 0 && newIndex < Outputs.Count)\n                        {\n                            var newImage = Outputs[newIndex];\n                            var newImageSource = new ImageSource(\n                                new FilePath(newImage.ImageFile.AbsolutePath)\n                            );\n\n                            // Preload\n                            await newImageSource.GetBitmapAsync();\n                            await newImageSource.GetOrRefreshTemplateKeyAsync();\n\n                            sender.ImageSource = newImageSource;\n                            sender.LocalImageFile = newImage.ImageFile;\n\n                            currentIndex = newIndex;\n                        }\n                    })\n                    .SafeFireAndForget();\n            });\n\n        await vm.GetDialog().ShowAsync();\n    }\n\n    public Task CopyImage(string imagePath)\n    {\n        var clipboard = App.Clipboard;\n        return clipboard.SetFileDataObjectAsync(imagePath);\n    }\n\n    public Task OpenImage(string imagePath) => ProcessRunner.OpenFileBrowser(imagePath);\n\n    public void Refresh()\n    {\n        Dispatcher.UIThread.Post(RefreshCategories);\n\n        var path =\n            CanShowOutputTypes && SelectedOutputType != SharedOutputType.All\n                ? Path.Combine(SelectedCategory.Path, SelectedOutputType.ToString())\n                : SelectedCategory.Path;\n        GetOutputs(path);\n    }\n\n    public async Task DeleteImage(OutputImageViewModel? item)\n    {\n        if (item is null)\n            return;\n\n        var itemPath = item.ImageFile.AbsolutePath;\n        var pathsToDelete = new List<string> { itemPath };\n\n        // Add .txt sidecar to paths if they exist\n        var sideCar = Path.ChangeExtension(itemPath, \".txt\");\n        if (File.Exists(sideCar))\n        {\n            pathsToDelete.Add(sideCar);\n        }\n\n        var vm = vmFactory.Get<ConfirmDeleteDialogViewModel>();\n        vm.PathsToDelete = pathsToDelete;\n\n        if (await vm.GetDialog().ShowAsync() != ContentDialogResult.Primary)\n        {\n            return;\n        }\n\n        try\n        {\n            await vm.ExecuteCurrentDeleteOperationAsync(failFast: true);\n        }\n        catch (Exception e)\n        {\n            notificationService.ShowPersistent(\"Error deleting files\", e.Message, NotificationType.Error);\n\n            Refresh();\n\n            return;\n        }\n\n        OutputsCache.Remove(item.ImageFile);\n\n        // Invalidate cache\n        if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)\n        {\n            loader.RemoveAllNamesFromCache(itemPath);\n        }\n    }\n\n    public void SendToTextToImage(OutputImageViewModel vm)\n    {\n        navigationService.NavigateTo<InferenceViewModel>();\n        EventManager.Instance.OnInferenceProjectRequested(vm.ImageFile, InferenceProjectType.TextToImage);\n    }\n\n    public void SendToUpscale(OutputImageViewModel vm)\n    {\n        navigationService.NavigateTo<InferenceViewModel>();\n        EventManager.Instance.OnInferenceProjectRequested(vm.ImageFile, InferenceProjectType.Upscale);\n    }\n\n    public void SendToImageToImage(OutputImageViewModel vm)\n    {\n        navigationService.NavigateTo<InferenceViewModel>();\n        EventManager.Instance.OnInferenceProjectRequested(vm.ImageFile, InferenceProjectType.ImageToImage);\n    }\n\n    public void SendToImageToVideo(OutputImageViewModel vm)\n    {\n        navigationService.NavigateTo<InferenceViewModel>();\n        EventManager.Instance.OnInferenceProjectRequested(vm.ImageFile, InferenceProjectType.ImageToVideo);\n    }\n\n    public void ClearSelection()\n    {\n        foreach (var output in Outputs)\n        {\n            output.IsSelected = false;\n        }\n    }\n\n    public void SelectAll()\n    {\n        foreach (var output in Outputs)\n        {\n            output.IsSelected = true;\n        }\n    }\n\n    public async Task DeleteAllSelected()\n    {\n        var pathsToDelete = new List<string>();\n\n        foreach (var path in Outputs.Where(o => o.IsSelected).Select(o => o.ImageFile.AbsolutePath))\n        {\n            pathsToDelete.Add(path);\n            // Add .txt sidecars to paths if they exist\n            var sideCar = Path.ChangeExtension(path, \".txt\");\n            if (File.Exists(sideCar))\n            {\n                pathsToDelete.Add(sideCar);\n            }\n        }\n\n        var vm = vmFactory.Get<ConfirmDeleteDialogViewModel>();\n        vm.PathsToDelete = pathsToDelete;\n\n        if (await vm.GetDialog().ShowAsync() != ContentDialogResult.Primary)\n        {\n            return;\n        }\n\n        try\n        {\n            await vm.ExecuteCurrentDeleteOperationAsync(failFast: true);\n        }\n        catch (Exception e)\n        {\n            notificationService.ShowPersistent(\"Error deleting files\", e.Message, NotificationType.Error);\n\n            Refresh();\n\n            return;\n        }\n\n        OutputsCache.Remove(pathsToDelete);\n        NumItemsSelected = 0;\n        ClearSelection();\n    }\n\n    public async Task ConsolidateImages()\n    {\n        var stackPanel = new StackPanel();\n        stackPanel.Children.Add(\n            new TextBlock\n            {\n                Text = Resources.Label_ConsolidateExplanation,\n                TextWrapping = TextWrapping.Wrap,\n                Margin = new Thickness(0, 8, 0, 16),\n            }\n        );\n        foreach (var category in Categories)\n        {\n            if (category.Name == \"Shared Output Folder\")\n            {\n                continue;\n            }\n\n            stackPanel.Children.Add(\n                new CheckBox\n                {\n                    Content = $\"{category.Name} ({category.Path})\",\n                    IsChecked = true,\n                    Margin = new Thickness(0, 8, 0, 0),\n                    Tag = category.Path,\n                }\n            );\n        }\n\n        var confirmationDialog = new BetterContentDialog\n        {\n            Title = Resources.Label_AreYouSure,\n            Content = stackPanel,\n            PrimaryButtonText = Resources.Action_Yes,\n            SecondaryButtonText = Resources.Action_Cancel,\n            DefaultButton = ContentDialogButton.Primary,\n            IsSecondaryButtonEnabled = true,\n        };\n\n        var dialogResult = await confirmationDialog.ShowAsync();\n        if (dialogResult != ContentDialogResult.Primary)\n            return;\n\n        IsConsolidating = true;\n\n        Directory.CreateDirectory(settingsManager.ConsolidatedImagesDirectory);\n\n        foreach (var category in stackPanel.Children.OfType<CheckBox>().Where(c => c.IsChecked == true))\n        {\n            if (\n                string.IsNullOrWhiteSpace(category.Tag?.ToString())\n                || !Directory.Exists(category.Tag?.ToString())\n            )\n                continue;\n\n            var directory = category.Tag.ToString();\n\n            foreach (\n                var path in Directory.EnumerateFiles(\n                    directory,\n                    \"*\",\n                    EnumerationOptionConstants.AllDirectories\n                )\n            )\n            {\n                try\n                {\n                    var file = new FilePath(path);\n                    if (!allowedExtensions.Contains(file.Extension))\n                        continue;\n\n                    var newPath = settingsManager.ConsolidatedImagesDirectory + file.Name;\n                    if (file.FullPath == newPath)\n                        continue;\n\n                    // ignore inference if not in inference directory\n                    if (\n                        file.FullPath.Contains(settingsManager.ImagesInferenceDirectory)\n                        && directory != settingsManager.ImagesInferenceDirectory\n                    )\n                    {\n                        continue;\n                    }\n\n                    await file.MoveToWithIncrementAsync(newPath);\n\n                    var sideCar = new FilePath(Path.ChangeExtension(file, \".txt\"));\n                    //If a .txt sidecar file exists, and the image was moved successfully, try to move the sidecar along with the image\n                    if (File.Exists(newPath) && File.Exists(sideCar))\n                    {\n                        var newSidecar = new FilePath(Path.ChangeExtension(newPath, \".txt\"));\n                        await sideCar.MoveToWithIncrementAsync(newSidecar);\n                    }\n                }\n                catch (Exception e)\n                {\n                    logger.LogError(e, \"Error when consolidating: \");\n                }\n            }\n        }\n\n        Refresh();\n        IsConsolidating = false;\n    }\n\n    public void ClearSearchQuery()\n    {\n        SearchQuery = string.Empty;\n    }\n\n    private void GetOutputs(string directory)\n    {\n        if (!settingsManager.IsLibraryDirSet)\n            return;\n\n        if (\n            !Directory.Exists(directory)\n            && (\n                SelectedCategory.Path != settingsManager.ImagesDirectory\n                || SelectedOutputType != SharedOutputType.All\n            )\n        )\n        {\n            Directory.CreateDirectory(directory);\n            return;\n        }\n\n        if (lastOutputCategory?.Path.Equals(directory) is not true)\n        {\n            OutputsCache.Clear();\n            IsChangingCategory = true;\n        }\n\n        IsLoading = true;\n\n        cancellationTokenSources.ForEach(cts => cts.Cancel());\n\n        Task.Run(() =>\n        {\n            var getOutputsTokenSource = new CancellationTokenSource();\n            cancellationTokenSources.Add(getOutputsTokenSource);\n\n            if (getOutputsTokenSource.IsCancellationRequested)\n            {\n                cancellationTokenSources.Remove(getOutputsTokenSource);\n                return;\n            }\n\n            var files = Directory\n                .EnumerateFiles(directory, \"*\", EnumerationOptionConstants.AllDirectories)\n                .Where(file =>\n                    allowedExtensions.Contains(new FilePath(file).Extension)\n                    && new FilePath(file).Info.DirectoryName?.EndsWith(\n                        \"thumbnails\",\n                        StringComparison.OrdinalIgnoreCase\n                    )\n                        is false\n                )\n                .Select(file => LocalImageFile.FromPath(file))\n                .ToList();\n\n            if (getOutputsTokenSource.IsCancellationRequested)\n            {\n                cancellationTokenSources.Remove(getOutputsTokenSource);\n                return;\n            }\n\n            Dispatcher.UIThread.Post(() =>\n            {\n                if (files.Count == 0 && OutputsCache.Count == 0)\n                {\n                    IsLoading = false;\n                    IsChangingCategory = false;\n                    return;\n                }\n\n                OutputsCache.EditDiff(\n                    files,\n                    (oldItem, newItem) => oldItem.AbsolutePath == newItem.AbsolutePath\n                );\n\n                IsLoading = false;\n                IsChangingCategory = false;\n            });\n            cancellationTokenSources.Remove(getOutputsTokenSource);\n        });\n    }\n\n    private void RefreshCategories()\n    {\n        if (Design.IsDesignMode)\n            return;\n\n        if (!settingsManager.IsLibraryDirSet)\n            return;\n\n        var previouslySelectedCategory = SelectedCategory;\n\n        var packageCategories = settingsManager\n            .Settings.InstalledPackages.Where(x => !x.UseSharedOutputFolder)\n            .Select(packageFactory.GetPackagePair)\n            .WhereNotNull()\n            .Where(p =>\n                p.BasePackage.SharedOutputFolders is { Count: > 0 }\n                && p.InstalledPackage is { FullPath: not null }\n            )\n            .Select(pair => new TreeViewDirectory\n            {\n                Path = Path.Combine(pair.InstalledPackage.FullPath!, pair.BasePackage.OutputFolderName),\n                Name = pair.InstalledPackage.DisplayName ?? \"\",\n                SubDirectories = GetSubfolders(\n                    Path.Combine(pair.InstalledPackage.FullPath!, pair.BasePackage.OutputFolderName)\n                ),\n            })\n            .OrderBy(d => d.Name)\n            .ToList();\n\n        packageCategories.Insert(\n            0,\n            new TreeViewDirectory\n            {\n                Path = settingsManager.ImagesDirectory,\n                Name = \"Shared Output Folder\",\n                SubDirectories = GetSubfolders(settingsManager.ImagesDirectory),\n            }\n        );\n\n        categoriesCache.Edit(updater => updater.Load(packageCategories));\n\n        if (!string.IsNullOrWhiteSpace(previouslySelectedCategory?.Path))\n        {\n            FindAndExpandPathToCategory(Categories, previouslySelectedCategory.Path);\n        }\n\n        SelectedCategory =\n            FindCategoryByPath(Categories, previouslySelectedCategory?.Path) ?? Categories.FirstOrDefault();\n    }\n\n    private ObservableCollection<TreeViewDirectory> GetSubfolders(string strPath)\n    {\n        var subfolders = new ObservableCollection<TreeViewDirectory>();\n\n        if (!Directory.Exists(strPath))\n            return subfolders;\n\n        var directories = Directory.EnumerateDirectories(\n            strPath,\n            \"*\",\n            EnumerationOptionConstants.TopLevelOnly\n        );\n\n        foreach (var dir in directories)\n        {\n            var category = new TreeViewDirectory { Name = Path.GetFileName(dir), Path = dir };\n\n            if (Directory.GetDirectories(dir, \"*\", EnumerationOptionConstants.TopLevelOnly).Length > 0)\n            {\n                category.SubDirectories = GetSubfolders(dir);\n            }\n\n            subfolders.Add(category);\n        }\n\n        subfolders = new ObservableCollection<TreeViewDirectory>(subfolders.OrderBy(d => d.Name));\n\n        return subfolders;\n    }\n\n    private TreeViewDirectory? FindCategoryByPath(IEnumerable<TreeViewDirectory> nodes, string? path)\n    {\n        // Can't find a category if the path is null or empty\n        if (string.IsNullOrEmpty(path))\n        {\n            return null;\n        }\n\n        foreach (var node in nodes)\n        {\n            // Check if the current node is the one we're looking for\n            if (node.Path == path)\n            {\n                return node;\n            }\n\n            if (node.SubDirectories is not { Count: > 0 })\n                continue;\n\n            // If not, and if this node has children, search within its children\n            var foundInChildren = FindCategoryByPath(node.SubDirectories, path);\n            if (foundInChildren != null)\n            {\n                // Found it in a sub-directory, so return it up the call stack\n                return foundInChildren;\n            }\n        }\n\n        // If we've searched all nodes at this level and their children without success\n        return null;\n    }\n\n    /// <summary>\n    /// Recursively searches for a category by its path and expands all parent nodes along the way.\n    /// </summary>\n    /// <param name=\"nodes\">The collection of nodes to search within.</param>\n    /// <param name=\"targetPath\">The path of the node to find.</param>\n    /// <returns>True if the target node was found in this branch; otherwise, false.</returns>\n    private bool FindAndExpandPathToCategory(IEnumerable<TreeViewDirectory> nodes, string? targetPath)\n    {\n        if (string.IsNullOrEmpty(targetPath))\n        {\n            return false;\n        }\n\n        foreach (var node in nodes)\n        {\n            // First, check if the target is a direct descendant of this node.\n            // This is the recursive part.\n            if (\n                node.SubDirectories is { Count: > 0 }\n                && FindAndExpandPathToCategory(node.SubDirectories, targetPath)\n            )\n            {\n                // If the recursive call returns true, it means the target was found\n                // in this node's children. Therefore, this node is an ancestor and\n                // must be expanded.\n                node.IsExpanded = true;\n                return true; // Bubble \"true\" up to the parent.\n            }\n\n            // After checking children, check if the current node itself is the target.\n            if (node.Path == targetPath)\n            {\n                return true; // Found it. Start the \"bubble up\" of true values.\n            }\n        }\n\n        return false; // Target was not found in this collection of nodes.\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Reactive.Linq;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing FluentAvalonia.UI.Controls;\nusing FluentIcons.Common;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Services;\nusing MainPackageManagerView = StabilityMatrix.Avalonia.Views.PackageManager.MainPackageManagerView;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.PackageManager;\n\n/// <summary>\n///  This is our ViewModel for the second page\n/// </summary>\n[View(typeof(MainPackageManagerView))]\n[ManagedService]\n[RegisterSingleton<MainPackageManagerViewModel>]\npublic partial class MainPackageManagerViewModel : PageViewModelBase\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly IServiceManager<ViewModelBase> dialogFactory;\n    private readonly INotificationService notificationService;\n    private readonly INavigationService<PackageManagerViewModel> packageNavigationService;\n    private readonly ILogger<MainPackageManagerViewModel> logger;\n    private readonly RunningPackageService runningPackageService;\n\n    public override string Title => Resources.Label_Packages;\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.Box, IconVariant = IconVariant.Filled };\n\n    /// <summary>\n    /// List of installed packages\n    /// </summary>\n    private readonly SourceCache<InstalledPackage, Guid> installedPackages = new(p => p.Id);\n\n    /// <summary>\n    /// List of indexed packages without a corresponding installed package\n    /// </summary>\n    private readonly SourceCache<InstalledPackage, Guid> unknownInstalledPackages = new(p => p.Id);\n\n    public IObservableCollection<InstalledPackage> Packages { get; } =\n        new ObservableCollectionExtended<InstalledPackage>();\n\n    public IObservableCollection<PackageCardViewModel> PackageCards { get; } =\n        new ObservableCollectionExtended<PackageCardViewModel>();\n\n    private DispatcherTimer timer;\n\n    public MainPackageManagerViewModel(\n        ISettingsManager settingsManager,\n        IServiceManager<ViewModelBase> dialogFactory,\n        INotificationService notificationService,\n        INavigationService<PackageManagerViewModel> packageNavigationService,\n        ILogger<MainPackageManagerViewModel> logger,\n        RunningPackageService runningPackageService\n    )\n    {\n        this.settingsManager = settingsManager;\n        this.dialogFactory = dialogFactory;\n        this.notificationService = notificationService;\n        this.packageNavigationService = packageNavigationService;\n        this.logger = logger;\n        this.runningPackageService = runningPackageService;\n\n        EventManager.Instance.InstalledPackagesChanged += OnInstalledPackagesChanged;\n        EventManager.Instance.OneClickInstallFinished += OnOneClickInstallFinished;\n\n        var installed = installedPackages.Connect();\n        var unknown = unknownInstalledPackages.Connect();\n\n        installed\n            .Or(unknown)\n            .DeferUntilLoaded()\n            .Bind(Packages)\n            .Transform(p =>\n                dialogFactory.Get<PackageCardViewModel>(vm =>\n                {\n                    vm.Package = p;\n                    vm.OnLoadedAsync().SafeFireAndForget();\n                })\n            )\n            .Bind(PackageCards)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        timer = new DispatcherTimer { Interval = TimeSpan.FromMinutes(60), IsEnabled = true };\n        timer.Tick += async (_, _) => await CheckPackagesForUpdates();\n    }\n\n    private void OnOneClickInstallFinished(object? sender, bool e)\n    {\n        Dispatcher.UIThread.Post(() => OnLoadedAsync().SafeFireAndForget());\n    }\n\n    public void SetPackages(IEnumerable<InstalledPackage> packages)\n    {\n        installedPackages.Edit(s => s.Load(packages));\n    }\n\n    public void SetUnknownPackages(IEnumerable<InstalledPackage> packages)\n    {\n        unknownInstalledPackages.Edit(s => s.Load(packages));\n    }\n\n    protected override async Task OnInitialLoadedAsync()\n    {\n        if (string.IsNullOrWhiteSpace(Program.Args.LaunchPackageName))\n        {\n            await base.OnInitialLoadedAsync();\n            return;\n        }\n\n        await LoadPackages();\n\n        var package = Packages.FirstOrDefault(x => x.DisplayName == Program.Args.LaunchPackageName);\n        if (package is not null)\n        {\n            await runningPackageService.StartPackage(package);\n            return;\n        }\n\n        package = Packages.FirstOrDefault(x => x.Id.ToString() == Program.Args.LaunchPackageName);\n        if (package is null)\n        {\n            await base.OnInitialLoadedAsync();\n            return;\n        }\n\n        await runningPackageService.StartPackage(package);\n    }\n\n    public override async Task OnLoadedAsync()\n    {\n        if (Design.IsDesignMode || !settingsManager.IsLibraryDirSet)\n            return;\n\n        await LoadPackages();\n\n        timer.Start();\n    }\n\n    public override void OnUnloaded()\n    {\n        timer.Stop();\n        base.OnUnloaded();\n    }\n\n    public void ShowInstallDialog(BasePackage? selectedPackage = null)\n    {\n        NavigateToSubPage(typeof(PackageInstallBrowserViewModel));\n    }\n\n    private async Task LoadPackages()\n    {\n        installedPackages.EditDiff(settingsManager.Settings.InstalledPackages, InstalledPackage.Comparer);\n\n        var currentUnknown = await Task.Run(IndexUnknownPackages);\n        unknownInstalledPackages.Edit(s => s.Load(currentUnknown));\n    }\n\n    private async Task CheckPackagesForUpdates()\n    {\n        foreach (var package in PackageCards)\n        {\n            try\n            {\n                await package.OnLoadedAsync();\n            }\n            catch (Exception e)\n            {\n                logger.LogError(\n                    e,\n                    \"Failed to check for updates for {Package}\",\n                    package?.Package?.PackageName\n                );\n            }\n        }\n    }\n\n    private IEnumerable<UnknownInstalledPackage> IndexUnknownPackages()\n    {\n        var packageDir = settingsManager.LibraryDir.JoinDir(\"Packages\");\n\n        if (!packageDir.Exists)\n        {\n            yield break;\n        }\n\n        var currentPackages = settingsManager.Settings.InstalledPackages.ToImmutableArray();\n\n        foreach (var subDir in packageDir.Info.EnumerateDirectories().Select(info => new DirectoryPath(info)))\n        {\n            var expectedLibraryPath = $\"Packages{Path.DirectorySeparatorChar}{subDir.Name}\";\n\n            // Skip if the package is already installed\n            if (currentPackages.Any(p => p.LibraryPath == expectedLibraryPath))\n            {\n                continue;\n            }\n\n            if (settingsManager.PackageInstallsInProgress.Contains(subDir.Name))\n            {\n                continue;\n            }\n\n            yield return UnknownInstalledPackage.FromDirectoryName(subDir.Name);\n        }\n    }\n\n    [RelayCommand]\n    private void NavigateToSubPage(Type viewModelType)\n    {\n        Dispatcher.UIThread.Post(\n            () =>\n                packageNavigationService.NavigateTo(\n                    viewModelType,\n                    BetterSlideNavigationTransition.PageSlideFromRight\n                ),\n            DispatcherPriority.Send\n        );\n    }\n\n    private void OnInstalledPackagesChanged(object? sender, EventArgs e) =>\n        OnLoadedAsync().SafeFireAndForget();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Collections.Specialized;\nusing AsyncAwaitBestPractices;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Data;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.PackageManager;\n\n[ManagedService]\n[RegisterTransient<PackageCardViewModel>]\npublic partial class PackageCardViewModel(\n    ILogger<PackageCardViewModel> logger,\n    IPackageFactory packageFactory,\n    INotificationService notificationService,\n    ISettingsManager settingsManager,\n    INavigationService<PackageManagerViewModel> navigationService,\n    IServiceManager<ViewModelBase> vmFactory,\n    RunningPackageService runningPackageService\n) : ProgressViewModel\n{\n    private string webUiUrl = string.Empty;\n    private string? lastLaunchCommand = null;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(PackageDisplayName))]\n    private InstalledPackage? package;\n\n    public string? PackageDisplayName => Package?.DisplayName;\n\n    [ObservableProperty]\n    private Uri? cardImageSource;\n\n    [ObservableProperty]\n    private bool isUpdateAvailable;\n\n    [ObservableProperty]\n    private string? installedVersion;\n\n    [ObservableProperty]\n    private bool isUnknownPackage;\n\n    [ObservableProperty]\n    private bool isSharedModelSymlink;\n\n    [ObservableProperty]\n    private bool isSharedModelConfig;\n\n    [ObservableProperty]\n    private bool isSharedModelDisabled;\n\n    [ObservableProperty]\n    private bool canUseConfigMethod;\n\n    [ObservableProperty]\n    private bool canUseSymlinkMethod;\n\n    [ObservableProperty]\n    private bool useSharedOutput;\n\n    [ObservableProperty]\n    private bool canUseSharedOutput;\n\n    [ObservableProperty]\n    private bool canUseExtensions;\n\n    [ObservableProperty]\n    private bool isRunning;\n\n    [ObservableProperty]\n    private bool showWebUiButton;\n\n    [ObservableProperty]\n    private DownloadPackageVersionOptions? updateVersion;\n\n    [ObservableProperty]\n    private bool dontCheckForUpdates;\n\n    [ObservableProperty]\n    private bool usesVenv;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ShowExtraCommands))]\n    private List<ExtraPackageCommand>? extraCommands;\n\n    [ObservableProperty]\n    private IReadOnlyDictionary<string, string> extraLaunchCommands = new Dictionary<string, string>();\n\n    public bool ShowExtraCommands => ExtraCommands is { Count: > 0 };\n\n    private void RunningPackagesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)\n    {\n        if (runningPackageService.RunningPackages.Select(x => x.Value) is not { } runningPackages)\n            return;\n\n        var runningViewModel = runningPackages.FirstOrDefault(x =>\n            x.RunningPackage.InstalledPackage.Id == Package?.Id\n        );\n        if (runningViewModel is not null)\n        {\n            IsRunning = true;\n            runningViewModel.RunningPackage.BasePackage.Exited += BasePackageOnExited;\n            runningViewModel.RunningPackage.BasePackage.StartupComplete += RunningPackageOnStartupComplete;\n        }\n        else if (runningViewModel is null && IsRunning)\n        {\n            IsRunning = false;\n            ShowWebUiButton = false;\n        }\n    }\n\n    partial void OnPackageChanged(InstalledPackage? value)\n    {\n        if (string.IsNullOrWhiteSpace(value?.PackageName))\n            return;\n\n        if (\n            value.PackageName == UnknownPackage.Key\n            || packageFactory.FindPackageByName(value.PackageName) is null\n        )\n        {\n            IsUnknownPackage = true;\n            CardImageSource = null;\n            InstalledVersion = \"Unknown\";\n        }\n        else\n        {\n            IsUnknownPackage = false;\n\n            var basePackage = packageFactory[value.PackageName!];\n            CardImageSource = basePackage?.PreviewImageUri ?? Assets.NoImage;\n            InstalledVersion = value.Version?.DisplayVersion ?? \"Unknown\";\n            CanUseConfigMethod =\n                basePackage?.AvailableSharedFolderMethods.Contains(SharedFolderMethod.Configuration) ?? false;\n            CanUseSymlinkMethod =\n                basePackage?.AvailableSharedFolderMethods.Contains(SharedFolderMethod.Symlink) ?? false;\n            UseSharedOutput = Package?.UseSharedOutputFolder ?? false;\n            CanUseSharedOutput = basePackage?.SharedOutputFolders != null;\n            CanUseExtensions = basePackage?.SupportsExtensions ?? false;\n            DontCheckForUpdates = Package?.DontCheckForUpdates ?? false;\n            UsesVenv = basePackage?.UsesVenv ?? true;\n            ExtraLaunchCommands = basePackage?.ExtraLaunchCommands ?? new Dictionary<string, string>();\n\n            // Set the extra commands if available from the package\n            var packageExtraCommands = basePackage?.GetExtraCommands();\n            ExtraCommands = packageExtraCommands?.Count > 0 ? packageExtraCommands : null;\n\n            runningPackageService.RunningPackages.CollectionChanged += RunningPackagesOnCollectionChanged;\n            EventManager.Instance.PackageRelaunchRequested += InstanceOnPackageRelaunchRequested;\n        }\n    }\n\n    private async Task InstanceOnPackageRelaunchRequested(\n        object? sender,\n        InstalledPackage e,\n        RunPackageOptions options\n    )\n    {\n        if (e.Id != Package?.Id)\n            return;\n\n        navigationService.GoBack();\n        await Launch(options.Command);\n    }\n\n    public override async Task OnLoadedAsync()\n    {\n        if (Design.IsDesignMode && Package?.DisplayName == \"Running Comfy\")\n        {\n            IsRunning = true;\n            IsUpdateAvailable = true;\n            ShowWebUiButton = true;\n        }\n\n        if (Design.IsDesignMode || !settingsManager.IsLibraryDirSet || Package is not { } currentPackage)\n            return;\n\n        if (\n            packageFactory.FindPackageByName(currentPackage.PackageName)\n            is { } basePackage\n                and not UnknownPackage\n        )\n        {\n            // Migrate old packages with null preferred shared folder method\n            currentPackage.PreferredSharedFolderMethod ??= basePackage.RecommendedSharedFolderMethod;\n\n            switch (currentPackage.PreferredSharedFolderMethod)\n            {\n                case SharedFolderMethod.Configuration:\n                    IsSharedModelConfig = true;\n                    break;\n                case SharedFolderMethod.Symlink:\n                    IsSharedModelSymlink = true;\n                    break;\n                case SharedFolderMethod.None:\n                    IsSharedModelDisabled = true;\n                    break;\n                default:\n                    throw new ArgumentOutOfRangeException();\n            }\n\n            IsUpdateAvailable = await HasUpdate();\n            if (IsUpdateAvailable)\n            {\n                UpdateVersion = await basePackage.GetUpdate(currentPackage);\n            }\n\n            if (\n                Package != null\n                && !IsRunning\n                && runningPackageService.RunningPackages.TryGetValue(Package.Id, out var runningPackageVm)\n            )\n            {\n                IsRunning = true;\n                runningPackageVm.RunningPackage.BasePackage.Exited += BasePackageOnExited;\n                runningPackageVm.RunningPackage.BasePackage.StartupComplete +=\n                    RunningPackageOnStartupComplete;\n                webUiUrl = runningPackageVm.WebUiUrl;\n                ShowWebUiButton = !string.IsNullOrWhiteSpace(webUiUrl);\n            }\n        }\n    }\n\n    protected override void Dispose(bool disposing)\n    {\n        if (!disposing)\n            return;\n\n        EventManager.Instance.PackageRelaunchRequested -= InstanceOnPackageRelaunchRequested;\n        runningPackageService.RunningPackages.CollectionChanged -= RunningPackagesOnCollectionChanged;\n\n        // Cleanup any running package event handlers\n        if (\n            Package?.Id != null\n            && runningPackageService.RunningPackages.TryGetValue(Package.Id, out var runningPackageVm)\n        )\n        {\n            runningPackageVm.RunningPackage.BasePackage.Exited -= BasePackageOnExited;\n            runningPackageVm.RunningPackage.BasePackage.StartupComplete -= RunningPackageOnStartupComplete;\n        }\n    }\n\n    public async Task Launch(string? command = null)\n    {\n        if (Package == null)\n            return;\n\n        var packagePair = await runningPackageService.StartPackage(Package, command);\n\n        if (packagePair != null)\n        {\n            IsRunning = true;\n            lastLaunchCommand = command;\n\n            packagePair.BasePackage.Exited += BasePackageOnExited;\n            packagePair.BasePackage.StartupComplete += RunningPackageOnStartupComplete;\n\n            var vm = runningPackageService.GetRunningPackageViewModel(packagePair.InstalledPackage.Id);\n            if (vm != null)\n            {\n                navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition());\n            }\n        }\n\n        // settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id);\n        //\n        // navigationService.NavigateTo<LaunchPageViewModel>(new BetterDrillInNavigationTransition());\n        // EventManager.Instance.OnPackageLaunchRequested(Package.Id);\n    }\n\n    public void NavToConsole()\n    {\n        if (Package == null)\n            return;\n\n        var vm = runningPackageService.GetRunningPackageViewModel(Package.Id);\n        if (vm != null)\n        {\n            navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition());\n        }\n    }\n\n    public void LaunchWebUi()\n    {\n        if (string.IsNullOrEmpty(webUiUrl))\n            return;\n\n        notificationService.TryAsync(\n            Task.Run(() => ProcessRunner.OpenUrl(webUiUrl)),\n            \"Failed to open URL\",\n            $\"{webUiUrl}\"\n        );\n    }\n\n    private void BasePackageOnExited(object? sender, int exitCode)\n    {\n        Dispatcher\n            .UIThread.InvokeAsync(async () =>\n            {\n                logger.LogTrace(\"Process exited ({Code}) at {Time:g}\", exitCode, DateTimeOffset.Now);\n\n                // Need to wait for streams to finish before detaching handlers\n                if (sender is BaseGitPackage { VenvRunner: not null } package)\n                {\n                    var process = package.VenvRunner.Process;\n                    if (process is not null)\n                    {\n                        // Max 5 seconds\n                        var ct = new CancellationTokenSource(5000).Token;\n                        try\n                        {\n                            await process.WaitUntilOutputEOF(ct);\n                        }\n                        catch (OperationCanceledException e)\n                        {\n                            logger.LogWarning(\"Waiting for process EOF timed out: {Message}\", e.Message);\n                        }\n                    }\n                }\n\n                // Detach handlers\n                if (sender is BasePackage basePackage)\n                {\n                    basePackage.Exited -= BasePackageOnExited;\n                    basePackage.StartupComplete -= RunningPackageOnStartupComplete;\n                }\n\n                if (Package?.Id != null)\n                {\n                    runningPackageService.RunningPackages.Remove(Package.Id);\n                }\n\n                IsRunning = false;\n                ShowWebUiButton = false;\n            })\n            .SafeFireAndForget();\n    }\n\n    public async Task Uninstall()\n    {\n        if (Package?.LibraryPath == null)\n        {\n            return;\n        }\n\n        var dialogViewModel = vmFactory.Get<ConfirmPackageDeleteDialogViewModel>(vm =>\n        {\n            vm.Package = Package;\n        });\n\n        var dialog = new BetterContentDialog\n        {\n            Content = dialogViewModel,\n            IsPrimaryButtonEnabled = false,\n            IsSecondaryButtonEnabled = false,\n            IsFooterVisible = false,\n        };\n        var result = await dialog.ShowAsync();\n\n        if (result == ContentDialogResult.Primary)\n        {\n            Text = Resources.Progress_UninstallingPackage;\n            IsIndeterminate = true;\n            Value = -1;\n\n            var packagePath = new DirectoryPath(settingsManager.LibraryDir, Package.LibraryPath);\n            var deleteTask = packagePath.DeleteVerboseAsync(logger);\n\n            var taskResult = await notificationService.TryAsync(\n                deleteTask,\n                Resources.Text_SomeFilesCouldNotBeDeleted\n            );\n            if (taskResult.IsSuccessful)\n            {\n                notificationService.Show(\n                    new Notification(\n                        Resources.Label_PackageUninstalled,\n                        Package.DisplayName,\n                        NotificationType.Success\n                    )\n                );\n\n                if (!IsUnknownPackage)\n                {\n                    settingsManager.Transaction(settings =>\n                    {\n                        settings.RemoveInstalledPackageAndUpdateActive(Package);\n                    });\n                }\n\n                EventManager.Instance.OnInstalledPackagesChanged();\n            }\n\n            Text = \"\";\n            IsIndeterminate = false;\n            Value = 0;\n        }\n    }\n\n    public async Task Update()\n    {\n        if (Package is null || IsUnknownPackage)\n            return;\n\n        var basePackage = packageFactory[Package.PackageName!];\n        if (basePackage == null)\n        {\n            logger.LogWarning(\"Could not find package {SelectedPackagePackageName}\", Package.PackageName);\n            notificationService.Show(\n                Resources.Label_InvalidPackageType,\n                Package.PackageName.ToRepr(),\n                NotificationType.Error\n            );\n            return;\n        }\n\n        if (!await ShowPythonUpgradeDialogIfNeeded(basePackage, Package))\n            return;\n\n        var packageName = Package.DisplayName ?? Package.PackageName ?? \"\";\n\n        Text = $\"Updating {packageName}\";\n        IsIndeterminate = true;\n\n        try\n        {\n            var runner = new PackageModificationRunner\n            {\n                ModificationCompleteMessage = $\"Updated {packageName}\",\n                ModificationFailedMessage = $\"Could not update {packageName}\",\n            };\n\n            runner.Completed += (_, completedRunner) =>\n            {\n                notificationService.OnPackageInstallCompleted(completedRunner);\n            };\n\n            var versionOptions = new DownloadPackageVersionOptions { IsLatest = true };\n            if (Package.Version.IsReleaseMode)\n            {\n                versionOptions = await basePackage.GetLatestVersion(Package.Version.IsPrerelease);\n            }\n            else\n            {\n                var commits = await basePackage.GetAllCommits(Package.Version.InstalledBranch);\n                var latest = commits?.FirstOrDefault();\n                if (latest == null)\n                    throw new Exception(\"Could not find latest commit\");\n\n                versionOptions.BranchName = Package.Version.InstalledBranch;\n                versionOptions.CommitHash = latest.Sha;\n            }\n\n            var updatePackageStep = new UpdatePackageStep(\n                settingsManager,\n                basePackage,\n                Package.FullPath!.Unwrap(),\n                Package,\n                new UpdatePackageOptions\n                {\n                    VersionOptions = versionOptions,\n                    PythonOptions =\n                    {\n                        TorchIndex = Package.PreferredTorchIndex,\n                        PythonVersion = PyVersion.TryParse(Package.PythonVersion, out var pv) ? pv : null,\n                    },\n                }\n            );\n            var steps = new List<IPackageStep> { updatePackageStep };\n\n            EventManager.Instance.OnPackageInstallProgressAdded(runner);\n            await runner.ExecuteSteps(steps);\n\n            IsUpdateAvailable = false;\n            InstalledVersion = Package.Version?.DisplayVersion ?? \"Unknown\";\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Error Updating Package ({PackageName})\", basePackage.Name);\n            notificationService.ShowPersistent(\n                string.Format(Resources.TextTemplate_ErrorUpdatingPackage, packageName),\n                e.Message,\n                NotificationType.Error\n            );\n        }\n        finally\n        {\n            IsIndeterminate = false;\n            Value = 0;\n            Text = \"\";\n        }\n    }\n\n    public async Task Import()\n    {\n        if (!IsUnknownPackage || Design.IsDesignMode)\n            return;\n\n        var viewModel = vmFactory.Get<PackageImportViewModel>(vm =>\n        {\n            vm.PackagePath = new DirectoryPath(Package?.FullPath ?? throw new InvalidOperationException());\n        });\n\n        var dialog = new TaskDialog\n        {\n            Content = new PackageImportDialog { DataContext = viewModel },\n            ShowProgressBar = false,\n            Buttons = new List<TaskDialogButton>\n            {\n                new(Resources.Action_Import, TaskDialogStandardResult.Yes) { IsDefault = true },\n                new(Resources.Action_Cancel, TaskDialogStandardResult.Cancel),\n            },\n        };\n\n        dialog.Closing += async (sender, e) =>\n        {\n            // We only want to use the deferral on the 'Yes' Button\n            if ((TaskDialogStandardResult)e.Result == TaskDialogStandardResult.Yes)\n            {\n                var deferral = e.GetDeferral();\n\n                sender.ShowProgressBar = true;\n                sender.SetProgressBarState(0, TaskDialogProgressState.Indeterminate);\n\n                await using (new MinimumDelay(200, 300))\n                {\n                    var result = await notificationService.TryAsync(viewModel.AddPackageWithCurrentInputs());\n                    if (result.IsSuccessful)\n                    {\n                        EventManager.Instance.OnInstalledPackagesChanged();\n                    }\n                }\n\n                deferral.Complete();\n            }\n        };\n\n        dialog.XamlRoot = App.VisualRoot;\n\n        await dialog.ShowAsync(true);\n    }\n\n    public async Task OpenFolder()\n    {\n        if (string.IsNullOrWhiteSpace(Package?.FullPath))\n            return;\n\n        await ProcessRunner.OpenFolderBrowser(Package.FullPath);\n    }\n\n    [RelayCommand]\n    private async Task ChangeVersion()\n    {\n        if (Package is null || IsUnknownPackage)\n            return;\n\n        var basePackage = packageFactory[Package.PackageName!];\n        if (basePackage == null)\n        {\n            logger.LogWarning(\"Could not find package {SelectedPackagePackageName}\", Package.PackageName);\n            notificationService.Show(\n                Resources.Label_InvalidPackageType,\n                Package.PackageName.ToRepr(),\n                NotificationType.Error\n            );\n            return;\n        }\n\n        if (!await ShowPythonUpgradeDialogIfNeeded(basePackage, Package))\n            return;\n\n        var packageName = Package.DisplayName ?? Package.PackageName ?? \"\";\n\n        Text = $\"Updating {packageName}\";\n        IsIndeterminate = true;\n\n        try\n        {\n            var viewModel = vmFactory.Get<PackageImportViewModel>(vm =>\n            {\n                vm.PackagePath = new DirectoryPath(\n                    Package?.FullPath ?? throw new InvalidOperationException()\n                );\n            });\n\n            viewModel.SelectedBasePackage = basePackage;\n            viewModel.CanSelectBasePackage = false;\n            viewModel.ShowPythonVersionSelection = false;\n            viewModel.IsReleaseMode = Package.Version?.IsReleaseMode ?? false;\n\n            var dialog = new TaskDialog\n            {\n                Content = new PackageImportDialog { DataContext = viewModel },\n                ShowProgressBar = false,\n                Buttons = new List<TaskDialogButton>\n                {\n                    new(Resources.Action_Update, TaskDialogStandardResult.Yes) { IsDefault = true },\n                    new(Resources.Action_Cancel, TaskDialogStandardResult.Cancel),\n                },\n                XamlRoot = App.VisualRoot,\n            };\n\n            var result = await dialog.ShowAsync(true);\n            if (result is not TaskDialogStandardResult.Yes)\n                return;\n\n            var runner = new PackageModificationRunner\n            {\n                ModificationCompleteMessage = $\"Updated {packageName}\",\n                ModificationFailedMessage = $\"Could not update {packageName}\",\n            };\n\n            var versionOptions = new DownloadPackageVersionOptions();\n\n            if (!string.IsNullOrWhiteSpace(viewModel.CustomCommitSha))\n            {\n                versionOptions.BranchName = viewModel.SelectedVersion?.TagName;\n                versionOptions.CommitHash = viewModel.CustomCommitSha;\n            }\n            else if (viewModel.SelectedVersionType == PackageVersionType.GithubRelease)\n            {\n                versionOptions.VersionTag = viewModel.SelectedVersion?.TagName;\n            }\n            else\n            {\n                versionOptions.BranchName = viewModel.SelectedVersion?.TagName;\n                versionOptions.CommitHash = viewModel.SelectedCommit?.Sha;\n            }\n\n            var updatePackageStep = new UpdatePackageStep(\n                settingsManager,\n                basePackage,\n                Package.FullPath!.Unwrap(),\n                Package,\n                new UpdatePackageOptions\n                {\n                    VersionOptions = versionOptions,\n                    PythonOptions =\n                    {\n                        TorchIndex = Package.PreferredTorchIndex,\n                        PythonVersion = PyVersion.TryParse(Package.PythonVersion, out var pyVer)\n                            ? pyVer\n                            : null,\n                    },\n                }\n            );\n            var steps = new List<IPackageStep> { updatePackageStep };\n\n            EventManager.Instance.OnPackageInstallProgressAdded(runner);\n            await runner.ExecuteSteps(steps);\n\n            EventManager.Instance.OnInstalledPackagesChanged();\n            IsUpdateAvailable = false;\n            InstalledVersion = Package.Version?.DisplayVersion ?? \"Unknown\";\n\n            if (runner.Failed)\n            {\n                notificationService.Show(\n                    Resources.Progress_UpdateFailed,\n                    string.Format(runner.ModificationFailedMessage, packageName),\n                    NotificationType.Error\n                );\n            }\n            else\n            {\n                notificationService.Show(\n                    Resources.Progress_UpdateComplete,\n                    string.Format(Resources.TextTemplate_PackageUpdatedToSelected, packageName),\n                    NotificationType.Success\n                );\n            }\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Error Updating Package ({PackageName})\", basePackage.Name);\n            notificationService.ShowPersistent(\n                string.Format(Resources.TextTemplate_ErrorUpdatingPackage, packageName),\n                e.Message,\n                NotificationType.Error\n            );\n        }\n        finally\n        {\n            IsIndeterminate = false;\n            Value = 0;\n            Text = \"\";\n        }\n    }\n\n    [RelayCommand]\n    public async Task OpenPythonPackagesDialog()\n    {\n        if (Package is not { FullPath: not null })\n            return;\n\n        var vm = vmFactory.Get<PythonPackagesViewModel>(vm =>\n        {\n            vm.VenvPath = new DirectoryPath(Package.FullPath, \"venv\");\n            vm.PythonVersion = PyVersion.Parse(Package.PythonVersion);\n        });\n\n        await vm.GetDialog().ShowAsync();\n    }\n\n    [RelayCommand]\n    public async Task OpenPythonDependenciesOverrideDialog()\n    {\n        if (Package is not { FullPath: not null })\n            return;\n\n        var vm = vmFactory.Get<PythonPackageSpecifiersViewModel>();\n\n        vm.LoadSpecifiers(Package.PipOverrides ?? []);\n\n        if (await vm.GetDialog().ShowAsync() is ContentDialogResult.Primary)\n        {\n            await using var st = settingsManager.BeginTransaction();\n            Package.PipOverrides = vm.GetSpecifiers().ToList();\n        }\n    }\n\n    [RelayCommand]\n    public async Task OpenExtensionsDialog()\n    {\n        if (\n            Package is not { FullPath: not null }\n            || packageFactory.GetPackagePair(Package) is not { } packagePair\n        )\n            return;\n\n        var vm = vmFactory.Get<PackageExtensionBrowserViewModel>(vm =>\n        {\n            vm.PackagePair = packagePair;\n        });\n\n        var dialog = new BetterContentDialog\n        {\n            Content = vm,\n            MinDialogWidth = 850,\n            MaxDialogHeight = 1100,\n            MaxDialogWidth = 850,\n            ContentMargin = new Thickness(16, 32),\n            CloseOnClickOutside = true,\n            FullSizeDesired = true,\n            IsFooterVisible = false,\n            ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled,\n        };\n\n        await dialog.ShowAsync();\n    }\n\n    [RelayCommand]\n    private void OpenOnGitHub()\n    {\n        if (Package is null)\n            return;\n\n        var basePackage = packageFactory[Package.PackageName!];\n        if (basePackage == null)\n        {\n            logger.LogWarning(\"Could not find package {SelectedPackagePackageName}\", Package.PackageName);\n            return;\n        }\n\n        ProcessRunner.OpenUrl(basePackage.GithubUrl);\n    }\n\n    [RelayCommand]\n    private async Task Stop()\n    {\n        if (Package is null)\n            return;\n\n        await runningPackageService.StopPackage(Package.Id);\n        IsRunning = false;\n        ShowWebUiButton = false;\n    }\n\n    [RelayCommand]\n    private async Task Restart()\n    {\n        await Stop();\n        await Launch(lastLaunchCommand);\n    }\n\n    [RelayCommand]\n    private async Task ShowLaunchOptions()\n    {\n        var basePackage = packageFactory.FindPackageByName(Package?.PackageName);\n        if (basePackage == null)\n        {\n            logger.LogWarning(\"Package {Name} not found\", Package?.PackageName);\n            return;\n        }\n\n        var viewModel = vmFactory.Get<LaunchOptionsViewModel>();\n        viewModel.Cards = LaunchOptionCard\n            .FromDefinitions(basePackage.LaunchOptions, Package?.LaunchArgs ?? [])\n            .ToImmutableArray();\n\n        logger.LogDebug(\"Launching config dialog with cards: {CardsCount}\", viewModel.Cards.Count);\n\n        var dialog = new BetterContentDialog\n        {\n            ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled,\n            IsPrimaryButtonEnabled = true,\n            PrimaryButtonText = Resources.Action_Save,\n            CloseButtonText = Resources.Action_Cancel,\n            FullSizeDesired = true,\n            DefaultButton = ContentDialogButton.Primary,\n            ContentMargin = new Thickness(32, 16),\n            Padding = new Thickness(0, 16),\n            Content = new LaunchOptionsDialog { DataContext = viewModel },\n        };\n\n        var result = await dialog.ShowAsync();\n\n        if (result == ContentDialogResult.Primary && Package != null)\n        {\n            // Save config\n            var args = viewModel.AsLaunchArgs();\n            settingsManager.SaveLaunchArgs(Package.Id, args);\n        }\n    }\n\n    [RelayCommand]\n    private async Task Rename()\n    {\n        if (Package is null || IsUnknownPackage)\n            return;\n\n        var currentName = Package.DisplayName ?? Package.PackageName ?? string.Empty;\n        var field = new TextBoxField\n        {\n            Label = Resources.Label_DisplayName,\n            Text = currentName,\n            Watermark = Resources.Watermark_EnterPackageName,\n            Validator = text =>\n            {\n                if (string.IsNullOrWhiteSpace(text))\n                {\n                    throw new DataValidationException(Resources.Validation_PackageNameCannotBeEmpty);\n                }\n\n                var directoryPath = new DirectoryPath(Path.GetDirectoryName(Package.FullPath!)!, text);\n                if (directoryPath.Exists)\n                {\n                    throw new DataValidationException(\n                        string.Format(Resources.ValidationError_PackageExists, text)\n                    );\n                }\n            },\n        };\n\n        var result = await DialogHelper.GetTextEntryDialogResultAsync(\n            field,\n            string.Format(Resources.Description_RenamePackage, currentName)\n        );\n\n        if (result.Result == ContentDialogResult.Primary && field.IsValid && field.Text != currentName)\n        {\n            var newPackagePath = new DirectoryPath(Path.GetDirectoryName(Package.FullPath!)!, field.Text);\n            var existingPath = new DirectoryPath(Package.FullPath!);\n            if (existingPath.FullPath == newPackagePath.FullPath)\n                return;\n\n            try\n            {\n                await existingPath.MoveToAsync(newPackagePath);\n            }\n            catch (Exception ex)\n            {\n                logger.LogError(\n                    ex,\n                    \"Failed to rename package directory from {OldPath} to {NewPath}\",\n                    existingPath.FullPath,\n                    newPackagePath.FullPath\n                );\n                notificationService.Show(\n                    Resources.Label_UnexpectedErrorOccurred,\n                    ex.Message,\n                    NotificationType.Error\n                );\n                return;\n            }\n\n            Package.DisplayName = field.Text;\n            OnPropertyChanged(nameof(PackageDisplayName));\n            settingsManager.Transaction(s =>\n            {\n                var packageToUpdate = s.InstalledPackages.FirstOrDefault(p => p.Id == Package.Id);\n                if (packageToUpdate != null)\n                {\n                    packageToUpdate.DisplayName = field.Text;\n                    packageToUpdate.LibraryPath = Path.Combine(\"Packages\", field.Text);\n                }\n            });\n        }\n    }\n\n    [RelayCommand]\n    private async Task ExecuteExtraCommand(string commandName)\n    {\n        var command = ExtraCommands?.FirstOrDefault(cmd => cmd.CommandName == commandName);\n        if (command == null)\n            return;\n\n        Text = $\"Executing {commandName}...\";\n        IsIndeterminate = true;\n        Value = -1;\n\n        try\n        {\n            await command.Command(Package!);\n            notificationService.Show(\"Command executed successfully\", commandName, NotificationType.Success);\n        }\n        catch (Exception ex)\n        {\n            logger.LogError(ex, \"Error executing command {CommandName}\", commandName);\n            notificationService.ShowPersistent(\n                $\"Error during {commandName} operation\",\n                ex.Message,\n                NotificationType.Error\n            );\n        }\n        finally\n        {\n            Text = \"\";\n            IsIndeterminate = false;\n            Value = 0;\n        }\n    }\n\n    private async Task<bool> HasUpdate()\n    {\n        if (Package == null || IsUnknownPackage || Design.IsDesignMode || Package.DontCheckForUpdates)\n            return false;\n\n        var basePackage = packageFactory[Package.PackageName!];\n        if (basePackage == null)\n            return false;\n\n        var canCheckUpdate =\n            Package.LastUpdateCheck == null || Package.LastUpdateCheck < DateTime.Now.AddMinutes(-15);\n\n        if (!canCheckUpdate)\n        {\n            return Package.UpdateAvailable;\n        }\n\n        try\n        {\n            var hasUpdate = await basePackage.CheckForUpdates(Package);\n            UpdateVersion = await basePackage.GetUpdate(Package);\n\n            await using (settingsManager.BeginTransaction())\n            {\n                Package.UpdateAvailable = hasUpdate;\n                Package.LastUpdateCheck = DateTimeOffset.Now;\n            }\n\n            return hasUpdate;\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Error checking {PackageName} for updates\", Package.PackageName);\n            return false;\n        }\n    }\n\n    private static bool RequiresPythonUpgradeNotice(\n        BasePackage basePackage,\n        InstalledPackage installedPackage\n    )\n    {\n        if (basePackage.MinimumPythonVersion is not { } minimumVersion)\n            return false;\n\n        return PyVersion.TryParse(installedPackage.PythonVersion, out var currentVersion)\n            && currentVersion < minimumVersion;\n    }\n\n    private async Task<bool> ShowPythonUpgradeDialogIfNeeded(\n        BasePackage basePackage,\n        InstalledPackage installedPackage\n    )\n    {\n        if (!RequiresPythonUpgradeNotice(basePackage, installedPackage))\n            return true;\n\n        var dialog = new BetterContentDialog\n        {\n            Title = \"Python Upgrade Required\",\n            Content =\n                \"This update will recreate the package venv to migrate from Python \"\n                + $\"{installedPackage.PythonVersion} to {basePackage.MinimumPythonVersion}.\\n\\n\"\n                + \"Any custom pip packages manually installed into the current venv may need to be reinstalled. \"\n                + \"Your launch options, extensions, and generated files are not affected.\\n\\n\"\n                + \"You can also install a fresh copy and migrate manually.\\n\\n\"\n                + \"Continue with update?\",\n            PrimaryButtonText = \"Continue\",\n            CloseButtonText = Resources.Action_Cancel,\n            DefaultButton = ContentDialogButton.Primary,\n        };\n\n        return await dialog.ShowAsync() == ContentDialogResult.Primary;\n    }\n\n    public void ToggleSharedModelSymlink() => IsSharedModelSymlink = !IsSharedModelSymlink;\n\n    public void ToggleSharedModelConfig() => IsSharedModelConfig = !IsSharedModelConfig;\n\n    public void ToggleSharedModelNone() => IsSharedModelDisabled = !IsSharedModelDisabled;\n\n    public void ToggleSharedOutput() => UseSharedOutput = !UseSharedOutput;\n\n    public void ToggleDontCheckForUpdates() => DontCheckForUpdates = !DontCheckForUpdates;\n\n    partial void OnUseSharedOutputChanged(bool value)\n    {\n        if (Package == null)\n            return;\n\n        if (value == Package.UseSharedOutputFolder)\n            return;\n\n        using var st = settingsManager.BeginTransaction();\n        Package.UseSharedOutputFolder = value;\n\n        var basePackage = packageFactory[Package.PackageName!];\n        if (basePackage == null)\n            return;\n\n        if (value)\n        {\n            basePackage.SetupOutputFolderLinks(Package.FullPath!);\n        }\n        else\n        {\n            basePackage.RemoveOutputFolderLinks(Package.FullPath!);\n        }\n    }\n\n    // fake radio button stuff\n    partial void OnIsSharedModelSymlinkChanged(bool oldValue, bool newValue)\n    {\n        if (oldValue == newValue)\n            return;\n\n        if (newValue != Package!.PreferredSharedFolderMethod is SharedFolderMethod.Symlink)\n        {\n            using var st = settingsManager.BeginTransaction();\n            Package.PreferredSharedFolderMethod = SharedFolderMethod.Symlink;\n        }\n\n        if (newValue)\n        {\n            IsSharedModelConfig = false;\n            IsSharedModelDisabled = false;\n        }\n        else\n        {\n            var basePackage = packageFactory[Package!.PackageName!];\n            basePackage!.RemoveModelFolderLinks(Package.FullPath!, SharedFolderMethod.Symlink);\n        }\n    }\n\n    partial void OnIsSharedModelConfigChanged(bool oldValue, bool newValue)\n    {\n        if (oldValue == newValue)\n            return;\n\n        if (newValue != Package!.PreferredSharedFolderMethod is SharedFolderMethod.Configuration)\n        {\n            using var st = settingsManager.BeginTransaction();\n            Package.PreferredSharedFolderMethod = SharedFolderMethod.Configuration;\n        }\n\n        if (newValue)\n        {\n            IsSharedModelSymlink = false;\n            IsSharedModelDisabled = false;\n        }\n        else\n        {\n            var basePackage = packageFactory[Package!.PackageName!];\n            basePackage!.RemoveModelFolderLinks(Package.FullPath!, SharedFolderMethod.Configuration);\n        }\n    }\n\n    partial void OnIsSharedModelDisabledChanged(bool value)\n    {\n        if (value)\n        {\n            if (Package!.PreferredSharedFolderMethod is not SharedFolderMethod.None)\n            {\n                using var st = settingsManager.BeginTransaction();\n                Package.PreferredSharedFolderMethod = SharedFolderMethod.None;\n            }\n\n            IsSharedModelSymlink = false;\n            IsSharedModelConfig = false;\n        }\n    }\n\n    partial void OnDontCheckForUpdatesChanged(bool value)\n    {\n        if (value)\n        {\n            UpdateVersion = null;\n            IsUpdateAvailable = false;\n\n            if (Package == null)\n                return;\n\n            Package.UpdateAvailable = false;\n            settingsManager.Transaction(s =>\n            {\n                s.SetUpdateCheckDisabledForPackage(Package, value);\n            });\n        }\n        else if (Package != null)\n        {\n            Package.LastUpdateCheck = DateTimeOffset.MinValue;\n            settingsManager.Transaction(s =>\n            {\n                s.SetUpdateCheckDisabledForPackage(Package, value);\n            });\n            OnLoadedAsync().SafeFireAndForget();\n        }\n    }\n\n    private void RunningPackageOnStartupComplete(object? sender, string e)\n    {\n        webUiUrl = e.Replace(\"0.0.0.0\", \"127.0.0.1\");\n        ShowWebUiButton = !string.IsNullOrWhiteSpace(webUiUrl);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageExtensionBrowserViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.ComponentModel;\nusing System.Diagnostics.Contracts;\nusing System.IO;\nusing System.Linq;\nusing System.Reactive.Disposables;\nusing System.Reactive.Linq;\nusing System.Text.Json;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Data;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Collections;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.Views.PackageManager;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Git;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.PackageManager;\n\n[View(typeof(PackageExtensionBrowserView))]\n[RegisterTransient<PackageExtensionBrowserViewModel>]\n[ManagedService]\npublic partial class PackageExtensionBrowserViewModel : ViewModelBase, IDisposable\n{\n    private readonly INotificationService notificationService;\n    private readonly ISettingsManager settingsManager;\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n    private readonly IPrerequisiteHelper prerequisiteHelper;\n    private readonly CompositeDisposable cleanUp;\n\n    public PackagePair? PackagePair { get; set; }\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ShowNoExtensionsFoundMessage))]\n    private bool isLoading;\n\n    private SourceCache<PackageExtension, string> availableExtensionsSource = new(ext =>\n        ext.Author + ext.Title + ext.Reference\n    );\n\n    public IObservableCollection<SelectableItem<PackageExtension>> SelectedAvailableItems { get; } =\n        new ObservableCollectionExtended<SelectableItem<PackageExtension>>();\n\n    public SearchCollection<\n        SelectableItem<PackageExtension>,\n        string,\n        string\n    > AvailableItemsSearchCollection { get; }\n\n    private SourceCache<InstalledPackageExtension, string> installedExtensionsSource = new(ext =>\n        ext.Paths.FirstOrDefault()?.ToString() ?? ext.GitRepositoryUrl ?? ext.GetHashCode().ToString()\n    );\n\n    public IObservableCollection<SelectableItem<InstalledPackageExtension>> SelectedInstalledItems { get; } =\n        new ObservableCollectionExtended<SelectableItem<InstalledPackageExtension>>();\n\n    public SearchCollection<\n        SelectableItem<InstalledPackageExtension>,\n        string,\n        string\n    > InstalledItemsSearchCollection { get; }\n\n    private SourceCache<ExtensionPack, string> extensionPackSource = new(ext => ext.Name);\n\n    public IObservableCollection<ExtensionPack> ExtensionPacks { get; } =\n        new ObservableCollectionExtended<ExtensionPack>();\n\n    private SourceCache<SavedPackageExtension, string> extensionPackExtensionsSource = new(ext =>\n        ext.PackageExtension.Author + ext.PackageExtension.Title + ext.PackageExtension.Reference\n    );\n\n    public IObservableCollection<\n        SelectableItem<SavedPackageExtension>\n    > SelectedExtensionPackExtensions { get; } =\n        new ObservableCollectionExtended<SelectableItem<SavedPackageExtension>>();\n\n    public SearchCollection<\n        SelectableItem<SavedPackageExtension>,\n        string,\n        string\n    > ExtensionPackExtensionsSearchCollection { get; }\n\n    [ObservableProperty]\n    private ExtensionPack? selectedExtensionPack;\n\n    [ObservableProperty]\n    private bool showNoExtensionsFoundMessage;\n\n    [ObservableProperty]\n    private bool areExtensionPacksLoading;\n\n    public PackageExtensionBrowserViewModel(\n        INotificationService notificationService,\n        ISettingsManager settingsManager,\n        IServiceManager<ViewModelBase> vmFactory,\n        IPrerequisiteHelper prerequisiteHelper\n    )\n    {\n        this.notificationService = notificationService;\n        this.settingsManager = settingsManager;\n        this.vmFactory = vmFactory;\n        this.prerequisiteHelper = prerequisiteHelper;\n\n        var availableItemsChangeSet = availableExtensionsSource\n            .Connect()\n            .Transform(ext => new SelectableItem<PackageExtension>(ext))\n            .ObserveOn(SynchronizationContext.Current!)\n            .Publish();\n\n        availableItemsChangeSet\n            .AutoRefresh(item => item.IsSelected)\n            .Filter(item => item.IsSelected)\n            .Bind(SelectedAvailableItems)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        var installedItemsChangeSet = installedExtensionsSource\n            .Connect()\n            .Transform(ext => new SelectableItem<InstalledPackageExtension>(ext))\n            .ObserveOn(SynchronizationContext.Current!)\n            .Publish();\n\n        installedItemsChangeSet\n            .AutoRefresh(item => item.IsSelected)\n            .Filter(item => item.IsSelected)\n            .Bind(SelectedInstalledItems)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        extensionPackSource\n            .Connect()\n            .Bind(ExtensionPacks)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        var extensionPackExtensionsChangeSet = extensionPackExtensionsSource\n            .Connect()\n            .Transform(ext => new SelectableItem<SavedPackageExtension>(ext))\n            .ObserveOn(SynchronizationContext.Current!)\n            .Publish();\n\n        extensionPackExtensionsChangeSet\n            .AutoRefresh(item => item.IsSelected)\n            .Filter(item => item.IsSelected)\n            .Bind(SelectedExtensionPackExtensions)\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        cleanUp = new CompositeDisposable(\n            AvailableItemsSearchCollection = new SearchCollection<\n                SelectableItem<PackageExtension>,\n                string,\n                string\n            >(\n                availableItemsChangeSet,\n                query =>\n                    string.IsNullOrWhiteSpace(query)\n                        ? _ => true\n                        : x => x.Item.Title.Contains(query, StringComparison.OrdinalIgnoreCase)\n            ),\n            availableItemsChangeSet.Connect(),\n            InstalledItemsSearchCollection = new SearchCollection<\n                SelectableItem<InstalledPackageExtension>,\n                string,\n                string\n            >(\n                installedItemsChangeSet,\n                query =>\n                    string.IsNullOrWhiteSpace(query)\n                        ? _ => true\n                        : x => x.Item.Title.Contains(query, StringComparison.OrdinalIgnoreCase)\n            ),\n            installedItemsChangeSet.Connect(),\n            ExtensionPackExtensionsSearchCollection = new SearchCollection<\n                SelectableItem<SavedPackageExtension>,\n                string,\n                string\n            >(\n                extensionPackExtensionsChangeSet,\n                query =>\n                    string.IsNullOrWhiteSpace(query)\n                        ? _ => true\n                        : x =>\n                            x.Item.PackageExtension.Title.Contains(query, StringComparison.OrdinalIgnoreCase)\n            ),\n            extensionPackExtensionsChangeSet.Connect()\n        );\n    }\n\n    /// <inheritdoc />\n    public override async Task OnLoadedAsync()\n    {\n        await base.OnLoadedAsync();\n        await LoadExtensionPacksAsync();\n        await Refresh();\n    }\n\n    public void AddExtensions(\n        IEnumerable<PackageExtension> packageExtensions,\n        IEnumerable<InstalledPackageExtension> installedExtensions\n    )\n    {\n        availableExtensionsSource.AddOrUpdate(packageExtensions);\n        installedExtensionsSource.AddOrUpdate(installedExtensions);\n    }\n\n    public void AddExtensionPacks(IEnumerable<ExtensionPack> packs)\n    {\n        extensionPackSource.AddOrUpdate(packs);\n        SelectedExtensionPack = packs.FirstOrDefault();\n        if (SelectedExtensionPack == null)\n            return;\n\n        extensionPackExtensionsSource.AddOrUpdate(SelectedExtensionPack.Extensions);\n    }\n\n    [RelayCommand]\n    public async Task InstallSelectedExtensions()\n    {\n        if (!await BeforeInstallCheck())\n            return;\n\n        var extensions = SelectedAvailableItems\n            .Select(item => item.Item)\n            .Where(extension => !extension.IsInstalled)\n            .ToArray();\n\n        if (extensions.Length == 0)\n            return;\n\n        var steps = extensions\n            .Select(ext => new InstallExtensionStep(\n                PackagePair!.BasePackage.ExtensionManager!,\n                PackagePair.InstalledPackage,\n                ext\n            ))\n            .Cast<IPackageStep>()\n            .ToArray();\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            ModificationCompleteTitle = \"Installed Extensions\",\n            ModificationCompleteMessage = \"Finished installing extensions\",\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n\n        await runner.ExecuteSteps(steps);\n\n        ClearSelection();\n\n        RefreshBackground();\n    }\n\n    [RelayCommand]\n    public async Task UpdateSelectedExtensions()\n    {\n        var extensions = SelectedInstalledItems.Select(x => x.Item).ToArray();\n\n        if (extensions.Length == 0)\n            return;\n\n        var steps = extensions\n            .Select(ext => new UpdateExtensionStep(\n                PackagePair!.BasePackage.ExtensionManager!,\n                PackagePair.InstalledPackage,\n                ext\n            ))\n            .Cast<IPackageStep>()\n            .ToArray();\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            ModificationCompleteTitle = \"Updated Extensions\",\n            ModificationCompleteMessage = \"Finished updating extensions\",\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n\n        await runner.ExecuteSteps(steps);\n\n        ClearSelection();\n\n        RefreshBackground();\n    }\n\n    [RelayCommand]\n    public async Task UninstallSelectedExtensions()\n    {\n        var extensions = SelectedInstalledItems.Select(x => x.Item).ToArray();\n\n        if (extensions.Length == 0)\n            return;\n\n        var steps = extensions\n            .Select(ext => new UninstallExtensionStep(\n                PackagePair!.BasePackage.ExtensionManager!,\n                PackagePair.InstalledPackage,\n                ext\n            ))\n            .Cast<IPackageStep>()\n            .ToArray();\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            ModificationCompleteTitle = \"Uninstalled Extensions\",\n            ModificationCompleteMessage = \"Finished uninstalling extensions\",\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n\n        await runner.ExecuteSteps(steps);\n\n        ClearSelection();\n\n        RefreshBackground();\n    }\n\n    [RelayCommand]\n    public async Task OpenExtensionsSettingsDialog()\n    {\n        if (PackagePair is null)\n            return;\n\n        var grid = new ExtensionSettingsPropertyGrid\n        {\n            ManifestUrls = new BindingList<string>(\n                PackagePair?.InstalledPackage.ExtraExtensionManifestUrls ?? []\n            ),\n        };\n\n        var dialog = vmFactory\n            .Get<PropertyGridViewModel>(vm =>\n            {\n                vm.Title = $\"{Resources.Label_Settings}\";\n                vm.SelectedObject = grid;\n                vm.IncludeCategories = [\"Base\"];\n            })\n            .GetSaveDialog();\n\n        dialog.MinDialogWidth = 750;\n        dialog.MaxDialogWidth = 750;\n\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            await using var _ = settingsManager.BeginTransaction();\n\n            PackagePair!.InstalledPackage.ExtraExtensionManifestUrls = grid.ManifestUrls.ToList();\n        }\n    }\n\n    [RelayCommand]\n    private async Task InstallExtensionPack()\n    {\n        if (SelectedExtensionPack == null)\n            return;\n\n        var steps = new List<IPackageStep>();\n\n        foreach (var extension in SelectedExtensionPack.Extensions)\n        {\n            var installedExtension = installedExtensionsSource.Items.FirstOrDefault(x =>\n                x.Definition?.Title == extension.PackageExtension.Title\n                && x.Definition.Reference == extension.PackageExtension.Reference\n            );\n\n            if (installedExtension != null)\n            {\n                steps.Add(\n                    new UpdateExtensionStep(\n                        PackagePair!.BasePackage.ExtensionManager!,\n                        PackagePair.InstalledPackage,\n                        installedExtension,\n                        extension.Version\n                    )\n                );\n            }\n            else\n            {\n                steps.Add(\n                    new InstallExtensionStep(\n                        PackagePair!.BasePackage.ExtensionManager!,\n                        PackagePair!.InstalledPackage,\n                        extension.PackageExtension,\n                        extension.Version\n                    )\n                );\n            }\n        }\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            CloseWhenFinished = true,\n            ModificationCompleteMessage = $\"Extension Pack {SelectedExtensionPack.Name} installed\",\n        };\n\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n        await runner.ExecuteSteps(steps);\n        await Refresh();\n    }\n\n    [RelayCommand]\n    public async Task CreateExtensionPackFromInstalled()\n    {\n        var extensions = SelectedInstalledItems.Select(x => x.Item).ToArray();\n        if (extensions.Length == 0)\n            return;\n\n        var (dialog, nameField) = GetNameEntryDialog();\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            var name = nameField.Text;\n            var newExtensionPack = new ExtensionPack\n            {\n                Name = name,\n                PackageType = PackagePair!.InstalledPackage.PackageName,\n                Extensions = SelectedInstalledItems\n                    .Where(x => x.Item.Definition != null)\n                    .Select(x => new SavedPackageExtension\n                    {\n                        PackageExtension = x.Item.Definition,\n                        Version = x.Item.Version,\n                    })\n                    .ToList(),\n            };\n\n            SaveExtensionPack(newExtensionPack, name);\n            await LoadExtensionPacksAsync();\n            notificationService.Show(\"Extension Pack Created\", \"The extension pack has been created\");\n        }\n    }\n\n    [RelayCommand]\n    public async Task CreateExtensionPackFromAvailable()\n    {\n        var extensions = SelectedAvailableItems.Select(x => x.Item).ToArray();\n        if (extensions.Length == 0)\n            return;\n\n        var (dialog, nameField) = GetNameEntryDialog();\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            var name = nameField.Text;\n            var newExtensionPack = new ExtensionPack\n            {\n                Name = name,\n                PackageType = PackagePair!.InstalledPackage.PackageName,\n                Extensions = SelectedAvailableItems\n                    .Select(x => new SavedPackageExtension { PackageExtension = x.Item, Version = null })\n                    .ToList(),\n            };\n\n            SaveExtensionPack(newExtensionPack, name);\n            await LoadExtensionPacksAsync();\n            notificationService.Show(\"Extension Pack Created\", \"The extension pack has been created\");\n        }\n    }\n\n    [RelayCommand]\n    public async Task AddInstalledExtensionToPack(ExtensionPack pack)\n    {\n        foreach (var extension in SelectedInstalledItems)\n        {\n            if (\n                pack.Extensions.Any(x =>\n                    x.PackageExtension.Title == extension.Item.Definition?.Title\n                    && x.PackageExtension.Author == extension.Item.Definition?.Author\n                    && x.PackageExtension.Reference == extension.Item.Definition?.Reference\n                )\n            )\n            {\n                continue;\n            }\n\n            pack.Extensions.Add(\n                new SavedPackageExtension\n                {\n                    PackageExtension = extension.Item.Definition!,\n                    Version = extension.Item.Version,\n                }\n            );\n        }\n\n        SaveExtensionPack(pack, pack.Name);\n        ClearSelection();\n        await LoadExtensionPacksAsync();\n        notificationService.Show(\n            \"Extensions added to pack\",\n            \"The selected extensions have been added to the pack\"\n        );\n    }\n\n    [RelayCommand]\n    public async Task AddExtensionToPack(ExtensionPack pack)\n    {\n        foreach (var extension in SelectedAvailableItems)\n        {\n            if (\n                pack.Extensions.Any(x =>\n                    x.PackageExtension.Title == extension.Item.Title\n                    && x.PackageExtension.Author == extension.Item.Author\n                    && x.PackageExtension.Reference == extension.Item.Reference\n                )\n            )\n            {\n                continue;\n            }\n\n            pack.Extensions.Add(\n                new SavedPackageExtension { PackageExtension = extension.Item, Version = null }\n            );\n        }\n\n        SaveExtensionPack(pack, pack.Name);\n        ClearSelection();\n        await LoadExtensionPacksAsync();\n        notificationService.Show(\n            \"Extensions added to pack\",\n            \"The selected extensions have been added to the pack\"\n        );\n    }\n\n    [RelayCommand]\n    public async Task RemoveExtensionFromPack()\n    {\n        if (SelectedExtensionPack is null)\n            return;\n\n        foreach (var extension in SelectedExtensionPackExtensions)\n        {\n            extensionPackExtensionsSource.Remove(extension.Item);\n            SelectedExtensionPack.Extensions.Remove(extension.Item);\n        }\n\n        SaveExtensionPack(SelectedExtensionPack, SelectedExtensionPack.Name);\n        ClearSelection();\n        await LoadExtensionPacksAsync();\n    }\n\n    [RelayCommand]\n    private async Task DeleteExtensionPackAsync(ExtensionPack pack)\n    {\n        var pathToDelete = settingsManager\n            .ExtensionPackDirectory.JoinDir(pack.PackageType)\n            .JoinFile($\"{pack.Name}.json\");\n\n        var confirmDeleteVm = vmFactory.Get<ConfirmDeleteDialogViewModel>();\n        confirmDeleteVm.PathsToDelete = [pathToDelete];\n\n        if (await confirmDeleteVm.GetDialog().ShowAsync() != ContentDialogResult.Primary)\n        {\n            return;\n        }\n\n        try\n        {\n            await confirmDeleteVm.ExecuteCurrentDeleteOperationAsync(failFast: true);\n        }\n        catch (Exception e)\n        {\n            notificationService.ShowPersistent(\"Error deleting files\", e.Message, NotificationType.Error);\n            return;\n        }\n\n        ClearSelection();\n        extensionPackSource.Remove(pack);\n    }\n\n    [RelayCommand]\n    private async Task OpenExtensionPackFolder()\n    {\n        var extensionPackDir = settingsManager.ExtensionPackDirectory.JoinDir(\n            PackagePair!.InstalledPackage.PackageName\n        );\n        if (!extensionPackDir.Exists)\n        {\n            extensionPackDir.Create();\n        }\n\n        if (SelectedExtensionPack is null || ExtensionPacks.Count <= 0)\n        {\n            await ProcessRunner.OpenFolderBrowser(extensionPackDir);\n        }\n        else\n        {\n            var extensionPackPath = extensionPackDir.JoinFile($\"{SelectedExtensionPack.Name}.json\");\n            await ProcessRunner.OpenFileBrowser(extensionPackPath);\n        }\n    }\n\n    [RelayCommand]\n    private async Task SetExtensionVersion(SavedPackageExtension selectedExtension)\n    {\n        if (SelectedExtensionPack is null)\n            return;\n\n        var vm = new GitVersionSelectorViewModel\n        {\n            GitVersionProvider = new CachedCommandGitVersionProvider(\n                selectedExtension.PackageExtension.Reference.ToString(),\n                prerequisiteHelper\n            ),\n        };\n\n        var dialog = vm.GetDialog();\n\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            if (string.IsNullOrWhiteSpace(vm.SelectedGitVersion.ToString()))\n                return;\n\n            // update the version and save pack\n            selectedExtension.Version = new PackageExtensionVersion\n            {\n                Branch = vm.SelectedGitVersion.Branch,\n                CommitSha = vm.SelectedGitVersion.CommitSha,\n                Tag = vm.SelectedGitVersion.Tag,\n            };\n            SaveExtensionPack(SelectedExtensionPack, SelectedExtensionPack.Name);\n\n            await LoadExtensionPacksAsync();\n        }\n    }\n\n    [RelayCommand]\n    public async Task Refresh()\n    {\n        if (PackagePair is null)\n            return;\n\n        IsLoading = true;\n\n        try\n        {\n            if (Design.IsDesignMode)\n            {\n                var (availableExtensions, installedExtensions) = SynchronizeExtensions(\n                    availableExtensionsSource.Items,\n                    installedExtensionsSource.Items\n                );\n\n                availableExtensionsSource.EditDiff(availableExtensions);\n                installedExtensionsSource.EditDiff(installedExtensions);\n\n                await Task.Delay(250);\n            }\n            else\n            {\n                await RefreshCore();\n            }\n        }\n        finally\n        {\n            IsLoading = false;\n            ShowNoExtensionsFoundMessage = AvailableItemsSearchCollection.FilteredItems.Count == 0;\n        }\n    }\n\n    [RelayCommand]\n    private void SelectAllInstalledExtensions()\n    {\n        foreach (var item in InstalledItemsSearchCollection.FilteredItems)\n        {\n            item.IsSelected = true;\n        }\n    }\n\n    [RelayCommand]\n    private async Task InstallExtensionManualAsync()\n    {\n        var textField = new TextBoxField\n        {\n            Label = \"Extension URL\",\n            Validator = text =>\n            {\n                if (string.IsNullOrWhiteSpace(text))\n                    throw new DataValidationException(\"URL is required\");\n\n                if (!Uri.TryCreate(text, UriKind.Absolute, out _))\n                    throw new DataValidationException(\"Invalid URL format\");\n            },\n        };\n        var dialog = DialogHelper.CreateTextEntryDialog(\"Manual Extension Install\", \"\", [textField]);\n\n        if (await dialog.ShowAsync() != ContentDialogResult.Primary)\n            return;\n\n        var url = textField.Text.Trim();\n        if (string.IsNullOrWhiteSpace(url))\n            return;\n\n        if (\n            !Uri.TryCreate(url, UriKind.Absolute, out var uri)\n            || !uri.Host.Equals(\"github.com\", StringComparison.OrdinalIgnoreCase)\n        )\n        {\n            notificationService.Show(\"Invalid URL\", \"Please provide a valid GitHub repository URL.\");\n            return;\n        }\n\n        var segments = uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);\n        if (segments.Length < 2)\n        {\n            notificationService.Show(\n                \"Invalid URL\",\n                \"The URL does not appear to be a valid GitHub repository.\"\n            );\n            return;\n        }\n\n        var author = segments[0];\n        var title = segments[1].Replace(\".git\", \"\");\n\n        // create a new PackageExtension\n        var packageExtension = new PackageExtension\n        {\n            Author = author,\n            Title = title,\n            Reference = new Uri(url),\n            IsInstalled = false,\n            InstallType = \"git-clone\",\n            Files = [new Uri(url)],\n        };\n\n        var steps = new List<IPackageStep>\n        {\n            new InstallExtensionStep(\n                PackagePair!.BasePackage.ExtensionManager!,\n                PackagePair.InstalledPackage,\n                packageExtension\n            ),\n        };\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            ModificationCompleteTitle = \"Installed Extensions\",\n            ModificationCompleteMessage = \"Finished installing extensions\",\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n\n        await runner.ExecuteSteps(steps);\n\n        ClearSelection();\n\n        RefreshBackground();\n    }\n\n    public void RefreshBackground()\n    {\n        RefreshCore()\n            .SafeFireAndForget(ex =>\n            {\n                notificationService.ShowPersistent(\"Failed to refresh extensions\", ex.ToString());\n            });\n    }\n\n    private async Task RefreshCore()\n    {\n        using var _ = CodeTimer.StartDebug();\n\n        if (PackagePair?.BasePackage.ExtensionManager is not { } extensionManager)\n            throw new NotSupportedException(\n                $\"The package {PackagePair?.BasePackage} does not support extensions.\"\n            );\n\n        var availableExtensions = (\n            await extensionManager.GetManifestExtensionsAsync(\n                extensionManager.GetManifests(PackagePair.InstalledPackage)\n            )\n        ).ToArray();\n\n        var installedExtensions = (\n            await extensionManager.GetInstalledExtensionsAsync(PackagePair.InstalledPackage)\n        ).ToArray();\n\n        // Synchronize\n        SynchronizeExtensions(availableExtensions, installedExtensions);\n\n        await Task.Run(() =>\n        {\n            availableExtensionsSource.Edit(updater =>\n            {\n                updater.Load(availableExtensions);\n            });\n\n            installedExtensionsSource.Edit(updater =>\n            {\n                updater.Load(installedExtensions);\n            });\n        });\n    }\n\n    public void ClearSelection()\n    {\n        foreach (var item in SelectedAvailableItems.ToImmutableArray())\n            item.IsSelected = false;\n\n        foreach (var item in SelectedInstalledItems.ToImmutableArray())\n            item.IsSelected = false;\n\n        foreach (var item in SelectedExtensionPackExtensions.ToImmutableArray())\n            item.IsSelected = false;\n    }\n\n    private (BetterContentDialog dialog, TextBoxField nameField) GetNameEntryDialog()\n    {\n        var textFields = new TextBoxField[]\n        {\n            new()\n            {\n                Label = \"Name\",\n                Validator = text =>\n                {\n                    if (string.IsNullOrWhiteSpace(text))\n                        throw new DataValidationException(\"Name is required\");\n\n                    if (ExtensionPacks.Any(pack => pack.Name == text))\n                        throw new DataValidationException(\"Pack already exists\");\n                },\n            },\n        };\n\n        return (DialogHelper.CreateTextEntryDialog(\"Pack Name\", \"\", textFields), textFields[0]);\n    }\n\n    private async Task<bool> BeforeInstallCheck()\n    {\n        if (\n            !settingsManager.Settings.SeenTeachingTips.Contains(\n                Core.Models.Settings.TeachingTip.PackageExtensionsInstallNotice\n            )\n        )\n        {\n            var dialog = new BetterContentDialog\n            {\n                Title = \"Installing Extensions\",\n                Content = \"\"\"\n                    Extensions, the extension index, and their dependencies are community provided and not verified by the Stability Matrix team. \n\n                    The install process may invoke external programs and scripts.\n\n                    Please review the extension's source code and applicable licenses before installing.\n                    \"\"\",\n                PrimaryButtonText = Resources.Action_Continue,\n                CloseButtonText = Resources.Action_Cancel,\n                DefaultButton = ContentDialogButton.Primary,\n                MaxDialogWidth = 400,\n            };\n\n            if (await dialog.ShowAsync() != ContentDialogResult.Primary)\n                return false;\n\n            settingsManager.Transaction(s =>\n                s.SeenTeachingTips.Add(Core.Models.Settings.TeachingTip.PackageExtensionsInstallNotice)\n            );\n        }\n\n        return true;\n    }\n\n    [Pure]\n    private static (\n        IEnumerable<PackageExtension> extensions,\n        IEnumerable<InstalledPackageExtension> installedExtensions\n    ) SynchronizeExtensions(\n        IEnumerable<PackageExtension> extensions,\n        IEnumerable<InstalledPackageExtension> installedExtensions\n    )\n    {\n        var availableArr = extensions.ToArray();\n        var installedArr = installedExtensions.ToArray();\n\n        SynchronizeExtensions(availableArr, installedArr);\n\n        return (availableArr, installedArr);\n    }\n\n    private static void SynchronizeExtensions(\n        IList<PackageExtension> extensions,\n        IList<InstalledPackageExtension> installedExtensions\n    )\n    {\n        // For extensions, map their file paths for lookup\n        var repoToExtension = extensions\n            .SelectMany(ext => ext.Files.Select(path => (path, ext)))\n            .ToLookup(kv => kv.path.ToString().StripEnd(\".git\"))\n            .ToDictionary(group => group.Key, x => x.First().ext);\n\n        // For installed extensions, add remote repo if available\n        var extensionsInstalled = new HashSet<PackageExtension>();\n\n        foreach (var (i, installedExt) in installedExtensions.Enumerate())\n            if (\n                installedExt.GitRepositoryUrl is not null\n                && repoToExtension.TryGetValue(\n                    installedExt.GitRepositoryUrl.StripEnd(\".git\"),\n                    out var mappedExt\n                )\n            )\n            {\n                extensionsInstalled.Add(mappedExt);\n\n                installedExtensions[i] = installedExt with { Definition = mappedExt };\n            }\n\n        // For available extensions, add installed status if available\n        foreach (var (i, ext) in extensions.Enumerate())\n            if (extensionsInstalled.Contains(ext))\n                extensions[i] = ext with { IsInstalled = true };\n    }\n\n    private async Task LoadExtensionPacksAsync()\n    {\n        if (Design.IsDesignMode)\n            return;\n\n        try\n        {\n            AreExtensionPacksLoading = true;\n\n            var packDir = settingsManager.ExtensionPackDirectory;\n            if (!packDir.Exists)\n                packDir.Create();\n\n            var jsonFiles = packDir.EnumerateFiles(\"*.json\", SearchOption.AllDirectories);\n            var packs = new List<ExtensionPack>();\n\n            foreach (var jsonFile in jsonFiles)\n            {\n                var json = await jsonFile.ReadAllTextAsync();\n                try\n                {\n                    var extensionPack = JsonSerializer.Deserialize<ExtensionPack>(json);\n                    if (\n                        extensionPack != null\n                        && extensionPack.PackageType == PackagePair!.InstalledPackage.PackageName\n                    )\n                        packs.Add(extensionPack);\n                }\n                catch (JsonException)\n                {\n                    // ignored for now, need to log\n                }\n            }\n\n            extensionPackSource.AddOrUpdate(packs);\n        }\n        finally\n        {\n            AreExtensionPacksLoading = false;\n        }\n    }\n\n    private void SaveExtensionPack(ExtensionPack newExtensionPack, string name)\n    {\n        var extensionPackDir = settingsManager.ExtensionPackDirectory.JoinDir(newExtensionPack.PackageType);\n\n        if (!extensionPackDir.Exists)\n        {\n            extensionPackDir.Create();\n        }\n\n        var path = extensionPackDir.JoinFile($\"{name}.json\");\n        var json = JsonSerializer.Serialize(newExtensionPack);\n        path.WriteAllText(json);\n    }\n\n    partial void OnSelectedExtensionPackChanged(ExtensionPack? value)\n    {\n        if (value != null)\n        {\n            extensionPackExtensionsSource.Edit(updater => updater.Load(value.Extensions));\n        }\n        else\n        {\n            extensionPackExtensionsSource.Clear();\n        }\n    }\n\n    /// <inheritdoc />\n    public void Dispose()\n    {\n        availableExtensionsSource.Dispose();\n        installedExtensionsSource.Dispose();\n\n        cleanUp.Dispose();\n\n        GC.SuppressFinalize(this);\n    }\n\n    private class ExtensionSettingsPropertyGrid : AbstractNotifyPropertyChanged\n    {\n        [Category(\"Base\")]\n        [DisplayName(\"Extension Manifest Sources\")]\n        public BindingList<string> ManifestUrls { get; init; } = [];\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallBrowserViewModel.cs",
    "content": "﻿using System;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing DynamicData;\nusing DynamicData.Alias;\nusing DynamicData.Binding;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.PackageManager;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Analytics;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.PackageManager;\n\n[View(typeof(PackageInstallBrowserView))]\n[ManagedService]\n[RegisterTransient<PackageInstallBrowserViewModel>]\npublic partial class PackageInstallBrowserViewModel(\n    IPackageFactory packageFactory,\n    INavigationService<PackageManagerViewModel> packageNavigationService,\n    ISettingsManager settingsManager,\n    INotificationService notificationService,\n    ILogger<PackageInstallDetailViewModel> logger,\n    IPrerequisiteHelper prerequisiteHelper,\n    IAnalyticsHelper analyticsHelper,\n    IPyInstallationManager pyInstallationManager\n) : PageViewModelBase\n{\n    [ObservableProperty]\n    private bool showIncompatiblePackages;\n\n    [ObservableProperty]\n    private string searchFilter = string.Empty;\n\n    private SourceCache<BasePackage, string> packageSource = new(p => p.Name);\n\n    public IObservableCollection<BasePackage> InferencePackages { get; } =\n        new ObservableCollectionExtended<BasePackage>();\n\n    public IObservableCollection<BasePackage> TrainingPackages { get; } =\n        new ObservableCollectionExtended<BasePackage>();\n\n    public IObservableCollection<BasePackage> LegacyPackages { get; } =\n        new ObservableCollectionExtended<BasePackage>();\n\n    public override string Title => \"Add Package\";\n    public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Add };\n\n    protected override void OnInitialLoaded()\n    {\n        base.OnInitialLoaded();\n\n        var incompatiblePredicate = this.WhenPropertyChanged(vm => vm.ShowIncompatiblePackages)\n            .Select(_ => new Func<BasePackage, bool>(p => p.IsCompatible || ShowIncompatiblePackages))\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchFilter)\n            .Select(_ => new Func<BasePackage, bool>(p =>\n                p.DisplayName.Contains(SearchFilter, StringComparison.OrdinalIgnoreCase)\n            ))\n            .ObserveOn(SynchronizationContext.Current)\n            .AsObservable();\n\n        packageSource\n            .Connect()\n            .DeferUntilLoaded()\n            .Filter(incompatiblePredicate)\n            .Filter(searchPredicate)\n            .Where(p => p is { PackageType: PackageType.SdInference })\n            .SortAndBind(\n                InferencePackages,\n                SortExpressionComparer<BasePackage>\n                    .Ascending(p => p.InstallerSortOrder)\n                    .ThenByAscending(p => p.DisplayName)\n            )\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        packageSource\n            .Connect()\n            .DeferUntilLoaded()\n            .Filter(incompatiblePredicate)\n            .Filter(searchPredicate)\n            .Where(p => p is { PackageType: PackageType.SdTraining })\n            .SortAndBind(\n                TrainingPackages,\n                SortExpressionComparer<BasePackage>\n                    .Ascending(p => p.InstallerSortOrder)\n                    .ThenByAscending(p => p.DisplayName)\n            )\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        packageSource\n            .Connect()\n            .DeferUntilLoaded()\n            .Filter(incompatiblePredicate)\n            .Filter(searchPredicate)\n            .Where(p => p is { PackageType: PackageType.Legacy })\n            .SortAndBind(\n                LegacyPackages,\n                SortExpressionComparer<BasePackage>\n                    .Ascending(p => p.InstallerSortOrder)\n                    .ThenByAscending(p => p.DisplayName)\n            )\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe();\n\n        packageSource.EditDiff(packageFactory.GetAllAvailablePackages(), (a, b) => a.Name == b.Name);\n    }\n\n    public void OnPackageSelected(BasePackage? package)\n    {\n        if (package is null)\n        {\n            return;\n        }\n\n        var vm = new PackageInstallDetailViewModel(\n            package,\n            settingsManager,\n            notificationService,\n            logger,\n            prerequisiteHelper,\n            packageNavigationService,\n            packageFactory,\n            analyticsHelper,\n            pyInstallationManager\n        );\n\n        Dispatcher.UIThread.Post(\n            () => packageNavigationService.NavigateTo(vm, BetterSlideNavigationTransition.PageSlideFromRight),\n            DispatcherPriority.Send\n        );\n    }\n\n    public void ClearSearchQuery()\n    {\n        SearchFilter = string.Empty;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageInstallDetailViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models.PackageSteps;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Analytics;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\nusing PackageInstallDetailView = StabilityMatrix.Avalonia.Views.PackageManager.PackageInstallDetailView;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.PackageManager;\n\n[View(typeof(PackageInstallDetailView))]\npublic partial class PackageInstallDetailViewModel(\n    BasePackage package,\n    ISettingsManager settingsManager,\n    INotificationService notificationService,\n    ILogger<PackageInstallDetailViewModel> logger,\n    IPrerequisiteHelper prerequisiteHelper,\n    INavigationService<PackageManagerViewModel> packageNavigationService,\n    IPackageFactory packageFactory,\n    IAnalyticsHelper analyticsHelper,\n    IPyInstallationManager pyInstallationManager\n) : PageViewModelBase\n{\n    public BasePackage SelectedPackage { get; } = package;\n    public override string Title { get; } = package.DisplayName;\n    public override IconSource IconSource => new SymbolIconSource();\n\n    public string FullInstallPath => Path.Combine(settingsManager.LibraryDir, \"Packages\", InstallName);\n    public bool ShowReleaseMode => SelectedPackage.ShouldIgnoreReleases == false;\n    public bool ShowBranchMode => SelectedPackage.ShouldIgnoreBranches == false;\n\n    public string? ReleaseTooltipText =>\n        ShowReleaseMode ? null : Resources.Label_ReleasesUnavailableForThisPackage;\n\n    public bool ShowTorchIndexOptions => SelectedTorchIndex != TorchIndex.None;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(FullInstallPath))]\n    private string installName = package.DisplayName;\n\n    [ObservableProperty]\n    private bool showDuplicateWarning;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ReleaseTooltipText))]\n    private bool isReleaseMode;\n\n    [ObservableProperty]\n    private IEnumerable<PackageVersion> availableVersions = new List<PackageVersion>();\n\n    [ObservableProperty]\n    private PackageVersion? selectedVersion;\n\n    [ObservableProperty]\n    private SharedFolderMethod selectedSharedFolderMethod;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(ShowTorchIndexOptions))]\n    private TorchIndex selectedTorchIndex;\n\n    [ObservableProperty]\n    private ObservableCollection<GitCommit>? availableCommits;\n\n    [ObservableProperty]\n    private GitCommit? selectedCommit;\n\n    [ObservableProperty]\n    private bool isOutputSharingEnabled = true;\n\n    [ObservableProperty]\n    private bool canInstall;\n\n    [ObservableProperty]\n    private ObservableCollection<UvPythonInfo> availablePythonVersions = new();\n\n    [ObservableProperty]\n    private UvPythonInfo selectedPythonVersion;\n\n    public PythonPackageSpecifiersViewModel PythonPackageSpecifiersViewModel { get; } =\n        new() { Title = null };\n\n    private PackageVersionOptions? allOptions;\n\n    public override async Task OnLoadedAsync()\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        OnInstallNameChanged(InstallName);\n\n        CanInstall = false;\n\n        SelectedTorchIndex = SelectedPackage.GetRecommendedTorchVersion();\n        SelectedSharedFolderMethod = SelectedPackage.RecommendedSharedFolderMethod;\n\n        // Initialize Python versions\n        await prerequisiteHelper.UnpackResourcesIfNecessary();\n        await prerequisiteHelper.InstallUvIfNecessary();\n        var pythonVersions = await pyInstallationManager.GetAllAvailablePythonsAsync();\n        AvailablePythonVersions = new ObservableCollection<UvPythonInfo>(pythonVersions);\n        SelectedPythonVersion = GetRecommendedPyVersion() ?? AvailablePythonVersions.LastOrDefault();\n\n        allOptions = await SelectedPackage.GetAllVersionOptions();\n        if (ShowReleaseMode)\n        {\n            IsReleaseMode = true;\n        }\n        else\n        {\n            UpdateVersions();\n            await UpdateCommits(SelectedPackage.MainBranch);\n        }\n\n        CanInstall = !ShowDuplicateWarning;\n    }\n\n    [RelayCommand]\n    private async Task Install()\n    {\n        if (string.IsNullOrWhiteSpace(InstallName))\n        {\n            notificationService.Show(\n                new Notification(\n                    \"Package name is empty\",\n                    \"Please enter a name for the package\",\n                    NotificationType.Error\n                )\n            );\n            return;\n        }\n\n        if (SelectedPackage.InstallRequiresAdmin)\n        {\n            var reason = $\"\"\"\n                # **{SelectedPackage.DisplayName}** may require administrator privileges during the installation. If necessary, you will be prompted to allow the installer to run with elevated privileges.\n\n                ## The reason for this requirement is:\n                {SelectedPackage.AdminRequiredReason}\n\n                ## Would you like to proceed?\n                \"\"\";\n            var dialog = DialogHelper.CreateMarkdownDialog(reason, string.Empty);\n            dialog.PrimaryButtonText = Resources.Action_Yes;\n            dialog.CloseButtonText = Resources.Action_Cancel;\n            dialog.IsPrimaryButtonEnabled = true;\n            dialog.DefaultButton = ContentDialogButton.Primary;\n\n            var result = await dialog.ShowAsync();\n            if (result != ContentDialogResult.Primary)\n            {\n                return;\n            }\n        }\n\n        if (SelectedPackage is StableSwarm)\n        {\n            var comfy = settingsManager.Settings.InstalledPackages.FirstOrDefault(x =>\n                x.PackageName is nameof(ComfyUI) or \"ComfyUI-Zluda\"\n            );\n\n            if (comfy == null)\n            {\n                // show dialog to install comfy\n                var dialog = new BetterContentDialog\n                {\n                    Title = Resources.Label_ComfyRequiredTitle,\n                    Content = Resources.Label_ComfyRequiredDetail,\n                    PrimaryButtonText = Resources.Action_Yes,\n                    CloseButtonText = Resources.Label_No,\n                    DefaultButton = ContentDialogButton.Primary,\n                };\n\n                var result = await dialog.ShowAsync();\n                if (result != ContentDialogResult.Primary)\n                    return;\n\n                packageNavigationService.GoBack();\n                var comfyPackage = packageFactory.FindPackageByName(nameof(ComfyUI));\n                if (comfyPackage is null)\n                    return;\n\n                var vm = new PackageInstallDetailViewModel(\n                    comfyPackage,\n                    settingsManager,\n                    notificationService,\n                    logger,\n                    prerequisiteHelper,\n                    packageNavigationService,\n                    packageFactory,\n                    analyticsHelper,\n                    pyInstallationManager\n                );\n                packageNavigationService.NavigateTo(vm);\n                return;\n            }\n        }\n\n        InstallName = InstallName.Trim();\n\n        var installLocation = Path.Combine(settingsManager.LibraryDir, \"Packages\", InstallName);\n        if (Directory.Exists(installLocation))\n        {\n            var installPath = new DirectoryPath(installLocation);\n            await installPath.DeleteVerboseAsync(logger);\n        }\n\n        var downloadOptions = new DownloadPackageVersionOptions();\n        var installedVersion = new InstalledPackageVersion();\n        if (IsReleaseMode)\n        {\n            if (SelectedVersion is not null)\n            {\n                downloadOptions.VersionTag = SelectedVersion.TagName;\n                downloadOptions.IsLatest =\n                    AvailableVersions?.FirstOrDefault()?.TagName == downloadOptions.VersionTag;\n                downloadOptions.IsPrerelease = SelectedVersion.IsPrerelease;\n\n                installedVersion.InstalledReleaseVersion = downloadOptions.VersionTag;\n                installedVersion.IsPrerelease = SelectedVersion.IsPrerelease;\n            }\n            else\n            {\n                downloadOptions.IsLatest = true;\n                downloadOptions.BranchName = SelectedPackage.MainBranch;\n                installedVersion.InstalledBranch = SelectedPackage.MainBranch;\n            }\n        }\n        else\n        {\n            if (SelectedCommit is not null && SelectedVersion is not null)\n            {\n                downloadOptions.CommitHash = SelectedCommit.Sha;\n                downloadOptions.BranchName = SelectedVersion.TagName;\n                downloadOptions.IsLatest = AvailableCommits?.FirstOrDefault()?.Sha == SelectedCommit.Sha;\n\n                installedVersion.InstalledBranch = SelectedVersion.TagName;\n                installedVersion.InstalledCommitSha = downloadOptions.CommitHash;\n            }\n            else\n            {\n                downloadOptions.IsLatest = true;\n                downloadOptions.BranchName = SelectedPackage.MainBranch;\n                installedVersion.InstalledBranch = SelectedPackage.MainBranch;\n            }\n        }\n\n        var pipOverrides = PythonPackageSpecifiersViewModel.GetSpecifiers().ToList();\n\n        var package = new InstalledPackage\n        {\n            DisplayName = InstallName,\n            LibraryPath = Path.Combine(\"Packages\", InstallName),\n            Id = Guid.NewGuid(),\n            PackageName = SelectedPackage.Name,\n            Version = installedVersion,\n            LaunchCommand = SelectedPackage.LaunchCommand,\n            LastUpdateCheck = DateTimeOffset.Now,\n            PreferredTorchIndex = SelectedTorchIndex,\n            PreferredSharedFolderMethod = SelectedSharedFolderMethod,\n            UseSharedOutputFolder = IsOutputSharingEnabled,\n            PipOverrides = pipOverrides.Count > 0 ? pipOverrides : null,\n            PythonVersion = SelectedPythonVersion.Version.StringValue,\n        };\n\n        var steps = new List<IPackageStep>\n        {\n            new SetPackageInstallingStep(settingsManager, InstallName),\n            new SetupPrerequisitesStep(prerequisiteHelper, SelectedPackage, SelectedPythonVersion.Version),\n            new DownloadPackageVersionStep(\n                SelectedPackage,\n                installLocation,\n                new DownloadPackageOptions { VersionOptions = downloadOptions }\n            ),\n            new UnpackSiteCustomizeStep(Path.Combine(installLocation, \"venv\")),\n            new InstallPackageStep(\n                SelectedPackage,\n                installLocation,\n                package,\n                new InstallPackageOptions\n                {\n                    SharedFolderMethod = SelectedSharedFolderMethod,\n                    VersionOptions = downloadOptions,\n                    PythonOptions =\n                    {\n                        TorchIndex = SelectedTorchIndex,\n                        PythonVersion = SelectedPythonVersion.Version,\n                    },\n                }\n            ),\n            new SetupModelFoldersStep(SelectedPackage, SelectedSharedFolderMethod, installLocation),\n        };\n\n        if (IsOutputSharingEnabled)\n        {\n            steps.Add(new SetupOutputSharingStep(SelectedPackage, installLocation));\n        }\n\n        steps.Add(new AddInstalledPackageStep(settingsManager, package));\n\n        var packageName = SelectedPackage.Name;\n\n        var runner = new PackageModificationRunner\n        {\n            ModificationCompleteMessage = $\"Installed {packageName} at [{installLocation}]\",\n            ModificationFailedMessage = $\"Could not install {packageName}\",\n            ShowDialogOnStart = true,\n        };\n        runner.Completed += (_, completedRunner) =>\n        {\n            notificationService.OnPackageInstallCompleted(completedRunner);\n        };\n\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n        await runner.ExecuteSteps(steps);\n\n        if (!runner.Failed)\n        {\n            if (ReferenceEquals(this, packageNavigationService.CurrentPageDataContext))\n            {\n                packageNavigationService.GoBack();\n                packageNavigationService.GoBack();\n                await Task.Delay(100);\n            }\n\n            EventManager.Instance.OnInstalledPackagesChanged();\n        }\n\n        analyticsHelper\n            .TrackPackageInstallAsync(packageName, installedVersion.DisplayVersion, !runner.Failed)\n            .SafeFireAndForget();\n    }\n\n    private void UpdateVersions()\n    {\n        CanInstall = false;\n\n        AvailableVersions =\n            IsReleaseMode && ShowReleaseMode ? allOptions.AvailableVersions : allOptions.AvailableBranches;\n\n        SelectedVersion = !IsReleaseMode\n            ? AvailableVersions?.FirstOrDefault(x => x.TagName == SelectedPackage.MainBranch)\n                ?? AvailableVersions?.FirstOrDefault()\n            : AvailableVersions?.FirstOrDefault(v => !v.IsPrerelease);\n\n        CanInstall = !ShowDuplicateWarning;\n    }\n\n    private async Task UpdateCommits(string branchName)\n    {\n        CanInstall = false;\n\n        var commits = await SelectedPackage.GetAllCommits(branchName);\n        if (commits != null)\n        {\n            AvailableCommits = new ObservableCollection<GitCommit>(\n                [.. commits, new GitCommit { Sha = \"Custom \" }]\n            );\n        }\n        else\n        {\n            AvailableCommits = [new GitCommit { Sha = \"Custom \" }];\n        }\n\n        SelectedCommit = AvailableCommits.FirstOrDefault();\n        CanInstall = !ShowDuplicateWarning;\n    }\n\n    partial void OnInstallNameChanged(string? value)\n    {\n        ShowDuplicateWarning = settingsManager.Settings.InstalledPackages.Any(p =>\n            p.LibraryPath == $\"Packages{Path.DirectorySeparatorChar}{value}\"\n        );\n        CanInstall = !ShowDuplicateWarning;\n    }\n\n    partial void OnIsReleaseModeChanged(bool value)\n    {\n        UpdateVersions();\n    }\n\n    partial void OnSelectedVersionChanged(PackageVersion? value)\n    {\n        if (IsReleaseMode)\n            return;\n\n        UpdateCommits(value?.TagName ?? SelectedPackage.MainBranch).SafeFireAndForget();\n    }\n\n    async partial void OnSelectedCommitChanged(GitCommit? oldValue, GitCommit? newValue)\n    {\n        if (newValue is not { Sha: \"Custom \" })\n            return;\n\n        List<TextBoxField> textBoxFields = [new() { Label = \"Commit hash\", MinWidth = 400 }];\n        var dialog = DialogHelper.CreateTextEntryDialog(\"Enter a commit hash\", string.Empty, textBoxFields);\n        var dialogResult = await dialog.ShowAsync();\n        if (dialogResult != ContentDialogResult.Primary)\n        {\n            SelectedCommit = oldValue;\n            return;\n        }\n\n        var commitHash = textBoxFields[0].Text;\n        if (string.IsNullOrWhiteSpace(commitHash))\n        {\n            SelectedCommit = oldValue;\n            return;\n        }\n\n        var commit = new GitCommit { Sha = commitHash };\n        AvailableCommits?.Insert(AvailableCommits.IndexOf(newValue), commit);\n        SelectedCommit = commit;\n    }\n\n    private UvPythonInfo? GetRecommendedPyVersion() =>\n        AvailablePythonVersions.LastOrDefault(x =>\n            x.Version.Major.Equals(SelectedPackage.RecommendedPythonVersion.Major)\n            && x.Version.Minor.Equals(SelectedPackage.RecommendedPythonVersion.Minor)\n        );\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing DynamicData;\nusing FluentAvalonia.UI.Controls;\nusing FluentIcons.Common;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.PackageManager;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Attributes;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(PackageManagerPage))]\n[RegisterSingleton<PackageManagerViewModel>]\npublic partial class PackageManagerViewModel : PageViewModelBase\n{\n    public override string Title => Resources.Label_Packages;\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.Box, IconVariant = IconVariant.Filled };\n\n    public IReadOnlyList<PageViewModelBase> SubPages { get; }\n\n    [ObservableProperty]\n    private ObservableCollection<PageViewModelBase> currentPagePath = [];\n\n    [ObservableProperty]\n    private PageViewModelBase? currentPage;\n\n    public PackageManagerViewModel(IServiceManager<ViewModelBase> vmFactory)\n    {\n        SubPages = new PageViewModelBase[]\n        {\n            vmFactory.Get<MainPackageManagerViewModel>(),\n            vmFactory.Get<PackageInstallBrowserViewModel>(),\n        };\n\n        CurrentPagePath.AddRange(SubPages);\n\n        CurrentPage = SubPages[0];\n    }\n\n    partial void OnCurrentPageChanged(PageViewModelBase? value)\n    {\n        if (value is null)\n        {\n            return;\n        }\n\n        if (value is MainPackageManagerViewModel)\n        {\n            CurrentPagePath.Clear();\n            CurrentPagePath.Add(value);\n        }\n        else if (value is PackageInstallDetailViewModel)\n        {\n            CurrentPagePath.Add(value);\n        }\n        else if (value is RunningPackageViewModel)\n        {\n            CurrentPagePath.Add(value);\n        }\n        else\n        {\n            CurrentPagePath.Clear();\n            CurrentPagePath.AddRange(new[] { SubPages[0], value });\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Progress/DownloadProgressItemViewModel.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Progress;\n\npublic class DownloadProgressItemViewModel : PausableProgressItemViewModelBase\n{\n    private readonly ITrackedDownloadService downloadService;\n    private readonly TrackedDownload download;\n\n    public DownloadProgressItemViewModel(ITrackedDownloadService downloadService, TrackedDownload download)\n    {\n        this.downloadService = downloadService;\n        this.download = download;\n\n        Id = download.Id;\n        Name = download.FileName;\n        State = download.ProgressState;\n        OnProgressStateChanged(State);\n\n        // If initial progress provided, load it\n        if (download is { TotalBytes: > 0, DownloadedBytes: > 0 })\n        {\n            var current = download.DownloadedBytes / (double)download.TotalBytes;\n            Progress.Value = (float)Math.Ceiling(Math.Clamp(current, 0, 1) * 100);\n        }\n\n        download.ProgressUpdate += (s, e) =>\n        {\n            Progress.Value = e.Percentage;\n            Progress.IsIndeterminate = e.IsIndeterminate;\n            Progress.DownloadSpeedInMBps = e.SpeedInMBps;\n        };\n\n        download.ProgressStateChanged += (s, e) =>\n        {\n            State = e;\n            OnProgressStateChanged(e);\n        };\n    }\n\n    private void OnProgressStateChanged(ProgressState state)\n    {\n        if (state is ProgressState.Inactive or ProgressState.Paused)\n        {\n            Progress.Text = \"Paused\";\n        }\n        else if (state == ProgressState.Working)\n        {\n            Progress.Text = \"Downloading...\";\n        }\n        else if (state == ProgressState.Success)\n        {\n            Progress.Text = \"Completed\";\n        }\n        else if (state == ProgressState.Cancelled)\n        {\n            Progress.Text = \"Cancelled\";\n        }\n        else if (state == ProgressState.Failed)\n        {\n            Progress.Text = \"Failed\";\n        }\n        else if (state == ProgressState.Pending)\n        {\n            Progress.Text = \"Waiting for other downloads to finish\";\n        }\n    }\n\n    /// <inheritdoc />\n    public override Task Cancel()\n    {\n        download.Cancel();\n        return Task.CompletedTask;\n    }\n\n    /// <inheritdoc />\n    public override Task Pause()\n    {\n        download.Pause();\n        State = ProgressState.Paused;\n        return Task.CompletedTask;\n    }\n\n    /// <inheritdoc />\n    public override Task Resume()\n    {\n        return downloadService.TryResumeDownload(download);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs",
    "content": "﻿using Avalonia.Controls;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing FluentAvalonia.UI.Controls;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Progress;\n\npublic class PackageInstallProgressItemViewModel : ProgressItemViewModelBase\n{\n    private readonly IPackageModificationRunner packageModificationRunner;\n    private BetterContentDialog? dialog;\n\n    public PackageInstallProgressItemViewModel(IPackageModificationRunner packageModificationRunner)\n    {\n        this.packageModificationRunner = packageModificationRunner;\n\n        Id = packageModificationRunner.Id;\n        Name = packageModificationRunner.CurrentStep?.ProgressTitle;\n        Progress.Value = packageModificationRunner.CurrentProgress.Percentage;\n        Progress.Text = packageModificationRunner.ConsoleOutput.LastOrDefault();\n        Progress.IsIndeterminate = packageModificationRunner.CurrentProgress.IsIndeterminate;\n        Progress.HideCloseButton = packageModificationRunner.HideCloseButton;\n\n        if (Design.IsDesignMode)\n            return;\n\n        Progress.Console.StartUpdates();\n        Progress.Console.Post(string.Join(Environment.NewLine, packageModificationRunner.ConsoleOutput));\n\n        packageModificationRunner.ProgressChanged += PackageModificationRunnerOnProgressChanged;\n    }\n\n    private void PackageModificationRunnerOnProgressChanged(object? sender, ProgressReport e)\n    {\n        Progress.Value = e.Percentage;\n        Progress.Description = e.ProcessOutput?.Text ?? e.Message;\n        Progress.IsIndeterminate = e.IsIndeterminate;\n        Progress.Text = packageModificationRunner.CurrentStep?.ProgressTitle;\n        Name = packageModificationRunner.CurrentStep?.ProgressTitle;\n        Failed = packageModificationRunner.Failed;\n\n        if (e.ProcessOutput == null && string.IsNullOrWhiteSpace(e.Message))\n            return;\n\n        if (!string.IsNullOrWhiteSpace(e.Message) && e.Message.Contains(\"Downloading...\"))\n            return;\n\n        if (e is { ProcessOutput: not null, PrintToConsole: true })\n        {\n            Progress.Console.Post(e.ProcessOutput.Value);\n        }\n        else if (e.PrintToConsole)\n        {\n            Progress.Console.PostLine(e.Message);\n        }\n\n        if (Progress.AutoScrollToBottom)\n        {\n            EventManager.Instance.OnScrollToBottomRequested();\n        }\n\n        if (\n            e is { Message: not null, Percentage: >= 100 }\n            && e.Message.Contains(\n                packageModificationRunner.ModificationCompleteMessage ?? \"Package Install Complete\"\n            )\n            && Progress.CloseWhenFinished\n        )\n        {\n            Dispatcher.UIThread.Post(() => dialog?.Hide());\n        }\n\n        if (Failed)\n        {\n            Progress.Text = \"Package Modification Failed\";\n            Name = \"Package Modification Failed\";\n        }\n    }\n\n    public async Task ShowProgressDialog()\n    {\n        Progress.CloseWhenFinished = packageModificationRunner.CloseWhenFinished;\n        dialog = new BetterContentDialog\n        {\n            MaxDialogWidth = 900,\n            MinDialogWidth = 900,\n            DefaultButton = ContentDialogButton.Close,\n            IsPrimaryButtonEnabled = false,\n            IsSecondaryButtonEnabled = false,\n            IsFooterVisible = false,\n            Content = new PackageModificationDialog { DataContext = Progress },\n        };\n        EventManager.Instance.OnToggleProgressFlyout();\n        await dialog.ShowAsync();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Progress/ProgressItemViewModel.cs",
    "content": "﻿using StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Progress;\n\npublic class ProgressItemViewModel : ProgressItemViewModelBase\n{\n    public ProgressItemViewModel(ProgressItem progressItem)\n    {\n        Id = progressItem.ProgressId;\n        Name = progressItem.Name;\n        Progress.Value = progressItem.Progress.Percentage;\n        Failed = progressItem.Failed;\n        Progress.Text = GetProgressText(progressItem.Progress);\n        Progress.IsIndeterminate = progressItem.Progress.IsIndeterminate;\n        \n        EventManager.Instance.ProgressChanged += OnProgressChanged;\n    }\n\n    private void OnProgressChanged(object? sender, ProgressItem e)\n    {\n        if (e.ProgressId != Id)\n            return;\n        \n        Progress.Value = e.Progress.Percentage;\n        Failed = e.Failed;\n        Progress.Text = GetProgressText(e.Progress);\n        Progress.IsIndeterminate = e.Progress.IsIndeterminate;\n    }\n\n    private string GetProgressText(ProgressReport report)\n    {\n        switch (report.Type)\n        {\n            case ProgressType.Generic:\n                break;\n            case ProgressType.Download:\n                return Failed ? \"Download Failed\" : \"Downloading...\";\n            case ProgressType.Extract:\n                return Failed ? \"Extraction Failed\" : \"Extracting...\";\n            case ProgressType.Update:\n                return Failed ? \"Update Failed\" : \"Updating...\";\n        }\n\n        if (Failed)\n        {\n            return \"Failed\";\n        }\n\n        return string.IsNullOrWhiteSpace(report.Message)\n            ? string.IsNullOrWhiteSpace(report.Title) \n                ? string.Empty \n                : report.Title\n            : report.Message;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Progress/ProgressManagerViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Collections;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Media.Animation;\nusing FluentIcons.Common;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Settings;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Services;\nusing Notification = DesktopNotifications.Notification;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Progress;\n\n[View(typeof(ProgressManagerPage))]\n[ManagedService]\n[RegisterSingleton<ProgressManagerViewModel>]\npublic partial class ProgressManagerViewModel : PageViewModelBase\n{\n    private readonly ITrackedDownloadService trackedDownloadService;\n    private readonly INotificationService notificationService;\n    private readonly INavigationService<MainWindowViewModel> navigationService;\n    private readonly INavigationService<SettingsViewModel> settingsNavService;\n\n    public override string Title => \"Download Manager\";\n\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.ArrowCircleDown, IconVariant = IconVariant.Filled };\n\n    public AvaloniaList<ProgressItemViewModelBase> ProgressItems { get; } = new();\n\n    [ObservableProperty]\n    private bool isOpen;\n\n    public ProgressManagerViewModel(\n        ITrackedDownloadService trackedDownloadService,\n        INotificationService notificationService,\n        INavigationService<MainWindowViewModel> navigationService,\n        INavigationService<SettingsViewModel> settingsNavService\n    )\n    {\n        this.trackedDownloadService = trackedDownloadService;\n        this.notificationService = notificationService;\n        this.navigationService = navigationService;\n        this.settingsNavService = settingsNavService;\n\n        // Attach to the event\n        trackedDownloadService.DownloadAdded += TrackedDownloadService_OnDownloadAdded;\n        EventManager.Instance.ToggleProgressFlyout += (_, _) => IsOpen = !IsOpen;\n        EventManager.Instance.PackageInstallProgressAdded += InstanceOnPackageInstallProgressAdded;\n        EventManager.Instance.RecommendedModelsDialogClosed += InstanceOnRecommendedModelsDialogClosed;\n    }\n\n    private void InstanceOnRecommendedModelsDialogClosed(object? sender, EventArgs e)\n    {\n        var vm = ProgressItems.OfType<PackageInstallProgressItemViewModel>().FirstOrDefault();\n        vm?.ShowProgressDialog().SafeFireAndForget();\n    }\n\n    private void InstanceOnPackageInstallProgressAdded(object? sender, IPackageModificationRunner runner)\n    {\n        AddPackageInstall(runner).SafeFireAndForget();\n    }\n\n    private void TrackedDownloadService_OnDownloadAdded(object? sender, TrackedDownload e)\n    {\n        // Attach notification handlers\n        // Use Changing because Changed might be called after the download is removed\n        e.ProgressStateChanged += (s, state) =>\n        {\n            Debug.WriteLine($\"Download {e.FileName} state changed to {state}\");\n            var download = s as TrackedDownload;\n\n            switch (state)\n            {\n                case ProgressState.Success:\n                    var imageFile = e\n                        .DownloadDirectory.EnumerateFiles(\n                            $\"{Path.GetFileNameWithoutExtension(e.FileName)}.preview.*\"\n                        )\n                        .FirstOrDefault();\n\n                    notificationService\n                        .ShowAsync(\n                            NotificationKey.Download_Completed,\n                            new Notification\n                            {\n                                Title = \"Download Completed\",\n                                Body = $\"Download of {e.FileName} completed successfully.\",\n                                BodyImagePath = imageFile?.FullPath,\n                            }\n                        )\n                        .SafeFireAndForget();\n\n                    break;\n                case ProgressState.Failed:\n                    var msg = \"\";\n                    if (download?.Exception is { } exception)\n                    {\n                        msg =\n                            $\"({exception.GetType().Name}) {exception.InnerException?.Message ?? exception.Message}\";\n\n                        if (\n                            exception is EarlyAccessException\n                            || exception.InnerException is EarlyAccessException\n                        )\n                        {\n                            msg =\n                                \"This asset is in Early Access. Please check the asset page for more information.\";\n                        }\n                        else if (\n                            exception is CivitLoginRequiredException\n                            || exception.InnerException is CivitLoginRequiredException\n                        )\n                        {\n                            ShowCivitLoginRequiredDialog();\n                            return;\n                        }\n                        else if (\n                            exception is HuggingFaceLoginRequiredException\n                            || exception.InnerException is HuggingFaceLoginRequiredException\n                        )\n                        {\n                            ShowHuggingFaceLoginRequiredDialog();\n                            return;\n                        }\n                        else if (\n                            exception is CivitDownloadDisabledException\n                            || exception.InnerException is CivitDownloadDisabledException\n                        )\n                        {\n                            Dispatcher.UIThread.InvokeAsync(async () =>\n                                await notificationService.ShowPersistentAsync(\n                                    NotificationKey.Download_Failed,\n                                    new Notification\n                                    {\n                                        Title = \"Download Disabled\",\n                                        Body =\n                                            $\"The creator of {e.FileName} has disabled downloads on this file\",\n                                    }\n                                )\n                            );\n                            return;\n                        }\n                    }\n\n                    Dispatcher.UIThread.InvokeAsync(async () =>\n                        await notificationService.ShowPersistentAsync(\n                            NotificationKey.Download_Failed,\n                            new Notification\n                            {\n                                Title = \"Download Failed\",\n                                Body = $\"Download of {e.FileName} failed: {msg}\",\n                            }\n                        )\n                    );\n\n                    break;\n                case ProgressState.Cancelled:\n                    notificationService\n                        .ShowAsync(\n                            NotificationKey.Download_Canceled,\n                            new Notification\n                            {\n                                Title = \"Download Cancelled\",\n                                Body = $\"Download of {e.FileName} was cancelled.\",\n                            }\n                        )\n                        .SafeFireAndForget();\n                    break;\n            }\n        };\n\n        var vm = new DownloadProgressItemViewModel(trackedDownloadService, e);\n\n        ProgressItems.Add(vm);\n    }\n\n    private void ShowCivitLoginRequiredDialog()\n    {\n        Dispatcher.UIThread.InvokeAsync(async () =>\n        {\n            var errorDialog = new BetterContentDialog\n            {\n                Title = Resources.Label_DownloadFailed,\n                Content = Resources.Label_CivitAiLoginRequired,\n                PrimaryButtonText = \"Go to Settings\",\n                SecondaryButtonText = \"Close\",\n                DefaultButton = ContentDialogButton.Primary,\n            };\n\n            var result = await errorDialog.ShowAsync();\n            if (result == ContentDialogResult.Primary)\n            {\n                navigationService.NavigateTo<SettingsViewModel>(new SuppressNavigationTransitionInfo());\n                await Task.Delay(100);\n                settingsNavService.NavigateTo<AccountSettingsViewModel>(\n                    new SuppressNavigationTransitionInfo()\n                );\n            }\n        });\n    }\n\n    private void ShowHuggingFaceLoginRequiredDialog()\n    {\n        Dispatcher.UIThread.InvokeAsync(async () =>\n        {\n            var errorDialog = new BetterContentDialog\n            {\n                Title = Resources.Label_DownloadFailed,\n                Content = Resources.Label_HuggingFaceLoginRequired,\n                PrimaryButtonText = \"Go to Settings\",\n                SecondaryButtonText = \"Close\",\n                DefaultButton = ContentDialogButton.Primary,\n            };\n\n            var result = await errorDialog.ShowAsync();\n            if (result == ContentDialogResult.Primary)\n            {\n                navigationService.NavigateTo<SettingsViewModel>(new SuppressNavigationTransitionInfo());\n                await Task.Delay(100);\n                settingsNavService.NavigateTo<AccountSettingsViewModel>(\n                    new SuppressNavigationTransitionInfo()\n                );\n            }\n        });\n    }\n\n    public void AddDownloads(IEnumerable<TrackedDownload> downloads)\n    {\n        foreach (var download in downloads)\n        {\n            if (ProgressItems.Any(vm => vm.Id == download.Id))\n                continue;\n            var vm = new DownloadProgressItemViewModel(trackedDownloadService, download);\n            ProgressItems.Add(vm);\n        }\n    }\n\n    private Task AddPackageInstall(IPackageModificationRunner packageModificationRunner)\n    {\n        if (ProgressItems.Any(vm => vm.Id == packageModificationRunner.Id))\n            return Task.CompletedTask;\n\n        var vm = new PackageInstallProgressItemViewModel(packageModificationRunner);\n        ProgressItems.Add(vm);\n\n        return packageModificationRunner.ShowDialogOnStart ? vm.ShowProgressDialog() : Task.CompletedTask;\n    }\n\n    private void ShowFailedNotification(string title, string message)\n    {\n        notificationService.ShowPersistent(title, message, NotificationType.Error);\n    }\n\n    public void StartEventListener()\n    {\n        EventManager.Instance.ProgressChanged += OnProgressChanged;\n    }\n\n    public void ClearDownloads()\n    {\n        ProgressItems.RemoveAll(ProgressItems.Where(x => x.IsCompleted));\n    }\n\n    private void OnProgressChanged(object? sender, ProgressItem e)\n    {\n        if (ProgressItems.Any(x => x.Id == e.ProgressId))\n            return;\n\n        ProgressItems.Add(new ProgressItemViewModel(e));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/RefreshBadgeViewModel.cs",
    "content": "﻿using System;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Threading.Tasks;\nusing Avalonia.Media;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Styles;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(RefreshBadge))]\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\n[ManagedService]\n[RegisterTransient<RefreshBadgeViewModel>]\npublic partial class RefreshBadgeViewModel : ViewModelBase\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    public string WorkingToolTipText { get; set; } = \"Loading...\";\n    public string SuccessToolTipText { get; set; } = \"Success\";\n    public string InactiveToolTipText { get; set; } = \"\";\n    public string FailToolTipText { get; set; } = \"Failed\";\n\n    public Symbol InactiveIcon { get; set; } = Symbol.Clear;\n    public Symbol SuccessIcon { get; set; } = Symbol.Checkmark;\n    public Symbol FailIcon { get; set; } = Symbol.AlertUrgent;\n\n    public IBrush SuccessColorBrush { get; set; } = ThemeColors.ThemeGreen;\n    public IBrush InactiveColorBrush { get; set; } = ThemeColors.ThemeYellow;\n    public IBrush FailColorBrush { get; set; } = ThemeColors.ThemeYellow;\n\n    public Func<Task<bool>>? RefreshFunc { get; set; }\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsWorking))]\n    [NotifyPropertyChangedFor(nameof(ColorBrush))]\n    [NotifyPropertyChangedFor(nameof(CurrentToolTip))]\n    [NotifyPropertyChangedFor(nameof(Icon))]\n    private ProgressState state;\n\n    public bool IsWorking => State == ProgressState.Working;\n\n    /*public ControlAppearance Appearance => State switch\n    {\n        ProgressState.Working => ControlAppearance.Info,\n        ProgressState.Success => ControlAppearance.Success,\n        ProgressState.Failed => ControlAppearance.Danger,\n        _ => ControlAppearance.Secondary\n    };*/\n\n    public IBrush ColorBrush =>\n        State switch\n        {\n            ProgressState.Success => SuccessColorBrush,\n            ProgressState.Inactive => InactiveColorBrush,\n            ProgressState.Failed => FailColorBrush,\n            _ => Brushes.Gray\n        };\n\n    public string CurrentToolTip =>\n        State switch\n        {\n            ProgressState.Working => WorkingToolTipText,\n            ProgressState.Success => SuccessToolTipText,\n            ProgressState.Inactive => InactiveToolTipText,\n            ProgressState.Failed => FailToolTipText,\n            _ => \"\"\n        };\n\n    public Symbol Icon =>\n        State switch\n        {\n            ProgressState.Success => SuccessIcon,\n            ProgressState.Failed => FailIcon,\n            _ => InactiveIcon\n        };\n\n    [RelayCommand]\n    private async Task Refresh()\n    {\n        Logger.Info(\"Running refresh command...\");\n        if (RefreshFunc == null)\n            return;\n\n        State = ProgressState.Working;\n        try\n        {\n            var result = await RefreshFunc.Invoke();\n            State = result ? ProgressState.Success : ProgressState.Failed;\n        }\n        catch (Exception ex)\n        {\n            State = ProgressState.Failed;\n            Logger.Error(ex, \"Refresh command failed: {Ex}\", ex.Message);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/RunningPackageViewModel.cs",
    "content": "﻿using System;\nusing System.Reactive.Disposables;\nusing System.Threading.Tasks;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\nusing TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(ConsoleOutputPage))]\npublic partial class RunningPackageViewModel : PageViewModelBase, IDisposable, IAsyncDisposable\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly INotificationService notificationService;\n    private readonly RunningPackageService runningPackageService;\n    private readonly RunPackageOptions runPackageOptions;\n\n    private readonly CompositeDisposable subscriptions = new();\n\n    public PackagePair RunningPackage { get; }\n    public ConsoleViewModel Console { get; }\n    public override string Title => RunningPackage.InstalledPackage.DisplayName ?? \"Running Package\";\n    public override IconSource IconSource => new SymbolIconSource();\n\n    [ObservableProperty]\n    private bool autoScrollToEnd;\n\n    [ObservableProperty]\n    private bool showWebUiButton;\n\n    [ObservableProperty]\n    private string webUiUrl = string.Empty;\n\n    [ObservableProperty]\n    private bool isRunning = true;\n\n    [ObservableProperty]\n    private string consoleInput = string.Empty;\n\n    [ObservableProperty]\n    private bool showWebUiTeachingTip;\n\n    /// <inheritdoc/>\n    public RunningPackageViewModel(\n        ISettingsManager settingsManager,\n        INotificationService notificationService,\n        RunningPackageService runningPackageService,\n        PackagePair runningPackage,\n        RunPackageOptions runPackageOptions,\n        ConsoleViewModel console\n    )\n    {\n        this.settingsManager = settingsManager;\n        this.notificationService = notificationService;\n        this.runningPackageService = runningPackageService;\n        this.runPackageOptions = runPackageOptions;\n\n        RunningPackage = runningPackage;\n        Console = console;\n        Console.MaxLines = settingsManager.Settings.ConsoleLogHistorySize;\n        Console.Document.LineCountChanged += DocumentOnLineCountChanged;\n        RunningPackage.BasePackage.StartupComplete += BasePackageOnStartupComplete;\n        RunningPackage.BasePackage.Exited += BasePackageOnExited;\n\n        subscriptions.Add(\n            settingsManager.RegisterPropertyChangedHandler(\n                settings => settings.ConsoleLogHistorySize,\n                newValue =>\n                {\n                    Console.MaxLines = newValue;\n                }\n            )\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.AutoScrollToEnd,\n            settings => settings.AutoScrollLaunchConsoleToEnd,\n            true\n        );\n    }\n\n    public override void OnLoaded()\n    {\n        if (AutoScrollToEnd)\n        {\n            EventManager.Instance.OnScrollToBottomRequested();\n        }\n    }\n\n    private void BasePackageOnExited(object? sender, int e)\n    {\n        IsRunning = false;\n        ShowWebUiButton = false;\n        Console.Document.LineCountChanged -= DocumentOnLineCountChanged;\n        RunningPackage.BasePackage.StartupComplete -= BasePackageOnStartupComplete;\n        RunningPackage.BasePackage.Exited -= BasePackageOnExited;\n        runningPackageService.RunningPackages.Remove(RunningPackage.InstalledPackage.Id);\n    }\n\n    private void BasePackageOnStartupComplete(object? sender, string url)\n    {\n        WebUiUrl = url.Replace(\"0.0.0.0\", \"127.0.0.1\");\n        ShowWebUiButton = !string.IsNullOrWhiteSpace(WebUiUrl);\n\n        if (settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.WebUiButtonMovedTip))\n            return;\n\n        ShowWebUiTeachingTip = true;\n        settingsManager.Transaction(s => s.SeenTeachingTips.Add(TeachingTip.WebUiButtonMovedTip));\n    }\n\n    private void DocumentOnLineCountChanged(object? sender, EventArgs e)\n    {\n        if (AutoScrollToEnd)\n        {\n            EventManager.Instance.OnScrollToBottomRequested();\n        }\n    }\n\n    partial void OnAutoScrollToEndChanged(bool value)\n    {\n        if (value)\n        {\n            EventManager.Instance.OnScrollToBottomRequested();\n        }\n    }\n\n    [RelayCommand]\n    private async Task Restart()\n    {\n        await Stop();\n        await Task.Delay(100);\n        LaunchPackage();\n    }\n\n    [RelayCommand]\n    private void LaunchPackage()\n    {\n        EventManager.Instance.OnPackageRelaunchRequested(RunningPackage.InstalledPackage, runPackageOptions);\n    }\n\n    [RelayCommand]\n    private async Task Stop()\n    {\n        IsRunning = false;\n        await runningPackageService.StopPackage(RunningPackage.InstalledPackage.Id);\n        Console.PostLine($\"{Environment.NewLine}Stopped process at {DateTimeOffset.Now}\");\n        await Console.StopUpdatesAsync();\n    }\n\n    [RelayCommand]\n    private void LaunchWebUi()\n    {\n        if (string.IsNullOrEmpty(WebUiUrl))\n            return;\n\n        notificationService.TryAsync(\n            Task.Run(() => ProcessRunner.OpenUrl(WebUiUrl)),\n            \"Failed to open URL\",\n            $\"{WebUiUrl}\"\n        );\n    }\n\n    [RelayCommand]\n    private async Task SendToConsole()\n    {\n        Console.PostLine(ConsoleInput);\n        if (RunningPackage?.BasePackage is BaseGitPackage gitPackage)\n        {\n            var venv = gitPackage.VenvRunner;\n            var process = venv?.Process;\n            if (process is not null)\n            {\n                await process.StandardInput.WriteLineAsync(ConsoleInput);\n            }\n        }\n\n        ConsoleInput = string.Empty;\n    }\n\n    protected override void Dispose(bool disposing)\n    {\n        if (disposing)\n        {\n            RunningPackage.BasePackage.Shutdown();\n            Console.Dispose();\n            subscriptions.Dispose();\n        }\n\n        base.Dispose(disposing);\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        RunningPackage.BasePackage.Shutdown();\n        await Console.DisposeAsync();\n        subscriptions.Dispose();\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Settings/AccountSettingsViewModel.cs",
    "content": "﻿using System;\nusing System.ComponentModel.DataAnnotations;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications; // Added this line\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing FluentAvalonia.UI.Controls;\nusing FluentIcons.Common;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Options;\nusing OpenIddict.Client;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.Views.Settings;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Api.LykosAuthApi;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Api.Lykos;\nusing StabilityMatrix.Core.Models.Configs;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\nusing TeachingTip = StabilityMatrix.Core.Models.Settings.TeachingTip;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Settings;\n\n[View(typeof(AccountSettingsPage))]\n[ManagedService]\n[RegisterSingleton<AccountSettingsViewModel>]\npublic partial class AccountSettingsViewModel : PageViewModelBase\n{\n    private readonly IAccountsService accountsService;\n    private readonly ISettingsManager settingsManager;\n    private readonly IServiceManager<ViewModelBase> vmFactory;\n    private readonly INotificationService notificationService;\n    private readonly ILykosAuthApiV2 lykosAuthApi;\n    private readonly IOptions<ApiOptions> apiOptions;\n\n    /// <inheritdoc />\n    public override string Title => \"Accounts\";\n\n    /// <inheritdoc />\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.Person, IconVariant = IconVariant.Filled };\n\n    [ObservableProperty]\n    [NotifyCanExecuteChangedFor(nameof(ConnectLykosCommand))]\n    [NotifyCanExecuteChangedFor(nameof(ConnectPatreonCommand))]\n    [NotifyCanExecuteChangedFor(nameof(ConnectCivitCommand))]\n    [NotifyCanExecuteChangedFor(nameof(ConnectHuggingFaceCommand))]\n    private bool isInitialUpdateFinished;\n\n    [ObservableProperty]\n    private string? lykosProfileImageUrl;\n\n    [ObservableProperty]\n    private bool isPatreonConnected;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(LykosProfileImageUrl))]\n    private LykosAccountStatusUpdateEventArgs lykosStatus = LykosAccountStatusUpdateEventArgs.Disconnected;\n\n    [ObservableProperty]\n    private CivitAccountStatusUpdateEventArgs civitStatus = CivitAccountStatusUpdateEventArgs.Disconnected;\n\n    // Assume HuggingFaceAccountStatusUpdateEventArgs will be created with at least these properties\n    // For now, using a placeholder or assuming a structure like:\n    // public record HuggingFaceAccountStatusUpdateEventArgs(bool IsConnected, string? Username);\n    // Initialize with a disconnected state.\n    [ObservableProperty]\n    private HuggingFaceAccountStatusUpdateEventArgs huggingFaceStatus = new(false, null);\n\n    [ObservableProperty]\n    private bool isHuggingFaceConnected;\n\n    [ObservableProperty]\n    private string huggingFaceUsernameWithParentheses = string.Empty;\n\n    public string LykosAccountManageUrl =>\n        apiOptions.Value.LykosAccountApiBaseUrl.Append(\"/manage\").ToString();\n\n    public AccountSettingsViewModel(\n        IAccountsService accountsService,\n        ISettingsManager settingsManager,\n        IServiceManager<ViewModelBase> vmFactory,\n        INotificationService notificationService,\n        ILykosAuthApiV2 lykosAuthApi,\n        IOptions<ApiOptions> apiOptions\n    )\n    {\n        this.accountsService = accountsService;\n        this.settingsManager = settingsManager;\n        this.vmFactory = vmFactory;\n        this.notificationService = notificationService;\n        this.lykosAuthApi = lykosAuthApi;\n        this.apiOptions = apiOptions;\n\n        accountsService.LykosAccountStatusUpdate += (_, args) =>\n        {\n            Dispatcher.UIThread.Post(() =>\n            {\n                IsInitialUpdateFinished = true;\n                LykosStatus = args;\n                IsPatreonConnected = args.IsPatreonConnected;\n            });\n        };\n\n        accountsService.CivitAccountStatusUpdate += (_, args) =>\n        {\n            Dispatcher.UIThread.Post(() =>\n            {\n                IsInitialUpdateFinished = true;\n                CivitStatus = args;\n            });\n        };\n\n        accountsService.HuggingFaceAccountStatusUpdate += (_, args) =>\n        {\n            Dispatcher.UIThread.Post(() =>\n            {\n                IsInitialUpdateFinished = true;\n                HuggingFaceStatus = args;\n                // IsHuggingFaceConnected and HuggingFaceUsernameWithParentheses will be updated by OnHuggingFaceStatusChanged\n            });\n        };\n    }\n\n    /// <inheritdoc />\n    public override void OnLoaded()\n    {\n        base.OnLoaded();\n\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        accountsService.RefreshAsync().SafeFireAndForget();\n    }\n\n    private async Task<bool> BeforeConnectCheck()\n    {\n        // Show credentials storage notice if not seen\n        if (!settingsManager.Settings.SeenTeachingTips.Contains(TeachingTip.AccountsCredentialsStorageNotice))\n        {\n            var dialog = new BetterContentDialog\n            {\n                Title = \"About Account Credentials\",\n                Content = \"\"\"\n                    Account credentials and tokens are stored locally on your computer, with at-rest AES encryption. \n\n                    If you make changes to your computer hardware, you may need to re-login to your accounts.\n\n                    Account tokens will not be viewable after saving, please make a note of them if you need to use them elsewhere.\n                    \"\"\",\n                PrimaryButtonText = Resources.Action_Continue,\n                CloseButtonText = Resources.Action_Cancel,\n                DefaultButton = ContentDialogButton.Primary,\n                MaxDialogWidth = 400,\n            };\n\n            if (await dialog.ShowAsync() != ContentDialogResult.Primary)\n            {\n                return false;\n            }\n\n            settingsManager.Transaction(s =>\n                s.SeenTeachingTips.Add(TeachingTip.AccountsCredentialsStorageNotice)\n            );\n        }\n\n        return true;\n    }\n\n    [RelayCommand(CanExecute = nameof(IsInitialUpdateFinished))]\n    private async Task ConnectLykos()\n    {\n        if (!await BeforeConnectCheck())\n            return;\n\n        var vm = vmFactory.Get<OAuthDeviceAuthViewModel>();\n        vm.ChallengeRequest = new OpenIddictClientModels.DeviceChallengeRequest\n        {\n            ProviderName = OpenIdClientConstants.LykosAccount.ProviderName,\n        };\n        await vm.ShowDialogAsync();\n\n        if (vm.AuthenticationResult is { } result)\n        {\n            await accountsService.LykosAccountV2LoginAsync(\n                new LykosAccountV2Tokens(result.AccessToken, result.RefreshToken, result.IdentityToken)\n            );\n        }\n    }\n\n    [RelayCommand]\n    private Task DisconnectLykos()\n    {\n        return accountsService.LykosAccountV2LogoutAsync();\n    }\n\n    [RelayCommand(CanExecute = nameof(IsInitialUpdateFinished))]\n    private async Task ConnectPatreon()\n    {\n        if (!await BeforeConnectCheck())\n            return;\n\n        if (LykosStatus.User?.Id is null)\n            return;\n\n        var urlResult = await notificationService.TryAsync(\n            lykosAuthApi.ApiV2OauthPatreonLink(Program.MessagePipeUri.Append(\"/oauth/patreon/callback\"))\n        );\n\n        if (!urlResult.IsSuccessful || urlResult.Result is not { } url)\n        {\n            return;\n        }\n\n        ProcessRunner.OpenUrl(urlResult.Result);\n\n        var dialogVm = vmFactory.Get<OAuthConnectViewModel>();\n        dialogVm.Title = \"Connect Patreon Account\";\n        dialogVm.Url = url.ToString();\n\n        if (await dialogVm.GetDialog().ShowAsync() == ContentDialogResult.Primary)\n        {\n            await accountsService.RefreshAsync();\n\n            // Bring main window to front since browser is probably covering\n            var main = App.TopLevel as Window;\n            main?.Activate();\n        }\n    }\n\n    [RelayCommand]\n    private async Task DisconnectPatreon()\n    {\n        await notificationService.TryAsync(accountsService.LykosPatreonOAuthLogoutAsync());\n    }\n\n    [RelayCommand(CanExecute = nameof(IsInitialUpdateFinished))]\n    private async Task ConnectCivit()\n    {\n        if (!await BeforeConnectCheck())\n            return;\n\n        var textFields = new TextBoxField[]\n        {\n            new()\n            {\n                Label = Resources.Label_ApiKey,\n                IsPassword = true, // Added this line\n                Validator = s =>\n                {\n                    if (string.IsNullOrWhiteSpace(s))\n                    {\n                        throw new ValidationException(\"API key is required\");\n                    }\n                },\n            },\n        };\n\n        var dialog = DialogHelper.CreateTextEntryDialog(\n            \"Connect CivitAI Account\",\n            \"\"\"\n            Login to [CivitAI](https://civitai.com/) and head to your [Account](https://civitai.com/user/account) page\n\n            Add a new API key and paste it below\n            \"\"\",\n            \"avares://StabilityMatrix.Avalonia/Assets/guide-civitai-api.webp\",\n            textFields\n        );\n        dialog.PrimaryButtonText = Resources.Action_Connect;\n\n        if (await dialog.ShowAsync() != ContentDialogResult.Primary || textFields[0].Text is not { } apiToken)\n        {\n            return;\n        }\n\n        var result = await notificationService.TryAsync(accountsService.CivitLoginAsync(apiToken));\n\n        if (result.IsSuccessful)\n        {\n            await accountsService.RefreshAsync();\n        }\n    }\n\n    [RelayCommand]\n    private Task DisconnectCivit()\n    {\n        return accountsService.CivitLogoutAsync();\n    }\n\n    [RelayCommand(CanExecute = nameof(IsInitialUpdateFinished))]\n    private async Task ConnectHuggingFace()\n    {\n        if (!await BeforeConnectCheck())\n            return;\n\n        var field = new TextBoxField \n        {\n            Label = \"Hugging Face Token\", // Assuming Label is for the prompt\n            IsPassword = true, // Assuming TextBoxField has an IsPassword property\n            Validator = s =>\n            {\n                if (string.IsNullOrWhiteSpace(s))\n                {\n                    throw new ValidationException(\"Token is required\");\n                }\n            },\n        };\n\n        var dialog = DialogHelper.CreateTextEntryDialog(\n            \"Connect Hugging Face Account\",\n            \"Go to [Hugging Face settings](https://huggingface.co/settings/tokens) to create a new Access Token. Ensure it has read permissions. Paste the token below.\",\n            [field] \n        );\n\n        var result = await dialog.ShowAsync();\n\n        if (result == ContentDialogResult.Primary && !string.IsNullOrWhiteSpace(field.Text))\n        {\n            await accountsService.HuggingFaceLoginAsync(field.Text); \n            await accountsService.RefreshAsync();\n        }\n    }\n\n    [RelayCommand]\n    private Task DisconnectHuggingFace()\n    {\n        // Assuming HuggingFaceLogoutAsync will be added to IAccountsService\n        return accountsService.HuggingFaceLogoutAsync();\n    }\n\n    /// <summary>\n    /// Update the Lykos profile image URL when the user changes.\n    /// </summary>\n    partial void OnLykosStatusChanged(LykosAccountStatusUpdateEventArgs value)\n    {\n        if (value.Email is { } userEmail)\n        {\n            userEmail = userEmail.Trim().ToLowerInvariant();\n\n            var hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(userEmail));\n            var hash = BitConverter.ToString(hashBytes).Replace(\"-\", \"\").ToLowerInvariant();\n\n            LykosProfileImageUrl = $\"https://gravatar.com/avatar/{hash}?s=512&d=retro\";\n        }\n        else\n        {\n            LykosProfileImageUrl = null;\n        }\n    }\n\n    partial void OnHuggingFaceStatusChanged(HuggingFaceAccountStatusUpdateEventArgs value)\n    {\n        IsHuggingFaceConnected = value.IsConnected;\n\n        if (value.IsConnected)\n        {\n            if (!string.IsNullOrWhiteSpace(value.Username))\n            {\n                HuggingFaceUsernameWithParentheses = $\"({value.Username})\";\n            }\n            else\n            {\n                HuggingFaceUsernameWithParentheses = \"(Connected)\"; // Fallback if no username\n            }\n        }\n        else\n        {\n            HuggingFaceUsernameWithParentheses = string.Empty;\n            if (!string.IsNullOrWhiteSpace(value.ErrorMessage))\n            {\n                // Assuming INotificationService.Show takes these parameters and NotificationType.Error is valid.\n                // Dispatcher.UIThread.Post might be needed if Show itself doesn't handle UI thread marshalling,\n                // but usually notification services are designed to be called from any thread.\n                // The event handler for HuggingFaceAccountStatusUpdate already posts to UIThread,\n                // so this method (OnHuggingFaceStatusChanged) is already on the UI thread.\n                notificationService.Show(\n                    \"Hugging Face Connection Error\",\n                    $\"Failed to connect Hugging Face account: {value.ErrorMessage}. Please check your token and try again.\",\n                    NotificationType.Error, // Assuming NotificationType.Error exists and is correct\n                    TimeSpan.FromSeconds(10) // Display for 10 seconds, or TimeSpan.Zero for persistent\n                );\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Settings/AnalyticsSettingsViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Settings;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Settings;\n\n[View(typeof(AnalyticsSettingsPage))]\n[ManagedService]\n[RegisterSingleton<AnalyticsSettingsViewModel>]\npublic partial class AnalyticsSettingsViewModel : PageViewModelBase\n{\n    public override string Title => Resources.Label_Analytics;\n\n    /// <inheritdoc />\n    public override IconSource IconSource => new FASymbolIconSource { Symbol = @\"fa-solid fa-chart-simple\" };\n\n    [ObservableProperty]\n    private bool isPackageInstallAnalyticsEnabled;\n\n    public AnalyticsSettingsViewModel(ISettingsManager settingsManager)\n    {\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.IsPackageInstallAnalyticsEnabled,\n            s => s.Analytics.IsUsageDataEnabled,\n            true\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.ComponentModel.DataAnnotations;\nusing System.Reactive.Linq;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Data;\nusing Avalonia.Platform.Storage;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData.Binding;\nusing FluentAvalonia.UI.Controls;\nusing FluentIcons.Common;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Settings;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Settings;\n\n[View(typeof(InferenceSettingsPage))]\n[ManagedService]\n[RegisterSingleton<InferenceSettingsViewModel>]\npublic partial class InferenceSettingsViewModel : PageViewModelBase\n{\n    private readonly INotificationService notificationService;\n    private readonly ISettingsManager settingsManager;\n    private readonly ICompletionProvider completionProvider;\n\n    /// <inheritdoc />\n    public override string Title => \"Inference\";\n\n    /// <inheritdoc />\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.Settings, IconVariant = IconVariant.Filled };\n\n    [ObservableProperty]\n    private bool isPromptCompletionEnabled = true;\n\n    [ObservableProperty]\n    private IReadOnlyList<string> availableTagCompletionCsvs = Array.Empty<string>();\n\n    [ObservableProperty]\n    private string? selectedTagCompletionCsv;\n\n    [ObservableProperty]\n    private bool isCompletionRemoveUnderscoresEnabled = true;\n\n    [ObservableProperty]\n    [CustomValidation(typeof(InferenceSettingsViewModel), nameof(ValidateOutputImageFileNameFormat))]\n    private string? outputImageFileNameFormat;\n\n    [ObservableProperty]\n    private string? outputImageFileNameFormatSample;\n\n    [ObservableProperty]\n    private bool isInferenceImageBrowserUseRecycleBinForDelete = true;\n\n    [ObservableProperty]\n    private bool filterExtraNetworksByBaseModel;\n\n    private List<string> ignoredFileNameFormatVars =\n    [\n        \"author\",\n        \"model_version_name\",\n        \"base_model\",\n        \"file_name\",\n        \"model_type\",\n        \"model_id\",\n        \"model_version_id\",\n        \"file_id\",\n    ];\n\n    [ObservableProperty]\n    public partial int InferenceDimensionStepChange { get; set; }\n\n    [ObservableProperty]\n    public partial ObservableHashSet<string> FavoriteDimensions { get; set; } = [];\n\n    public IEnumerable<FileNameFormatVar> OutputImageFileNameFormatVars =>\n        FileNameFormatProvider\n            .GetSample()\n            .Substitutions.Where(kv => !ignoredFileNameFormatVars.Contains(kv.Key))\n            .Select(kv => new FileNameFormatVar { Variable = $\"{{{kv.Key}}}\", Example = kv.Value.Invoke() });\n\n    [ObservableProperty]\n    private bool isImageViewerPixelGridEnabled = true;\n\n    public InferenceSettingsViewModel(\n        INotificationService notificationService,\n        IPrerequisiteHelper prerequisiteHelper,\n        IPyRunner pyRunner,\n        IServiceManager<ViewModelBase> dialogFactory,\n        ICompletionProvider completionProvider,\n        ITrackedDownloadService trackedDownloadService,\n        IModelIndexService modelIndexService,\n        INavigationService<SettingsViewModel> settingsNavigationService,\n        IAccountsService accountsService,\n        ISettingsManager settingsManager\n    )\n    {\n        this.settingsManager = settingsManager;\n        this.notificationService = notificationService;\n        this.completionProvider = completionProvider;\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.SelectedTagCompletionCsv,\n            settings => settings.TagCompletionCsv\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.IsPromptCompletionEnabled,\n            settings => settings.IsPromptCompletionEnabled,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.IsCompletionRemoveUnderscoresEnabled,\n            settings => settings.IsCompletionRemoveUnderscoresEnabled,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.IsInferenceImageBrowserUseRecycleBinForDelete,\n            settings => settings.IsInferenceImageBrowserUseRecycleBinForDelete,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.FilterExtraNetworksByBaseModel,\n            settings => settings.FilterExtraNetworksByBaseModel,\n            true\n        );\n\n        this.WhenPropertyChanged(vm => vm.OutputImageFileNameFormat)\n            .Throttle(TimeSpan.FromMilliseconds(50))\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(formatProperty =>\n            {\n                var provider = FileNameFormatProvider.GetSample();\n                var template = formatProperty.Value ?? string.Empty;\n\n                if (\n                    !string.IsNullOrEmpty(template)\n                    && provider.Validate(template) == ValidationResult.Success\n                )\n                {\n                    var format = FileNameFormat.Parse(template, provider);\n                    OutputImageFileNameFormatSample = format.GetFileName() + \".png\";\n                }\n                else\n                {\n                    // Use default format if empty\n                    var defaultFormat = FileNameFormat.Parse(FileNameFormat.DefaultTemplate, provider);\n                    OutputImageFileNameFormatSample = defaultFormat.GetFileName() + \".png\";\n                }\n            });\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.OutputImageFileNameFormat,\n            settings => settings.InferenceOutputImageFileNameFormat,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.IsImageViewerPixelGridEnabled,\n            settings => settings.IsImageViewerPixelGridEnabled,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.InferenceDimensionStepChange,\n            settings => settings.InferenceDimensionStepChange,\n            true\n        );\n\n        FavoriteDimensions\n            .ToObservableChangeSet()\n            .Throttle(TimeSpan.FromMilliseconds(50))\n            .ObserveOn(SynchronizationContext.Current)\n            .Subscribe(_ =>\n            {\n                if (\n                    FavoriteDimensions is not { Count: > 0 }\n                    || FavoriteDimensions.SetEquals(settingsManager.Settings.SavedInferenceDimensions)\n                )\n                    return;\n\n                settingsManager.Transaction(s => s.SavedInferenceDimensions = FavoriteDimensions.ToHashSet());\n            });\n\n        ImportTagCsvCommand.WithNotificationErrorHandler(notificationService, LogLevel.Warn);\n    }\n\n    /// <summary>\n    /// Validator for <see cref=\"OutputImageFileNameFormat\"/>\n    /// </summary>\n    public static ValidationResult ValidateOutputImageFileNameFormat(\n        string? format,\n        ValidationContext context\n    )\n    {\n        return FileNameFormatProvider.GetSample().Validate(format ?? string.Empty);\n    }\n\n    /// <inheritdoc />\n    public override void OnLoaded()\n    {\n        base.OnLoaded();\n        FavoriteDimensions.Clear();\n        FavoriteDimensions.AddRange(\n            settingsManager.Settings.SavedInferenceDimensions.OrderDescending(\n                DimensionStringComparer.Instance\n            )\n        );\n\n        UpdateAvailableTagCompletionCsvs();\n    }\n\n    #region Commands\n\n    [RelayCommand(FlowExceptionsToTaskScheduler = true)]\n    private async Task ImportTagCsv()\n    {\n        var storage = App.StorageProvider;\n        var files = await storage.OpenFilePickerAsync(\n            new FilePickerOpenOptions\n            {\n                FileTypeFilter = new List<FilePickerFileType> { new(\"CSV\") { Patterns = [\"*.csv\"] } },\n            }\n        );\n\n        if (files.Count == 0)\n            return;\n\n        var sourceFile = new FilePath(files[0].TryGetLocalPath()!);\n\n        var tagsDir = settingsManager.TagsDirectory;\n        tagsDir.Create();\n\n        // Copy to tags directory\n        var targetFile = tagsDir.JoinFile(sourceFile.Name);\n        await sourceFile.CopyToAsync(targetFile);\n\n        // Update index\n        UpdateAvailableTagCompletionCsvs();\n\n        // Trigger load\n        completionProvider.BackgroundLoadFromFile(targetFile, true);\n\n        notificationService.Show(\n            $\"Imported {sourceFile.Name}\",\n            $\"The {sourceFile.Name} file has been imported.\",\n            NotificationType.Success\n        );\n    }\n\n    [RelayCommand]\n    private async Task AddRow()\n    {\n        // FavoriteDimensions.Add(string.Empty);\n        var textFields = new TextBoxField[]\n        {\n            new()\n            {\n                Label = \"Width\",\n                Validator = text =>\n                {\n                    if (string.IsNullOrWhiteSpace(text))\n                        throw new DataValidationException(\"Width is required\");\n\n                    if (!int.TryParse(text, out var width) || width <= 0)\n                        throw new DataValidationException(\"Width must be a positive integer\");\n                },\n                Watermark = \"1024\",\n            },\n            new()\n            {\n                Label = \"Height\",\n                Validator = text =>\n                {\n                    if (string.IsNullOrWhiteSpace(text))\n                        throw new DataValidationException(\"Height is required\");\n\n                    if (!int.TryParse(text, out var height) || height <= 0)\n                        throw new DataValidationException(\"Height must be a positive integer\");\n                },\n                Watermark = \"1024\",\n            },\n        };\n\n        var dialog = DialogHelper.CreateTextEntryDialog(\"Add Favorite Dimensions\", \"\", textFields);\n\n        if (await dialog.ShowAsync() != ContentDialogResult.Primary)\n            return;\n\n        var width = textFields[0].Text;\n        var height = textFields[1].Text;\n\n        if (string.IsNullOrWhiteSpace(width) || string.IsNullOrWhiteSpace(height))\n            return;\n\n        FavoriteDimensions.Add($\"{width} x {height}\");\n    }\n\n    [RelayCommand]\n    private void RemoveSelectedRow(string item)\n    {\n        FavoriteDimensions.Remove(item);\n    }\n\n    #endregion\n\n    private void UpdateAvailableTagCompletionCsvs()\n    {\n        if (!settingsManager.IsLibraryDirSet)\n            return;\n\n        if (settingsManager.TagsDirectory is not { Exists: true } tagsDir)\n            return;\n\n        var csvFiles = tagsDir.Info.EnumerateFiles(\"*.csv\");\n        AvailableTagCompletionCsvs = csvFiles.Select(f => f.Name).ToImmutableArray();\n\n        // Set selected to current if exists\n        var settingsCsv = settingsManager.Settings.TagCompletionCsv;\n        if (settingsCsv is not null && AvailableTagCompletionCsvs.Contains(settingsCsv))\n        {\n            SelectedTagCompletionCsv = settingsCsv;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Collections.ObjectModel;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing System.Globalization;\nusing System.IO;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Reflection;\nusing System.Runtime.Versioning;\nusing System.Text;\nusing System.Text.Json;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing AsyncImageLoader;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Notifications;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Platform.Storage;\nusing Avalonia.Styling;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing DynamicData;\nusing DynamicData.Binding;\nusing FluentAvalonia.UI.Controls;\nusing FluentIcons.Common;\nusing Injectio.Attributes;\nusing KGySoft.CoreLibraries;\nusing Microsoft.Win32;\nusing NLog;\nusing SkiaSharp;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.DesignData;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.CheckpointManager;\nusing StabilityMatrix.Avalonia.ViewModels.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.ViewModels.Inference;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.Avalonia.Views.Settings;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Git;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Settings;\n\n[View(typeof(MainSettingsPage))]\n[ManagedService]\n[RegisterSingleton<MainSettingsViewModel>]\npublic partial class MainSettingsViewModel : PageViewModelBase\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private readonly INotificationService notificationService;\n    private readonly ISettingsManager settingsManager;\n    private readonly IPrerequisiteHelper prerequisiteHelper;\n    private readonly IPyRunner pyRunner;\n    private readonly IServiceManager<ViewModelBase> dialogFactory;\n    private readonly ICompletionProvider completionProvider;\n    private readonly ITrackedDownloadService trackedDownloadService;\n    private readonly IModelIndexService modelIndexService;\n    private readonly INavigationService<SettingsViewModel> settingsNavigationService;\n    private readonly IAccountsService accountsService;\n    private readonly ICivitBaseModelTypeService baseModelTypeService;\n\n    public SharedState SharedState { get; }\n\n    public bool IsMacOS => Compat.IsMacOS;\n\n    public override string Title => \"Settings\";\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.Settings, IconVariant = IconVariant.Filled };\n\n    // ReSharper disable once MemberCanBeMadeStatic.Global\n    public string AppVersion =>\n        $\"Version {Compat.AppVersion.ToDisplayString()}\" + (Program.IsDebugBuild ? \" (Debug)\" : \"\");\n\n    // Theme section\n    [ObservableProperty]\n    private string? selectedTheme;\n\n    public IReadOnlyList<string> AvailableThemes { get; } = new[] { \"Light\", \"Dark\", \"System\" };\n\n    [ObservableProperty]\n    private CultureInfo selectedLanguage;\n\n    // ReSharper disable once MemberCanBeMadeStatic.Global\n    public IReadOnlyList<CultureInfo> AvailableLanguages => Cultures.SupportedCultures;\n\n    [ObservableProperty]\n    private NumberFormatMode selectedNumberFormatMode;\n\n    public IReadOnlyList<NumberFormatMode> NumberFormatModes { get; } =\n        Enum.GetValues<NumberFormatMode>().Where(mode => mode != default).ToList();\n\n    public IReadOnlyList<float> AnimationScaleOptions { get; } =\n        new[] { 0f, 0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f };\n\n    public IReadOnlyList<HolidayMode> HolidayModes { get; } = Enum.GetValues<HolidayMode>().ToList();\n\n    [ObservableProperty]\n    private float selectedAnimationScale;\n\n    // Shared folder options\n    [ObservableProperty]\n    private bool removeSymlinksOnShutdown;\n\n    // Integrations section\n    [ObservableProperty]\n    private bool isDiscordRichPresenceEnabled;\n\n    // Console section\n    [ObservableProperty]\n    private int consoleLogHistorySize;\n\n    // Debug section\n    [ObservableProperty]\n    private string? debugPaths;\n\n    [ObservableProperty]\n    private string? debugCompatInfo;\n\n    [ObservableProperty]\n    private string? debugGpuInfo;\n\n    [ObservableProperty]\n    private HolidayMode holidayModeSetting;\n\n    [ObservableProperty]\n    private bool infinitelyScrollWorkflowBrowser;\n\n    [ObservableProperty]\n    private bool autoLoadCivitModels;\n\n    [ObservableProperty]\n    private bool moveFilesOnImport;\n\n    [ObservableProperty]\n    private int maxConcurrentDownloads;\n\n    [ObservableProperty]\n    private bool showAllAvailablePythonVersions;\n\n    [ObservableProperty]\n    public partial List<BaseModelOptionViewModel> AllBaseModelTypes { get; set; } = [];\n\n    private SourceCache<string, string> BaseModelTypesCache { get; } = new(s => s);\n\n    #region System Settings\n\n    [ObservableProperty]\n    private bool isWindowsLongPathsEnabled;\n\n    [ObservableProperty]\n    private ObservableCollection<GpuInfo> gpuInfoCollection = [];\n\n    [ObservableProperty]\n    private GpuInfo? preferredGpu;\n\n    #endregion\n\n    #region System Info\n\n    private static Lazy<IReadOnlyList<GpuInfo>> GpuInfosLazy { get; } =\n        new(() => HardwareHelper.IterGpuInfo().ToImmutableArray());\n\n    public static IReadOnlyList<GpuInfo> GpuInfos => GpuInfosLazy.Value;\n\n    [ObservableProperty]\n    private MemoryInfo memoryInfo;\n\n    private readonly DispatcherTimer hardwareInfoUpdateTimer = new()\n    {\n        Interval = TimeSpan.FromSeconds(2.627),\n    };\n\n    public Task<CpuInfo> CpuInfoAsync => HardwareHelper.GetCpuInfoAsync();\n\n    #endregion\n\n    // Info section\n    private const int VersionTapCountThreshold = 7;\n\n    [ObservableProperty, NotifyPropertyChangedFor(nameof(VersionFlyoutText))]\n    private int versionTapCount;\n\n    [ObservableProperty]\n    private bool isVersionTapTeachingTipOpen;\n    public string VersionFlyoutText =>\n        $\"You are {VersionTapCountThreshold - VersionTapCount} clicks away from enabling Debug options.\";\n\n    public string DataDirectory => settingsManager.IsLibraryDirSet ? settingsManager.LibraryDir : \"Not set\";\n    public string ModelsDirectory => settingsManager.ModelsDirectory;\n\n    public MainSettingsViewModel(\n        INotificationService notificationService,\n        ISettingsManager settingsManager,\n        IPrerequisiteHelper prerequisiteHelper,\n        IPyRunner pyRunner,\n        IServiceManager<ViewModelBase> dialogFactory,\n        ITrackedDownloadService trackedDownloadService,\n        SharedState sharedState,\n        ICompletionProvider completionProvider,\n        IModelIndexService modelIndexService,\n        INavigationService<SettingsViewModel> settingsNavigationService,\n        IAccountsService accountsService,\n        ICivitBaseModelTypeService baseModelTypeService\n    )\n    {\n        this.notificationService = notificationService;\n        this.settingsManager = settingsManager;\n        this.prerequisiteHelper = prerequisiteHelper;\n        this.pyRunner = pyRunner;\n        this.dialogFactory = dialogFactory;\n        this.trackedDownloadService = trackedDownloadService;\n        this.completionProvider = completionProvider;\n        this.modelIndexService = modelIndexService;\n        this.settingsNavigationService = settingsNavigationService;\n        this.accountsService = accountsService;\n        this.baseModelTypeService = baseModelTypeService;\n\n        SharedState = sharedState;\n\n        if (Program.Args.DebugMode)\n        {\n            SharedState.IsDebugMode = true;\n        }\n\n        SelectedTheme = settingsManager.Settings.Theme ?? AvailableThemes[1];\n        SelectedLanguage = Cultures.GetSupportedCultureOrDefault(settingsManager.Settings.Language);\n        RemoveSymlinksOnShutdown = settingsManager.Settings.RemoveFolderLinksOnShutdown;\n        SelectedAnimationScale = settingsManager.Settings.AnimationScale;\n        HolidayModeSetting = settingsManager.Settings.HolidayModeSetting;\n\n        settingsManager.RelayPropertyFor(this, vm => vm.SelectedTheme, settings => settings.Theme);\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.IsDiscordRichPresenceEnabled,\n            settings => settings.IsDiscordRichPresenceEnabled,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.SelectedAnimationScale,\n            settings => settings.AnimationScale\n        );\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.HolidayModeSetting,\n            settings => settings.HolidayModeSetting\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.InfinitelyScrollWorkflowBrowser,\n            settings => settings.IsWorkflowInfiniteScrollEnabled,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.SelectedNumberFormatMode,\n            settings => settings.NumberFormatMode,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.AutoLoadCivitModels,\n            settings => settings.AutoLoadCivitModels,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.MoveFilesOnImport,\n            settings => settings.MoveFilesOnImport,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.ConsoleLogHistorySize,\n            settings => settings.ConsoleLogHistorySize,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.PreferredGpu,\n            settings => settings.PreferredGpu,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.MaxConcurrentDownloads,\n            settings => settings.MaxConcurrentDownloads,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.ShowAllAvailablePythonVersions,\n            settings => settings.ShowAllAvailablePythonVersions,\n            true\n        );\n\n        AddDisposable(\n            BaseModelTypesCache\n                .Connect()\n                .DeferUntilLoaded()\n                .Transform(x => new BaseModelOptionViewModel\n                {\n                    ModelType = x,\n                    IsSelected = !settingsManager.Settings.DisabledBaseModelTypes.Contains(x),\n                })\n                .SortAndBind(\n                    AllBaseModelTypes,\n                    SortExpressionComparer<BaseModelOptionViewModel>.Ascending(x => x.ModelType)\n                )\n                .WhenPropertyChanged(vm => vm.IsSelected)\n                .ObserveOn(SynchronizationContext.Current)\n                .Subscribe(next =>\n                {\n                    if (next.Sender.IsSelected)\n                    {\n                        settingsManager.Transaction(s =>\n                            s.DisabledBaseModelTypes.TryRemove(next.Sender.ModelType)\n                        );\n                    }\n                    else\n                    {\n                        settingsManager.Transaction(s =>\n                            s.DisabledBaseModelTypes.TryAdd(next.Sender.ModelType)\n                        );\n                    }\n                })\n        );\n\n        DebugThrowAsyncExceptionCommand.WithNotificationErrorHandler(notificationService, LogLevel.Warn);\n\n        hardwareInfoUpdateTimer.Tick += OnHardwareInfoUpdateTimerTick;\n    }\n\n    /// <inheritdoc />\n    public override void OnLoaded()\n    {\n        base.OnLoaded();\n\n        hardwareInfoUpdateTimer.Start();\n\n        if (Compat.IsWindows)\n        {\n            UpdateRegistrySettings();\n        }\n    }\n\n    /// <inheritdoc />\n    public override void OnUnloaded()\n    {\n        base.OnUnloaded();\n\n        hardwareInfoUpdateTimer.Stop();\n    }\n\n    /// <inheritdoc />\n    public override async Task OnLoadedAsync()\n    {\n        await base.OnLoadedAsync();\n\n        await notificationService.TryAsync(completionProvider.Setup());\n\n        var gpuInfos = HardwareHelper.IterGpuInfo();\n        GpuInfoCollection = new ObservableCollection<GpuInfo>(gpuInfos);\n        PreferredGpu ??=\n            GpuInfos.FirstOrDefault(gpu =>\n                gpu.Name?.Contains(\"nvidia\", StringComparison.InvariantCultureIgnoreCase) ?? false\n            ) ?? GpuInfos.FirstOrDefault();\n\n        if (Design.IsDesignMode)\n            return;\n\n        var baseModelTypes = await baseModelTypeService.GetBaseModelTypes(includeAllOption: false);\n        BaseModelTypesCache.Edit(updater => updater.Load(baseModelTypes));\n\n        // Start accounts update\n        accountsService\n            .RefreshAsync()\n            .SafeFireAndForget(ex =>\n            {\n                Logger.Error(ex, \"Failed to refresh accounts\");\n                notificationService.ShowPersistent(\n                    \"Failed to update account status\",\n                    ex.ToString(),\n                    NotificationType.Error\n                );\n            });\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    private void UpdateRegistrySettings()\n    {\n        try\n        {\n            using var fsKey =\n                Registry.LocalMachine.OpenSubKey(@\"SYSTEM\\CurrentControlSet\\Control\\FileSystem\")\n                ?? throw new InvalidOperationException(\n                    \"Could not open registry key 'SYSTEM\\\\CurrentControlSet\\\\Control\\\\FileSystem'\"\n                );\n\n            IsWindowsLongPathsEnabled = Convert.ToBoolean(fsKey.GetValue(\"LongPathsEnabled\", null));\n        }\n        catch (Exception e)\n        {\n            Logger.Error(e, \"Could not read registry settings\");\n            notificationService.Show(\"Could not read registry settings\", e.Message, NotificationType.Error);\n        }\n    }\n\n    private void OnHardwareInfoUpdateTimerTick(object? sender, EventArgs e)\n    {\n        if (HardwareHelper.IsMemoryInfoAvailable && HardwareHelper.TryGetMemoryInfo(out var newMemoryInfo))\n        {\n            MemoryInfo = newMemoryInfo;\n        }\n\n        // Stop timer if live memory info is not available\n        if (!HardwareHelper.IsLiveMemoryUsageInfoAvailable)\n        {\n            (sender as DispatcherTimer)?.Stop();\n        }\n    }\n\n    partial void OnSelectedThemeChanged(string? value)\n    {\n        // In case design / tests\n        if (Application.Current is null)\n            return;\n        // Change theme\n        Application.Current.RequestedThemeVariant = value switch\n        {\n            \"Dark\" => ThemeVariant.Dark,\n            \"Light\" => ThemeVariant.Light,\n            _ => ThemeVariant.Default,\n        };\n    }\n\n    partial void OnSelectedLanguageChanged(CultureInfo? oldValue, CultureInfo newValue)\n    {\n        if (oldValue is null || newValue.Name == Cultures.Current?.Name)\n            return;\n\n        // Set locale\n        if (AvailableLanguages.Contains(newValue))\n        {\n            Logger.Info(\"Changing language from {Old} to {New}\", oldValue, newValue);\n\n            Cultures.TrySetSupportedCulture(newValue, settingsManager.Settings.NumberFormatMode);\n            settingsManager.Transaction(s => s.Language = newValue.Name);\n\n            var dialog = new BetterContentDialog\n            {\n                Title = Resources.Label_RelaunchRequired,\n                Content = Resources.Text_RelaunchRequiredToApplyLanguage,\n                DefaultButton = ContentDialogButton.Primary,\n                PrimaryButtonText = Resources.Action_Relaunch,\n                CloseButtonText = Resources.Action_RelaunchLater,\n            };\n\n            Dispatcher.UIThread.InvokeAsync(async () =>\n            {\n                if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n                {\n                    // Start the new app while passing our own PID to wait for exit\n                    Process.Start(Compat.AppCurrentPath, $\"--wait-for-exit-pid {Environment.ProcessId}\");\n\n                    // Shutdown the current app\n                    App.Shutdown();\n                }\n            });\n        }\n        else\n        {\n            Logger.Info(\"Requested invalid language change from {Old} to {New}\", oldValue, newValue);\n        }\n    }\n\n    partial void OnRemoveSymlinksOnShutdownChanged(bool value)\n    {\n        settingsManager.Transaction(s => s.RemoveFolderLinksOnShutdown = value);\n    }\n\n    partial void OnMaxConcurrentDownloadsChanged(int value)\n    {\n        trackedDownloadService.UpdateMaxConcurrentDownloads(value);\n    }\n\n    public async Task ResetCheckpointCache()\n    {\n        await notificationService.TryAsync(modelIndexService.RefreshIndex());\n\n        notificationService.Show(\n            \"Checkpoint cache reset\",\n            \"The checkpoint cache has been reset.\",\n            NotificationType.Success\n        );\n    }\n\n    [RelayCommand]\n    private void NavigateToSubPage(Type viewModelType)\n    {\n        Dispatcher.UIThread.Post(\n            () =>\n                settingsNavigationService.NavigateTo(\n                    viewModelType,\n                    BetterSlideNavigationTransition.PageSlideFromRight\n                ),\n            DispatcherPriority.Send\n        );\n    }\n\n    #region Package Environment\n\n    [RelayCommand]\n    private async Task OpenEnvVarsDialog()\n    {\n        var viewModel = dialogFactory.Get<EnvVarsViewModel>();\n\n        // Load current settings\n        var current = settingsManager.Settings.UserEnvironmentVariables ?? new Dictionary<string, string>();\n        viewModel.EnvVars = new ObservableCollection<EnvVarKeyPair>(\n            current.Select(kvp => new EnvVarKeyPair(kvp.Key, kvp.Value))\n        );\n\n        var dialog = viewModel.GetDialog();\n\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            // Save settings\n            var newEnvVars = viewModel\n                .EnvVars.Where(kvp => !string.IsNullOrWhiteSpace(kvp.Key))\n                .GroupBy(kvp => kvp.Key, StringComparer.Ordinal)\n                .ToDictionary(g => g.Key, g => g.First().Value, StringComparer.Ordinal);\n            settingsManager.Transaction(s => s.UserEnvironmentVariables = newEnvVars);\n        }\n    }\n\n    [RelayCommand]\n    private async Task CheckPythonVersion()\n    {\n        var isInstalled = prerequisiteHelper.IsPythonInstalled;\n        Logger.Debug($\"Check python installed: {isInstalled}\");\n        // Ensure python installed\n        if (!prerequisiteHelper.IsPythonInstalled)\n        {\n            // Need 7z as well for site packages repack\n            Logger.Debug(\"Python not installed, unpacking resources...\");\n            await prerequisiteHelper.UnpackResourcesIfNecessary();\n            Logger.Debug(\"Unpacked resources, installing python...\");\n            await prerequisiteHelper.InstallPythonIfNecessary();\n        }\n\n        // Get python version\n        await pyRunner.Initialize();\n        var result = await pyRunner.GetVersionInfo();\n        // Show dialog box\n        var dialog = new ContentDialog\n        {\n            Title = Resources.Label_PythonVersionInfo,\n            Content = result,\n            PrimaryButtonText = Resources.Action_OK,\n            IsPrimaryButtonEnabled = true,\n        };\n        await dialog.ShowAsync();\n    }\n\n    [RelayCommand]\n    private async Task RunPythonProcess()\n    {\n        await prerequisiteHelper.UnpackResourcesIfNecessary();\n        await prerequisiteHelper.InstallPythonIfNecessary();\n\n        var processPath = new FilePath(PyRunner.PythonExePath);\n\n        if (\n            await DialogHelper.GetTextEntryDialogResultAsync(\n                new TextBoxField { Label = \"Arguments\", InnerLeftText = processPath.Name },\n                title: \"Run Python\"\n            )\n            is not { IsPrimary: true } dialogResult\n        )\n        {\n            return;\n        }\n\n        var step = new ProcessStep\n        {\n            FileName = processPath,\n            Args = dialogResult.Value.Text,\n            WorkingDirectory = Compat.AppCurrentDir,\n            EnvironmentVariables = settingsManager.Settings.EnvironmentVariables.ToImmutableDictionary(),\n        };\n\n        ConsoleProcessRunner.RunProcessStepAsync(step).SafeFireAndForget();\n    }\n\n    [RelayCommand]\n    private async Task ClearPipCache()\n    {\n        await prerequisiteHelper.UnpackResourcesIfNecessary();\n        await prerequisiteHelper.InstallPythonIfNecessary();\n\n        var processPath = new FilePath(PyRunner.PythonExePath);\n\n        var step = new ProcessStep\n        {\n            FileName = processPath,\n            Args = [\"-m\", \"pip\", \"cache\", \"purge\"],\n            WorkingDirectory = Compat.AppCurrentDir,\n            EnvironmentVariables = settingsManager.Settings.EnvironmentVariables.ToImmutableDictionary(),\n        };\n\n        ConsoleProcessRunner.RunProcessStepAsync(step).SafeFireAndForget();\n    }\n\n    [RelayCommand]\n    private async Task ClearUvCache()\n    {\n        await prerequisiteHelper.InstallUvIfNecessary();\n        var processPath = new FilePath(prerequisiteHelper.UvExePath);\n\n        var step = new ProcessStep\n        {\n            FileName = processPath,\n            Args = [\"cache\", \"clean\"],\n            WorkingDirectory = Compat.AppCurrentDir,\n            EnvironmentVariables = settingsManager.Settings.EnvironmentVariables.ToImmutableDictionary(),\n        };\n\n        ConsoleProcessRunner.RunProcessStepAsync(step).SafeFireAndForget();\n    }\n\n    [RelayCommand]\n    private async Task RunGitProcess()\n    {\n        await prerequisiteHelper.InstallGitIfNecessary();\n\n        FilePath processPath;\n\n        if (Compat.IsWindows)\n        {\n            processPath = new FilePath(prerequisiteHelper.GitBinPath, \"git.exe\");\n        }\n        else\n        {\n            var whichGitResult = await ProcessRunner.RunBashCommand([\"which\", \"git\"]).EnsureSuccessExitCode();\n            processPath = new FilePath(whichGitResult.StandardOutput?.Trim() ?? \"git\");\n        }\n\n        if (\n            await DialogHelper.GetTextEntryDialogResultAsync(\n                new TextBoxField { Label = \"Arguments\", InnerLeftText = \"git\" },\n                title: \"Run Git\"\n            )\n            is not { IsPrimary: true } dialogResult\n        )\n        {\n            return;\n        }\n\n        var step = new ProcessStep\n        {\n            FileName = processPath,\n            Args = dialogResult.Value.Text,\n            WorkingDirectory = Compat.AppCurrentDir,\n            EnvironmentVariables = settingsManager.Settings.EnvironmentVariables.ToImmutableDictionary(),\n        };\n\n        ConsoleProcessRunner.RunProcessStepAsync(step).SafeFireAndForget();\n    }\n\n    [RelayCommand]\n    private async Task FixGitLongPaths()\n    {\n        var result = await prerequisiteHelper.FixGitLongPaths();\n        if (result)\n        {\n            notificationService.Show(\n                \"Long Paths Enabled\",\n                \"Git long paths have been enabled.\",\n                NotificationType.Success\n            );\n        }\n        else\n        {\n            notificationService.Show(\n                \"Long Paths Not Enabled\",\n                \"Could not enable Git long paths.\",\n                NotificationType.Error\n            );\n        }\n    }\n\n    partial void OnShowAllAvailablePythonVersionsChanged(bool value)\n    {\n        if (!value)\n            return;\n\n        Dispatcher.UIThread.InvokeAsync(async () =>\n        {\n            var dialog = DialogHelper.CreateMarkdownDialog(\n                Resources.Label_UnsupportedPythonVersionWarningDescription,\n                Resources.Label_PythonVersionWarningTitle\n            );\n            dialog.IsPrimaryButtonEnabled = true;\n            dialog.IsSecondaryButtonEnabled = true;\n            dialog.PrimaryButtonText = Resources.Action_Yes;\n            dialog.CloseButtonText = Resources.Label_No;\n            dialog.DefaultButton = ContentDialogButton.Primary;\n\n            var result = await dialog.ShowAsync();\n            if (result is not ContentDialogResult.Primary)\n            {\n                ShowAllAvailablePythonVersions = false;\n            }\n        });\n    }\n\n    #endregion\n\n    #region Directory Shortcuts\n\n    public CommandItem[] DirectoryShortcutCommands =>\n        [\n            new CommandItem(\n                new AsyncRelayCommand(() => ProcessRunner.OpenFolderBrowser(Compat.AppDataHome)),\n                Resources.Label_AppData\n            ),\n            new CommandItem(\n                new AsyncRelayCommand(() =>\n                    ProcessRunner.OpenFolderBrowser(Compat.AppDataHome.JoinDir(\"Logs\"))\n                ),\n                Resources.Label_Logs\n            ),\n            new CommandItem(\n                new AsyncRelayCommand(() => ProcessRunner.OpenFolderBrowser(settingsManager.LibraryDir)),\n                Resources.Label_DataDirectory\n            ),\n            new CommandItem(\n                new AsyncRelayCommand(() => ProcessRunner.OpenFolderBrowser(settingsManager.ModelsDirectory)),\n                Resources.Label_Checkpoints\n            ),\n            new CommandItem(\n                new AsyncRelayCommand(() =>\n                    ProcessRunner.OpenFolderBrowser(settingsManager.LibraryDir.JoinDir(\"Packages\"))\n                ),\n                Resources.Label_Packages\n            ),\n        ];\n\n    #endregion\n\n    #region System\n\n    /// <summary>\n    /// Adds Stability Matrix to Start Menu for the current user.\n    /// </summary>\n    [RelayCommand]\n    private async Task AddToStartMenu()\n    {\n        if (!Compat.IsWindows)\n        {\n            notificationService.Show(\"Not supported\", \"This feature is only supported on Windows.\");\n            return;\n        }\n\n        await using var _ = new MinimumDelay(200, 300);\n\n        var shortcutDir = new DirectoryPath(\n            Environment.GetFolderPath(Environment.SpecialFolder.StartMenu),\n            \"Programs\"\n        );\n        var shortcutLink = shortcutDir.JoinFile(\"Stability Matrix.lnk\");\n\n        var appPath = Compat.AppCurrentPath;\n        var iconPath = shortcutDir.JoinFile(\"Stability Matrix.ico\");\n        await Assets.AppIcon.ExtractTo(iconPath);\n\n        WindowsShortcuts.CreateShortcut(shortcutLink, appPath, iconPath, \"Stability Matrix\");\n\n        notificationService.Show(\n            \"Added to Start Menu\",\n            \"Stability Matrix has been added to the Start Menu.\",\n            NotificationType.Success\n        );\n    }\n\n    /// <summary>\n    /// Add Stability Matrix to Start Menu for all users.\n    /// <remarks>Requires Admin elevation.</remarks>\n    /// </summary>\n    [RelayCommand]\n    private async Task AddToGlobalStartMenu()\n    {\n        if (!Compat.IsWindows)\n        {\n            notificationService.Show(\"Not supported\", \"This feature is only supported on Windows.\");\n            return;\n        }\n\n        // Confirmation dialog\n        var dialog = new BetterContentDialog\n        {\n            Title = \"This will create a shortcut for Stability Matrix in the Start Menu for all users\",\n            Content = \"You will be prompted for administrator privileges. Continue?\",\n            PrimaryButtonText = Resources.Action_Yes,\n            CloseButtonText = Resources.Action_Cancel,\n            DefaultButton = ContentDialogButton.Primary,\n        };\n\n        if (await dialog.ShowAsync() != ContentDialogResult.Primary)\n        {\n            return;\n        }\n\n        await using var _ = new MinimumDelay(200, 300);\n\n        var shortcutDir = new DirectoryPath(\n            Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu),\n            \"Programs\"\n        );\n        var shortcutLink = shortcutDir.JoinFile(\"Stability Matrix.lnk\");\n\n        var appPath = Compat.AppCurrentPath;\n        var iconPath = shortcutDir.JoinFile(\"Stability Matrix.ico\");\n\n        // We can't directly write to the targets, so extract to temporary directory first\n        using var tempDir = new TempDirectoryPath();\n\n        await Assets.AppIcon.ExtractTo(tempDir.JoinFile(\"Stability Matrix.ico\"));\n        WindowsShortcuts.CreateShortcut(\n            tempDir.JoinFile(\"Stability Matrix.lnk\"),\n            appPath,\n            iconPath,\n            \"Stability Matrix\"\n        );\n\n        // Move to target\n        try\n        {\n            var moveLinkResult = await WindowsElevated.MoveFiles(\n                (tempDir.JoinFile(\"Stability Matrix.lnk\"), shortcutLink),\n                (tempDir.JoinFile(\"Stability Matrix.ico\"), iconPath)\n            );\n            if (moveLinkResult != 0)\n            {\n                notificationService.ShowPersistent(\n                    \"Failed to create shortcut\",\n                    $\"Could not copy shortcut\",\n                    NotificationType.Error\n                );\n            }\n        }\n        catch (Win32Exception e)\n        {\n            // We'll get this exception if user cancels UAC\n            Logger.Warn(e, \"Could not create shortcut\");\n            notificationService.Show(\"Could not create shortcut\", \"\", NotificationType.Warning);\n            return;\n        }\n\n        notificationService.Show(\n            \"Added to Start Menu\",\n            \"Stability Matrix has been added to the Start Menu for all users.\",\n            NotificationType.Success\n        );\n    }\n\n    public async Task PickNewDataDirectory()\n    {\n        var viewModel = dialogFactory.Get<SelectDataDirectoryViewModel>();\n        var dialog = new BetterContentDialog\n        {\n            IsPrimaryButtonEnabled = false,\n            IsSecondaryButtonEnabled = false,\n            IsFooterVisible = false,\n            Content = new SelectDataDirectoryDialog { DataContext = viewModel },\n        };\n\n        var result = await dialog.ShowAsync();\n        if (result == ContentDialogResult.Primary)\n        {\n            // 1. For portable mode, call settings.SetPortableMode()\n            if (viewModel.IsPortableMode)\n            {\n                settingsManager.SetPortableMode();\n            }\n            // 2. For custom path, call settings.SetLibraryPath(path)\n            else\n            {\n                settingsManager.SetLibraryPath(viewModel.DataDirectory);\n            }\n\n            // Restart\n            var restartDialog = new BetterContentDialog\n            {\n                Title = \"Restart required\",\n                Content = \"Stability Matrix must be restarted for the changes to take effect.\",\n                PrimaryButtonText = Resources.Action_Restart,\n                DefaultButton = ContentDialogButton.Primary,\n                IsSecondaryButtonEnabled = false,\n            };\n            await restartDialog.ShowAsync();\n\n            Process.Start(Compat.AppCurrentPath);\n            App.Shutdown();\n        }\n    }\n\n    public async Task PickNewModelsFolder()\n    {\n        var provider = App.StorageProvider;\n        var result = await provider.OpenFolderPickerAsync(new FolderPickerOpenOptions());\n\n        if (result.Count == 0)\n            return;\n\n        var newPath = (result[0].Path.LocalPath);\n        settingsManager.Transaction(s => s.ModelDirectoryOverride = newPath);\n        SharedFolders.SetupSharedModelFolders(newPath);\n\n        // Restart\n        var restartDialog = new BetterContentDialog\n        {\n            Title = \"Restart required\",\n            Content = \"Stability Matrix must be restarted for the changes to take effect.\",\n            PrimaryButtonText = Resources.Action_Restart,\n            DefaultButton = ContentDialogButton.Primary,\n            IsSecondaryButtonEnabled = false,\n        };\n        await restartDialog.ShowAsync();\n\n        Process.Start(Compat.AppCurrentPath);\n        App.Shutdown();\n    }\n\n    #endregion\n\n    #region Debug Section\n    public void LoadDebugInfo()\n    {\n        var assembly = Assembly.GetExecutingAssembly();\n        var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);\n        DebugPaths = $\"\"\"\n            Current Working Directory [Environment.CurrentDirectory]\n              \"{Environment.CurrentDirectory}\"\n            App Directory [Assembly.GetExecutingAssembly().Location]\n              \"{assembly.Location}\"\n            App Directory [AppContext.BaseDirectory]\n              \"{AppContext.BaseDirectory}\"\n            AppData Directory [SpecialFolder.ApplicationData]\n              \"{appData}\"\n            \"\"\";\n\n        // 1. Check portable mode\n        var appDir = Compat.AppCurrentDir;\n        var expectedPortableFile = Path.Combine(appDir, \"Data\", \".sm-portable\");\n        var isPortableMode = File.Exists(expectedPortableFile);\n\n        DebugCompatInfo = $\"\"\"\n            Platform: {Compat.Platform}\n            AppData: {Compat.AppData}\n            AppDataHome: {Compat.AppDataHome}\n            AppCurrentDir: {Compat.AppCurrentDir}\n            ExecutableName: {Compat.GetExecutableName()}\n            AppName: {Compat.GetAppName()}\n            -- Settings --\n            Expected Portable Marker file: {expectedPortableFile}\n            Portable Marker file exists: {isPortableMode}\n            IsLibraryDirSet = {settingsManager.IsLibraryDirSet}\n            IsPortableMode = {settingsManager.IsPortableMode}\n            \"\"\";\n\n        // Get Gpu info\n        var gpuInfo = \"\";\n        foreach (var (i, gpu) in HardwareHelper.IterGpuInfo().Enumerate())\n        {\n            gpuInfo += $\"[{i + 1}] {gpu}\\n\";\n        }\n        DebugGpuInfo = gpuInfo;\n    }\n\n    // Debug buttons\n    [RelayCommand]\n    private void DebugNotification()\n    {\n        notificationService.Show(\n            new Notification(\n                title: \"Test Notification\",\n                message: \"Here is some message\",\n                type: NotificationType.Information\n            )\n        );\n    }\n\n    [RelayCommand]\n    private async Task DebugContentDialog()\n    {\n        var dialog = new ContentDialog\n        {\n            DefaultButton = ContentDialogButton.Primary,\n            Title = \"Test title\",\n            PrimaryButtonText = Resources.Action_OK,\n            CloseButtonText = Resources.Action_Close,\n        };\n\n        var result = await dialog.ShowAsync();\n        notificationService.Show(new Notification(\"Content dialog closed\", $\"Result: {result}\"));\n    }\n\n    [RelayCommand]\n    private void DebugThrowException()\n    {\n        throw new OperationCanceledException(\"Example Message\");\n    }\n\n    [RelayCommand(FlowExceptionsToTaskScheduler = true)]\n    private async Task DebugThrowAsyncException()\n    {\n        await Task.Yield();\n\n        throw new ApplicationException(\"Example Message\");\n    }\n\n    [RelayCommand]\n    private void DebugThrowDispatcherException()\n    {\n        Dispatcher.UIThread.Post(() => throw new OperationCanceledException(\"Example Message\"));\n    }\n\n    [RelayCommand]\n    private async Task DebugMakeImageGrid()\n    {\n        var provider = App.StorageProvider;\n        var files = await provider.OpenFilePickerAsync(new FilePickerOpenOptions() { AllowMultiple = true });\n\n        if (files.Count == 0)\n            return;\n\n        var images = await files.SelectAsync(async f => SKImage.FromEncodedData(await f.OpenReadAsync()));\n\n        var grid = ImageProcessor.CreateImageGrid(images.ToImmutableArray());\n\n        // Show preview\n\n        using var peekPixels = grid.PeekPixels();\n        using var data = peekPixels.Encode(SKEncodedImageFormat.Jpeg, 100);\n        await using var stream = data.AsStream();\n\n        var bitmap = WriteableBitmap.Decode(stream);\n\n        var galleryImages = new List<ImageSource> { new(bitmap) };\n        galleryImages.AddRange(files.Select(f => new ImageSource(f.Path.ToString())));\n\n        var imageBox = new ImageGalleryCard\n        {\n            Width = 1000,\n            Height = 900,\n            DataContext = dialogFactory.Get<ImageGalleryCardViewModel>(vm =>\n            {\n                vm.ImageSources.AddRange(galleryImages);\n            }),\n        };\n\n        var dialog = new BetterContentDialog\n        {\n            MaxDialogWidth = 1000,\n            MaxDialogHeight = 1000,\n            FullSizeDesired = true,\n            Content = imageBox,\n            CloseButtonText = \"Close\",\n            ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled,\n        };\n\n        await dialog.ShowAsync();\n    }\n\n    [RelayCommand]\n    private async Task DebugLoadCompletionCsv()\n    {\n        var provider = App.StorageProvider;\n        var files = await provider.OpenFilePickerAsync(new FilePickerOpenOptions());\n\n        if (files.Count == 0)\n            return;\n\n        await completionProvider.LoadFromFile(files[0].TryGetLocalPath()!, true);\n\n        notificationService.Show(\"Loaded completion file\", \"\");\n    }\n\n    [RelayCommand]\n    private async Task DebugImageMetadata()\n    {\n        var provider = App.StorageProvider;\n        var files = await provider.OpenFilePickerAsync(new FilePickerOpenOptions());\n\n        if (files.Count == 0)\n            return;\n\n        var metadata = ImageMetadata.ParseFile(files[0].TryGetLocalPath()!);\n        var textualTags = metadata.GetTextualData()?.ToArray();\n\n        if (textualTags is null)\n        {\n            notificationService.Show(\"No textual data found\", \"\");\n            return;\n        }\n\n        if (metadata.GetGenerationParameters() is { } parameters)\n        {\n            var parametersJson = JsonSerializer.Serialize(parameters);\n            var dialog = DialogHelper.CreateJsonDialog(parametersJson, \"Generation Parameters\");\n            await dialog.ShowAsync();\n        }\n    }\n\n    [RelayCommand]\n    private async Task DebugRefreshModelsIndex()\n    {\n        await modelIndexService.RefreshIndex();\n    }\n\n    [RelayCommand]\n    private async Task DebugTrackedDownload()\n    {\n        var textFields = new TextBoxField[]\n        {\n            new() { Label = \"Url\" },\n            new() { Label = \"File path\" },\n        };\n\n        var dialog = DialogHelper.CreateTextEntryDialog(\"Add download\", \"\", textFields);\n\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            var url = textFields[0].Text;\n            var filePath = textFields[1].Text;\n            var download = trackedDownloadService.NewDownload(new Uri(url), new FilePath(filePath));\n            await trackedDownloadService.TryStartDownload(download);\n        }\n    }\n\n    [RelayCommand]\n    private async Task DebugWhich()\n    {\n        var textFields = new TextBoxField[] { new() { Label = \"Thing\" } };\n\n        var dialog = DialogHelper.CreateTextEntryDialog(\"Which\", \"\", textFields);\n\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            var result = await Utilities.WhichAsync(textFields[0].Text);\n            await DialogHelper.CreateMarkdownDialog(result).ShowAsync();\n        }\n    }\n\n    [RelayCommand]\n    private async Task DebugRobocopy()\n    {\n        var textFields = new TextBoxField[]\n        {\n            new() { Label = \"Source\" },\n            new() { Label = \"Destination\" },\n        };\n\n        var dialog = DialogHelper.CreateTextEntryDialog(\"Robocopy\", \"\", textFields);\n\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            var result = await WindowsElevated.Robocopy(textFields[0].Text, textFields[1].Text);\n            await DialogHelper.CreateMarkdownDialog(result.ToString()).ShowAsync();\n        }\n    }\n\n    [RelayCommand]\n    private async Task DebugInstallUv()\n    {\n        await prerequisiteHelper.InstallUvIfNecessary();\n        notificationService.Show(\"Installed Uv\", \"Uv has been installed.\", NotificationType.Success);\n    }\n\n    [RelayCommand]\n    private async Task DebugRunUv()\n    {\n        var textFields = new TextBoxField[]\n        {\n            new() { Label = \"uv\", Watermark = \"uv\" },\n        };\n\n        var dialog = DialogHelper.CreateTextEntryDialog(\"UV Run\", \"\", textFields);\n\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            var uv = new UvManager(settingsManager);\n\n            var result = await uv.ListAvailablePythonsAsync(onConsoleOutput: output =>\n            {\n                Logger.Info(output.Text);\n            });\n\n            var sb = new StringBuilder();\n            foreach (var info in result)\n            {\n                sb.AppendLine($\"{info}\\r\\n\\r\\n\");\n            }\n            await DialogHelper.CreateMarkdownDialog(sb.ToString()).ShowAsync();\n        }\n    }\n\n    #endregion\n\n    #region Debug Commands\n\n    public CommandItem[] DebugCommands =>\n        [\n            new CommandItem(DebugRefreshModelIndexCommand),\n            new CommandItem(DebugFindLocalModelFromIndexCommand),\n            new CommandItem(DebugExtractDmgCommand),\n            new CommandItem(DebugShowNativeNotificationCommand),\n            new CommandItem(DebugClearImageCacheCommand),\n            new CommandItem(DebugGCCollectCommand),\n            new CommandItem(DebugExtractImagePromptsToTxtCommand),\n            new CommandItem(DebugShowImageMaskEditorCommand),\n            new CommandItem(DebugExtractImagePromptsToTxtCommand),\n            new CommandItem(DebugShowConfirmDeleteDialogCommand),\n            new CommandItem(DebugShowModelMetadataEditorDialogCommand),\n            new CommandItem(DebugNvidiaSmiCommand),\n            new CommandItem(DebugShowGitVersionSelectorDialogCommand),\n            new CommandItem(DebugShowMockGitVersionSelectorDialogCommand),\n            new CommandItem(DebugWhichCommand),\n            new CommandItem(DebugRobocopyCommand),\n            new CommandItem(DebugInstallUvCommand),\n            new CommandItem(DebugRunUvCommand),\n        ];\n\n    [RelayCommand]\n    private async Task DebugShowGitVersionSelectorDialog()\n    {\n        var vm = new GitVersionSelectorViewModel\n        {\n            GitVersionProvider = new CachedCommandGitVersionProvider(\n                \"https://github.com/ltdrdata/ComfyUI-Manager\",\n                prerequisiteHelper\n            ),\n        };\n        var dialog = vm.GetDialog();\n\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            notificationService.ShowPersistent(\"Selected version\", $\"{vm.SelectedGitVersion}\");\n        }\n    }\n\n    [RelayCommand]\n    private async Task DebugShowMockGitVersionSelectorDialog()\n    {\n        var vm = new GitVersionSelectorViewModel { GitVersionProvider = new MockGitVersionProvider() };\n        var dialog = vm.GetDialog();\n        await dialog.ShowAsync();\n    }\n\n    [RelayCommand]\n    private async Task DebugShowModelMetadataEditorDialog()\n    {\n        var vm = dialogFactory.Get<ModelMetadataEditorDialogViewModel>();\n        vm.ThumbnailFilePath = Assets.NoImage.ToString();\n        vm.Tags = \"tag1, tag2, tag3\";\n        vm.ModelDescription = \"This is a description\";\n        vm.ModelName = \"Model Name\";\n        vm.VersionName = \"1.0.0\";\n        vm.TrainedWords = \"word1, word2, word3\";\n        vm.ModelType = CivitModelType.Checkpoint;\n        vm.BaseModelType = \"Pony\";\n\n        var dialog = vm.GetDialog();\n        dialog.MinDialogHeight = 800;\n        dialog.IsPrimaryButtonEnabled = true;\n        dialog.IsFooterVisible = true;\n        dialog.PrimaryButtonText = \"Save\";\n        dialog.DefaultButton = ContentDialogButton.Primary;\n        dialog.CloseButtonText = \"Cancel\";\n\n        await dialog.ShowAsync();\n    }\n\n    [RelayCommand]\n    private async Task DebugShowConfirmDeleteDialog()\n    {\n        var vm = dialogFactory.Get<ConfirmDeleteDialogViewModel>();\n\n        vm.IsRecycleBinAvailable = false;\n        vm.PathsToDelete = Enumerable\n            .Range(1, 64)\n            .Select(i => $\"C:/Users/ExampleUser/Data/ExampleFile{i}.txt\")\n            .ToArray();\n\n        await vm.GetDialog().ShowAsync();\n    }\n\n    [RelayCommand]\n    private async Task DebugRefreshModelIndex()\n    {\n        await modelIndexService.RefreshIndex();\n    }\n\n    [RelayCommand]\n    private async Task DebugFindLocalModelFromIndex()\n    {\n        var textFields = new TextBoxField[]\n        {\n            new() { Label = \"Blake3 Hash\" },\n            new() { Label = \"SharedFolderType\" },\n        };\n\n        var dialog = DialogHelper.CreateTextEntryDialog(\"Find Local Model\", \"\", textFields);\n\n        if (await dialog.ShowAsync() == ContentDialogResult.Primary)\n        {\n            var timer = new Stopwatch();\n            List<LocalModelFile> results;\n\n            if (textFields.ElementAtOrDefault(0)?.Text is { } hash && !string.IsNullOrWhiteSpace(hash))\n            {\n                timer.Restart();\n                results = (await modelIndexService.FindByHashAsync(hash)).ToList();\n                timer.Stop();\n            }\n            else if (textFields.ElementAtOrDefault(1)?.Text is { } type && !string.IsNullOrWhiteSpace(type))\n            {\n                var folderTypes = Enum.Parse<SharedFolderType>(type, true);\n                timer.Restart();\n                results = (await modelIndexService.FindByModelTypeAsync(folderTypes)).ToList();\n                timer.Stop();\n            }\n            else\n            {\n                return;\n            }\n\n            if (results.Count != 0)\n            {\n                await DialogHelper\n                    .CreateMarkdownDialog(\n                        string.Join(\n                            \"\\n\\n\",\n                            results.Select(\n                                (model, i) =>\n                                    $\"[{i + 1}] {model.RelativePath.ToRepr()} \"\n                                    + $\"({model.DisplayModelName}, {model.DisplayModelVersion})\"\n                            )\n                        ),\n                        $\"Found Models ({CodeTimer.FormatTime(timer.Elapsed)})\"\n                    )\n                    .ShowAsync();\n            }\n            else\n            {\n                await DialogHelper\n                    .CreateMarkdownDialog(\":(\", $\"No models found ({CodeTimer.FormatTime(timer.Elapsed)})\")\n                    .ShowAsync();\n            }\n        }\n    }\n\n    [RelayCommand(CanExecute = nameof(IsMacOS))]\n    private async Task DebugExtractDmg()\n    {\n        if (!Compat.IsMacOS)\n            return;\n\n        // Select File\n        var files = await App.StorageProvider.OpenFilePickerAsync(\n            new FilePickerOpenOptions { Title = \"Select .dmg file\" }\n        );\n        if (files.FirstOrDefault()?.TryGetLocalPath() is not { } dmgFile)\n            return;\n\n        // Select output directory\n        var folders = await App.StorageProvider.OpenFolderPickerAsync(\n            new FolderPickerOpenOptions { Title = \"Select output directory\" }\n        );\n        if (folders.FirstOrDefault()?.TryGetLocalPath() is not { } outputDir)\n            return;\n\n        // Extract\n        notificationService.Show(\"Extracting...\", dmgFile);\n\n        await ArchiveHelper.ExtractDmg(dmgFile, outputDir);\n\n        notificationService.Show(\"Extraction Complete\", dmgFile);\n    }\n\n    [RelayCommand]\n    private async Task DebugShowNativeNotification()\n    {\n        var nativeManager = await notificationService.GetNativeNotificationManagerAsync();\n\n        if (nativeManager is null)\n        {\n            notificationService.Show(\n                \"Not supported\",\n                \"Native notifications are not supported on this platform.\",\n                NotificationType.Warning\n            );\n            return;\n        }\n\n        await nativeManager.ShowNotification(\n            new DesktopNotifications.Notification\n            {\n                Title = \"Test Notification\",\n                Body = \"Here is some message\",\n                Buttons = { (\"Action\", \"__Debug_Action\"), (\"Close\", \"__Debug_Close\") },\n            }\n        );\n    }\n\n    [RelayCommand]\n    private void DebugClearImageCache()\n    {\n        if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)\n        {\n            loader.ClearCache();\n        }\n    }\n\n    [RelayCommand]\n    private void DebugGCCollect()\n    {\n        GC.Collect();\n    }\n\n    [RelayCommand]\n    private async Task DebugExtractImagePromptsToTxt()\n    {\n        // Choose images\n        var provider = App.StorageProvider;\n        var files = await provider.OpenFilePickerAsync(new FilePickerOpenOptions { AllowMultiple = true });\n\n        if (files.Count == 0)\n            return;\n\n        var images = await Task.Run(() =>\n            files.Select(f => LocalImageFile.FromPath(f.TryGetLocalPath()!)).ToList()\n        );\n\n        var successfulFiles = new List<LocalImageFile>();\n\n        foreach (var localImage in images)\n        {\n            var imageFile = new FilePath(localImage.AbsolutePath);\n\n            // Write a txt with the same name as the image\n            var txtFile = imageFile.WithName(imageFile.NameWithoutExtension + \".txt\");\n\n            // Read metadata\n            if (localImage.GenerationParameters?.PositivePrompt is { } positivePrompt)\n            {\n                await File.WriteAllTextAsync(txtFile, positivePrompt);\n\n                successfulFiles.Add(localImage);\n            }\n        }\n\n        notificationService.Show(\n            \"Extracted prompts\",\n            $\"Extracted prompts from {successfulFiles.Count}/{images.Count} images.\",\n            NotificationType.Success\n        );\n    }\n\n    [RelayCommand]\n    private async Task DebugShowImageMaskEditor()\n    {\n        // Choose background image\n        var provider = App.StorageProvider;\n        var files = await provider.OpenFilePickerAsync(new FilePickerOpenOptions());\n\n        if (files.Count == 0)\n            return;\n\n        var bitmap = await Task.Run(() => SKBitmap.Decode(files[0].TryGetLocalPath()!));\n\n        var vm = dialogFactory.Get<MaskEditorViewModel>();\n\n        vm.PaintCanvasViewModel.BackgroundImage = bitmap;\n\n        await vm.GetDialog().ShowAsync();\n    }\n\n    [RelayCommand]\n    private void DebugNvidiaSmi()\n    {\n        HardwareHelper.IterGpuInfoNvidiaSmi();\n    }\n\n    #endregion\n\n    #region Systems Setting Section\n\n    [RelayCommand]\n    private async Task OnWindowsLongPathsToggleClick()\n    {\n        if (!Compat.IsWindows)\n            return;\n\n        // Command is called after value is set, so if false we need to disable\n        var requestedValue = IsWindowsLongPathsEnabled;\n\n        try\n        {\n            var result = await WindowsElevated.SetRegistryValue(\n                @\"HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\",\n                @\"LongPathsEnabled\",\n                requestedValue ? 1 : 0\n            );\n\n            if (result != 0)\n            {\n                notificationService.Show(\n                    \"Failed to toggle long paths\",\n                    $\"Error code: {result}\",\n                    NotificationType.Error\n                );\n                return;\n            }\n\n            await new BetterContentDialog\n            {\n                Title = Resources.Label_ChangesApplied,\n                Content = Resources.Text_RestartMayBeRequiredForSystemChanges,\n                CloseButtonText = Resources.Action_Close,\n            }.ShowAsync();\n        }\n        catch (Win32Exception e)\n        {\n            if (\n                e.Message.EndsWith(\n                    @\"The operation was canceled by the user.\",\n                    StringComparison.OrdinalIgnoreCase\n                )\n            )\n                return;\n\n            notificationService.Show(\"Failed to toggle long paths\", e.Message, NotificationType.Error);\n        }\n        finally\n        {\n            UpdateRegistrySettings();\n        }\n    }\n\n    #endregion\n\n    #region Info Section\n\n    public void OnVersionClick()\n    {\n        // Ignore if already enabled\n        if (SharedState.IsDebugMode)\n            return;\n\n        VersionTapCount++;\n\n        switch (VersionTapCount)\n        {\n            // Reached required threshold\n            case >= VersionTapCountThreshold:\n            {\n                IsVersionTapTeachingTipOpen = false;\n                // Enable debug options\n                SharedState.IsDebugMode = true;\n                notificationService.Show(\n                    \"Debug options enabled\",\n                    \"Warning: Improper use may corrupt application state or cause loss of data.\"\n                );\n                VersionTapCount = 0;\n                break;\n            }\n            // Open teaching tip above 3rd click\n            case >= 3:\n                IsVersionTapTeachingTipOpen = true;\n                break;\n        }\n    }\n\n    [RelayCommand]\n    private async Task ShowLicensesDialog()\n    {\n        try\n        {\n            var markdown = GetLicensesMarkdown();\n\n            var dialog = DialogHelper.CreateMarkdownDialog(markdown, \"Licenses\");\n            dialog.MaxDialogHeight = 600;\n            await dialog.ShowAsync();\n        }\n        catch (Exception e)\n        {\n            notificationService.Show(\"Failed to read licenses information\", $\"{e}\", NotificationType.Error);\n        }\n    }\n\n    private static string GetLicensesMarkdown()\n    {\n        // Read licenses.json\n        using var reader = new StreamReader(Assets.LicensesJson.Open());\n        var licenses =\n            JsonSerializer.Deserialize<IReadOnlyList<LicenseInfo>>(reader.ReadToEnd())\n            ?? throw new InvalidOperationException(\"Failed to read licenses.json\");\n\n        // Generate markdown\n        var builder = new StringBuilder();\n        foreach (var license in licenses)\n        {\n            builder.AppendLine(\n                $\"## [{license.PackageName}]({license.PackageUrl}) by {string.Join(\", \", license.Authors)}\"\n            );\n            builder.AppendLine();\n            builder.AppendLine(license.Description);\n            builder.AppendLine();\n            builder.AppendLine($\"[{license.LicenseUrl}]({license.LicenseUrl})\");\n            builder.AppendLine();\n        }\n\n        return builder.ToString();\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Settings/NotificationSettingsItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Settings;\n\npublic partial class NotificationSettingsItem(ISettingsManager settingsManager) : ObservableObject\n{\n    public NotificationKey? Key { get; set; }\n\n    [ObservableProperty]\n    private NotificationOption? option;\n\n    public static IEnumerable<NotificationOption> AvailableOptions => Enum.GetValues<NotificationOption>();\n\n    partial void OnOptionChanged(NotificationOption? oldValue, NotificationOption? newValue)\n    {\n        if (Key is null || oldValue is null || newValue is null)\n            return;\n\n        settingsManager.Transaction(settings =>\n        {\n            settings.NotificationOptions[Key] = newValue.Value;\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Settings/NotificationSettingsViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Linq;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Settings;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Settings;\n\n[View(typeof(NotificationSettingsPage))]\n[RegisterSingleton<NotificationSettingsViewModel>]\n[ManagedService]\npublic partial class NotificationSettingsViewModel(ISettingsManager settingsManager) : PageViewModelBase\n{\n    public override string Title => \"Notifications\";\n    public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Alert };\n\n    [ObservableProperty]\n    private IReadOnlyList<NotificationSettingsItem> items = [];\n\n    public override void OnLoaded()\n    {\n        base.OnLoaded();\n\n        Items = GetItems().OrderBy(item => item.Key?.Value).ToImmutableArray();\n    }\n\n    private IEnumerable<NotificationSettingsItem> GetItems()\n    {\n        var settingsOptions = settingsManager.Settings.NotificationOptions;\n\n        foreach (var notificationKey in NotificationKey.All.Values)\n        {\n            // If in settings, include settings value, otherwise default\n            if (!settingsOptions.TryGetValue(notificationKey, out var option))\n            {\n                option = notificationKey.DefaultOption;\n            }\n\n            yield return new NotificationSettingsItem(settingsManager)\n            {\n                Key = notificationKey,\n                Option = option\n            };\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/Settings/UpdateSettingsViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Threading;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing Exceptionless.DateTimeExtensions;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Media.Animation;\nusing FluentIcons.Common;\nusing Injectio.Attributes;\nusing Semver;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views.Settings;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Update;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Core.Updater;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\n\nnamespace StabilityMatrix.Avalonia.ViewModels.Settings;\n\n[View(typeof(UpdateSettingsPage))]\n[ManagedService]\n[RegisterSingleton<UpdateSettingsViewModel>]\npublic partial class UpdateSettingsViewModel : PageViewModelBase\n{\n    private readonly IUpdateHelper updateHelper;\n    private readonly IAccountsService accountsService;\n    private readonly INavigationService<SettingsViewModel> settingsNavService;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(IsUpdateAvailable))]\n    [NotifyPropertyChangedFor(nameof(HeaderText))]\n    [NotifyPropertyChangedFor(nameof(SubtitleText))]\n    private UpdateStatusChangedEventArgs? updateStatus;\n\n    public bool IsUpdateAvailable => UpdateStatus?.LatestUpdate != null;\n\n    public string HeaderText =>\n        IsUpdateAvailable ? Resources.Label_UpdateAvailable : Resources.Label_YouAreUpToDate;\n\n    public string? SubtitleText =>\n        UpdateStatus is null\n            ? null\n            : string.Format(\n                Resources.TextTemplate_LastChecked,\n                UpdateStatus.CheckedAt.ToApproximateAgeString()\n            );\n\n    [ObservableProperty]\n    private bool isAutoCheckUpdatesEnabled = true;\n\n    [ObservableProperty]\n    [NotifyPropertyChangedFor(nameof(SelectedUpdateChannelCard))]\n    private UpdateChannel preferredUpdateChannel = UpdateChannel.Stable;\n\n    public UpdateChannelCard? SelectedUpdateChannelCard\n    {\n        get => AvailableUpdateChannelCards.First(c => c.UpdateChannel == PreferredUpdateChannel);\n        set => PreferredUpdateChannel = value?.UpdateChannel ?? UpdateChannel.Stable;\n    }\n\n    public IReadOnlyList<UpdateChannelCard> AvailableUpdateChannelCards { get; } =\n        new UpdateChannelCard[]\n        {\n            new()\n            {\n                UpdateChannel = UpdateChannel.Development,\n                Description = Resources.Label_UpdatesDevChannelDescription\n            },\n            new()\n            {\n                UpdateChannel = UpdateChannel.Preview,\n                Description = Resources.Label_UpdatesPreviewChannelDescription\n            },\n            new() { UpdateChannel = UpdateChannel.Stable }\n        };\n\n    public UpdateSettingsViewModel(\n        ISettingsManager settingsManager,\n        IUpdateHelper updateHelper,\n        IAccountsService accountsService,\n        INavigationService<SettingsViewModel> settingsNavService\n    )\n    {\n        this.updateHelper = updateHelper;\n        this.accountsService = accountsService;\n        this.settingsNavService = settingsNavService;\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.PreferredUpdateChannel,\n            settings => settings.PreferredUpdateChannel,\n            true\n        );\n\n        settingsManager.RelayPropertyFor(\n            this,\n            vm => vm.IsAutoCheckUpdatesEnabled,\n            settings => settings.CheckForUpdates,\n            true\n        );\n\n        accountsService.LykosAccountStatusUpdate += (_, args) =>\n        {\n            var isBetaChannelsEnabled =\n                args.User?.Permissions.Contains(\"read:stabilitymatrix.builds.preview\") == true;\n\n            foreach (\n                var card in AvailableUpdateChannelCards.Where(c => c.UpdateChannel > UpdateChannel.Stable)\n            )\n            {\n                card.IsSelectable = isBetaChannelsEnabled;\n            }\n        };\n\n        // On update status changed\n        updateHelper.UpdateStatusChanged += (_, args) =>\n        {\n            UpdateStatus = args;\n        };\n    }\n\n    /// <inheritdoc />\n    public override async Task OnLoadedAsync()\n    {\n        if (UpdateStatus is null)\n        {\n            await CheckForUpdates();\n        }\n        OnPropertyChanged(nameof(SubtitleText));\n    }\n\n    [RelayCommand]\n    private async Task CheckForUpdates()\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n        await updateHelper.CheckForUpdate();\n    }\n\n    /// <summary>\n    /// Verify a new channel selection is valid, else returns false.\n    /// </summary>\n    /// <param name=\"card\"></param>\n    /// <returns></returns>\n    public bool VerifyChannelSelection(UpdateChannelCard card)\n    {\n        if (card.UpdateChannel == UpdateChannel.Stable)\n        {\n            return true;\n        }\n\n        if (\n            accountsService.LykosStatus?.User is { } user\n            && user.Permissions.Any(p => p.StartsWith(\"read:stabilitymatrix.builds.\"))\n        )\n        {\n            return true;\n        }\n\n        return false;\n    }\n\n    public void ShowLoginRequiredDialog()\n    {\n        Dispatcher\n            .UIThread.InvokeAsync(async () =>\n            {\n                var dialog = DialogHelper.CreateTaskDialog(\n                    \"Become a Supporter\",\n                    \"\"\n                        + \"Support the Stability Matrix Team and get access to early development builds and be the first to test new features. \"\n                );\n\n                dialog.Buttons = new[]\n                {\n                    new(Resources.Label_Accounts, TaskDialogStandardResult.OK),\n                    TaskDialogButton.CloseButton\n                };\n\n                dialog.Commands = new[]\n                {\n                    new TaskDialogCommand\n                    {\n                        Text = \"Patreon\",\n                        Description = \"https://patreon.com/StabilityMatrix\",\n                        Command = new RelayCommand(() =>\n                        {\n                            ProcessRunner.OpenUrl(\"https://patreon.com/StabilityMatrix\");\n                        })\n                    }\n                };\n\n                if (await dialog.ShowAsync(true) is TaskDialogStandardResult.OK)\n                {\n                    settingsNavService.NavigateTo<AccountSettingsViewModel>(\n                        new SuppressNavigationTransitionInfo()\n                    );\n                }\n            })\n            .SafeFireAndForget();\n    }\n\n    partial void OnUpdateStatusChanged(UpdateStatusChangedEventArgs? value)\n    {\n        // Update the update channel cards\n\n        // Use maximum version from platforms equal or lower than current\n        foreach (var card in AvailableUpdateChannelCards)\n        {\n            card.LatestVersion = value\n                ?.UpdateChannels\n                .Where(kv => kv.Key <= card.UpdateChannel)\n                .Select(kv => kv.Value)\n                .MaxBy(info => info.Version, SemVersion.PrecedenceComparer)\n                ?.Version;\n        }\n    }\n\n    partial void OnPreferredUpdateChannelChanged(UpdateChannel value)\n    {\n        CheckForUpdatesCommand.ExecuteAsync(null).SafeFireAndForget();\n    }\n\n    /// <inheritdoc />\n    public override string Title => \"Updates\";\n\n    /// <inheritdoc />\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.Settings, IconVariant = IconVariant.Filled };\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing DynamicData;\nusing FluentAvalonia.UI.Controls;\nusing FluentIcons.Common;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.Settings;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Attributes;\nusing Symbol = FluentIcons.Common.Symbol;\nusing SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(SettingsPage))]\n[RegisterSingleton<SettingsViewModel>]\npublic partial class SettingsViewModel : PageViewModelBase\n{\n    public override string Title => \"Settings\";\n    public override IconSource IconSource =>\n        new SymbolIconSource { Symbol = Symbol.Settings, IconVariant = IconVariant.Filled };\n\n    public IReadOnlyList<PageViewModelBase> SubPages { get; }\n\n    [ObservableProperty]\n    private ObservableCollection<PageViewModelBase> currentPagePath = [];\n\n    [ObservableProperty]\n    private PageViewModelBase? currentPage;\n\n    public SettingsViewModel(IServiceManager<ViewModelBase> vmFactory)\n    {\n        SubPages = new PageViewModelBase[]\n        {\n            vmFactory.Get<MainSettingsViewModel>(),\n            vmFactory.Get<InferenceSettingsViewModel>(),\n            vmFactory.Get<AccountSettingsViewModel>(),\n            vmFactory.Get<UpdateSettingsViewModel>(),\n            vmFactory.Get<NotificationSettingsViewModel>(),\n            vmFactory.Get<AnalyticsSettingsViewModel>()\n        };\n\n        CurrentPagePath.AddRange(SubPages);\n\n        CurrentPage = SubPages[0];\n    }\n\n    partial void OnCurrentPageChanged(PageViewModelBase? value)\n    {\n        if (value is null)\n        {\n            return;\n        }\n\n        if (value is MainSettingsViewModel)\n        {\n            CurrentPagePath.Clear();\n            CurrentPagePath.Add(value);\n        }\n        else\n        {\n            CurrentPagePath.Clear();\n            CurrentPagePath.AddRange(new[] { SubPages[0], value });\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/ViewModels/WorkflowsPageViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing Avalonia.Controls;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.ViewModels;\n\n[View(typeof(WorkflowsPage))]\n[RegisterSingleton<WorkflowsPageViewModel>]\npublic partial class WorkflowsPageViewModel : PageViewModelBase\n{\n    public override string Title => Resources.Label_Workflows;\n    public override IconSource IconSource => new FASymbolIconSource { Symbol = \"fa-solid fa-circle-nodes\" };\n\n    public IReadOnlyList<TabItem> Pages { get; }\n\n    [ObservableProperty]\n    private TabItem? selectedPage;\n\n    /// <inheritdoc/>\n    public WorkflowsPageViewModel(\n        OpenArtBrowserViewModel openArtBrowserViewModel,\n        InstalledWorkflowsViewModel installedWorkflowsViewModel\n    )\n    {\n        Pages = new List<TabItem>(\n            new List<TabViewModelBase>([openArtBrowserViewModel, installedWorkflowsViewModel]).Select(\n                vm => new TabItem { Header = vm.Header, Content = vm }\n            )\n        );\n        SelectedPage = Pages.FirstOrDefault();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n                          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                          xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n                          xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n                          xmlns:designData=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n                          xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n                          xmlns:viewModels=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n                          xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n                          xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n                          d:DataContext=\"{x:Static designData:DesignData.CheckpointBrowserViewModel}\"\n                          d:DesignHeight=\"700\"\n                          d:DesignWidth=\"800\"\n                          x:CompileBindings=\"True\"\n                          x:DataType=\"viewModels:CheckpointBrowserViewModel\"\n                          mc:Ignorable=\"d\"\n                          x:Class=\"StabilityMatrix.Avalonia.Views.CheckpointBrowserPage\">\n        <TabControl ItemsSource=\"{Binding Pages}\"\n                    SelectedItem=\"{Binding SelectedPage}\"/>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<CheckpointBrowserPage>]\npublic partial class CheckpointBrowserPage : UserControlBase\n{\n    public CheckpointBrowserPage()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.CheckpointsPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:behaviors=\"clr-namespace:StabilityMatrix.Avalonia.Behaviors\"\n    xmlns:checkpointManager=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointManager\"\n    xmlns:componentModel=\"clr-namespace:System.ComponentModel;assembly=System.ComponentModel.TypeConverter\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:helpers=\"clr-namespace:StabilityMatrix.Avalonia.Helpers\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:models1=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:scroll=\"clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    x:Name=\"ControlBase\"\n    d:DataContext=\"{x:Static mocks:DesignData.CheckpointsPageViewModel}\"\n    d:DesignHeight=\"650\"\n    d:DesignWidth=\"850\"\n    x:CompileBindings=\"True\"\n    x:DataType=\"viewModels:CheckpointsPageViewModel\"\n    mc:Ignorable=\"d\">\n    <controls:UserControlBase.Resources>\n        <converters:FileUriConverter x:Key=\"FileUriConverter\" />\n        <converters:EnumStringConverter x:Key=\"EnumStringConverter\" />\n        <converters:MultiplyConverter x:Key=\"MultiplyConverter\" />\n        <system:Boolean x:Key=\"False\">False</system:Boolean>\n        <system:Boolean x:Key=\"True\">True</system:Boolean>\n        <input:StandardUICommand x:Key=\"ItemClickCommand\" Command=\"{Binding ItemClickCommand}\" />\n        <input:StandardUICommand x:Key=\"BaseModelTypeClickCommand\" />\n    </controls:UserControlBase.Resources>\n\n    <controls:UserControlBase.Styles>\n        <!--  hide the chevron in the button  -->\n        <Style Selector=\"ui|CommandBarButton:flyout\">\n            <Style Selector=\"^ ui|FontIcon\">\n                <Setter Property=\"Foreground\" Value=\"Transparent\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"TreeViewItem.dragover\">\n            <Setter Property=\"Background\" Value=\"#22FFFFFF\" />\n        </Style>\n        <Style Selector=\"Border.dragover\">\n            <Setter Property=\"Background\" Value=\"#22FFFFFF\" />\n        </Style>\n        <Style Selector=\"Border#HoverBorder\">\n            <Setter Property=\"Transitions\">\n                <Transitions>\n                    <BoxShadowsTransition Property=\"BoxShadow\" Duration=\"0:0:0.237\" />\n                </Transitions>\n            </Setter>\n\n            <Style Selector=\"^ vendorLabs|BetterAsyncImage\">\n                <Setter Property=\"Transitions\">\n                    <Transitions>\n                        <TransformOperationsTransition Property=\"RenderTransform\" Duration=\"0:0:0.237\">\n                            <TransformOperationsTransition.Easing>\n                                <QuadraticEaseInOut />\n                            </TransformOperationsTransition.Easing>\n                        </TransformOperationsTransition>\n                    </Transitions>\n                </Setter>\n            </Style>\n\n            <Style Selector=\"^:pointerover\">\n                <Setter Property=\"BoxShadow\" Value=\"0 0 40 0 #60000000\" />\n                <Setter Property=\"Cursor\" Value=\"Hand\" />\n                <Style Selector=\"^ vendorLabs|BetterAsyncImage\">\n                    <Setter Property=\"CornerRadius\" Value=\"12\" />\n                    <Setter Property=\"RenderTransform\" Value=\"scale(1.03, 1.03)\" />\n                </Style>\n                <Style Selector=\"^ Border#ModelCardBottom\">\n                    <Setter Property=\"Background\" Value=\"#CC000000\" />\n                </Style>\n            </Style>\n\n            <Style Selector=\"^:not(:pointerover)\">\n                <Setter Property=\"BoxShadow\" Value=\"0 0 20 0 #60000000\" />\n                <Setter Property=\"Cursor\" Value=\"Arrow\" />\n                <Style Selector=\"^ vendorLabs|BetterAsyncImage\">\n                    <Setter Property=\"CornerRadius\" Value=\"8\" />\n                    <Setter Property=\"RenderTransform\" Value=\"scale(1, 1)\" />\n                </Style>\n                <Style Selector=\"^ Border#ModelCardBottom\">\n                    <Setter Property=\"Background\" Value=\"#99000000\" />\n                </Style>\n            </Style>\n        </Style>\n\n        <!--  markdown table styles  -->\n        <Style Selector=\"Border.TableHeader\">\n            <Setter Property=\"Margin\" Value=\"2\" />\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColor}\" />\n        </Style>\n        <Style Selector=\"Border.OddTableRow\">\n            <Setter Property=\"Margin\" Value=\"4\" />\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark1}\" />\n        </Style>\n\n        <Style Selector=\"Border.EvenTableRow\">\n            <Setter Property=\"Margin\" Value=\"4\" />\n            <Setter Property=\"Background\" Value=\"{DynamicResource SystemAccentColorDark2}\" />\n        </Style>\n    </controls:UserControlBase.Styles>\n\n    <Grid\n        Margin=\"16\"\n        ColumnDefinitions=\"Auto, *\"\n        RowDefinitions=\"Auto,*\">\n        <Grid\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            Margin=\"0,0,0,8\"\n            ColumnDefinitions=\"*, Auto\">\n            <TextBox\n                MinHeight=\"40\"\n                Margin=\"4,0\"\n                VerticalAlignment=\"Center\"\n                VerticalContentAlignment=\"Center\"\n                FontSize=\"16\"\n                Text=\"{Binding SearchQuery, Mode=TwoWay}\"\n                TextAlignment=\"Left\"\n                Watermark=\"{x:Static lang:Resources.Action_Search}\">\n                <TextBox.InnerRightContent>\n                    <Grid>\n                        <Button\n                            Classes=\"transparent-full\"\n                            Command=\"{Binding ClearSearchQuery}\"\n                            IsVisible=\"{Binding !!SearchQuery.Length}\">\n                            <ui:SymbolIcon Symbol=\"Cancel\" />\n                        </Button>\n                        <ui:SymbolIcon\n                            Margin=\"0,0,10,0\"\n                            FontSize=\"16\"\n                            IsVisible=\"{Binding !SearchQuery.Length}\"\n                            Symbol=\"Find\" />\n                    </Grid>\n                </TextBox.InnerRightContent>\n            </TextBox>\n            <ui:CommandBar\n                x:Name=\"CommandBar\"\n                Grid.Column=\"1\"\n                Margin=\"8,0,0,0\"\n                HorizontalAlignment=\"Right\"\n                VerticalAlignment=\"Center\"\n                HorizontalContentAlignment=\"Right\"\n                VerticalContentAlignment=\"Center\"\n                DefaultLabelPosition=\"Right\">\n                <ui:CommandBar.PrimaryCommands>\n                    <ui:CommandBarElementContainer>\n                        <SplitButton\n                            Padding=\"8\"\n                            HorizontalAlignment=\"Left\"\n                            Classes=\"transparent-full\"\n                            Command=\"{Binding ScanMetadataCommand}\"\n                            CommandParameter=\"{StaticResource False}\"\n                            FontFamily=\"{DynamicResource ContentControlThemeFontFamily}\"\n                            FontSize=\"12\"\n                            FontWeight=\"Normal\">\n                            <SplitButton.Content>\n                                <Grid ColumnDefinitions=\"Auto, Auto\">\n                                    <ui:SymbolIcon\n                                        Grid.Column=\"0\"\n                                        FontSize=\"18\"\n                                        Symbol=\"CloudDownload\" />\n                                    <TextBlock\n                                        Grid.Column=\"1\"\n                                        Margin=\"8,0,0,0\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"{x:Static lang:Resources.Label_FindConnectedMetadata}\" />\n                                </Grid>\n                            </SplitButton.Content>\n\n                            <SplitButton.Flyout>\n                                <ui:FAMenuFlyout Placement=\"Bottom\">\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding ScanMetadataCommand}\"\n                                        CommandParameter=\"{StaticResource True}\"\n                                        Text=\"{x:Static lang:Resources.Action_UpdateExistingMetadata}\" />\n                                </ui:FAMenuFlyout>\n                            </SplitButton.Flyout>\n                        </SplitButton>\n                    </ui:CommandBarElementContainer>\n                    <ui:CommandBarToggleButton IsChecked=\"{Binding ShowModelsInSubfolders, Mode=TwoWay}\" Label=\"Show Nested Models\">\n                        <ui:CommandBarToggleButton.IconSource>\n                            <controls:FASymbolIconSource Symbol=\"fa-solid fa-folder-tree\" />\n                        </ui:CommandBarToggleButton.IconSource>\n                    </ui:CommandBarToggleButton>\n\n                    <ui:CommandBarSeparator Padding=\"8,4\" />\n\n                    <ui:CommandBarButton\n                        VerticalAlignment=\"Center\"\n                        Command=\"{Binding RefreshCommand}\"\n                        IconSource=\"Refresh\"\n                        Label=\"{x:Static lang:Resources.Action_Refresh}\" />\n                    <ui:CommandBarButton\n                        VerticalAlignment=\"Center\"\n                        IconSource=\"Zoom\"\n                        Label=\"View\">\n                        <ui:CommandBarButton.Flyout>\n                            <Flyout>\n                                <StackPanel Orientation=\"Vertical\">\n                                    <TextBlock\n                                        HorizontalAlignment=\"Center\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"Show\" />\n\n                                    <Separator Margin=\"0,8\" />\n\n                                    <ToggleSwitch\n                                        Margin=\"8,-4\"\n                                        HorizontalAlignment=\"Left\"\n                                        HorizontalContentAlignment=\"Center\"\n                                        IsChecked=\"{Binding !HideEmptyRootCategories}\"\n                                        OffContent=\"Empty Root Categories\"\n                                        OnContent=\"Empty Root Categories\" />\n\n                                    <ToggleSwitch\n                                        Margin=\"8,-4\"\n                                        HorizontalAlignment=\"Left\"\n                                        HorizontalContentAlignment=\"Center\"\n                                        IsChecked=\"{Binding ShowNsfwImages}\"\n                                        OffContent=\"NSFW Content\"\n                                        OnContent=\"NSFW Content\" />\n\n\n                                    <Separator Margin=\"0,8\" />\n\n                                    <TextBlock\n                                        HorizontalAlignment=\"Center\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"Zoom\" />\n                                    <Slider\n                                        MinWidth=\"150\"\n                                        Maximum=\"1.5\"\n                                        Minimum=\"0.6\"\n                                        SmallChange=\"0.05\"\n                                        Value=\"{Binding ResizeFactor, Mode=TwoWay}\" />\n                                    <TextBlock\n                                        Margin=\"0,-4,0,0\"\n                                        HorizontalAlignment=\"Center\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"{Binding ResizeFactor, StringFormat={}{0:F2}x}\" />\n                                </StackPanel>\n                            </Flyout>\n                        </ui:CommandBarButton.Flyout>\n                    </ui:CommandBarButton>\n                    <ui:CommandBarButton\n                        VerticalAlignment=\"Center\"\n                        IconSource=\"Sort\"\n                        Label=\"{x:Static lang:Resources.Label_Sort}\">\n                        <ui:CommandBarButton.Flyout>\n                            <Flyout>\n                                <StackPanel Orientation=\"Vertical\">\n                                    <ListBox ItemsSource=\"{Binding SortOptions}\" SelectedItem=\"{Binding SelectedSortOption}\">\n                                        <ListBox.Template>\n                                            <ControlTemplate>\n                                                <ItemsPresenter />\n                                            </ControlTemplate>\n                                        </ListBox.Template>\n                                        <ListBox.ItemTemplate>\n                                            <DataTemplate DataType=\"{x:Type models:CheckpointSortMode}\">\n                                                <TextBlock Text=\"{Binding ., Converter={StaticResource EnumStringConverter}}\" />\n                                            </DataTemplate>\n                                        </ListBox.ItemTemplate>\n                                    </ListBox>\n                                    <Separator Margin=\"8\" />\n                                    <ListBox ItemsSource=\"{Binding SortDirections}\" SelectedItem=\"{Binding SelectedSortDirection}\">\n                                        <ListBox.Template>\n                                            <ControlTemplate>\n                                                <ItemsPresenter />\n                                            </ControlTemplate>\n                                        </ListBox.Template>\n                                        <ListBox.ItemTemplate>\n                                            <DataTemplate DataType=\"{x:Type componentModel:ListSortDirection}\">\n                                                <TextBlock Text=\"{Binding ., Converter={StaticResource EnumStringConverter}}\" />\n                                            </DataTemplate>\n                                        </ListBox.ItemTemplate>\n                                    </ListBox>\n                                    <Separator Margin=\"8\" />\n                                    <ToggleSwitch Content=\"Sort Connected Models First\" IsChecked=\"{Binding SortConnectedModelsFirst, Mode=TwoWay}\" />\n                                </StackPanel>\n                            </Flyout>\n                        </ui:CommandBarButton.Flyout>\n                    </ui:CommandBarButton>\n                    <ui:CommandBarButton\n                        VerticalAlignment=\"Center\"\n                        IconSource=\"Filter\"\n                        Label=\"Filter\">\n                        <ui:CommandBarButton.Styles>\n                            <Style Selector=\"FlyoutPresenter\">\n                                <Setter Property=\"MaxHeight\" Value=\"1000\" />\n                            </Style>\n                        </ui:CommandBarButton.Styles>\n                        <ui:CommandBarButton.Flyout>\n                            <Flyout>\n                                <StackPanel MaxWidth=\"350\" Orientation=\"Vertical\">\n                                    <Button\n                                        Margin=\"0,0,0,8\"\n                                        HorizontalAlignment=\"Stretch\"\n                                        Classes=\"transparent\"\n                                        Command=\"{Binding ClearOrSelectAllBaseModelsCommand}\"\n                                        Content=\"{Binding ClearButtonText}\" />\n                                    <ui:ItemsRepeater ItemsSource=\"{Binding BaseModelOptions}\">\n                                        <ui:ItemsRepeater.Layout>\n                                            <ui:FlowLayout\n                                                LineAlignment=\"Center\"\n                                                MinColumnSpacing=\"4\"\n                                                MinRowSpacing=\"4\"\n                                                Orientation=\"Horizontal\" />\n                                        </ui:ItemsRepeater.Layout>\n                                        <ui:ItemsRepeater.ItemTemplate>\n                                            <DataTemplate DataType=\"{x:Type checkpointManager:BaseModelOptionViewModel}\">\n                                                <ToggleButton Content=\"{Binding ModelType}\" IsChecked=\"{Binding IsSelected}\" />\n                                            </DataTemplate>\n                                        </ui:ItemsRepeater.ItemTemplate>\n                                    </ui:ItemsRepeater>\n                                </StackPanel>\n                            </Flyout>\n                        </ui:CommandBarButton.Flyout>\n                    </ui:CommandBarButton>\n                    <!--  <ui:CommandBarToggleButton  -->\n                    <!--  IconSource=\"Image\"  -->\n                    <!--  Label=\"{x:Static lang:Resources.Label_ShowModelImages}\"  -->\n                    <!--  IsChecked=\"{Binding ShowConnectedModelImages, Mode=TwoWay}\">  -->\n                    <!-- </ui:CommandBarToggleButton> -->\n                </ui:CommandBar.PrimaryCommands>\n\n                <ui:CommandBar.SecondaryCommands>\n                    <ui:CommandBarButton\n                        VerticalAlignment=\"Center\"\n                        Command=\"{Binding ShowFolderReferenceCommand}\"\n                        IconSource=\"Help\"\n                        Label=\"Folder Reference\" />\n                    <ui:CommandBarButton\n                        VerticalAlignment=\"Center\"\n                        Command=\"{x:Static helpers:IOCommands.OpenFolderBrowserCommand}\"\n                        CommandParameter=\"{Binding ModelsFolder}\"\n                        IconSource=\"OpenFolder\"\n                        Label=\"{x:Static lang:Resources.Label_ModelsFolder}\" />\n                    <ui:CommandBarToggleButton\n                        IconSource=\"Cloud\"\n                        IsChecked=\"{Binding IsImportAsConnectedEnabled, Mode=TwoWay}\"\n                        Label=\"{x:Static lang:Resources.Label_ImportAsConnected}\" />\n                    <ui:CommandBarToggleButton\n                        IconSource=\"MoveToFolder\"\n                        IsChecked=\"{Binding DragMovesAllSelected, Mode=TwoWay}\"\n                        Label=\"Move All Selected Models\"\n                        ToolTip.Tip=\"When enabled, dragging and dropping a model will also move any selected models to the drop location.\" />\n                </ui:CommandBar.SecondaryCommands>\n            </ui:CommandBar>\n        </Grid>\n\n        <TreeView\n            x:Name=\"CategoryTree\"\n            Name=\"CategoryTree\"\n            Grid.Row=\"1\"\n            Grid.Column=\"0\"\n            Margin=\"0,0,12,0\"\n            DragDrop.AllowDrop=\"True\"\n            IsVisible=\"{Binding ShowFolders}\"\n            ItemsSource=\"{Binding Categories}\"\n            SelectedItem=\"{Binding SelectedCategory}\"\n            SelectionMode=\"AlwaysSelected\">\n            <TreeView.Styles>\n                <Style Selector=\"TreeViewItem\">\n                    <Setter Property=\"IsExpanded\" Value=\"{Binding $self.((models1:CheckpointCategory)DataContext).IsExpanded, Mode=TwoWay}\" />\n                </Style>\n            </TreeView.Styles>\n            <TreeView.ContextFlyout>\n                <ui:FAMenuFlyout>\n                    <ui:MenuFlyoutItem\n                        Command=\"{Binding OpenFolderFromTreeviewCommand}\"\n                        CommandParameter=\"{Binding $parent[TreeView].SelectedItem}\"\n                        Text=\"{x:Static lang:Resources.Action_OpenInExplorer}\">\n                        <ui:MenuFlyoutItem.IconSource>\n                            <ui:SymbolIconSource Symbol=\"OpenFolder\" />\n                        </ui:MenuFlyoutItem.IconSource>\n                    </ui:MenuFlyoutItem>\n                    <ui:MenuFlyoutItem\n                        Command=\"{Binding CreateFolderCommand}\"\n                        CommandParameter=\"{Binding $parent[TreeView].SelectedItem}\"\n                        Text=\"{x:Static lang:Resources.Label_NewFolder}\">\n                        <ui:MenuFlyoutItem.IconSource>\n                            <ui:SymbolIconSource Symbol=\"NewFolder\" />\n                        </ui:MenuFlyoutItem.IconSource>\n                    </ui:MenuFlyoutItem>\n\n                    <ui:MenuFlyoutSeparator />\n\n                    <ui:MenuFlyoutItem\n                        Command=\"{Binding DeleteFolderCommand}\"\n                        CommandParameter=\"{Binding $parent[TreeView].SelectedItem}\"\n                        Text=\"{x:Static lang:Resources.Action_Delete}\">\n                        <ui:MenuFlyoutItem.IconSource>\n                            <ui:SymbolIconSource Symbol=\"Delete\" />\n                        </ui:MenuFlyoutItem.IconSource>\n                    </ui:MenuFlyoutItem>\n                </ui:FAMenuFlyout>\n            </TreeView.ContextFlyout>\n            <TreeView.ItemTemplate>\n                <TreeDataTemplate ItemsSource=\"{Binding SubDirectories}\">\n                    <TextBlock\n                        MaxWidth=\"250\"\n                        Margin=\"0,0,12,0\"\n                        TextTrimming=\"CharacterEllipsis\"\n                        TextWrapping=\"NoWrap\"\n                        ToolTip.Tip=\"{Binding Tooltip}\">\n                        <Run Text=\"{Binding Name}\" />\n                        <Run Text=\" (\" /><Run Text=\"{Binding Count}\" /><Run Text=\")\" />\n                    </TextBlock>\n                </TreeDataTemplate>\n            </TreeView.ItemTemplate>\n        </TreeView>\n\n        <scroll:BetterScrollViewer\n            Name=\"ImageScrollViewer\"\n            Grid.Row=\"1\"\n            Grid.Column=\"1\"\n            Padding=\"0,0,4,0\"\n            DragDrop.AllowDrop=\"True\">\n            <ItemsRepeater\n                HorizontalAlignment=\"Center\"\n                VerticalAlignment=\"Top\"\n                ItemsSource=\"{Binding Models}\">\n                <Interaction.Behaviors>\n                    <behaviors:ResizeBehavior\n                        MaxResizeFactor=\"1.5\"\n                        MinResizeFactor=\"0.6\"\n                        PointerWheelTargetObject=\"{Binding #ImageScrollViewer}\"\n                        ResizeFactor=\"{Binding ResizeFactor}\" />\n                </Interaction.Behaviors>\n                <ItemsRepeater.Layout>\n                    <UniformGridLayout MinColumnSpacing=\"8\" MinRowSpacing=\"8\" />\n                </ItemsRepeater.Layout>\n                <ItemsRepeater.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type checkpointManager:CheckpointFileViewModel}\">\n                        <Border\n                            Name=\"HoverBorder\"\n                            ClipToBounds=\"True\"\n                            CornerRadius=\"12\"\n                            DragDrop.AllowDrop=\"True\">\n                            <Interaction.Behaviors>\n                                <BehaviorCollection>\n                                    <ContextDragBehavior HorizontalDragThreshold=\"6\" VerticalDragThreshold=\"6\" />\n                                </BehaviorCollection>\n                            </Interaction.Behaviors>\n                            <Border.ContextFlyout>\n                                <ui:FAMenuFlyout>\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding CopyTriggerWordsCommand}\"\n                                        IconSource=\"Copy\"\n                                        IsVisible=\"{Binding CanShowTriggerWords}\"\n                                        Text=\"{x:Static lang:Resources.Action_CopyTriggerWords}\" />\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding FindOnModelBrowserCommand}\"\n                                        IconSource=\"Find\"\n                                        Text=\"{x:Static lang:Resources.Label_FindInModelBrowser}\">\n                                        <ui:MenuFlyoutItem.IsVisible>\n                                            <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                                <CompiledBinding Path=\"CheckpointFile.HasConnectedModel\" />\n                                                <CompiledBinding Path=\"CheckpointFile.HasCivitMetadata\" />\n                                            </MultiBinding>\n                                        </ui:MenuFlyoutItem.IsVisible>\n                                    </ui:MenuFlyoutItem>\n                                    <ui:MenuFlyoutSeparator IsVisible=\"{Binding CanShowTriggerWords}\" />\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding OpenOnCivitAiCommand}\"\n                                        IconSource=\"Link\"\n                                        Text=\"{x:Static lang:Resources.Action_OpenOnCivitAi}\">\n                                        <ui:MenuFlyoutItem.IsVisible>\n                                            <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                                <CompiledBinding Path=\"CheckpointFile.HasConnectedModel\" />\n                                                <CompiledBinding Path=\"CheckpointFile.HasCivitMetadata\" />\n                                            </MultiBinding>\n                                        </ui:MenuFlyoutItem.IsVisible>\n                                    </ui:MenuFlyoutItem>\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding OpenInExplorerCommand}\"\n                                        IconSource=\"Folder\"\n                                        Text=\"{OnPlatform Default={x:Static lang:Resources.Action_OpenInExplorer},\n                                                          macOS={x:Static lang:Resources.Action_OpenInFinder}}\" />\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding CopyModelUrlCommand}\"\n                                        IconSource=\"Clipboard\"\n                                        IsVisible=\"{Binding CheckpointFile.HasConnectedModel}\"\n                                        Text=\"{x:Static lang:Resources.Label_CopyLinkToClipboard}\" />\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding FindConnectedMetadataCommand}\"\n                                        CommandParameter=\"{StaticResource False}\"\n                                        IconSource=\"Find\"\n                                        IsVisible=\"{Binding !CheckpointFile.HasConnectedModel}\"\n                                        Text=\"{x:Static lang:Resources.Label_FindConnectedMetadata}\" />\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding FindConnectedMetadataCommand}\"\n                                        CommandParameter=\"{StaticResource True}\"\n                                        IconSource=\"Sync\"\n                                        Text=\"{x:Static lang:Resources.Action_UpdateExistingMetadata}\">\n                                        <ui:MenuFlyoutItem.IsVisible>\n                                            <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                                <CompiledBinding Path=\"CheckpointFile.HasConnectedModel\" />\n                                                <CompiledBinding Path=\"CheckpointFile.HasCivitMetadata\" />\n                                            </MultiBinding>\n                                        </ui:MenuFlyoutItem.IsVisible>\n                                    </ui:MenuFlyoutItem>\n\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding OpenSafetensorMetadataViewerCommand}\"\n                                        IconSource=\"Tag\"\n                                        IsVisible=\"{Binding CheckpointFile.IsSafetensorFile}\"\n                                        Text=\"View Safetensor Metadata\" />\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding OpenMetadataEditorCommand}\"\n                                        IconSource=\"Edit\"\n                                        Text=\"Edit Metadata\" />\n\n                                    <ui:MenuFlyoutSeparator />\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding RenameCommand}\"\n                                        IconSource=\"Rename\"\n                                        Text=\"{x:Static lang:Resources.Action_Rename}\" />\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{Binding DeleteCommand}\"\n                                        CommandParameter=\"{StaticResource True}\"\n                                        IconSource=\"Delete\"\n                                        Text=\"{x:Static lang:Resources.Action_Delete}\" />\n                                </ui:FAMenuFlyout>\n                            </Border.ContextFlyout>\n                            <Grid MaxWidth=\"{Binding $parent[scroll:BetterScrollViewer].((viewModels:CheckpointsPageViewModel)DataContext).ResizeFactor, FallbackValue=1, TargetNullValue=1, Converter={StaticResource MultiplyConverter}, ConverterParameter=330}\" RowDefinitions=\"*, Auto\">\n                                <CheckBox\n                                    Margin=\"14,8\"\n                                    Padding=\"0\"\n                                    HorizontalAlignment=\"Right\"\n                                    VerticalAlignment=\"Top\"\n                                    IsChecked=\"{Binding IsSelected, Mode=TwoWay}\"\n                                    ZIndex=\"100\">\n                                    <CheckBox.RenderTransform>\n                                        <ScaleTransform ScaleX=\"1.5\" ScaleY=\"1.5\" />\n                                    </CheckBox.RenderTransform>\n                                    <CheckBox.Styles>\n                                        <Style Selector=\"CheckBox\">\n                                            <Setter Property=\"CornerRadius\" Value=\"16\" />\n                                        </Style>\n                                    </CheckBox.Styles>\n                                </CheckBox>\n                                <Button\n                                    Grid.Row=\"0\"\n                                    Grid.RowSpan=\"2\"\n                                    Margin=\"0\"\n                                    Padding=\"0\"\n                                    VerticalAlignment=\"Center\"\n                                    VerticalContentAlignment=\"Center\"\n                                    ClipToBounds=\"True\"\n                                    Command=\"{StaticResource ItemClickCommand}\"\n                                    CommandParameter=\"{Binding}\"\n                                    CornerRadius=\"12\">\n                                    <LayoutTransformControl Name=\"TransformControl\">\n                                        <LayoutTransformControl.LayoutTransform>\n                                            <ScaleTransform ScaleX=\"{Binding $parent[scroll:BetterScrollViewer].((viewModels:CheckpointsPageViewModel)DataContext).ResizeFactor, FallbackValue=1, TargetNullValue=1}\" ScaleY=\"{Binding $parent[scroll:BetterScrollViewer].((viewModels:CheckpointsPageViewModel)DataContext).ResizeFactor, FallbackValue=1, TargetNullValue=1}\" />\n                                        </LayoutTransformControl.LayoutTransform>\n                                        <Grid Width=\"330\" Height=\"400\">\n                                            <vendorLabs:BetterAsyncImage\n                                                ClipToBounds=\"True\"\n                                                CornerRadius=\"8\"\n                                                IsVisible=\"{Binding !HideImage}\"\n                                                Source=\"{Binding ThumbnailUri, Converter={StaticResource FileUriConverter}}\"\n                                                Stretch=\"UniformToFill\" />\n                                            <TextBlock\n                                                Padding=\"0,0,0,24\"\n                                                VerticalAlignment=\"Center\"\n                                                FontSize=\"48\"\n                                                IsVisible=\"{Binding HideImage}\"\n                                                Text=\"{Binding NoImageMessage}\"\n                                                TextAlignment=\"Center\"\n                                                TextWrapping=\"Wrap\" />\n                                        </Grid>\n                                    </LayoutTransformControl>\n                                </Button>\n                                <LayoutTransformControl Grid.Row=\"0\">\n                                    <LayoutTransformControl.LayoutTransform>\n                                        <ScaleTransform ScaleX=\"{Binding $parent[scroll:BetterScrollViewer].((viewModels:CheckpointsPageViewModel)DataContext).ModelCardBottomResizeFactor, FallbackValue=1, TargetNullValue=1}\" ScaleY=\"{Binding $parent[scroll:BetterScrollViewer].((viewModels:CheckpointsPageViewModel)DataContext).ModelCardBottomResizeFactor, FallbackValue=1, TargetNullValue=1}\" />\n                                    </LayoutTransformControl.LayoutTransform>\n                                    <WrapPanel IsVisible=\"{Binding CheckpointFile.HasConnectedModel}\" Orientation=\"Horizontal\">\n                                        <controls:Card\n                                            Height=\"24\"\n                                            Margin=\"8,8,0,0\"\n                                            Padding=\"4\"\n                                            HorizontalAlignment=\"Left\"\n                                            VerticalAlignment=\"Top\"\n                                            Classes=\"info\">\n\n                                            <TextBlock\n                                                HorizontalAlignment=\"Center\"\n                                                VerticalAlignment=\"Center\"\n                                                FontSize=\"11\"\n                                                FontWeight=\"Medium\"\n                                                Text=\"{Binding ModelType}\" />\n                                        </controls:Card>\n                                        <controls:Card\n                                            Height=\"24\"\n                                            Margin=\"4,8,0,0\"\n                                            Padding=\"4\"\n                                            HorizontalAlignment=\"Left\"\n                                            VerticalAlignment=\"Top\"\n                                            Classes=\"info\">\n\n                                            <TextBlock\n                                                HorizontalAlignment=\"Center\"\n                                                VerticalAlignment=\"Center\"\n                                                FontSize=\"11\"\n                                                FontWeight=\"Medium\"\n                                                IsVisible=\"{Binding BaseModelName, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                                                Text=\"{Binding BaseModelName}\" />\n                                        </controls:Card>\n                                        <controls:Card\n                                            Height=\"24\"\n                                            Margin=\"4,8,0,0\"\n                                            Padding=\"4\"\n                                            HorizontalAlignment=\"Left\"\n                                            VerticalAlignment=\"Top\"\n                                            Classes=\"success\"\n                                            IsVisible=\"{Binding HasStandardUpdate}\">\n\n                                            <TextBlock\n                                                HorizontalAlignment=\"Center\"\n                                                VerticalAlignment=\"Center\"\n                                                FontSize=\"11\"\n                                                FontWeight=\"Medium\">\n                                                <Run Text=\"{x:Static lang:Resources.Label_UpdateAvailable}\" />\n                                                <Run Text=\"{Binding CheckpointFile.LatestModelInfo.LatestVersionCreatedAt}\" />\n                                            </TextBlock>\n                                        </controls:Card>\n                                        <controls:Card\n                                            Height=\"24\"\n                                            Margin=\"4,8,0,0\"\n                                            Padding=\"4\"\n                                            HorizontalAlignment=\"Left\"\n                                            VerticalAlignment=\"Top\"\n                                            Classes=\"early-access\"\n                                            IsVisible=\"{Binding HasEarlyAccessUpdateOnly}\">\n\n                                            <TextBlock\n                                                HorizontalAlignment=\"Center\"\n                                                VerticalAlignment=\"Center\"\n                                                FontSize=\"11\"\n                                                FontWeight=\"Medium\">\n                                                <Run Text=\"{x:Static lang:Resources.Label_EarlyAccessUpdateAvailable}\" />\n                                                <Run Text=\"{Binding CheckpointFile.LatestModelInfo.LatestVersionCreatedAt}\" />\n                                            </TextBlock>\n                                        </controls:Card>\n                                    </WrapPanel>\n                                </LayoutTransformControl>\n\n                                <LayoutTransformControl Grid.Row=\"1\">\n                                    <LayoutTransformControl.LayoutTransform>\n                                        <ScaleTransform ScaleX=\"{Binding $parent[scroll:BetterScrollViewer].((viewModels:CheckpointsPageViewModel)DataContext).ModelCardBottomResizeFactor, FallbackValue=1, TargetNullValue=1}\" ScaleY=\"{Binding $parent[scroll:BetterScrollViewer].((viewModels:CheckpointsPageViewModel)DataContext).ModelCardBottomResizeFactor, FallbackValue=1, TargetNullValue=1}\" />\n                                    </LayoutTransformControl.LayoutTransform>\n                                    <Border\n                                        Name=\"ModelCardBottom\"\n                                        Padding=\"8\"\n                                        CornerRadius=\"0,0,8,8\"\n                                        IsHitTestVisible=\"False\">\n\n                                        <StackPanel>\n                                            <Grid ColumnDefinitions=\"*, Auto\">\n                                                <TextBlock\n                                                    MaxWidth=\"300\"\n                                                    HorizontalAlignment=\"Left\"\n                                                    Text=\"{Binding CheckpointFile.DisplayModelName}\"\n                                                    TextWrapping=\"NoWrap\"\n                                                    ToolTip.Tip=\"{Binding CheckpointFile.DisplayModelName}\" />\n                                                <TextBlock\n                                                    Grid.Column=\"1\"\n                                                    MaxWidth=\"300\"\n                                                    Margin=\"4,0,0,0\"\n                                                    HorizontalAlignment=\"Left\"\n                                                    VerticalAlignment=\"Center\"\n                                                    FontSize=\"13\"\n                                                    Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                                    Text=\"{Binding FileSize, Converter={x:Static converters:FileSizeConverters.HumanizeBinaryFileSizeConverter}}\"\n                                                    TextWrapping=\"NoWrap\" />\n                                            </Grid>\n                                            <TextBlock\n                                                Margin=\"0,4\"\n                                                HorizontalAlignment=\"Left\"\n                                                FontSize=\"13\"\n                                                Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                                IsVisible=\"{Binding CheckpointFile.HasConnectedModel}\"\n                                                Text=\"{Binding CheckpointFile.DisplayModelVersion, FallbackValue=''}\"\n                                                TextWrapping=\"NoWrap\" />\n\n                                            <TextBlock\n                                                MaxWidth=\"300\"\n                                                Margin=\"0,0,0,4\"\n                                                HorizontalAlignment=\"Left\"\n                                                FontSize=\"13\"\n                                                Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                                Text=\"{Binding CheckpointFile.FileName}\"\n                                                TextWrapping=\"NoWrap\"\n                                                ToolTip.Tip=\"{Binding CheckpointFile.FileName}\" />\n\n                                            <TextBlock\n                                                MaxWidth=\"300\"\n                                                HorizontalAlignment=\"Left\"\n                                                FontSize=\"13\"\n                                                Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                                IsVisible=\"{Binding CanShowTriggerWords}\"\n                                                TextWrapping=\"NoWrap\"\n                                                ToolTip.Tip=\"{Binding CheckpointFile.ConnectedModelInfo.TrainedWordsString}\">\n                                                <Run Text=\"Trigger words:\" />\n                                                <Run Text=\"{Binding CheckpointFile.ConnectedModelInfo.TrainedWordsString}\" />\n                                            </TextBlock>\n                                        </StackPanel>\n                                    </Border>\n                                </LayoutTransformControl>\n                                <!--  loading stuff  -->\n                                <Border\n                                    Grid.Row=\"0\"\n                                    Grid.RowSpan=\"2\"\n                                    Margin=\"-14,-16\"\n                                    Background=\"#BB000000\"\n                                    CornerRadius=\"12\"\n                                    IsVisible=\"{Binding IsLoading}\" />\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Grid.RowSpan=\"2\"\n                                    Margin=\"0,48,0,0\"\n                                    VerticalAlignment=\"Center\"\n                                    IsVisible=\"{Binding IsLoading}\"\n                                    Text=\"{Binding Progress.Title}\"\n                                    TextAlignment=\"Center\"\n                                    TextWrapping=\"Wrap\" />\n                                <!--  Progress ring  -->\n                                <controls:ProgressRing\n                                    Grid.Row=\"0\"\n                                    Grid.RowSpan=\"2\"\n                                    Width=\"64\"\n                                    Height=\"64\"\n                                    Margin=\"0,0,0,48\"\n                                    Padding=\"0\"\n                                    HorizontalAlignment=\"Center\"\n                                    VerticalAlignment=\"Center\"\n                                    BorderThickness=\"4\"\n                                    Foreground=\"{DynamicResource ThemeRedColor}\"\n                                    IsEnabled=\"{Binding IsLoading}\"\n                                    IsIndeterminate=\"{Binding Progress.IsIndeterminate}\"\n                                    IsVisible=\"{Binding IsLoading}\"\n                                    Value=\"{Binding Progress.Percentage}\" />\n                            </Grid>\n                        </Border>\n                    </DataTemplate>\n                </ItemsRepeater.ItemTemplate>\n            </ItemsRepeater>\n        </scroll:BetterScrollViewer>\n\n        <Border\n            Grid.Row=\"1\"\n            Grid.Column=\"1\"\n            Background=\"#AA000000\"\n            CornerRadius=\"8\"\n            IsVisible=\"{Binding IsLoading}\">\n            <StackPanel VerticalAlignment=\"Center\" Orientation=\"Vertical\">\n                <TextBlock\n                    HorizontalAlignment=\"Center\"\n                    VerticalAlignment=\"Center\"\n                    FontSize=\"20\"\n                    Text=\"{Binding Progress.Text, FallbackValue=Loading..., TargetNullValue=Loading...}\" />\n                <ProgressBar\n                    Margin=\"8,36,8,0\"\n                    IsIndeterminate=\"{Binding Progress.IsIndeterminate}\"\n                    Value=\"{Binding Progress.Value}\" />\n            </StackPanel>\n        </Border>\n\n        <Border\n            Grid.Row=\"1\"\n            Grid.Column=\"1\"\n            Background=\"#AA000000\"\n            CornerRadius=\"8\"\n            DragDrop.AllowDrop=\"True\"\n            IsVisible=\"{Binding IsDragOver}\"\n            Tag=\"DragOverlay\">\n            <StackPanel VerticalAlignment=\"Center\" Orientation=\"Vertical\">\n                <TextBlock\n                    HorizontalAlignment=\"Center\"\n                    VerticalAlignment=\"Center\"\n                    FontSize=\"20\"\n                    Text=\"{x:Static lang:Resources.Label_DragAndDropCheckpointsHereToImport}\" />\n            </StackPanel>\n        </Border>\n\n        <ui:InfoBar\n            Grid.Row=\"1\"\n            Grid.Column=\"1\"\n            Margin=\"0,0,8,8\"\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Bottom\"\n            HorizontalContentAlignment=\"Right\"\n            Background=\"#AA000000\"\n            CloseButtonCommand=\"{Binding ClearSelectionCommand}\"\n            CornerRadius=\"8\"\n            IsIconVisible=\"False\"\n            IsOpen=\"{Binding !!NumItemsSelected}\">\n            <ui:InfoBar.Title>\n                <MultiBinding StringFormat=\"{}{0} Selected\">\n                    <Binding Path=\"NumItemsSelected\" />\n                </MultiBinding>\n            </ui:InfoBar.Title>\n            <ui:InfoBar.ActionButton>\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                    <Button\n                        Classes=\"accent\"\n                        Command=\"{Binding SelectAllCommand}\"\n                        Content=\"{x:Static lang:Resources.Action_SelectAll}\" />\n                    <Rectangle\n                        Width=\"2\"\n                        Margin=\"4,2\"\n                        VerticalAlignment=\"Stretch\"\n                        Fill=\"Gray\" />\n                    <Button\n                        Classes=\"danger\"\n                        Command=\"{Binding DeleteCommand}\"\n                        Content=\"{x:Static lang:Resources.Action_Delete}\" />\n                </StackPanel>\n            </ui:InfoBar.ActionButton>\n        </ui:InfoBar>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing Avalonia.Controls;\nusing Avalonia.Input;\nusing Avalonia.Platform.Storage;\nusing Avalonia.Threading;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Controls.Scroll;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels;\nusing StabilityMatrix.Avalonia.ViewModels.CheckpointManager;\nusing StabilityMatrix.Core.Models.Database;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<CheckpointsPage>]\npublic partial class CheckpointsPage : UserControlBase\n{\n    private Dictionary<CheckpointCategory, DispatcherTimer> dragTimers = new();\n\n    public CheckpointsPage()\n    {\n        InitializeComponent();\n\n        AddHandler(DragDrop.DropEvent, OnDrop);\n        AddHandler(DragDrop.DragEnterEvent, OnDragEnter);\n        AddHandler(DragDrop.DragLeaveEvent, OnDragExit);\n        AddHandler(DragDrop.DragOverEvent, OnDragOver);\n    }\n\n    private void OnDragOver(object? sender, DragEventArgs e)\n    {\n        if (e.Data.Get(DataFormats.Files) is not IEnumerable<IStorageItem> files)\n            return;\n\n        var paths = files.Select(f => f.Path.LocalPath);\n        if (paths.All(p => LocalModelFile.SupportedCheckpointExtensions.Contains(Path.GetExtension(p))))\n            return;\n\n        e.DragEffects = DragDropEffects.None;\n        e.Handled = true;\n    }\n\n    private void OnDragExit(object? sender, DragEventArgs e)\n    {\n        if (e.Source is not Control control)\n            return;\n\n        if (DataContext as CheckpointsPageViewModel is not { SelectedCategory: not null } checkpointsVm)\n            return;\n\n        switch (control)\n        {\n            case TreeViewItem treeViewItem:\n                treeViewItem.Classes.Remove(\"dragover\");\n                break;\n            case Border { Tag: \"DragOverlay\" }:\n                checkpointsVm.IsDragOver = false;\n                break;\n            case Border border:\n                border.Classes.Remove(\"dragover\");\n                break;\n            case TextBlock textBlock:\n                textBlock.Parent?.Classes.Remove(\"dragover\");\n                break;\n        }\n\n        var sourceDataContext = control switch\n        {\n            TreeViewItem treeView => treeView.DataContext,\n            Border border => border.Parent?.Parent?.DataContext,\n            TextBlock textBlock => textBlock.Parent?.DataContext,\n            _ => null\n        };\n\n        if (sourceDataContext is not CheckpointCategory category)\n            return;\n\n        if (!dragTimers.TryGetValue(category, out var timer))\n            return;\n\n        timer.Stop();\n        dragTimers.Remove(category);\n    }\n\n    private void OnDragEnter(object? sender, DragEventArgs e)\n    {\n        if (e.Source is not Control control)\n            return;\n\n        if (DataContext as CheckpointsPageViewModel is not { SelectedCategory: not null } checkpointsVm)\n            return;\n\n        if (e.Data.Get(DataFormats.Files) is IEnumerable<IStorageItem> files)\n        {\n            var paths = files.Select(f => f.Path.LocalPath);\n            if (paths.Any(p => !LocalModelFile.SupportedCheckpointExtensions.Contains(Path.GetExtension(p))))\n            {\n                e.DragEffects = DragDropEffects.None;\n                e.Handled = true;\n                return;\n            }\n        }\n\n        switch (control)\n        {\n            case TreeViewItem treeViewItem:\n                treeViewItem.Classes.Add(\"dragover\");\n                break;\n            case Border border:\n                border.Classes.Add(\"dragover\");\n                break;\n            case TextBlock textBlock:\n                textBlock.Parent?.Classes.Add(\"dragover\");\n                break;\n            case BetterScrollContentPresenter _:\n                checkpointsVm.IsDragOver = true;\n                break;\n        }\n\n        var sourceDataContext = control switch\n        {\n            TreeViewItem treeView => treeView.DataContext,\n            Border border => border.Parent?.Parent?.DataContext,\n            TextBlock textBlock => textBlock.Parent?.DataContext,\n            _ => null\n        };\n\n        if (sourceDataContext is not CheckpointCategory category)\n            return;\n\n        if (dragTimers.TryGetValue(category, out var timer))\n        {\n            timer.Stop();\n            timer.Start();\n        }\n        else\n        {\n            var newTimer = new DispatcherTimer\n            {\n                Interval = TimeSpan.FromMilliseconds(500),\n                IsEnabled = true\n            };\n            newTimer.Tick += (timerObj, _) =>\n            {\n                var treeViewItem = control switch\n                {\n                    TreeViewItem treeView => treeView,\n                    Border border => border.Parent?.Parent as TreeViewItem,\n                    TextBlock textBlock => textBlock.Parent as TreeViewItem,\n                    _ => null\n                };\n\n                if (treeViewItem != null && category.SubDirectories.Count > 0)\n                {\n                    treeViewItem.IsExpanded = true;\n                }\n\n                (timerObj as DispatcherTimer)?.Stop();\n                dragTimers.Remove(category);\n            };\n            dragTimers.Add(category, newTimer);\n        }\n    }\n\n    private async void OnDrop(object? sender, DragEventArgs e)\n    {\n        var control = e.Source as Control;\n        var sourceDataContext = control?.DataContext;\n        if (DataContext as CheckpointsPageViewModel is not { SelectedCategory: not null } checkpointsVm)\n        {\n            return;\n        }\n\n        switch (control)\n        {\n            case TreeViewItem treeViewItem:\n                treeViewItem.Classes.Remove(\"dragover\");\n                break;\n            case Border border:\n                border.Classes.Remove(\"dragover\");\n                break;\n            case TextBlock textBlock:\n                textBlock.Parent?.Classes.Remove(\"dragover\");\n                break;\n        }\n\n        checkpointsVm.IsDragOver = false;\n\n        switch (sourceDataContext)\n        {\n            case CheckpointCategory category:\n                if (e.Data.GetContext<CheckpointFileViewModel>() is { } vm)\n                {\n                    var allSelectedCheckpoints = checkpointsVm.Models.Where(x => x.IsSelected).ToList();\n\n                    if (allSelectedCheckpoints.Any() && checkpointsVm.DragMovesAllSelected)\n                    {\n                        if (!allSelectedCheckpoints.Contains(vm))\n                        {\n                            allSelectedCheckpoints.Add(vm);\n                        }\n                        await checkpointsVm.MoveBetweenFolders(allSelectedCheckpoints, category.Path);\n                    }\n                    else\n                    {\n                        await checkpointsVm.MoveBetweenFolders(vm.CheckpointFile, category.Path);\n                    }\n                }\n                else if (e.Data.Get(DataFormats.Files) is IEnumerable<IStorageItem> files)\n                {\n                    var paths = files.Select(f => f.Path.LocalPath).ToArray();\n                    await checkpointsVm.ImportFilesAsync(paths, category.Path);\n                }\n                break;\n            case CheckpointsPageViewModel _:\n            case CheckpointFileViewModel _:\n                if (e.Data.GetContext<CheckpointFileViewModel>() is { } fileVm)\n                {\n                    await checkpointsVm.MoveBetweenFolders(\n                        fileVm.CheckpointFile,\n                        checkpointsVm.SelectedCategory.Path\n                    );\n                }\n                else if (e.Data.Get(DataFormats.Files) is IEnumerable<IStorageItem> files)\n                {\n                    var paths = files.Select(f => f.Path.LocalPath).ToArray();\n                    await checkpointsVm.ImportFilesAsync(paths, checkpointsVm.SelectedCategory.Path);\n                }\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.CivitAiBrowserPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:behaviors=\"clr-namespace:StabilityMatrix.Avalonia.Behaviors\"\n    xmlns:checkpointBrowser=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser\"\n    xmlns:checkpointManager=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointManager\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:e=\"clr-namespace:StabilityMatrix.Avalonia.MarkupExtensions\"\n    xmlns:fa=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    xmlns:fluent=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:helpers=\"clr-namespace:StabilityMatrix.Avalonia.Helpers\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:scroll=\"clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n    d:DataContext=\"{x:Static designData:DesignData.CivitAiBrowserViewModel}\"\n    d:DesignHeight=\"700\"\n    d:DesignWidth=\"1000\"\n    x:CompileBindings=\"True\"\n    x:DataType=\"checkpointBrowser:CivitAiBrowserViewModel\"\n    mc:Ignorable=\"d\">\n\n    <UserControl.Styles>\n        <Style Selector=\"Border#HoverBorder\">\n            <Setter Property=\"Transitions\">\n                <Transitions>\n                    <BoxShadowsTransition Property=\"BoxShadow\" Duration=\"0:0:0.237\" />\n                </Transitions>\n            </Setter>\n\n            <Style Selector=\"^ vendorLabs|BetterAsyncImage\">\n                <Setter Property=\"Transitions\">\n                    <Transitions>\n                        <TransformOperationsTransition Property=\"RenderTransform\" Duration=\"0:0:0.237\">\n                            <TransformOperationsTransition.Easing>\n                                <QuadraticEaseInOut />\n                            </TransformOperationsTransition.Easing>\n                        </TransformOperationsTransition>\n                    </Transitions>\n                </Setter>\n            </Style>\n\n            <Style Selector=\"^:pointerover\">\n                <Setter Property=\"BoxShadow\" Value=\"0 0 40 0 #60000000\" />\n                <Setter Property=\"Cursor\" Value=\"Hand\" />\n                <Style Selector=\"^ vendorLabs|BetterAsyncImage\">\n                    <Setter Property=\"CornerRadius\" Value=\"12\" />\n                    <Setter Property=\"RenderTransform\" Value=\"scale(1.03, 1.03)\" />\n                </Style>\n                <Style Selector=\"^ Border#ModelCardBottom\">\n                    <Setter Property=\"Background\" Value=\"#CC000000\" />\n                </Style>\n            </Style>\n\n            <Style Selector=\"^:not(:pointerover)\">\n                <Setter Property=\"BoxShadow\" Value=\"0 0 20 0 #60000000\" />\n                <Setter Property=\"Cursor\" Value=\"Arrow\" />\n                <Style Selector=\"^ vendorLabs|BetterAsyncImage\">\n                    <Setter Property=\"CornerRadius\" Value=\"8\" />\n                    <Setter Property=\"RenderTransform\" Value=\"scale(1, 1)\" />\n                </Style>\n                <Style Selector=\"^ Border#ModelCardBottom\">\n                    <Setter Property=\"Background\" Value=\"#99000000\" />\n                </Style>\n            </Style>\n        </Style>\n\n        <!--  hide the chevron in the button  -->\n        <Style Selector=\"ui|CommandBarButton:flyout\">\n            <Style Selector=\"^ ui|FontIcon\">\n                <Setter Property=\"Foreground\" Value=\"Transparent\" />\n            </Style>\n        </Style>\n    </UserControl.Styles>\n\n    <UserControl.Resources>\n        <system:Boolean x:Key=\"False\">False</system:Boolean>\n        <converters:KiloFormatterStringConverter x:Key=\"KiloFormatterConverter\" />\n        <converters:CivitImageWidthConverter x:Key=\"CivitImageWidthConverter\" />\n        <input:StandardUICommand x:Key=\"ShowDetailsPageCommand\" Command=\"{Binding ShowVersionDialogCommand}\" />\n        <DataTemplate x:Key=\"CivitModelTemplate\" DataType=\"{x:Type checkpointBrowser:CheckpointBrowserCardViewModel}\">\n            <Border\n                Name=\"HoverBorder\"\n                Margin=\"8\"\n                Padding=\"0\"\n                BorderThickness=\"0\"\n                ClipToBounds=\"True\"\n                CornerRadius=\"8\">\n                <Border.ContextFlyout>\n                    <MenuFlyout>\n                        <MenuItem Command=\"{Binding OpenModelCommand}\" Header=\"{x:Static lang:Resources.Action_OpenOnCivitAi}\">\n                            <MenuItem.Icon>\n                                <ui:SymbolIcon Symbol=\"Open\" />\n                            </MenuItem.Icon>\n                        </MenuItem>\n                        <MenuItem Command=\"{Binding SearchAuthorCommand}\" Header=\"Search by Creator\">\n                            <MenuItem.Icon>\n                                <ui:SymbolIcon Symbol=\"Find\" />\n                            </MenuItem.Icon>\n                        </MenuItem>\n                    </MenuFlyout>\n                </Border.ContextFlyout>\n                <Button\n                    Name=\"ModelCard\"\n                    Padding=\"0\"\n                    VerticalContentAlignment=\"Top\"\n                    BorderThickness=\"0\"\n                    Classes=\"transparent-full\"\n                    Command=\"{StaticResource ShowDetailsPageCommand}\"\n                    CommandParameter=\"{Binding CivitModel}\"\n                    CornerRadius=\"8\"\n                    IsEnabled=\"{Binding !IsLoading}\">\n                    <Grid RowDefinitions=\"*, Auto\">\n                        <vendorLabs:BetterAsyncImage\n                            Grid.Row=\"0\"\n                            Height=\"75\"\n                            Margin=\"0,8,0,0\"\n                            HorizontalAlignment=\"Center\"\n                            VerticalAlignment=\"Top\"\n                            CornerRadius=\"8\"\n                            IsEnabled=\"{Binding ShowSantaHats, FallbackValue=False}\"\n                            IsVisible=\"{Binding ShowSantaHats, FallbackValue=False}\"\n                            Source=\"avares://StabilityMatrix.Avalonia/Assets/santahat.png\"\n                            ZIndex=\"10\">\n                            <!-- <controls:BetterAdvancedImage.RenderTransform> -->\n                            <!--     <RotateTransform Angle=\"315\"></RotateTransform> -->\n                            <!-- </controls:BetterAdvancedImage.RenderTransform> -->\n                        </vendorLabs:BetterAsyncImage>\n                        <LayoutTransformControl Grid.Row=\"0\" Grid.RowSpan=\"2\">\n                            <LayoutTransformControl.LayoutTransform>\n                                <ScaleTransform ScaleX=\"{Binding $parent[scroll:BetterScrollViewer].((checkpointBrowser:CivitAiBrowserViewModel)DataContext).ResizeFactor, FallbackValue=1, TargetNullValue=1}\" ScaleY=\"{Binding $parent[scroll:BetterScrollViewer].((checkpointBrowser:CivitAiBrowserViewModel)DataContext).ResizeFactor, FallbackValue=1, TargetNullValue=1}\" />\n                            </LayoutTransformControl.LayoutTransform>\n                            <vendorLabs:BetterAsyncImage\n                                Width=\"330\"\n                                Height=\"400\"\n                                CornerRadius=\"8\"\n                                IsCacheEnabled=\"True\"\n                                Source=\"{Binding CardImage, Converter={StaticResource CivitImageWidthConverter}, ConverterParameter=450}\"\n                                Stretch=\"UniformToFill\" />\n                        </LayoutTransformControl>\n\n                        <StackPanel\n                            Grid.Row=\"0\"\n                            HorizontalAlignment=\"Right\"\n                            Orientation=\"Horizontal\">\n                            <Button\n                                Margin=\"0,8,8,0\"\n                                HorizontalAlignment=\"Right\"\n                                VerticalAlignment=\"Top\"\n                                Classes=\"transparent-info\"\n                                Command=\"{Binding ToggleFavoriteCommand}\"\n                                FontSize=\"20\"\n                                IsVisible=\"{Binding !IsFavorite}\">\n                                <Grid>\n                                    <ui:SymbolIcon Symbol=\"StarAdd\" />\n                                </Grid>\n                            </Button>\n                            <Button\n                                Margin=\"0,8,8,0\"\n                                HorizontalAlignment=\"Right\"\n                                VerticalAlignment=\"Top\"\n                                Classes=\"success\"\n                                Command=\"{Binding ToggleFavoriteCommand}\"\n                                FontSize=\"20\"\n                                IsVisible=\"{Binding IsFavorite}\">\n                                <Grid>\n                                    <ui:SymbolIcon Symbol=\"StarFilled\" />\n                                </Grid>\n                            </Button>\n                        </StackPanel>\n\n                        <!--  Username pill card  -->\n                        <Border\n                            Grid.Row=\"0\"\n                            Margin=\"4\"\n                            HorizontalAlignment=\"Left\"\n                            VerticalAlignment=\"Bottom\"\n                            BoxShadow=\"inset 1.2 0 80 1.8 #66000000\"\n                            CornerRadius=\"16\">\n                            <Border.Resources>\n                                <DropShadowEffect\n                                    x:Key=\"TextDropShadowEffect\"\n                                    BlurRadius=\"12\"\n                                    Opacity=\"0.9\"\n                                    Color=\"#FF000000\" />\n                                <DropShadowEffect\n                                    x:Key=\"ImageDropShadowEffect\"\n                                    BlurRadius=\"12\"\n                                    Opacity=\"0.2\"\n                                    Color=\"#FF000000\" />\n                            </Border.Resources>\n                            <Button\n                                Padding=\"10,4\"\n                                Classes=\"transparent\"\n                                Command=\"{x:Static helpers:IOCommands.OpenUrlCommand}\"\n                                CommandParameter=\"{Binding CivitModel.Creator.ProfileUrl}\"\n                                CornerRadius=\"16\">\n                                <Button.ContextFlyout>\n                                    <MenuFlyout>\n                                        <MenuItem Command=\"{Binding SearchAuthorCommand}\" Header=\"Search by Creator\">\n                                            <MenuItem.Icon>\n                                                <ui:SymbolIcon Symbol=\"Find\" />\n                                            </MenuItem.Icon>\n                                        </MenuItem>\n                                    </MenuFlyout>\n                                </Button.ContextFlyout>\n                                <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                                    <vendorLabs:BetterAsyncImage\n                                        Width=\"22\"\n                                        Height=\"22\"\n                                        CornerRadius=\"11\"\n                                        Effect=\"{StaticResource ImageDropShadowEffect}\"\n                                        IsCacheEnabled=\"True\"\n                                        IsVisible=\"{Binding CivitModel.Creator.Image, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                                        Source=\"{Binding CivitModel.Creator.Image, Converter={StaticResource CivitImageWidthConverter}, ConverterParameter=32}\" />\n                                    <TextBlock\n                                        VerticalAlignment=\"Center\"\n                                        Effect=\"{StaticResource TextDropShadowEffect}\"\n                                        Text=\"{Binding CivitModel.Creator.Username}\" />\n                                </StackPanel>\n                            </Button>\n                        </Border>\n\n                        <WrapPanel Grid.Row=\"0\" Orientation=\"Horizontal\">\n                            <controls:Card\n                                Height=\"24\"\n                                Margin=\"8,8,0,0\"\n                                Padding=\"4\"\n                                HorizontalAlignment=\"Left\"\n                                VerticalAlignment=\"Top\"\n                                Classes=\"info\">\n\n                                <TextBlock\n                                    HorizontalAlignment=\"Center\"\n                                    VerticalAlignment=\"Center\"\n                                    FontSize=\"11\"\n                                    FontWeight=\"Medium\"\n                                    Text=\"{Binding CivitModel.Type}\" />\n                            </controls:Card>\n                            <controls:Card\n                                Height=\"24\"\n                                Margin=\"4,8,0,0\"\n                                Padding=\"4\"\n                                HorizontalAlignment=\"Left\"\n                                VerticalAlignment=\"Top\"\n                                Classes=\"info\">\n\n                                <TextBlock\n                                    HorizontalAlignment=\"Center\"\n                                    VerticalAlignment=\"Center\"\n                                    FontSize=\"11\"\n                                    FontWeight=\"Medium\"\n                                    Text=\"{Binding CivitModel.BaseModelType}\" />\n                            </controls:Card>\n                            <controls:Card\n                                Height=\"24\"\n                                Margin=\"4,8,0,0\"\n                                Padding=\"4\"\n                                HorizontalAlignment=\"Left\"\n                                VerticalAlignment=\"Top\"\n                                Classes=\"success\"\n                                IsVisible=\"{Binding ShowUpdateCard}\">\n\n                                <TextBlock\n                                    HorizontalAlignment=\"Center\"\n                                    VerticalAlignment=\"Center\"\n                                    FontSize=\"11\"\n                                    FontWeight=\"Medium\"\n                                    Text=\"{Binding UpdateCardText}\" />\n                            </controls:Card>\n                        </WrapPanel>\n                        <Border\n                            Grid.Row=\"0\"\n                            Grid.RowSpan=\"2\"\n                            Margin=\"0,0,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            VerticalAlignment=\"Stretch\"\n                            Background=\"#DD000000\"\n                            CornerRadius=\"8\"\n                            IsVisible=\"{Binding IsLoading}\" />\n                        <StackPanel\n                            Grid.Row=\"0\"\n                            Grid.RowSpan=\"2\"\n                            HorizontalAlignment=\"Stretch\"\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding IsLoading}\"\n                            Orientation=\"Vertical\"\n                            ZIndex=\"10\">\n                            <controls:ProgressRing\n                                Width=\"120\"\n                                Height=\"120\"\n                                HorizontalAlignment=\"Center\"\n                                VerticalAlignment=\"Center\"\n                                EndAngle=\"450\"\n                                IsIndeterminate=\"False\"\n                                StartAngle=\"90\"\n                                Value=\"{Binding Value}\" />\n                            <TextBlock\n                                MaxWidth=\"200\"\n                                Margin=\"0,8,0,0\"\n                                HorizontalAlignment=\"Center\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"{Binding Text, TargetNullValue=Importing...}\"\n                                TextWrapping=\"Wrap\"\n                                ZIndex=\"100\" />\n                        </StackPanel>\n\n                        <Border Name=\"ModelCardBottom\" Grid.Row=\"1\">\n                            <Grid ColumnDefinitions=\"*, Auto\" RowDefinitions=\"Auto, Auto, Auto\">\n\n                                <!--\n                                    TextTrimming causing issues with unicode chars until\n                                    https://github.com/AvaloniaUI/Avalonia/pull/13385 is released\n                                -->\n                                <TextBlock\n                                    Grid.ColumnSpan=\"2\"\n                                    MaxWidth=\"250\"\n                                    Margin=\"8,0,8,0\"\n                                    HorizontalAlignment=\"Left\"\n                                    VerticalAlignment=\"Center\"\n                                    FontWeight=\"SemiBold\"\n                                    Foreground=\"{DynamicResource TextControlForeground}\"\n                                    LetterSpacing=\"0.33\"\n                                    Text=\"{Binding CivitModel.Name}\"\n                                    TextWrapping=\"NoWrap\"\n                                    ToolTip.Tip=\"{Binding CivitModel.Name}\" />\n\n                                <TextBlock\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"0\"\n                                    Margin=\"8,-4,0,0\"\n                                    VerticalAlignment=\"Center\"\n                                    FontSize=\"11\"\n                                    Foreground=\"{DynamicResource TextControlForeground}\"\n                                    Text=\"{Binding CivitModel.LatestModelVersionName, FallbackValue=''}\" />\n                                <LayoutTransformControl\n                                    Grid.Row=\"2\"\n                                    Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"2\">\n                                    <LayoutTransformControl.LayoutTransform>\n                                        <ScaleTransform ScaleX=\"{Binding $parent[scroll:BetterScrollViewer].((checkpointBrowser:CivitAiBrowserViewModel)DataContext).StatsResizeFactor, FallbackValue=1, TargetNullValue=1}\" ScaleY=\"{Binding $parent[scroll:BetterScrollViewer].((checkpointBrowser:CivitAiBrowserViewModel)DataContext).StatsResizeFactor, FallbackValue=1, TargetNullValue=1}\" />\n                                    </LayoutTransformControl.LayoutTransform>\n\n                                    <Grid ColumnDefinitions=\"*, Auto\">\n                                        <StackPanel\n                                            Grid.Row=\"0\"\n                                            Grid.Column=\"1\"\n                                            IsVisible=\"{Binding CivitModel.ModelVersionStats.RatingCount, FallbackValue=False, TargetNullValue=False}\"\n                                            Orientation=\"Horizontal\">\n                                            <controls:StarsRating\n                                                Margin=\"8,8,0,8\"\n                                                Background=\"#66000000\"\n                                                FontSize=\"16\"\n                                                Foreground=\"{DynamicResource ThemeEldenRingOrangeColor}\"\n                                                Value=\"{Binding CivitModel.ModelVersionStats.Rating}\" />\n                                            <TextBlock\n                                                Margin=\"4,0,0,0\"\n                                                VerticalAlignment=\"Center\"\n                                                Text=\"{Binding CivitModel.ModelVersionStats.RatingCount}\"\n                                                TextAlignment=\"Center\" />\n                                        </StackPanel>\n\n                                        <StackPanel\n                                            Grid.Row=\"0\"\n                                            Grid.Column=\"0\"\n                                            Margin=\"8\"\n                                            HorizontalAlignment=\"Left\"\n                                            Orientation=\"Horizontal\">\n                                            <fa:Icon Value=\"fa-solid fa-heart\" />\n                                            <TextBlock\n                                                Margin=\"4,0\"\n                                                VerticalAlignment=\"Center\"\n                                                Text=\"{Binding CivitModel.Stats.ThumbsUpCount, Converter={StaticResource KiloFormatterConverter}}\" />\n\n                                            <fa:Icon Margin=\"4,0\" Value=\"fa-solid fa-download\" />\n                                            <TextBlock\n                                                Margin=\"0,0,4,0\"\n                                                VerticalAlignment=\"Center\"\n                                                Text=\"{Binding CivitModel.Stats.DownloadCount, Converter={StaticResource KiloFormatterConverter}}\" />\n                                        </StackPanel>\n                                    </Grid>\n                                </LayoutTransformControl>\n                                <Button\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"1\"\n                                    Width=\"32\"\n                                    Margin=\"0,4,4,0\"\n                                    HorizontalAlignment=\"Right\"\n                                    VerticalAlignment=\"Top\"\n                                    HorizontalContentAlignment=\"Right\"\n                                    VerticalContentAlignment=\"Top\"\n                                    BorderThickness=\"0\"\n                                    Classes=\"transparent\">\n                                    <ui:SymbolIcon FontSize=\"18\" Symbol=\"MoreVertical\" />\n                                    <Button.Flyout>\n                                        <MenuFlyout>\n                                            <MenuItem Command=\"{Binding OpenModelCommand}\" Header=\"{x:Static lang:Resources.Action_OpenOnCivitAi}\">\n                                                <MenuItem.Icon>\n                                                    <ui:SymbolIcon Symbol=\"Open\" />\n                                                </MenuItem.Icon>\n                                            </MenuItem>\n                                        </MenuFlyout>\n                                    </Button.Flyout>\n                                </Button>\n                            </Grid>\n                        </Border>\n                    </Grid>\n                </Button>\n            </Border>\n\n        </DataTemplate>\n    </UserControl.Resources>\n\n    <Grid RowDefinitions=\"Auto,*,Auto\">\n\n        <StackPanel Margin=\"8\" Orientation=\"Vertical\">\n\n            <ui:TeachingTip\n                x:Name=\"AcceleratorButtonTeachingTip\"\n                ActionButtonCommand=\"{Binding UseDiscoveryToggleCommand}\"\n                ActionButtonContent=\"{e:Ternary {Binding CivitUseDiscoveryApi},\n                                                True={x:Static lang:Resources.Action_Disable},\n                                                False={x:Static lang:Resources.Action_Enable}}\"\n                FontSize=\"14\"\n                PlacementMargin=\"12,0,0,0\"\n                PreferredPlacement=\"BottomLeft\"\n                Target=\"{Binding ElementName=AcceleratorButton}\">\n                <ui:TeachingTip.Title>\n                    <MultiBinding StringFormat=\"{}{0} ({1})\">\n                        <Binding Source=\"{x:Static lang:Resources.ModelDiscovery_Title}\" />\n                        <Binding Source=\"{x:Static lang:Resources.Label_Beta}\" />\n                    </MultiBinding>\n                </ui:TeachingTip.Title>\n                <StackPanel Spacing=\"4\">\n                    <StackPanel Orientation=\"Horizontal\">\n                        <Label Content=\"Status:\" />\n                        <Label\n                            Classes=\"Green\"\n                            Content=\"{x:Static lang:Resources.ModelDiscovery_Active}\"\n                            IsVisible=\"{Binding CivitUseDiscoveryApi}\"\n                            Theme=\"{StaticResource TagLabel}\" />\n                        <Label\n                            Classes=\"White\"\n                            Content=\"{x:Static lang:Resources.ModelDiscovery_Inactive}\"\n                            IsVisible=\"{Binding !CivitUseDiscoveryApi}\"\n                            Theme=\"{StaticResource TagLabel}\" />\n                    </StackPanel>\n                    <TextBlock\n                        Text=\"{x:Static lang:Resources.ModelDiscovery_Description}\"\n                        TextWrapping=\"Wrap\"\n                        Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                    <Separator Margin=\"4\" />\n                    <TextBlock\n                        FontSize=\"13\"\n                        Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                        Text=\"{x:Static lang:Resources.ModelDiscovery_Disclaimer}\"\n                        TextWrapping=\"Wrap\"\n                        Theme=\"{StaticResource BodyTextBlockStyle}\" />\n                </StackPanel>\n            </ui:TeachingTip>\n\n            <Grid ColumnDefinitions=\"*,Auto\">\n\n                <TextBox\n                    x:Name=\"SearchTextBox\"\n                    HorizontalAlignment=\"Stretch\"\n                    KeyDown=\"InputElement_OnKeyDown\"\n                    Text=\"{Binding SearchQuery, Mode=TwoWay}\"\n                    Watermark=\"{x:Static lang:Resources.Label_ModelSearchWatermark}\">\n                    <TextBox.InnerRightContent>\n                        <StackPanel Margin=\"2\" Orientation=\"Horizontal\">\n                            <Button\n                                Classes=\"transparent-full\"\n                                Command=\"{Binding ClearSearchQuery}\"\n                                IsVisible=\"{Binding SearchQuery.Length}\">\n                                <ui:SymbolIcon Symbol=\"Cancel\" />\n                            </Button>\n\n                            <Button\n                                x:Name=\"AcceleratorButton\"\n                                Classes=\"accelerator-indicator\"\n                                Classes.active=\"{Binding CivitUseDiscoveryApi}\"\n                                Classes.searching=\"{Binding SearchModelsCommand.IsRunning}\"\n                                Theme=\"{DynamicResource AcceleratorButtonStyle}\">\n                                <fa:Icon FontSize=\"14\" Value=\"fa-solid fa-bolt-lightning\" />\n                            </Button>\n                        </StackPanel>\n                    </TextBox.InnerRightContent>\n                </TextBox>\n\n                <Button\n                    Grid.Column=\"1\"\n                    Width=\"80\"\n                    Margin=\"8,0,8,0\"\n                    VerticalAlignment=\"Stretch\"\n                    Classes=\"accent\"\n                    Command=\"{Binding SearchModelsCommand}\"\n                    CommandParameter=\"{StaticResource False}\"\n                    IsDefault=\"True\">\n                    <Grid>\n                        <controls:ProgressRing\n                            MinWidth=\"16\"\n                            MinHeight=\"16\"\n                            VerticalAlignment=\"Center\"\n                            BorderThickness=\"4\"\n                            IsIndeterminate=\"True\"\n                            IsVisible=\"{Binding SearchModelsCommand.IsRunning}\" />\n                        <TextBlock\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding !SearchModelsCommand.IsRunning}\"\n                            Text=\"{x:Static lang:Resources.Action_Search}\" />\n                    </Grid>\n                </Button>\n            </Grid>\n            <Grid ColumnDefinitions=\"Auto,Auto,Auto,Auto,*\" RowDefinitions=\"Auto,Auto,Auto\">\n\n                <Label\n                    Grid.Row=\"0\"\n                    Grid.Column=\"0\"\n                    Margin=\"0,8,4,0\"\n                    Content=\"{x:Static lang:Resources.Label_Sort}\" />\n                <ComboBox\n                    Grid.Row=\"1\"\n                    Grid.Column=\"0\"\n                    MinWidth=\"100\"\n                    Margin=\"0,0,4,8\"\n                    ItemsSource=\"{Binding AllSortModes}\"\n                    SelectedItem=\"{Binding SortMode}\" />\n\n                <Label\n                    Grid.Row=\"0\"\n                    Grid.Column=\"1\"\n                    Margin=\"4,8,4,0\"\n                    Content=\"{x:Static lang:Resources.Label_TimePeriod}\" />\n                <ComboBox\n                    Grid.Row=\"1\"\n                    Grid.Column=\"1\"\n                    MinWidth=\"100\"\n                    Margin=\"4,0,4,8\"\n                    ItemsSource=\"{Binding AllCivitPeriods}\"\n                    SelectedItem=\"{Binding SelectedPeriod}\" />\n\n                <Label\n                    Grid.Row=\"0\"\n                    Grid.Column=\"2\"\n                    Margin=\"4,8,4,0\"\n                    Content=\"{x:Static lang:Resources.Label_ModelType}\" />\n                <ComboBox\n                    Grid.Row=\"1\"\n                    Grid.Column=\"2\"\n                    MinWidth=\"100\"\n                    Margin=\"4,0,4,8\"\n                    ItemsSource=\"{Binding AllModelTypes}\"\n                    SelectedItem=\"{Binding SelectedModelType}\" />\n\n                <Label\n                    Grid.Row=\"0\"\n                    Grid.Column=\"3\"\n                    Margin=\"4,8,4,0\"\n                    Content=\"{x:Static lang:Resources.Label_BaseModel}\" />\n                <Button\n                    Grid.Row=\"1\"\n                    Grid.Column=\"3\"\n                    Height=\"32\"\n                    Margin=\"4,0,4,8\"\n                    HorizontalAlignment=\"Stretch\"\n                    VerticalAlignment=\"Stretch\"\n                    VerticalContentAlignment=\"Center\"\n                    BorderBrush=\"{DynamicResource ComboBoxBorderBrush}\"\n                    BorderThickness=\"1\">\n                    <Button.Content>\n                        <Grid HorizontalAlignment=\"Stretch\" ColumnDefinitions=\"1*, 32\">\n                            <TextBlock IsVisible=\"{Binding ShowFilterNumber}\">\n                                <Run Text=\"{x:Static lang:Resources.Label_Filter}\" />\n                                <Run Text=\"(\" /><Run Text=\"{Binding SelectedBaseModels.Count}\" /><Run Text=\")\" />\n                            </TextBlock>\n                            <TextBlock\n                                Grid.Column=\"0\"\n                                IsVisible=\"{Binding !ShowFilterNumber}\"\n                                Text=\"{x:Static lang:Resources.Label_Filter}\" />\n                            <ui:SymbolIcon\n                                Grid.Column=\"1\"\n                                HorizontalAlignment=\"Right\"\n                                VerticalAlignment=\"Center\"\n                                FontSize=\"12\"\n                                Foreground=\"{DynamicResource TextFillColorSecondary}\"\n                                Symbol=\"ChevronDown\" />\n                        </Grid>\n                    </Button.Content>\n                    <Button.Flyout>\n                        <Flyout>\n                            <StackPanel MaxWidth=\"350\" Orientation=\"Vertical\">\n                                <Button\n                                    Margin=\"0,0,0,8\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    Classes=\"transparent\"\n                                    Command=\"{Binding ClearOrSelectAllBaseModelsCommand}\"\n                                    Content=\"{Binding ClearButtonText}\" />\n                                <ui:ItemsRepeater ItemsSource=\"{Binding AllBaseModels}\">\n                                    <ui:ItemsRepeater.Layout>\n                                        <ui:FlowLayout\n                                            LineAlignment=\"Center\"\n                                            MinColumnSpacing=\"4\"\n                                            MinRowSpacing=\"4\"\n                                            Orientation=\"Horizontal\" />\n                                    </ui:ItemsRepeater.Layout>\n                                    <ui:ItemsRepeater.ItemTemplate>\n                                        <DataTemplate DataType=\"{x:Type checkpointManager:BaseModelOptionViewModel}\">\n                                            <ToggleButton Content=\"{Binding ModelType}\" IsChecked=\"{Binding IsSelected}\" />\n                                        </DataTemplate>\n                                    </ui:ItemsRepeater.ItemTemplate>\n                                </ui:ItemsRepeater>\n                            </StackPanel>\n                        </Flyout>\n                    </Button.Flyout>\n                </Button>\n\n                <ui:CommandBar\n                    Grid.Row=\"0\"\n                    Grid.RowSpan=\"2\"\n                    Grid.Column=\"4\"\n                    Margin=\"8,0,0,0\"\n                    HorizontalAlignment=\"Right\"\n                    VerticalAlignment=\"Bottom\"\n                    DefaultLabelPosition=\"Right\">\n                    <ui:CommandBar.PrimaryCommands>\n                        <ui:CommandBarButton VerticalAlignment=\"Center\" Label=\"Zoom\">\n                            <ui:CommandBarButton.IconSource>\n                                <controls:FASymbolIconSource Symbol=\"fa-solid fa-magnifying-glass\" />\n                            </ui:CommandBarButton.IconSource>\n                            <ui:CommandBarButton.Flyout>\n                                <Flyout>\n                                    <StackPanel Orientation=\"Vertical\">\n                                        <TextBlock\n                                            HorizontalAlignment=\"Center\"\n                                            VerticalAlignment=\"Center\"\n                                            Text=\"Zoom\" />\n                                        <Slider\n                                            MinWidth=\"150\"\n                                            Maximum=\"1.5\"\n                                            Minimum=\"0.6\"\n                                            SmallChange=\"0.05\"\n                                            Value=\"{Binding ResizeFactor, Mode=TwoWay}\" />\n                                        <TextBlock\n                                            Margin=\"0,-4,0,0\"\n                                            HorizontalAlignment=\"Center\"\n                                            VerticalAlignment=\"Center\"\n                                            Text=\"{Binding ResizeFactor, StringFormat={}{0:F2}x}\" />\n                                    </StackPanel>\n                                </Flyout>\n                            </ui:CommandBarButton.Flyout>\n                        </ui:CommandBarButton>\n\n                        <ui:CommandBarToggleButton IsChecked=\"{Binding HideInstalledModels, Mode=TwoWay}\" Label=\"Hide Installed Models\">\n                            <ui:CommandBarToggleButton.IconSource>\n                                <controls:FASymbolIconSource Symbol=\"fa-solid fa-database\" />\n                            </ui:CommandBarToggleButton.IconSource>\n                        </ui:CommandBarToggleButton>\n\n                        <ui:CommandBarToggleButton IsChecked=\"{Binding HideEarlyAccessModels, Mode=TwoWay}\" Label=\"Hide Early Access Models\">\n                            <ui:CommandBarToggleButton.IconSource>\n                                <controls:FASymbolIconSource Symbol=\"fa-regular fa-credit-card\" />\n                            </ui:CommandBarToggleButton.IconSource>\n                        </ui:CommandBarToggleButton>\n\n                        <ui:CommandBarToggleButton IsChecked=\"{Binding ShowNsfw, Mode=TwoWay}\" Label=\"{x:Static lang:Resources.Label_ShowNsfwContent}\">\n                            <ui:CommandBarToggleButton.IconSource>\n                                <controls:FASymbolIconSource Symbol=\"fa-solid fa-triangle-exclamation\" />\n                            </ui:CommandBarToggleButton.IconSource>\n                        </ui:CommandBarToggleButton>\n                    </ui:CommandBar.PrimaryCommands>\n                </ui:CommandBar>\n            </Grid>\n\n        </StackPanel>\n\n        <scroll:BetterScrollViewer\n            Name=\"ImageScrollViewer\"\n            Grid.Row=\"1\"\n            ScrollChanged=\"ScrollViewer_OnScrollChanged\">\n            <ItemsRepeater\n                HorizontalAlignment=\"Center\"\n                ItemTemplate=\"{StaticResource CivitModelTemplate}\"\n                ItemsSource=\"{Binding ModelCards}\">\n                <Interaction.Behaviors>\n                    <behaviors:ResizeBehavior\n                        MaxResizeFactor=\"1.5\"\n                        MinResizeFactor=\"0.6\"\n                        PointerWheelTargetObject=\"{Binding #ImageScrollViewer}\"\n                        ResizeFactor=\"{Binding ResizeFactor}\" />\n                </Interaction.Behaviors>\n                <ItemsRepeater.Layout>\n                    <UniformGridLayout Orientation=\"Horizontal\" />\n                </ItemsRepeater.Layout>\n            </ItemsRepeater>\n        </scroll:BetterScrollViewer>\n\n        <TextBlock\n            Grid.Row=\"2\"\n            Margin=\"0,0,0,8\"\n            Text=\"End of results\"\n            TextAlignment=\"Center\">\n            <TextBlock.IsVisible>\n                <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                    <Binding Path=\"HasSearched\" />\n                    <Binding Converter=\"{x:Static StringConverters.IsNullOrEmpty}\" Path=\"NextPageCursor\" />\n                </MultiBinding>\n            </TextBlock.IsVisible>\n        </TextBlock>\n\n        <TextBlock\n            Grid.Row=\"0\"\n            Grid.RowSpan=\"3\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            FontSize=\"20\"\n            IsVisible=\"{Binding NoResultsFound}\"\n            Text=\"{Binding NoResultsText, FallbackValue=No results found}\" />\n\n        <controls:ProgressRing\n            Grid.Row=\"1\"\n            Width=\"128\"\n            Height=\"128\"\n            HorizontalAlignment=\"Stretch\"\n            VerticalAlignment=\"Stretch\"\n            IsIndeterminate=\"True\"\n            IsVisible=\"{Binding ShowMainLoadingSpinner, FallbackValue=False}\" />\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml.cs",
    "content": "﻿using System;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Avalonia.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models;\nusing CivitAiBrowserViewModel = StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser.CivitAiBrowserViewModel;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<CivitAiBrowserPage>]\npublic partial class CivitAiBrowserPage : UserControlBase\n{\n    public CivitAiBrowserPage()\n    {\n        InitializeComponent();\n\n        AcceleratorButtonTeachingTip.Target = AcceleratorButton;\n        AcceleratorButton.Click += (s, e) =>\n        {\n            AcceleratorButtonTeachingTip.IsOpen ^= true;\n        };\n    }\n\n    private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e)\n    {\n        if (sender is not ScrollViewer scrollViewer)\n            return;\n\n        if (scrollViewer.Offset.Y == 0)\n            return;\n\n        var isAtEnd = Math.Abs(scrollViewer.Offset.Y - scrollViewer.ScrollBarMaximum.Y) < 1f;\n\n        if (isAtEnd && DataContext is IInfinitelyScroll scroll)\n        {\n            scroll.LoadNextPageAsync().SafeFireAndForget();\n        }\n    }\n\n    private void InputElement_OnKeyDown(object? sender, KeyEventArgs e)\n    {\n        if (e.Key == Key.Escape && DataContext is CivitAiBrowserViewModel viewModel)\n        {\n            viewModel.ClearSearchQuery();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/CivitDetailsPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.CivitDetailsPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:avalonia1=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:checkpointBrowser=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:helpers=\"clr-namespace:StabilityMatrix.Avalonia.Helpers\"\n    xmlns:inference=\"clr-namespace:StabilityMatrix.Avalonia.Models.Inference\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:scroll=\"clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    d:DataContext=\"{x:Static mocks:DesignData.CivitDetailsPageViewModel}\"\n    d:DesignHeight=\"1000\"\n    d:DesignWidth=\"1000\"\n    x:DataType=\"checkpointBrowser:CivitDetailsPageViewModel\"\n    mc:Ignorable=\"d\">\n    <controls:UserControlBase.Resources>\n        <converters:KiloFormatterStringConverter x:Key=\"KiloFormatterConverter\" />\n        <input:StandardUICommand x:Key=\"ShowImageDialogCommand\" Command=\"{Binding ShowImageDialogCommand}\" />\n        <input:StandardUICommand x:Key=\"DeleteModelVersionCommand\" Command=\"{Binding DeleteModelVersionCommand}\" />\n    </controls:UserControlBase.Resources>\n\n    <Grid\n        Margin=\"16\"\n        ColumnDefinitions=\"*, Auto\"\n        RowDefinitions=\"Auto, Auto, Auto, *\">\n\n        <StackPanel\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            VerticalAlignment=\"Center\"\n            Orientation=\"Horizontal\"\n            Spacing=\"8\">\n            <Button\n                Padding=\"0\"\n                Classes=\"transparent-full\"\n                Command=\"{Binding GoBackCommand}\">\n                <Button.Styles>\n                    <Style Selector=\"Button:pointerover /template/ ContentPresenter\">\n                        <Setter Property=\"Background\" Value=\"Transparent\" />\n                        <Setter Property=\"BorderBrush\" Value=\"Transparent\" />\n                    </Style>\n                </Button.Styles>\n                <TextBlock\n                    VerticalAlignment=\"Center\"\n                    FontSize=\"24\"\n                    Text=\"{x:Static lang:Resources.Label_CivitAi}\">\n                    <TextBlock.Styles>\n                        <Style Selector=\"TextBlock:pointerover\">\n                            <Setter Property=\"Foreground\" Value=\"{DynamicResource TextControlPlaceholderForegroundPointerOver}\" />\n                        </Style>\n                        <Style Selector=\"Button:pressed &gt; TextBlock\">\n                            <Setter Property=\"Foreground\" Value=\"{DynamicResource TextControlForegroundDisabled}\" />\n                        </Style>\n                    </TextBlock.Styles>\n                </TextBlock>\n            </Button>\n            <ui:SymbolIcon\n                Margin=\"0,4,0,0\"\n                VerticalAlignment=\"Center\"\n                FontSize=\"24\"\n                Symbol=\"ChevronRight\" />\n            <TextBlock\n                VerticalAlignment=\"Center\"\n                FontSize=\"24\"\n                Text=\"{Binding CivitModel.Name}\" />\n            <avalonia:Icon\n                Margin=\"16,0,0,0\"\n                VerticalAlignment=\"Center\"\n                Value=\"fa-solid fa-thumbs-up\" />\n            <TextBlock VerticalAlignment=\"Center\" Text=\"{Binding CivitModel.Stats.ThumbsUpCount, Converter={StaticResource KiloFormatterConverter}}\" />\n\n            <avalonia:Icon\n                Margin=\"8,0,0,0\"\n                VerticalAlignment=\"Center\"\n                Value=\"fa-solid fa-download\" />\n            <TextBlock VerticalAlignment=\"Center\" Text=\"{Binding CivitModel.Stats.DownloadCount, Converter={StaticResource KiloFormatterConverter}}\" />\n        </StackPanel>\n\n        <StackPanel\n            Grid.Row=\"0\"\n            Grid.Column=\"1\"\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Center\"\n            Orientation=\"Horizontal\"\n            Spacing=\"8\">\n            <Button\n                Classes=\"accent\"\n                Command=\"{Binding PreviousModelCommand}\"\n                IsEnabled=\"{Binding CanGoPrevious}\">\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"8\">\n                    <avalonia:Icon\n                        Margin=\"0,1,0,0\"\n                        VerticalAlignment=\"Center\"\n                        FontSize=\"12\"\n                        Value=\"fa-solid fa-chevron-left\" />\n                    <TextBlock VerticalAlignment=\"Center\" Text=\"Previous Model\" />\n                </StackPanel>\n            </Button>\n            <Button\n                Classes=\"accent\"\n                Command=\"{Binding NextModelCommand}\"\n                IsEnabled=\"{Binding CanGoNext}\">\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"8\">\n                    <TextBlock VerticalAlignment=\"Center\" Text=\"Next Model\" />\n                    <avalonia:Icon\n                        Margin=\"0,1,0,0\"\n                        VerticalAlignment=\"Center\"\n                        FontSize=\"12\"\n                        Value=\"fa-solid fa-chevron-right\" />\n                </StackPanel>\n            </Button>\n        </StackPanel>\n\n        <ItemsRepeater\n            Grid.Row=\"1\"\n            Grid.Column=\"0\"\n            Margin=\"0,8,0,0\"\n            ItemsSource=\"{Binding CivitModel.Tags}\">\n            <ItemsRepeater.Layout>\n                <StackLayout Orientation=\"Horizontal\" Spacing=\"4\" />\n            </ItemsRepeater.Layout>\n            <ItemsRepeater.ItemTemplate>\n                <DataTemplate>\n                    <controls:Card Padding=\"8,2\">\n                        <TextBlock\n                            FontSize=\"13\"\n                            FontWeight=\"Light\"\n                            Text=\"{Binding}\"\n                            TextAlignment=\"Center\" />\n                    </controls:Card>\n                </DataTemplate>\n            </ItemsRepeater.ItemTemplate>\n        </ItemsRepeater>\n\n        <ui:CommandBar\n            Grid.Row=\"1\"\n            Grid.Column=\"1\"\n            Margin=\"0,8,0,0\"\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Center\"\n            DefaultLabelPosition=\"Right\">\n            <ui:CommandBar.Styles>\n                <Style Selector=\"ui|CommandBarButton:flyout\">\n                    <Style Selector=\"^ ui|FontIcon\">\n                        <Setter Property=\"Foreground\" Value=\"Transparent\" />\n                    </Style>\n                </Style>\n            </ui:CommandBar.Styles>\n            <ui:CommandBar.PrimaryCommands>\n                <ui:CommandBarButton IconSource=\"Zoom\" Label=\"{x:Static lang:Resources.Label_View}\">\n                    <ui:CommandBarButton.Flyout>\n                        <Flyout>\n                            <StackPanel Orientation=\"Vertical\">\n                                <TextBlock\n                                    HorizontalAlignment=\"Center\"\n                                    VerticalAlignment=\"Center\"\n                                    Text=\"{x:Static lang:Resources.Label_Show}\" />\n\n                                <Separator Margin=\"0,8\" />\n\n                                <ToggleSwitch\n                                    Margin=\"8,-4\"\n                                    HorizontalAlignment=\"Left\"\n                                    HorizontalContentAlignment=\"Center\"\n                                    IsChecked=\"{Binding !HideEarlyAccess}\"\n                                    OffContent=\"{x:Static lang:Resources.Label_EarlyAccessModels}\"\n                                    OnContent=\"{x:Static lang:Resources.Label_EarlyAccessModels}\" />\n\n                                <ToggleSwitch\n                                    Margin=\"8,-4\"\n                                    HorizontalAlignment=\"Left\"\n                                    HorizontalContentAlignment=\"Center\"\n                                    IsChecked=\"{Binding ShowNsfw}\"\n                                    OffContent=\"{x:Static lang:Resources.Label_NsfwContent}\"\n                                    OnContent=\"{x:Static lang:Resources.Label_NsfwContent}\" />\n\n                                <ToggleSwitch\n                                    Margin=\"8,-4\"\n                                    HorizontalAlignment=\"Left\"\n                                    HorizontalContentAlignment=\"Center\"\n                                    IsChecked=\"{Binding ShowTrainingData}\"\n                                    OffContent=\"{x:Static lang:Resources.Label_NonModelFiles}\"\n                                    OnContent=\"{x:Static lang:Resources.Label_NonModelFiles}\" />\n\n                                <ToggleSwitch\n                                    Margin=\"8,-4\"\n                                    HorizontalAlignment=\"Left\"\n                                    HorizontalContentAlignment=\"Center\"\n                                    IsChecked=\"{Binding !HideInstalledModels}\"\n                                    OffContent=\"{x:Static lang:Resources.Label_InstalledModels}\"\n                                    OnContent=\"{x:Static lang:Resources.Label_InstalledModels}\" />\n                            </StackPanel>\n                        </Flyout>\n                    </ui:CommandBarButton.Flyout>\n                </ui:CommandBarButton>\n            </ui:CommandBar.PrimaryCommands>\n            <ui:CommandBar.SecondaryCommands>\n                <ui:CommandBarButton\n                    Command=\"{x:Static helpers:IOCommands.OpenUrlCommand}\"\n                    CommandParameter=\"{Binding CivitUrl}\"\n                    IconSource=\"Open\"\n                    Label=\"{x:Static lang:Resources.Action_OpenOnCivitAi}\" />\n\n                <ui:CommandBarButton\n                    Command=\"{Binding ShowBulkDownloadDialogCommand}\"\n                    IconSource=\"Download\"\n                    Label=\"{x:Static lang:Resources.Label_DownloadAllFilesAllVersions}\" />\n            </ui:CommandBar.SecondaryCommands>\n        </ui:CommandBar>\n\n        <scroll:BetterScrollViewer\n            Grid.Row=\"2\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            HorizontalScrollBarVisibility=\"Auto\"\n            PointerWheelChanged=\"InputElement_OnPointerWheelChanged\"\n            VerticalScrollBarVisibility=\"Disabled\">\n            <ListBox\n                Padding=\"0,12\"\n                ItemsSource=\"{Binding ModelVersions}\"\n                SelectedItem=\"{Binding SelectedVersion}\"\n                SelectionMode=\"AlwaysSelected\">\n                <ListBox.Styles>\n                    <Style Selector=\"ListBoxItem:selected /template/ ContentPresenter\">\n                        <Setter Property=\"Background\" Value=\"{DynamicResource AccentButtonBackground}\" />\n                        <Setter Property=\"Foreground\" Value=\"{DynamicResource AccentButtonForeground}\" />\n                    </Style>\n                    <Style Selector=\"ListBoxItem\">\n                        <Setter Property=\"Padding\" Value=\"16,0\" />\n                    </Style>\n                    <Style Selector=\"ListBoxItem:selected /template/ Rectangle#SelectionIndicator\">\n                        <Setter Property=\"IsVisible\" Value=\"False\" />\n                    </Style>\n                </ListBox.Styles>\n                <ListBox.ItemsPanel>\n                    <ItemsPanelTemplate>\n                        <StackPanel\n                            Margin=\"0\"\n                            Orientation=\"Horizontal\"\n                            Spacing=\"0\" />\n                    </ItemsPanelTemplate>\n                </ListBox.ItemsPanel>\n                <ListBox.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type dialogs:ModelVersionViewModel}\">\n                        <Grid HorizontalAlignment=\"Center\" ColumnDefinitions=\"Auto, *, Auto\">\n                            <Grid.ContextFlyout>\n                                <ui:FAMenuFlyout>\n                                    <ui:MenuFlyoutItem\n                                        Command=\"{StaticResource DeleteModelVersionCommand}\"\n                                        CommandParameter=\"{Binding ModelVersion}\"\n                                        IconSource=\"Delete\"\n                                        IsVisible=\"{Binding IsInstalled}\"\n                                        Text=\"{x:Static lang:Resources.Action_Delete}\" />\n                                </ui:FAMenuFlyout>\n                            </Grid.ContextFlyout>\n                            <avalonia:Icon\n                                Margin=\"0,2,8,0\"\n                                HorizontalAlignment=\"Left\"\n                                VerticalAlignment=\"Center\"\n                                IsVisible=\"{Binding ModelVersion.IsEarlyAccess}\"\n                                ToolTip.Tip=\"Early Access\"\n                                Value=\"fa-solid fa-coins\" />\n                            <TextBlock\n                                Grid.Column=\"1\"\n                                VerticalAlignment=\"Center\"\n                                FontWeight=\"Light\"\n                                Text=\"{Binding ModelVersion.Name}\"\n                                TextAlignment=\"Center\" />\n                            <avalonia:Icon\n                                Grid.Column=\"2\"\n                                Margin=\"8,2,0,0\"\n                                HorizontalAlignment=\"Right\"\n                                VerticalAlignment=\"Center\"\n                                IsVisible=\"{Binding IsInstalled}\"\n                                Value=\"fa-solid fa-check\" />\n                        </Grid>\n                    </DataTemplate>\n                </ListBox.ItemTemplate>\n            </ListBox>\n        </scroll:BetterScrollViewer>\n\n        <Grid\n            Grid.Row=\"3\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            ColumnDefinitions=\"2*, *\"\n            RowDefinitions=\"3*, Auto, *, Auto, *\">\n\n            <!--  image carousel  -->\n            <scroll:BetterScrollViewer\n                Grid.Row=\"0\"\n                Grid.Column=\"0\"\n                Margin=\"0,8,0,8\"\n                AllowAutoHide=\"True\"\n                HorizontalScrollBarVisibility=\"Auto\"\n                PointerWheelChanged=\"InputElement_OnPointerWheelChanged\"\n                VerticalScrollBarVisibility=\"Disabled\">\n                <ItemsRepeater ItemsSource=\"{Binding ImageSources}\">\n                    <ItemsRepeater.Layout>\n                        <StackLayout Orientation=\"Horizontal\" Spacing=\"12\" />\n                    </ItemsRepeater.Layout>\n                    <ItemsRepeater.ItemTemplate>\n                        <DataTemplate DataType=\"{x:Type models:ImageSource}\">\n                            <controls:BetterAdvancedImage\n                                CornerRadius=\"8\"\n                                Cursor=\"Hand\"\n                                Source=\"{Binding}\"\n                                Stretch=\"UniformToFill\"\n                                StretchDirection=\"Both\">\n                                <Interaction.Behaviors>\n                                    <EventTriggerBehavior EventName=\"PointerPressed\">\n                                        <InvokeCommandAction Command=\"{StaticResource ShowImageDialogCommand}\" CommandParameter=\"{Binding}\" />\n                                    </EventTriggerBehavior>\n                                </Interaction.Behaviors>\n                            </controls:BetterAdvancedImage>\n                        </DataTemplate>\n                    </ItemsRepeater.ItemTemplate>\n                </ItemsRepeater>\n            </scroll:BetterScrollViewer>\n\n            <GridSplitter\n                Grid.Row=\"1\"\n                Grid.Column=\"0\"\n                MaxWidth=\"100\"\n                Margin=\"0,0,0,6\"\n                VerticalAlignment=\"Center\"\n                Background=\"White\"\n                BorderThickness=\"1\"\n                CornerRadius=\"4\"\n                Opacity=\"0.5\" />\n\n            <!--  Description Card  -->\n            <controls:Card\n                Grid.Row=\"2\"\n                Grid.RowSpan=\"{Binding DescriptionRowSpan}\"\n                Grid.Column=\"0\"\n                Margin=\"0,0,0,8\"\n                VerticalAlignment=\"Stretch\"\n                VerticalContentAlignment=\"Top\">\n                <ScrollViewer>\n                    <controls:MarkdownViewer Html=\"{Binding Description}\" />\n                </ScrollViewer>\n            </controls:Card>\n\n            <GridSplitter\n                Grid.Row=\"3\"\n                Grid.Column=\"0\"\n                MaxWidth=\"100\"\n                Margin=\"0,0,0,6\"\n                VerticalAlignment=\"Center\"\n                Background=\"White\"\n                BorderThickness=\"1\"\n                CornerRadius=\"4\"\n                IsVisible=\"{Binding ModelVersionDescription, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                Opacity=\"0.5\" />\n\n\n            <!--  Model Version Description Card  -->\n            <controls:Card\n                Grid.Row=\"4\"\n                Grid.Column=\"0\"\n                Margin=\"0,0,0,8\"\n                VerticalAlignment=\"Stretch\"\n                VerticalContentAlignment=\"Top\"\n                IsVisible=\"{Binding ModelVersionDescription, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\">\n                <ScrollViewer>\n                    <controls:MarkdownViewer Html=\"{Binding ModelVersionDescription}\" />\n                </ScrollViewer>\n            </controls:Card>\n\n            <!--  right column  -->\n            <ScrollViewer\n                Grid.Row=\"0\"\n                Grid.RowSpan=\"5\"\n                Grid.Column=\"1\"\n                Margin=\"0,0,0,0\">\n                <Grid RowDefinitions=\"Auto, Auto, Auto, Auto\">\n                    <!--  Details Card  -->\n                    <controls:Card\n                        Grid.Row=\"0\"\n                        MinWidth=\"200\"\n                        Margin=\"8,8,0,8\"\n                        Padding=\"12\"\n                        VerticalAlignment=\"Top\">\n                        <StackPanel Spacing=\"8\">\n                            <avalonia1:SpacedGrid ColumnDefinitions=\"*, *\">\n                                <TextBlock\n                                    FontSize=\"20\"\n                                    FontWeight=\"Light\"\n                                    Text=\"{x:Static lang:Resources.Label_Details}\" />\n                                <Label\n                                    Grid.Column=\"1\"\n                                    Classes=\"Circle Solid Red\"\n                                    Content=\"Early Access\"\n                                    IsVisible=\"{Binding IsEarlyAccess}\"\n                                    Theme=\"{StaticResource TagLabel}\" />\n                            </avalonia1:SpacedGrid>\n                            <Separator Margin=\"0\" />\n                            <avalonia1:SpacedGrid ColumnDefinitions=\"*,*\" RowDefinitions=\"*,Auto,*,Auto,*,Auto,*,Auto,*\">\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"0\"\n                                    Text=\"{x:Static lang:Resources.Label_Author}\" />\n\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"1\"\n                                    Cursor=\"Hand\"\n                                    Foreground=\"{DynamicResource AccentButtonBackground}\"\n                                    Text=\"{Binding CivitModel.Creator.Username}\"\n                                    TextDecorations=\"Underline\">\n                                    <Interaction.Behaviors>\n                                        <EventTriggerBehavior EventName=\"PointerPressed\">\n                                            <InvokeCommandAction Command=\"{Binding SearchByAuthorCommand}\" />\n                                        </EventTriggerBehavior>\n                                    </Interaction.Behaviors>\n                                </TextBlock>\n\n                                <Separator\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"2\"\n                                    Margin=\"0,4,0,4\" />\n\n                                <TextBlock\n                                    Grid.Row=\"2\"\n                                    Grid.Column=\"0\"\n                                    Text=\"{x:Static lang:Resources.Label_ModelType}\" />\n\n                                <TextBlock\n                                    Grid.Row=\"2\"\n                                    Grid.Column=\"1\"\n                                    Text=\"{Binding CivitModel.Type}\" />\n\n                                <Separator\n                                    Grid.Row=\"3\"\n                                    Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"2\"\n                                    Margin=\"0,4,0,4\" />\n\n                                <TextBlock\n                                    Grid.Row=\"4\"\n                                    Grid.Column=\"0\"\n                                    Text=\"{x:Static lang:Resources.Label_BaseModel}\" />\n\n                                <TextBlock\n                                    Grid.Row=\"4\"\n                                    Grid.Column=\"1\"\n                                    Text=\"{Binding BaseModelType}\" />\n\n                                <Separator\n                                    Grid.Row=\"5\"\n                                    Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"2\"\n                                    Margin=\"0,4,0,4\" />\n\n                                <TextBlock\n                                    Grid.Row=\"6\"\n                                    Grid.Column=\"0\"\n                                    Text=\"{x:Static lang:Resources.Label_Hash}\" />\n\n                                <TextBlock\n                                    Grid.Row=\"6\"\n                                    Grid.Column=\"1\"\n                                    Text=\"{Binding ShortSha256}\" />\n\n                                <Separator\n                                    Grid.Row=\"7\"\n                                    Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"2\"\n                                    Margin=\"0,4,0,4\" />\n\n                                <TextBlock\n                                    Grid.Row=\"8\"\n                                    Grid.Column=\"0\"\n                                    Text=\"{x:Static lang:Resources.Label_LastUpdatedAt}\" />\n\n                                <TextBlock\n                                    Grid.Row=\"8\"\n                                    Grid.Column=\"1\"\n                                    Text=\"{Binding LastUpdated}\" />\n\n                            </avalonia1:SpacedGrid>\n                        </StackPanel>\n                    </controls:Card>\n\n                    <!--  File Name Pattern Card  -->\n                    <controls:Card\n                        Grid.Row=\"1\"\n                        Margin=\"8,0,0,8\"\n                        VerticalAlignment=\"Top\"\n                        VerticalContentAlignment=\"Top\">\n                        <Grid\n                            VerticalAlignment=\"Stretch\"\n                            RowDefinitions=\"Auto, Auto, Auto\"\n                            RowSpacing=\"8\">\n                            <TextBlock\n                                Grid.Row=\"0\"\n                                FontSize=\"20\"\n                                FontWeight=\"Light\"\n                                Text=\"{x:Static lang:Resources.Label_FileNamePattern}\" />\n                            <TextBox\n                                Name=\"ModelFileNameFormatTextBox\"\n                                Grid.Row=\"1\"\n                                MinWidth=\"150\"\n                                FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n                                FontSize=\"13\"\n                                Text=\"{Binding ModelFileNameFormat}\"\n                                Watermark=\"{x:Static inference:FileNameFormat.DefaultModelBrowserTemplate}\" />\n\n                            <TextBlock\n                                Grid.Row=\"2\"\n                                FontSize=\"12\"\n                                Text=\"{Binding ModelNameFormatSample}\" />\n                        </Grid>\n                    </controls:Card>\n\n                    <!--  Civit Files Card  -->\n                    <controls:Card\n                        Grid.Row=\"2\"\n                        Margin=\"8,0,0,8\"\n                        Padding=\"4\"\n                        VerticalAlignment=\"Center\">\n                        <Grid RowDefinitions=\"Auto, *\">\n                            <TextBlock\n                                Margin=\"8,8,0,0\"\n                                FontSize=\"20\"\n                                FontWeight=\"Light\"\n                                Text=\"{x:Static lang:Resources.Label_Files}\" />\n\n                            <ItemsRepeater\n                                Grid.Row=\"1\"\n                                Margin=\"8,4,4,4\"\n                                ItemsSource=\"{Binding CivitFiles}\">\n                                <ItemsRepeater.ItemTemplate>\n                                    <DataTemplate DataType=\"{x:Type dialogs:CivitFileViewModel}\">\n                                        <Grid\n                                            Margin=\"0,4,0,4\"\n                                            ColumnDefinitions=\"*, Auto\"\n                                            RowDefinitions=\"Auto, Auto, Auto, Auto\">\n                                            <TextBlock\n                                                Grid.Row=\"0\"\n                                                Grid.Column=\"0\"\n                                                VerticalAlignment=\"Center\"\n                                                Text=\"{Binding CivitFile.DisplayName}\"\n                                                TextWrapping=\"Wrap\" />\n\n                                            <TextBlock\n                                                Grid.Row=\"0\"\n                                                Grid.RowSpan=\"3\"\n                                                Grid.Column=\"1\"\n                                                HorizontalAlignment=\"Center\"\n                                                VerticalAlignment=\"Center\"\n                                                Foreground=\"{DynamicResource AccentButtonBackground}\"\n                                                IsVisible=\"{Binding IsInstalled}\"\n                                                Text=\"{x:Static lang:Resources.Label_Installed}\"\n                                                TextAlignment=\"Center\"\n                                                TextTrimming=\"CharacterEllipsis\"\n                                                TextWrapping=\"NoWrap\" />\n\n                                            <TextBlock\n                                                Grid.Row=\"1\"\n                                                Grid.Column=\"0\"\n                                                Margin=\"0,8,0,4\"\n                                                FontSize=\"12\"\n                                                TextWrapping=\"Wrap\">\n                                                <Run Text=\"{Binding CivitFile.Metadata.Format}\" />\n                                                <Run Text=\"-\" />\n                                                <Run Text=\"{Binding CivitFile.Metadata.Size}\" />\n                                                <Run Text=\"-\" />\n                                                <Run Text=\"{Binding CivitFile.Metadata.Fp}\" />\n                                                <Run Text=\"-\" />\n                                                <Run Text=\"{Binding CivitFile.Type}\" />\n                                                <Run Text=\"-\" />\n                                                <Run Text=\"{Binding CivitFile.FullFilesSize, Mode=OneWay}\" />\n                                            </TextBlock>\n\n                                            <TextBlock\n                                                Grid.Row=\"2\"\n                                                Grid.Column=\"0\"\n                                                Margin=\"0,4,0,4\"\n                                                FontSize=\"12\"\n                                                Foreground=\"{DynamicResource TextControlForegroundDisabled}\"\n                                                TextWrapping=\"Wrap\">\n                                                <Run Text=\"SHA256:\" />\n                                                <Run Text=\"{Binding CivitFile.Hashes.ShortSha256}\" />\n                                                <Run Text=\"-\" />\n                                                <Run Text=\"BLAKE3\" />\n                                                <Run Text=\"{Binding CivitFile.Hashes.ShortBlake3}\" />\n                                            </TextBlock>\n\n\n                                            <SplitButton\n                                                Grid.Row=\"3\"\n                                                Grid.Column=\"0\"\n                                                Margin=\"0,4,0,0\"\n                                                Padding=\"4\"\n                                                HorizontalAlignment=\"Stretch\"\n                                                VerticalAlignment=\"Center\"\n                                                HorizontalContentAlignment=\"Center\"\n                                                Classes=\"accent\"\n                                                Command=\"{Binding DownloadToDefaultCommand}\"\n                                                IsEnabled=\"{Binding CanImport}\"\n                                                ToolTip.Tip=\"{Binding DownloadTooltip}\">\n                                                <SplitButton.Resources>\n                                                    <input:StandardUICommand x:Key=\"DownloadToSelectedLocationCommand\" Command=\"{Binding DownloadToSelectedLocationCommand}\" />\n                                                </SplitButton.Resources>\n                                                <SplitButton.Content>\n                                                    <StackPanel Margin=\"2,0,0,0\" Orientation=\"Horizontal\">\n                                                        <avalonia:Icon\n                                                            Margin=\"0,0,8,0\"\n                                                            VerticalAlignment=\"Center\"\n                                                            Value=\"fa-solid fa-download\" />\n                                                        <TextBlock Text=\"{x:Static lang:Resources.Action_Download}\" />\n                                                    </StackPanel>\n                                                </SplitButton.Content>\n                                                <SplitButton.Flyout>\n                                                    <ui:FAMenuFlyout ItemsSource=\"{Binding InstallLocations}\" Placement=\"BottomEdgeAlignedRight\">\n                                                        <ui:FAMenuFlyout.ItemTemplate>\n                                                            <DataTemplate DataType=\"system:String\">\n                                                                <ui:MenuFlyoutItem\n                                                                    Command=\"{StaticResource DownloadToSelectedLocationCommand}\"\n                                                                    CommandParameter=\"{Binding .}\"\n                                                                    Text=\"{Binding .}\" />\n                                                            </DataTemplate>\n                                                        </ui:FAMenuFlyout.ItemTemplate>\n                                                    </ui:FAMenuFlyout>\n                                                </SplitButton.Flyout>\n                                            </SplitButton>\n                                            <Button\n                                                Grid.Row=\"3\"\n                                                Grid.Column=\"1\"\n                                                Margin=\"8,4,0,0\"\n                                                Padding=\"4\"\n                                                HorizontalAlignment=\"Stretch\"\n                                                VerticalAlignment=\"Center\"\n                                                HorizontalContentAlignment=\"Stretch\"\n                                                Classes=\"danger\"\n                                                Command=\"{Binding DeleteCommand}\"\n                                                IsVisible=\"{Binding IsInstalled}\">\n                                                <StackPanel Margin=\"2,0,2,0\" Orientation=\"Horizontal\">\n                                                    <avalonia:Icon\n                                                        Margin=\"0,0,8,0\"\n                                                        VerticalAlignment=\"Center\"\n                                                        Value=\"fa-solid fa-trash\" />\n                                                    <TextBlock FontSize=\"14\" Text=\"{x:Static lang:Resources.Action_Delete}\" />\n                                                </StackPanel>\n                                            </Button>\n                                        </Grid>\n                                    </DataTemplate>\n                                </ItemsRepeater.ItemTemplate>\n                            </ItemsRepeater>\n                        </Grid>\n                    </controls:Card>\n\n                    <!--  Preferred Defaults Card  -->\n                    <controls:Card\n                        Grid.Row=\"3\"\n                        Margin=\"8,0,0,8\"\n                        Padding=\"4\"\n                        VerticalAlignment=\"Stretch\"\n                        IsVisible=\"{Binding ShowInferenceDefaultsSection}\">\n                        <Grid\n                            HorizontalAlignment=\"Stretch\"\n                            ColumnDefinitions=\"Auto, *, Auto\"\n                            RowDefinitions=\"Auto, *\">\n                            <TextBlock\n                                Margin=\"8,8,0,0\"\n                                FontSize=\"20\"\n                                FontWeight=\"Light\"\n                                Text=\"{x:Static lang:Resources.Label_InferenceDefaultsHeader}\" />\n\n                            <ToggleSwitch\n                                Grid.Row=\"0\"\n                                Grid.Column=\"2\"\n                                Margin=\"0,-2,12,0\"\n                                IsChecked=\"{Binding IsInferenceDefaultsEnabled}\" />\n\n                            <avalonia:Icon\n                                Grid.Row=\"0\"\n                                Grid.Column=\"1\"\n                                Margin=\"12,2,0,0\"\n                                HorizontalAlignment=\"Left\"\n                                VerticalAlignment=\"Center\"\n                                ToolTip.Tip=\"{x:Static lang:Resources.Tooltip_InferenceDefaults}\"\n                                Value=\"fa-solid fa-circle-info\" />\n\n                            <controls:SamplerCard\n                                Grid.Row=\"1\"\n                                Grid.Column=\"0\"\n                                Grid.ColumnSpan=\"3\"\n                                Margin=\"8\"\n                                HorizontalAlignment=\"Stretch\"\n                                Background=\"Transparent\"\n                                DataContext=\"{Binding SamplerCardViewModel}\" />\n                        </Grid>\n                    </controls:Card>\n                </Grid>\n            </ScrollViewer>\n        </Grid>\n\n        <ui:TeachingTip\n            Title=\"Format Variables\"\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            IsOpen=\"{Binding #ModelFileNameFormatTextBox.IsFocused}\"\n            PreferredPlacement=\"Top\"\n            Target=\"{Binding #ModelFileNameFormatTextBox, Mode=OneWay}\">\n            <DataGrid AutoGenerateColumns=\"True\" ItemsSource=\"{Binding ModelFileNameFormatVars}\" />\n        </ui:TeachingTip>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/CivitDetailsPage.axaml.cs",
    "content": "﻿using Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Input;\nusing Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterTransient<CivitDetailsPage>]\npublic partial class CivitDetailsPage : UserControlBase\n{\n    public CivitDetailsPage()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n\n    private void InputElement_OnPointerWheelChanged(object? sender, PointerWheelEventArgs e)\n    {\n        if (sender is not ScrollViewer sv)\n            return;\n\n        var scrollAmount = e.Delta.Y * 75;\n        sv.Offset = new Vector(sv.Offset.X - scrollAmount, sv.Offset.Y);\n        e.Handled = true;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.ConsoleOutputPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:avaloniaEdit=\"https://github.com/avaloniaui/avaloniaedit\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"viewModels:RunningPackageViewModel\"\n    mc:Ignorable=\"d\">\n    <Grid RowDefinitions=\"Auto, *, Auto\">\n        <ui:CommandBar\n            Grid.Row=\"0\"\n            Margin=\"12,0,0,4\"\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Center\"\n            HorizontalContentAlignment=\"Left\"\n            VerticalContentAlignment=\"Center\"\n            DefaultLabelPosition=\"Right\">\n            <ui:CommandBar.PrimaryCommands>\n                <ui:CommandBarButton\n                    VerticalAlignment=\"Center\"\n                    Classes=\"danger\"\n                    Command=\"{Binding StopCommand}\"\n                    IconSource=\"Stop\"\n                    IsVisible=\"{Binding IsRunning}\"\n                    Label=\"{x:Static lang:Resources.Action_Stop}\" />\n                <ui:CommandBarButton\n                    VerticalAlignment=\"Center\"\n                    Classes=\"success\"\n                    Command=\"{Binding LaunchPackageCommand}\"\n                    IconSource=\"Play\"\n                    IsVisible=\"{Binding !IsRunning}\"\n                    Label=\"{x:Static lang:Resources.Action_Launch}\" />\n                <ui:CommandBarButton\n                    Margin=\"4,0,0,0\"\n                    VerticalAlignment=\"Center\"\n                    Classes=\"info\"\n                    Command=\"{Binding RestartCommand}\"\n                    IconSource=\"Refresh\"\n                    IsVisible=\"{Binding IsRunning}\"\n                    Label=\"{x:Static lang:Resources.Action_Restart}\" />\n\n                <ui:CommandBarSeparator Margin=\"16,0,16,0\" />\n\n                <ui:CommandBarToggleButton\n                    VerticalAlignment=\"Center\"\n                    IsChecked=\"{Binding AutoScrollToEnd}\"\n                    Label=\"{x:Static lang:Resources.Label_ToggleAutoScrolling}\"\n                    ToolTip.Tip=\"{x:Static lang:Resources.Label_AutoScrollToEnd}\">\n                    <ui:CommandBarToggleButton.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-solid fa-arrow-down-wide-short\" />\n                    </ui:CommandBarToggleButton.IconSource>\n                </ui:CommandBarToggleButton>\n\n                <ui:CommandBarButton\n                    x:Name=\"OpenWebUiButton\"\n                    VerticalAlignment=\"Center\"\n                    Command=\"{Binding LaunchWebUiCommand}\"\n                    IsVisible=\"{Binding ShowWebUiButton}\"\n                    Label=\"{x:Static lang:Resources.Action_OpenWebUI}\">\n                    <ui:CommandBarButton.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-solid fa-up-right-from-square\" />\n                    </ui:CommandBarButton.IconSource>\n                </ui:CommandBarButton>\n            </ui:CommandBar.PrimaryCommands>\n        </ui:CommandBar>\n\n        <avaloniaEdit:TextEditor\n            x:Name=\"Console\"\n            Grid.Row=\"1\"\n            Margin=\"8,8,16,10\"\n            DataContext=\"{Binding Console}\"\n            Document=\"{Binding Document}\"\n            FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n            FontSize=\"14\"\n            IsReadOnly=\"True\"\n            LineNumbersForeground=\"DarkSlateGray\"\n            ShowLineNumbers=\"True\"\n            VerticalScrollBarVisibility=\"Auto\"\n            WordWrap=\"True\">\n            <avaloniaEdit:TextEditor.ContextFlyout>\n                <MenuFlyout>\n                    <MenuItem\n                        Command=\"{Binding CopySelectionCommand}\"\n                        CommandParameter=\"{Binding ., ElementName=Console}\"\n                        Header=\"{x:Static lang:Resources.Action_Copy}\">\n                        <MenuItem.Icon>\n                            <avalonia:Icon Value=\"fa-solid fa-copy\" />\n                        </MenuItem.Icon>\n                    </MenuItem>\n                    <MenuItem\n                        Command=\"{Binding SelectAllCommand}\"\n                        CommandParameter=\"{Binding ., ElementName=Console}\"\n                        Header=\"{x:Static lang:Resources.Action_SelectAll}\"\n                        IsEnabled=\"{Binding CanSelectAll, ElementName=Console}\">\n                        <MenuItem.Icon>\n                            <avalonia:Icon Value=\"fa-solid fa-table-cells\" />\n                        </MenuItem.Icon>\n                    </MenuItem>\n                    <MenuItem\n                        Command=\"{Binding SearchWithGoogleCommand}\"\n                        CommandParameter=\"{Binding ., ElementName=Console}\"\n                        Header=\"Search with Google\">\n                        <MenuItem.Icon>\n                            <avalonia:Icon Value=\"fa-brands fa-google\" />\n                        </MenuItem.Icon>\n                    </MenuItem>\n                    <MenuItem\n                        Command=\"{Binding SearchWithChatGptCommand}\"\n                        CommandParameter=\"{Binding ., ElementName=Console}\"\n                        Header=\"Search with ChatGPT\">\n                        <MenuItem.Icon>\n                            <controls:BetterAdvancedImage Source=\"avares://StabilityMatrix.Avalonia/Assets/openai-white-logomark.png\" />\n                        </MenuItem.Icon>\n                    </MenuItem>\n                </MenuFlyout>\n            </avaloniaEdit:TextEditor.ContextFlyout>\n        </avaloniaEdit:TextEditor>\n\n        <Grid\n            Grid.Row=\"2\"\n            Margin=\"16,4,16,16\"\n            ColumnDefinitions=\"*, Auto\">\n            <TextBox\n                Grid.Column=\"0\"\n                Margin=\"0,0,8,0\"\n                Text=\"{Binding ConsoleInput, Mode=TwoWay}\" />\n            <Button\n                Grid.Column=\"1\"\n                Classes=\"accent\"\n                Command=\"{Binding SendToConsoleCommand}\"\n                Content=\"{x:Static lang:Resources.Action_Send}\"\n                IsDefault=\"True\"\n                IsEnabled=\"{Binding IsRunning}\" />\n        </Grid>\n\n        <ui:TeachingTip\n            Name=\"TeachingTip1\"\n            Title=\"{x:Static lang:Resources.TeachingTip_WebUiButtonMoved}\"\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            IsOpen=\"{Binding ShowWebUiTeachingTip}\"\n            PreferredPlacement=\"Bottom\"\n            Target=\"{Binding #OpenWebUiButton}\" />\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/ConsoleOutputPage.axaml.cs",
    "content": "﻿using System;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Interactivity;\nusing Avalonia.Threading;\nusing AvaloniaEdit;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Core.Helper;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterTransient<ConsoleOutputPage>]\npublic partial class ConsoleOutputPage : UserControlBase\n{\n    private const int LineOffset = 5;\n\n    public ConsoleOutputPage()\n    {\n        InitializeComponent();\n    }\n\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n        TextEditorConfigs.Configure(Console, TextEditorPreset.Console);\n    }\n\n    protected override void OnUnloaded(RoutedEventArgs e)\n    {\n        base.OnUnloaded(e);\n        EventManager.Instance.ScrollToBottomRequested -= OnScrollToBottomRequested;\n    }\n\n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        EventManager.Instance.ScrollToBottomRequested += OnScrollToBottomRequested;\n        base.OnLoaded(e);\n    }\n\n    private void OnScrollToBottomRequested(object? sender, EventArgs e)\n    {\n        Dispatcher.UIThread.Invoke(() =>\n        {\n            var editor = this.FindControl<TextEditor>(\"Console\");\n            if (editor?.Document == null)\n                return;\n            var line = Math.Max(editor.Document.LineCount - LineOffset, 1);\n            editor.ScrollToLine(line);\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/AnalyticsOptInDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.AnalyticsOptInDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:avaloniaEdit=\"https://github.com/avaloniaui/avaloniaedit\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    d:DataContext=\"{Binding [AnalyticsOptInViewModel], Source={x:Static mocks:DesignData.Types}}\"\n    d:DesignHeight=\"550\"\n    d:DesignWidth=\"600\"\n    x:DataType=\"dialogs:AnalyticsOptInViewModel\"\n    mc:Ignorable=\"d\">\n    <Grid RowDefinitions=\"Auto, Auto, Auto, Auto, Auto, Auto, Auto, Auto\">\n        <avalonia:Icon\n            Grid.Row=\"0\"\n            Margin=\"16,16,16,0\"\n            FontSize=\"48\"\n            Value=\"fa-solid fa-chart-simple\" />\n        <TextBlock\n            Grid.Row=\"1\"\n            Margin=\"4,4,4,16\"\n            Text=\"{x:Static lang:Resources.Label_Analytics}\"\n            TextAlignment=\"Center\"\n            Theme=\"{DynamicResource TitleTextBlockStyle}\" />\n        <TextBlock\n            Grid.Row=\"2\"\n            Margin=\"4\"\n            FontSize=\"16\"\n            FontWeight=\"Light\"\n            Text=\"{x:Static lang:Resources.Text_AnalyticsDescription}\"\n            TextAlignment=\"Center\"\n            TextWrapping=\"Wrap\"\n            Theme=\"{DynamicResource BodyTextBlockStyle}\" />\n\n        <controls:HyperlinkIconButton\n            Grid.Row=\"3\"\n            Margin=\"0,16\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Bottom\"\n            Content=\"{x:Static lang:Resources.Label_PrivacyPolicy}\"\n            NavigateUri=\"https://lykos.ai/privacy\" />\n\n        <TextBlock\n            Grid.Row=\"4\"\n            Margin=\"4\"\n            Foreground=\"{DynamicResource TextFillColorTertiary}\"\n            Text=\"{Binding ChangeThisBehaviorInSettings}\"\n            TextAlignment=\"Center\"\n            TextWrapping=\"Wrap\" />\n\n        <TextBlock\n            Grid.Row=\"5\"\n            Margin=\"0,16,0,4\"\n            FontWeight=\"Light\"\n            Text=\"Example Telemetry\"\n            TextAlignment=\"Center\" />\n\n        <avaloniaEdit:TextEditor\n            Grid.Row=\"6\"\n            MaxWidth=\"500\"\n            Margin=\"16,0,16,16\"\n            Padding=\"8\"\n            Background=\"#33000000\"\n            BorderThickness=\"0\"\n            CornerRadius=\"8\"\n            FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n            FontSize=\"14\"\n            IsReadOnly=\"True\"\n            SyntaxHighlighting=\"JavaScript\"\n            Text=\"{x:Static lang:Resources.AnalyticsExample_InstallData}\" />\n        <StackPanel\n            Grid.Row=\"6\"\n            Margin=\"0,48,0,0\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Bottom\"\n            Orientation=\"Horizontal\"\n            Spacing=\"8\" />\n\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/AnalyticsOptInDialog.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<AnalyticsOptInDialog>]\npublic partial class AnalyticsOptInDialog : UserControlBase\n{\n    public AnalyticsOptInDialog()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/ConfirmBulkDownloadDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.ConfirmBulkDownloadDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    d:DataContext=\"{x:Static mocks:DesignData.ConfirmBulkDownloadDialogViewModel}\"\n    d:DesignHeight=\"550\"\n    d:DesignWidth=\"500\"\n    x:DataType=\"dialogs:ConfirmBulkDownloadDialogViewModel\"\n    mc:Ignorable=\"d\">\n    <avalonia:SpacedGrid ColumnDefinitions=\"*,*,*\" RowDefinitions=\"Auto,Auto,*,Auto,Auto,Auto,Auto,Auto\">\n        <TextBlock\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"3\"\n            Margin=\"0,0,0,8\"\n            Text=\"Confirm Bulk Download\"\n            TextWrapping=\"Wrap\"\n            Theme=\"{DynamicResource TitleTextBlockStyle}\" />\n\n        <TextBlock\n            Grid.Row=\"1\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"3\"\n            Margin=\"4,0,4,8\"\n            FontSize=\"16\"\n            Foreground=\"{DynamicResource ThemeAmberColor}\"\n            Text=\"{Binding DownloadFollowingFilesText}\"\n            Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n\n        <controls:Card\n            Grid.Row=\"2\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"3\"\n            Margin=\"2\"\n            Padding=\"4,6\"\n            VerticalAlignment=\"Top\">\n            <ScrollViewer HorizontalScrollBarVisibility=\"Disabled\" VerticalScrollBarVisibility=\"Visible\">\n                <ItemsRepeater ItemsSource=\"{Binding FilesToDownload}\">\n                    <ItemsRepeater.Layout>\n                        <StackLayout Orientation=\"Vertical\" />\n                    </ItemsRepeater.Layout>\n                    <ItemsRepeater.ItemTemplate>\n                        <DataTemplate DataType=\"{x:Type dialogs:CivitFileDisplayViewModel}\">\n                            <TextBlock Margin=\"8\" FontSize=\"16\">\n                                <Run Text=\"{Binding FileViewModel.CivitFile.Name}\" />\n                                <Run Text=\"(\" /><Run Text=\"{Binding FileViewModel.CivitFile.FullFilesSize}\" /><Run Text=\")\" />\n                            </TextBlock>\n                        </DataTemplate>\n                    </ItemsRepeater.ItemTemplate>\n                </ItemsRepeater>\n            </ScrollViewer>\n        </controls:Card>\n\n        <ToggleSwitch\n            Grid.Row=\"3\"\n            Grid.RowSpan=\"2\"\n            Grid.Column=\"0\"\n            Margin=\"8,0\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            IsChecked=\"{Binding IncludeVae}\"\n            OffContent=\"Include VAEs\"\n            OnContent=\"Include VAEs\" />\n\n        <ToggleSwitch\n            Grid.Row=\"3\"\n            Grid.RowSpan=\"2\"\n            Grid.Column=\"1\"\n            Margin=\"8,0\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            IsChecked=\"{Binding PreferPruned}\"\n            OffContent=\"Prefer Pruned\"\n            OnContent=\"Prefer Pruned\" />\n\n        <TextBlock\n            Grid.Row=\"3\"\n            Grid.Column=\"2\"\n            Margin=\"8\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            Text=\"Preferred Precision:\" />\n\n        <controls:BetterComboBox\n            Grid.Row=\"4\"\n            Grid.Column=\"2\"\n            MinWidth=\"100\"\n            Margin=\"8,0\"\n            HorizontalAlignment=\"Center\"\n            ItemsSource=\"{Binding AvailableFpTypes}\"\n            SelectedItem=\"{Binding FpTypePreference}\" />\n\n        <TextBlock\n            Grid.Row=\"5\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"3\"\n            Margin=\"4,0,4,4\"\n            VerticalAlignment=\"Bottom\"\n            Text=\"{x:Static lang:Resources.Label_AreYouSure}\"\n            Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n\n        <UniformGrid\n            Grid.Row=\"6\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"3\"\n            Margin=\"0,4,0,-24\"\n            HorizontalAlignment=\"Stretch\"\n            VerticalAlignment=\"Bottom\">\n            <Button\n                Margin=\"0,0,8,0\"\n                HorizontalAlignment=\"Stretch\"\n                Command=\"{Binding OnCloseButtonClick}\"\n                Content=\"{x:Static lang:Resources.Action_Cancel}\" />\n\n            <Button\n                Margin=\"0,0,8,0\"\n                HorizontalAlignment=\"Stretch\"\n                Classes=\"accent\"\n                Command=\"{Binding OnPrimaryButtonClick}\"\n                Content=\"Confirm\" />\n        </UniformGrid>\n    </avalonia:SpacedGrid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/ConfirmBulkDownloadDialog.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<ConfirmBulkDownloadDialog>]\npublic partial class ConfirmBulkDownloadDialog : UserControlBase\n{\n    public ConfirmBulkDownloadDialog()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/ConfirmDeleteDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.ConfirmDeleteDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    d:DataContext=\"{x:Static mocks:DesignData.ConfirmDeleteDialogViewModel}\"\n    d:DesignHeight=\"550\"\n    d:DesignWidth=\"500\"\n    x:DataType=\"dialogs:ConfirmDeleteDialogViewModel\"\n    mc:Ignorable=\"d\">\n    \n    <sg:SpacedGrid\n        RowDefinitions=\"Auto,Auto,*,Auto,Auto,Auto,Auto\"\n        RowSpacing=\"12\">\n        <TextBlock\n            Margin=\"0,0,0,8\"\n            Text=\"{Binding Title}\"\n            TextWrapping=\"Wrap\"\n            Theme=\"{DynamicResource TitleTextBlockStyle}\" />\n\n        <TextBlock\n            Grid.Row=\"1\"\n            Margin=\"4,0\"\n            Text=\"{Binding DeleteFollowingFilesText}\"\n            Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n\n        <controls:Card\n            Grid.Row=\"2\"\n            Margin=\"2\"\n            Padding=\"4,6\"\n            VerticalAlignment=\"Top\"\n            ScrollViewer.VerticalScrollBarVisibility=\"Visible\">\n            <ListBox ItemsSource=\"{Binding PathsToDelete}\" />\n        </controls:Card>\n\n        <!--<ui:InfoBar Grid.Row=\"3\"\n                    Margin=\"4,0\"\n                    IsClosable=\"False\"\n                    VerticalAlignment=\"Bottom\"\n                    IsOpen=\"{Binding ShowFilesWillBeMovedToRecycleBinNotice}\"\n                    Title=\"{x:Static lang:Resources.Label_FilesWillBeMovedToRecycleBin}\"\n                    Severity=\"Informational\"/>-->\n\n        <ui:InfoBar\n            Title=\"{x:Static lang:Resources.Label_ActionCannotBeUndone}\"\n            Grid.Row=\"3\"\n            Margin=\"4,0\"\n            VerticalAlignment=\"Bottom\"\n            IsClosable=\"False\"\n            IsOpen=\"{Binding IsPermanentDelete}\"\n            Severity=\"Warning\" />\n\n        <CheckBox\n            Grid.Row=\"5\"\n            Margin=\"12,0\"\n            IsVisible=\"{Binding IsRecycleBinAvailable}\"\n            IsChecked=\"{Binding IsRecycleBinOptOutChecked}\"\n            Content=\"{x:Static lang:Resources.Label_DeletePermanently}\" />\n\n        <TextBlock\n            Grid.Row=\"4\"\n            Margin=\"4,0\"\n            VerticalAlignment=\"Bottom\"\n            Text=\"{x:Static lang:Resources.Label_AreYouSure}\"\n            Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n        \n        <UniformGrid\n            Grid.Row=\"6\"\n            Margin=\"0,4,0,-24\"\n            HorizontalAlignment=\"Stretch\"\n            VerticalAlignment=\"Bottom\">\n            <Button\n                Margin=\"0,0,8,0\"\n                HorizontalAlignment=\"Stretch\"\n                Command=\"{Binding OnCloseButtonClick}\"\n                Content=\"{x:Static lang:Resources.Action_Cancel}\"/>\n            <Button\n                Margin=\"0,0,8,0\"\n                HorizontalAlignment=\"Stretch\"\n                Classes=\"danger\"\n                Command=\"{Binding ConfirmDeleteClickCommand}\"\n                Content=\"{Binding ConfirmDeleteButtonText}\"/>\n        </UniformGrid>\n    </sg:SpacedGrid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/ConfirmDeleteDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<ConfirmDeleteDialog>]\npublic partial class ConfirmDeleteDialog : UserControlBase\n{\n    public ConfirmDeleteDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/ConfirmPackageDeleteDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.ConfirmPackageDeleteDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:controls1=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    d:DataContext=\"{x:Static designData:DesignData.ConfirmPackageDeleteDialogViewModel}\"\n    d:DesignHeight=\"650\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"dialogs:ConfirmPackageDeleteDialogViewModel\"\n    mc:Ignorable=\"d\">\n    <Grid Margin=\"8\" RowDefinitions=\"Auto, Auto, Auto, Auto, Auto, Auto, Auto, Auto, *\">\n        <StackPanel\n            Grid.Row=\"0\"\n            Margin=\"0,4,0,0\"\n            HorizontalAlignment=\"Center\"\n            Orientation=\"Horizontal\">\n            <avalonia:Icon\n                Margin=\"0,0,-4,0\"\n                FontSize=\"18\"\n                Value=\"fa-solid fa-wind\">\n                <avalonia:Icon.RenderTransform>\n                    <ScaleTransform ScaleX=\"-1\" />\n                </avalonia:Icon.RenderTransform>\n            </avalonia:Icon>\n            <avalonia:Icon FontSize=\"32\" Value=\"fa-solid fa-dolly\" />\n        </StackPanel>\n\n        <TextBlock\n            Grid.Row=\"1\"\n            Margin=\"0,4,0,0\"\n            FontSize=\"24\"\n            Text=\"{Binding ExpectedPackageName, StringFormat={}Delete {0}?}\"\n            TextAlignment=\"Center\" />\n\n        <TextBlock\n            Grid.Row=\"2\"\n            Margin=\"0,4,0,0\"\n            Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n            Text=\"{Binding Package.FullPath}\"\n            TextAlignment=\"Center\" />\n\n        <controls1:InfoBar\n            Title=\"{x:Static lang:Resources.Label_ActionCannotBeUndone}\"\n            Grid.Row=\"3\"\n            Margin=\"0,16,0,0\"\n            IsClosable=\"False\"\n            IsOpen=\"True\"\n            Severity=\"Warning\" />\n\n        <TextBlock\n            Grid.Row=\"4\"\n            Margin=\"0,16,0,8\"\n            VerticalAlignment=\"Center\"\n            FontSize=\"16\"\n            FontWeight=\"Bold\"\n            Foreground=\"{DynamicResource ThemeCoralRedColor}\"\n            Text=\"This will permanently delete the following:\"\n            TextWrapping=\"Wrap\" />\n\n        <TextBlock\n            Grid.Row=\"5\"\n            Margin=\"0,0,0,8\"\n            VerticalAlignment=\"Center\"\n            FontSize=\"16\"\n            Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n            Text=\"{Binding DeleteWarningText}\"\n            TextWrapping=\"Wrap\" />\n\n        <StackPanel\n            Grid.Row=\"6\"\n            Margin=\"0,32,0,4\"\n            HorizontalAlignment=\"Center\"\n            Orientation=\"Horizontal\"\n            Spacing=\"0\">\n            <TextBlock\n                FontSize=\"16\"\n                Text=\"To confirm, please type &quot;\"\n                TextAlignment=\"Center\" />\n            <TextBlock\n                Cursor=\"Hand\"\n                FontSize=\"16\"\n                Text=\"{Binding ExpectedPackageName}\"\n                TextAlignment=\"Center\"\n                ToolTip.Tip=\"Click to copy\">\n                <Interaction.Behaviors>\n                    <EventTriggerBehavior EventName=\"PointerPressed\">\n                        <InvokeCommandAction Command=\"{Binding CopyExpectedPackageNameCommand}\" />\n                    </EventTriggerBehavior>\n                </Interaction.Behaviors>\n                <TextBlock.Styles>\n                    <Style Selector=\"TextBlock:pointerover\">\n                        <Setter Property=\"Foreground\" Value=\"{DynamicResource TextFillColorSecondaryBrush}\" />\n                    </Style>\n                </TextBlock.Styles>\n            </TextBlock>\n            <TextBlock\n                FontSize=\"16\"\n                Text=\"&quot; in the box below:\"\n                TextAlignment=\"Center\" />\n        </StackPanel>\n\n        <TextBox\n            Grid.Row=\"7\"\n            Margin=\"0,8,0,0\"\n            Text=\"{Binding PackageName, Mode=TwoWay}\" />\n\n        <UniformGrid\n            Grid.Row=\"8\"\n            Margin=\"0,32,0,0\"\n            HorizontalAlignment=\"Stretch\"\n            VerticalAlignment=\"Bottom\">\n            <Button\n                Margin=\"0,0,4,0\"\n                HorizontalAlignment=\"Stretch\"\n                Classes=\"danger\"\n                Command=\"{Binding OnPrimaryButtonClick}\"\n                Content=\"{x:Static lang:Resources.Action_Delete}\"\n                FontSize=\"16\"\n                IsEnabled=\"{Binding IsValid}\" />\n            <Button\n                Margin=\"4,0,0,0\"\n                HorizontalAlignment=\"Stretch\"\n                Command=\"{Binding OnCloseButtonClick}\"\n                Content=\"{x:Static lang:Resources.Action_Cancel}\"\n                FontSize=\"16\" />\n        </UniformGrid>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/ConfirmPackageDeleteDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<ConfirmPackageDeleteDialog>]\npublic partial class ConfirmPackageDeleteDialog : UserControlBase\n{\n    public ConfirmPackageDeleteDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/DownloadResourceDialog.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n                          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                          xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n                          xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n                          xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n                          xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n                          xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n                          xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n                          xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n                          xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n                          xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n                          xmlns:processes=\"clr-namespace:StabilityMatrix.Core.Processes;assembly=StabilityMatrix.Core\"\n                          d:DataContext=\"{x:Static mocks:DesignData.DownloadResourceViewModel}\"\n                          x:DataType=\"vmDialogs:DownloadResourceViewModel\"\n                          mc:Ignorable=\"d\" d:DesignWidth=\"500\" d:DesignHeight=\"450\"\n                          x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.DownloadResourceDialog\">\n    \n    <Grid RowDefinitions=\"*,*\" Margin=\"8,4\">\n        <StackPanel>\n            <ui:SettingsExpander \n                Command=\"{Binding OpenInfoUrlCommand}\"\n                Header=\"{Binding FileNameWithHash}\" \n                Description=\"{Binding Resource.Author, StringFormat={}by {0}}\"\n                Footer=\"{Binding FileSizeText}\">\n            </ui:SettingsExpander>\n            \n            <ui:SettingsExpander \n                Header=\"{x:Static lang:Resources.Label_License}\" >\n                <ui:SettingsExpander.Footer>\n                    <Button \n                        Tapped=\"LicenseButton_OnTapped\"\n                        Classes=\"transparent-full\" \n                        Foreground=\"{DynamicResource HyperlinkButtonForeground}\">\n                        <StackPanel Orientation=\"Horizontal\">\n                            <ui:SymbolIcon \n                                Symbol=\"Link\" \n                                FontSize=\"15\"\n                                Foreground=\"{DynamicResource HyperlinkButtonForeground}\"\n                                Margin=\"0,1,4,0\"/>\n                            <TextBlock \n                                Foreground=\"{DynamicResource HyperlinkButtonForeground}\"\n                                Text=\"{Binding Resource.LicenseType}\"/>\n                        </StackPanel>\n                    </Button>\n                </ui:SettingsExpander.Footer>\n            </ui:SettingsExpander>\n            \n        </StackPanel>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/DownloadResourceDialog.axaml.cs",
    "content": "﻿using Avalonia.Input;\nusing Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<DownloadResourceDialog>]\npublic partial class DownloadResourceDialog : UserControlBase\n{\n    public DownloadResourceDialog()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n\n    private void LicenseButton_OnTapped(object? sender, TappedEventArgs e)\n    {\n        var url = ((DownloadResourceViewModel)DataContext!).Resource.LicenseUrl;\n        ProcessRunner.OpenUrl(url!.ToString());\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/EnvVarsDialog.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n                          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                          xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n                          xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n                          xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n                          xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n                          xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n                          xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n                          xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n                          xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n                          d:DataContext=\"{x:Static mocks:DesignData.EnvVarsViewModel}\"\n                          x:DataType=\"dialogs:EnvVarsViewModel\"\n                          mc:Ignorable=\"d\" d:DesignWidth=\"800\" d:DesignHeight=\"450\"\n                          Focusable=\"True\"\n                          x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.EnvVarsDialog\">\n    \n    <controls:UserControlBase.Styles>\n        <Style Selector=\"DataGridRow:not(:selected) /template/ Rectangle#BackgroundRectangle\">\n            <Setter Property=\"Fill\" Value=\"{DynamicResource ComboBoxItemBackgroundSelected}\"/>\n        </Style>\n        <Style Selector=\"DataGridRow:selected /template/ Rectangle#BackgroundRectangle\">\n            <Setter Property=\"Fill\" Value=\"{DynamicResource ComboBoxItemBackgroundPointerOver}\"/>\n        </Style>\n    </controls:UserControlBase.Styles>\n    \n    <controls:UserControlBase.Resources>\n        <DataTemplate x:Key=\"DataGridCellDividerTemplate\">\n            <Rectangle VerticalAlignment=\"Stretch\" HorizontalAlignment=\"Stretch\" Fill=\"#08ffffff\"/>\n        </DataTemplate>\n    </controls:UserControlBase.Resources>\n    \n    <Grid RowDefinitions=\"Auto,Auto,*\" MinHeight=\"300\" Margin=\"8\">\n        <StackPanel\n            HorizontalAlignment=\"Stretch\"\n            Spacing=\"4\"\n            Margin=\"0,0,0,0\"\n            Orientation=\"Vertical\">\n            <!--  Title  -->\n            <TextBlock\n                FontSize=\"24\"\n                FontWeight=\"Bold\"\n                Margin=\"16,16,16,0\"\n                Text=\"{Binding Title}\"\n                TextWrapping=\"Wrap\" />\n        </StackPanel>\n        \n        <Panel Grid.Row=\"1\">\n            <ui:CommandBar>\n                <ui:CommandBar.PrimaryCommands>\n                    <ui:CommandBarButton Width=\"45\" Height=\"50\" \n                                         IconSource=\"Add\" \n                                         Label=\"{x:Static lang:Resources.Action_Save}\" \n                                         Command=\"{Binding AddRowCommand}\"/>\n                    <ui:CommandBarButton Width=\"45\" Height=\"50\" \n                                         IconSource=\"Remove\" \n                                         Label=\"{x:Static lang:Resources.Action_Remove}\" \n                                         CommandParameter=\"{Binding #EnvVarsGrid.SelectedIndex}\"\n                                         Command=\"{Binding RemoveSelectedRowCommand}\">\n                        <ui:CommandBarButton.IsEnabled>\n                            <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                <Binding Path=\"!EnvVarsView.IsEmpty\" />\n                                <Binding ElementName=\"EnvVarsGrid\" Path=\"SelectedItem\" Converter=\"{x:Static ObjectConverters.IsNotNull}\" />\n                            </MultiBinding>\n                        </ui:CommandBarButton.IsEnabled>\n                    </ui:CommandBarButton>\n                    <ui:CommandBarSeparator />\n                    <ui:CommandBarButton Width=\"45\" Height=\"50\" FontSize=\"5\" IconSource=\"Copy\" Label=\"Bold\" />\n                </ui:CommandBar.PrimaryCommands>\n            </ui:CommandBar>\n        </Panel>\n\n        <!--  Option Cards  -->\n        <controls:Card Grid.Row=\"2\" \n                       Background=\"Transparent\"\n                       VerticalAlignment=\"Stretch\" \n                       VerticalContentAlignment=\"Stretch\">\n            <DataGrid \n                x:Name=\"EnvVarsGrid\"\n                MinHeight=\"200\"\n                ItemsSource=\"{Binding EnvVarsView}\" >\n                <DataGrid.Columns>\n                    <DataGridTextColumn x:DataType=\"models:EnvVarKeyPair\" \n                                        Width=\"5*\"\n                                        FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n                                        Header=\"{x:Static lang:Resources.Label_EnvVarsTable_Name}\" \n                                        Binding=\"{Binding Key}\" />\n                    <DataGridTemplateColumn MinWidth=\"0\" Width=\"1\" CellTemplate=\"{StaticResource DataGridCellDividerTemplate}\"/>\n                    <DataGridTextColumn x:DataType=\"models:EnvVarKeyPair\" \n                                        Width=\"5*\"\n                                        FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n                                        Header=\"{x:Static lang:Resources.Label_EnvVarsTable_Value}\" \n                                        Binding=\"{Binding Value}\" />\n                </DataGrid.Columns>\n            </DataGrid>\n        </controls:Card>\n\n    </Grid>\n    \n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/EnvVarsDialog.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<EnvVarsDialog>]\npublic partial class EnvVarsDialog : UserControlBase\n{\n    public EnvVarsDialog()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/ExceptionDialog.axaml",
    "content": "﻿<controls:AppWindowBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.ExceptionDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    Title=\"Stability Matrix - Unexpected Error\"\n    Width=\"800\"\n    Height=\"550\"\n    d:DataContext=\"{x:Static mocks:DesignData.ExceptionViewModel}\"\n    d:DesignHeight=\"550\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"dialogs:ExceptionViewModel\"\n    Icon=\"/Assets/Icon.ico\"\n    mc:Ignorable=\"d\">\n\n    <Grid RowDefinitions=\"Auto,Auto,*,Auto\">\n        <!--  Info  -->\n        <TextBlock\n            Grid.Row=\"0\"\n            Margin=\"16,16,16,8\"\n            VerticalAlignment=\"Top\"\n            Text=\"{x:Static lang:Resources.Label_UnexpectedErrorOccurred}\"\n            TextWrapping=\"Wrap\"\n            Theme=\"{DynamicResource SubtitleTextBlockStyle}\" />\n\n        <!--  Notice bar  -->\n        <ui:InfoBar\n            Grid.Row=\"1\"\n            Margin=\"16,8\"\n            IsClosable=\"False\"\n            IsOpen=\"True\"\n            Message=\"{Binding Description}\"\n            Severity=\"Informational\" />\n\n        <!--  Exception details  -->\n        <Expander\n            Grid.Row=\"2\"\n            Margin=\"16,0\"\n            Padding=\"12,12,12,12\"\n            VerticalAlignment=\"Top\"\n            Header=\"{Binding ExceptionType, Mode=OneWay, TargetNullValue=-, FallbackValue=Unknown Exception}\"\n            IsExpanded=\"True\">\n            <ScrollViewer VerticalScrollBarVisibility=\"Auto\">\n                <StackPanel DataContext=\"{Binding Exception}\" Spacing=\"4\">\n                    <!--  Exception Message  -->\n                    <TextBlock Text=\"{x:Static lang:Resources.Label_Details}\" Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                    <SelectableTextBlock\n                        Margin=\"0,0,0,8\"\n                        SelectionBrush=\"DodgerBlue\"\n                        Text=\"{Binding Message}\"\n                        TextWrapping=\"WrapWithOverflow\" />\n                    <!--  Callstack  -->\n                    <TextBlock Text=\"{x:Static lang:Resources.Label_Callstack}\" Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                    <controls:Card Margin=\"0,0,0,8\" Background=\"{DynamicResource ComboBoxDropDownBackground}\">\n                        <SelectableTextBlock\n                            FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n                            FontSize=\"13\"\n                            SelectionBrush=\"DodgerBlue\"\n                            Text=\"{Binding StackTrace}\"\n                            TextWrapping=\"Wrap\" />\n                    </controls:Card>\n                    <!--  Inner exception  -->\n                    <TextBlock\n                        IsVisible=\"{Binding InnerException, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                        Text=\"{x:Static lang:Resources.Label_InnerException}\"\n                        Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                    <controls:Card Background=\"{DynamicResource ComboBoxDropDownBackground}\" IsVisible=\"{Binding InnerException, Converter={x:Static ObjectConverters.IsNotNull}}\">\n                        <SelectableTextBlock\n                            FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n                            FontSize=\"13\"\n                            SelectionBrush=\"DodgerBlue\"\n                            Text=\"{Binding InnerException}\"\n                            TextWrapping=\"Wrap\" />\n                    </controls:Card>\n                </StackPanel>\n            </ScrollViewer>\n        </Expander>\n\n        <StackPanel\n            Grid.Row=\"3\"\n            Margin=\"16\"\n            HorizontalAlignment=\"Right\"\n            Orientation=\"Horizontal\"\n            Spacing=\"12\">\n            <StackPanel.Styles>\n                <Style Selector=\"Button\">\n                    <Setter Property=\"MinWidth\" Value=\"90\" />\n                </Style>\n            </StackPanel.Styles>\n\n            <!--  Show Logs  -->\n            <Button Padding=\"12,8\" Command=\"{Binding OpenLogZipInFileBrowserCommand}\">\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                    <fluentIcons:SymbolIcon\n                        Margin=\"0,1,0,0\"\n                        VerticalAlignment=\"Center\"\n                        FontSize=\"15\"\n                        Symbol=\"FolderZip\" />\n                    <TextBlock Text=\"{OnPlatform Default={x:Static lang:Resources.Action_ShowLogInExplorer}, macOS={x:Static lang:Resources.Action_ShowLogInFinder}}\" />\n                </StackPanel>\n            </Button>\n\n            <!--  Copy Button  -->\n            <Button\n                Name=\"CopyButton\"\n                Padding=\"12,8\"\n                Click=\"CopyButton_OnClick\">\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                    <fluentIcons:SymbolIcon\n                        Margin=\"0,1,0,0\"\n                        VerticalAlignment=\"Center\"\n                        FontSize=\"15\"\n                        Symbol=\"Copy\" />\n                    <TextBlock Text=\"{x:Static lang:Resources.Action_CopyDetails}\" />\n                </StackPanel>\n            </Button>\n\n            <Separator VerticalAlignment=\"Center\" />\n\n            <!--  Close Button  -->\n            <Button\n                Name=\"ExitButton\"\n                Padding=\"12,8\"\n                Click=\"ExitButton_OnClick\"\n                Content=\"{x:Static lang:Resources.Action_ExitApplication}\"\n                IsDefault=\"{Binding !IsRecoverable}\" />\n\n            <!--  Continue Button  -->\n            <Button\n                Padding=\"12,8\"\n                Classes=\"accent\"\n                Click=\"ContinueButton_OnClick\"\n                Content=\"{x:Static lang:Resources.Action_Continue}\"\n                IsDefault=\"{Binding IsRecoverable}\"\n                IsVisible=\"{Binding IsRecoverable}\" />\n        </StackPanel>\n\n    </Grid>\n\n</controls:AppWindowBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/ExceptionDialog.axaml.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing Avalonia.Interactivity;\nusing Avalonia.Markup.Xaml;\nusing FluentAvalonia.UI.Windowing;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<ExceptionDialog>]\npublic partial class ExceptionDialog : AppWindowBase\n{\n    public ExceptionDialog()\n    {\n        InitializeComponent();\n\n        TitleBar.ExtendsContentIntoTitleBar = true;\n        TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex;\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n\n    [SuppressMessage(\"ReSharper\", \"UnusedParameter.Local\")]\n    private async void CopyButton_OnClick(object? sender, RoutedEventArgs e)\n    {\n        var content = (DataContext as ExceptionViewModel)?.FormatAsMarkdown();\n\n        if (content is not null && Clipboard is not null)\n        {\n            await Clipboard.SetTextAsync(content);\n        }\n    }\n\n    private void ContinueButton_OnClick(object? sender, RoutedEventArgs e)\n    {\n        if (DataContext is ExceptionViewModel viewModel)\n        {\n            viewModel.IsContinueResult = true;\n        }\n\n        Close();\n    }\n\n    [SuppressMessage(\"ReSharper\", \"UnusedParameter.Local\")]\n    private void ExitButton_OnClick(object? sender, RoutedEventArgs e)\n    {\n        Close();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.ImageViewerDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:avaloniaEdit=\"https://github.com/avaloniaui/avaloniaedit\"\n    xmlns:civitTrpc=\"clr-namespace:StabilityMatrix.Core.Models.Api.CivitTRPC;assembly=StabilityMatrix.Core\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:generic=\"clr-namespace:System.Collections.Generic;assembly=System.Collections\"\n    xmlns:generic1=\"clr-namespace:System.Collections.Generic;assembly=System.Runtime\"\n    xmlns:gif=\"clr-namespace:Avalonia.Gif;assembly=Avalonia.Gif\"\n    xmlns:icons=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    HorizontalContentAlignment=\"Stretch\"\n    VerticalContentAlignment=\"Stretch\"\n    d:DataContext=\"{x:Static mocks:DesignData.ImageViewerViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"vmDialogs:ImageViewerViewModel\"\n    Focusable=\"True\"\n    mc:Ignorable=\"d\">\n\n    <controls:UserControlBase.KeyBindings>\n        <KeyBinding Command=\"{Binding NavigateNextCommand}\" Gesture=\"Down\" />\n        <KeyBinding Command=\"{Binding NavigateNextCommand}\" Gesture=\"Right\" />\n        <KeyBinding Command=\"{Binding NavigatePreviousCommand}\" Gesture=\"Up\" />\n        <KeyBinding Command=\"{Binding NavigatePreviousCommand}\" Gesture=\"Left\" />\n    </controls:UserControlBase.KeyBindings>\n\n    <!--<controls:UserControlBase.ContextFlyout>\n        <controls:BetterFlyout\n            Placement=\"Left\">\n            <StackPanel>\n                <TextBlock Text=\"{Binding LocalImageFile.GenerationParameters.PositivePrompt}\"/>\n                <TextBlock Text=\"{Binding LocalImageFile.GenerationParameters.NegativePrompt}\"/>\n            </StackPanel>\n        </controls:BetterFlyout>\n    </controls:UserControlBase.ContextFlyout>-->\n\n    <controls:UserControlBase.Styles>\n        <Style Selector=\"SelectableTextBlock\">\n            <Setter Property=\"TextWrapping\" Value=\"WrapWithOverflow\" />\n            <Setter Property=\"SelectionBrush\" Value=\"{DynamicResource CompletionSelectionForegroundBrush}\" />\n        </Style>\n    </controls:UserControlBase.Styles>\n\n    <Grid\n        HorizontalAlignment=\"Stretch\"\n        VerticalAlignment=\"Stretch\"\n        RowDefinitions=\"*,Auto\">\n        <Grid.Resources>\n            <input:StandardUICommand x:Key=\"CopyImageCommand\" Command=\"{Binding CopyImageCommand}\" />\n            <input:StandardUICommand x:Key=\"CopyImageAsBitmapCommand\" Command=\"{Binding CopyImageAsBitmapCommand}\" />\n            <input:StandardUICommand x:Key=\"NavigateToModelCommand\" Command=\"{Binding NavigateToModelCommand}\" />\n        </Grid.Resources>\n\n        <!--  Tag is not used but sets TemplateKey which is used to select the DataTemplate later  -->\n        <ContentPresenter\n            Grid.Row=\"0\"\n            Content=\"{Binding}\"\n            DataContext=\"{Binding ImageSource}\"\n            Tag=\"{Binding TemplateKeyAsync^}\">\n            <ContentPresenter.ContentTemplate>\n                <controls:DataTemplateSelector x:TypeArguments=\"models:ImageSourceTemplateType\">\n                    <DataTemplate x:Key=\"{x:Static models:ImageSourceTemplateType.WebpAnimation}\" DataType=\"models:ImageSource\">\n                        <gif:GifImage SourceUri=\"{Binding LocalFile.FullPath}\" Stretch=\"Uniform\" />\n                    </DataTemplate>\n\n                    <DataTemplate x:Key=\"{x:Static models:ImageSourceTemplateType.Image}\" DataType=\"models:ImageSource\">\n                        <controls:AdvancedImageBox\n                            Name=\"ImageBox\"\n                            CornerRadius=\"4\"\n                            Focusable=\"False\"\n                            Image=\"{Binding BitmapAsync^}\"\n                            SizeMode=\"Fit\">\n                            <controls:AdvancedImageBox.ContextFlyout>\n                                <ui:FAMenuFlyout>\n                                    <ui:MenuFlyoutItem\n                                        x:Name=\"CopyMenuItem\"\n                                        Command=\"{StaticResource CopyImageCommand}\"\n                                        CommandParameter=\"{Binding}\"\n                                        HotKey=\"Ctrl+C\"\n                                        IconSource=\"Copy\"\n                                        Text=\"{x:Static lang:Resources.Action_Copy}\" />\n                                    <ui:MenuFlyoutItem\n                                        x:Name=\"CopyAsBitmapMenuItem\"\n                                        Command=\"{StaticResource CopyImageAsBitmapCommand}\"\n                                        CommandParameter=\"{Binding}\"\n                                        HotKey=\"Shift+Ctrl+C\"\n                                        IsVisible=\"{OnPlatform Windows=True,\n                                                               Default=False}\"\n                                        Text=\"{x:Static lang:Resources.Action_CopyAsBitmap}\" />\n                                </ui:FAMenuFlyout>\n                            </controls:AdvancedImageBox.ContextFlyout>\n                        </controls:AdvancedImageBox>\n                    </DataTemplate>\n\n                    <DataTemplate x:Key=\"{x:Static models:ImageSourceTemplateType.Default}\" DataType=\"models:ImageSource\">\n                        <TextBlock\n                            HorizontalAlignment=\"Center\"\n                            VerticalAlignment=\"Center\"\n                            Text=\"Unsupported Format\" />\n                    </DataTemplate>\n                </controls:DataTemplateSelector>\n            </ContentPresenter.ContentTemplate>\n        </ContentPresenter>\n\n        <!--  Info button  -->\n        <Grid\n            Grid.Row=\"0\"\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Top\">\n            <Button\n                Name=\"InfoButton\"\n                Grid.Row=\"0\"\n                Margin=\"8,8,0,0\"\n                Padding=\"8\"\n                HorizontalAlignment=\"Left\"\n                VerticalAlignment=\"Top\"\n                icons:Attached.Icon=\"fa-solid fa-info\"\n                Classes=\"transparent-full\"\n                Tapped=\"InfoButton_OnTapped\">\n                <Button.IsEnabled>\n                    <MultiBinding Converter=\"{x:Static BoolConverters.Or}\">\n                        <Binding Path=\"HasLocalGenerationParameters\" />\n                        <Binding Path=\"HasCivitImageMetadata\" />\n                    </MultiBinding>\n                </Button.IsEnabled>\n            </Button>\n        </Grid>\n\n\n        <ui:TeachingTip\n            Name=\"InfoTeachingTip\"\n            Grid.Row=\"0\"\n            MinWidth=\"100\"\n            PlacementMargin=\"16,0,16,0\"\n            PreferredPlacement=\"LeftBottom\"\n            TailVisibility=\"Collapsed\"\n            Target=\"{Binding #InfoButton}\">\n            <Grid>\n                <Grid.Resources>\n                    <input:StandardUICommand x:Key=\"CopyThingToClipboardCommand\" Command=\"{Binding CopyThingToClipboardCommand}\" />\n                </Grid.Resources>\n                <avalonia:SpacedGrid\n                    ColumnSpacing=\"8\"\n                    DataContext=\"{Binding CivitImageMetadata}\"\n                    IsVisible=\"{Binding $parent[Grid].((vmDialogs:ImageViewerViewModel)DataContext).HasCivitImageMetadata}\"\n                    RowDefinitions=\"Auto, Auto, Auto, Auto, Auto, Auto, Auto, Auto, Auto, Auto\"\n                    RowSpacing=\"8\">\n                    <TextBlock\n                        Grid.Row=\"0\"\n                        VerticalAlignment=\"Center\"\n                        FontSize=\"20\"\n                        FontWeight=\"Light\"\n                        Text=\"{x:Static lang:Resources.Label_Prompt}\" />\n\n                    <TextBlock\n                        Grid.Row=\"1\"\n                        Margin=\"0,-4,0,0\"\n                        Text=\"{Binding Metadata.Prompt}\"\n                        TextWrapping=\"Wrap\"\n                        ToolTip.ShowDelay=\"1000\"\n                        ToolTip.Tip=\"Click to Copy\">\n                        <Interaction.Behaviors>\n                            <EventTriggerBehavior EventName=\"PointerPressed\">\n                                <InvokeCommandAction Command=\"{StaticResource CopyThingToClipboardCommand}\" CommandParameter=\"{Binding Metadata.Prompt}\" />\n                            </EventTriggerBehavior>\n                        </Interaction.Behaviors>\n                        <TextBlock.Styles>\n                            <Style Selector=\"TextBlock:pointerover\">\n                                <Setter Property=\"Foreground\" Value=\"{DynamicResource TextControlButtonForegroundPointerOver}\" />\n                                <Setter Property=\"Cursor\" Value=\"Hand\" />\n                            </Style>\n                        </TextBlock.Styles>\n                    </TextBlock>\n\n                    <TextBlock\n                        Grid.Row=\"2\"\n                        Margin=\"0,8,0,0\"\n                        VerticalAlignment=\"Center\"\n                        FontSize=\"20\"\n                        FontWeight=\"Light\"\n                        IsVisible=\"{Binding Metadata.NegativePrompt, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                        Text=\"{x:Static lang:Resources.Label_NegativePrompt}\" />\n\n                    <TextBlock\n                        Grid.Row=\"3\"\n                        Margin=\"0,-4,0,0\"\n                        IsVisible=\"{Binding Metadata.NegativePrompt, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                        Text=\"{Binding Metadata.NegativePrompt}\"\n                        TextWrapping=\"Wrap\"\n                        ToolTip.ShowDelay=\"1000\"\n                        ToolTip.Tip=\"Click to Copy\">\n                        <Interaction.Behaviors>\n                            <EventTriggerBehavior EventName=\"PointerPressed\">\n                                <InvokeCommandAction Command=\"{StaticResource CopyThingToClipboardCommand}\" CommandParameter=\"{Binding Metadata.NegativePrompt}\" />\n                            </EventTriggerBehavior>\n                        </Interaction.Behaviors>\n                        <TextBlock.Styles>\n                            <Style Selector=\"TextBlock:pointerover\">\n                                <Setter Property=\"Foreground\" Value=\"{DynamicResource TextControlButtonForegroundPointerOver}\" />\n                                <Setter Property=\"Cursor\" Value=\"Hand\" />\n                            </Style>\n                        </TextBlock.Styles>\n                    </TextBlock>\n\n                    <TextBlock\n                        Grid.Row=\"4\"\n                        Margin=\"0,8,0,0\"\n                        FontSize=\"20\"\n                        FontWeight=\"Light\"\n                        IsVisible=\"{Binding Resources.Count}\"\n                        Text=\"Resources Used\" />\n\n                    <ItemsRepeater\n                        Grid.Row=\"5\"\n                        HorizontalAlignment=\"Stretch\"\n                        IsVisible=\"{Binding Resources.Count}\"\n                        ItemsSource=\"{Binding Resources}\">\n                        <ItemsRepeater.Layout>\n                            <StackLayout Orientation=\"Vertical\" Spacing=\"8\" />\n                        </ItemsRepeater.Layout>\n                        <ItemsRepeater.ItemTemplate>\n                            <DataTemplate DataType=\"{x:Type civitTrpc:CivitImageResource}\">\n                                <Button\n                                    Padding=\"8\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    HorizontalContentAlignment=\"Stretch\"\n                                    Classes=\"transparent\"\n                                    Command=\"{StaticResource NavigateToModelCommand}\"\n                                    CommandParameter=\"{Binding ModelId}\"\n                                    CornerRadius=\"16\">\n                                    <Grid ColumnDefinitions=\"*, Auto\" RowDefinitions=\"Auto, Auto\">\n                                        <TextBlock\n                                            Grid.Row=\"0\"\n                                            Grid.Column=\"0\"\n                                            VerticalAlignment=\"Center\"\n                                            FontSize=\"16\"\n                                            Text=\"{Binding ModelName}\"\n                                            TextWrapping=\"Wrap\" />\n\n                                        <Label\n                                            Grid.Row=\"0\"\n                                            Grid.Column=\"1\"\n                                            HorizontalAlignment=\"Right\"\n                                            VerticalAlignment=\"Top\"\n                                            Classes=\"Circle LightBlue\"\n                                            Content=\"{Binding ModelType}\"\n                                            Theme=\"{StaticResource TagLabel}\" />\n\n                                        <TextBlock\n                                            Grid.Row=\"1\"\n                                            Grid.Column=\"0\"\n                                            Margin=\"0,0,0,4\"\n                                            VerticalAlignment=\"Center\"\n                                            FontSize=\"13\"\n                                            Foreground=\"{DynamicResource TextControlButtonForegroundPointerOver}\"\n                                            Text=\"{Binding VersionName}\" />\n                                    </Grid>\n                                </Button>\n                            </DataTemplate>\n                        </ItemsRepeater.ItemTemplate>\n                    </ItemsRepeater>\n\n                    <TextBlock\n                        Grid.Row=\"6\"\n                        Margin=\"0,8,0,0\"\n                        FontSize=\"20\"\n                        FontWeight=\"Light\"\n                        Text=\"Other Metadata\" />\n\n                    <ItemsRepeater\n                        Grid.Row=\"7\"\n                        HorizontalAlignment=\"Stretch\"\n                        ItemsSource=\"{Binding OtherMetadata}\">\n                        <ItemsRepeater.Layout>\n                            <WrapLayout HorizontalSpacing=\"4\" VerticalSpacing=\"4\" />\n                        </ItemsRepeater.Layout>\n                        <ItemsRepeater.ItemTemplate>\n                            <DataTemplate>\n                                <DataTemplate.DataType>\n                                    <x:Type x:TypeArguments=\"x:String, x:String\" TypeName=\"generic1:KeyValuePair\" />\n                                </DataTemplate.DataType>\n                                <Label\n                                    HorizontalAlignment=\"Left\"\n                                    VerticalAlignment=\"Center\"\n                                    Classes=\"Circle LightBlue\"\n                                    Theme=\"{StaticResource TagLabel}\"\n                                    ToolTip.ShowDelay=\"1000\"\n                                    ToolTip.Tip=\"Click to Copy\"\n                                    ToolTip.VerticalOffset=\"16\">\n                                    <Interaction.Behaviors>\n                                        <EventTriggerBehavior EventName=\"PointerPressed\">\n                                            <InvokeCommandAction Command=\"{StaticResource CopyThingToClipboardCommand}\" CommandParameter=\"{Binding Value}\" />\n                                        </EventTriggerBehavior>\n                                    </Interaction.Behaviors>\n                                    <Label.Styles>\n                                        <Style Selector=\"Label:pointerover\">\n                                            <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostCyanForeground}\" />\n                                        </Style>\n                                        <Style Selector=\"Label:pressed\">\n                                            <Setter Property=\"Foreground\" Value=\"{DynamicResource LabelTagGhostIndigoForeground}\" />\n                                        </Style>\n                                    </Label.Styles>\n                                    <TextBlock FontSize=\"13\">\n                                        <Run Text=\"{Binding Key}\" /><Run Text=\":\" />\n                                        <Run Text=\"{Binding Value}\" />\n                                    </TextBlock>\n                                </Label>\n                            </DataTemplate>\n                        </ItemsRepeater.ItemTemplate>\n                    </ItemsRepeater>\n\n\n                </avalonia:SpacedGrid>\n\n                <StackPanel\n                    Margin=\"4,0\"\n                    DataContext=\"{Binding LocalImageFile.GenerationParameters}\"\n                    IsVisible=\"{Binding $parent[Grid].((vmDialogs:ImageViewerViewModel)DataContext).HasLocalGenerationParameters}\"\n                    Spacing=\"8\">\n                    <Grid RowDefinitions=\"Auto,*\">\n                        <TextBlock Text=\"{x:Static lang:Resources.Label_Prompt}\" Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                        <SelectableTextBlock Grid.Row=\"1\" Text=\"{Binding PositivePrompt}\" />\n                    </Grid>\n\n                    <Grid RowDefinitions=\"Auto,*\">\n                        <TextBlock Text=\"{x:Static lang:Resources.Label_NegativePrompt}\" Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                        <SelectableTextBlock Grid.Row=\"1\" Text=\"{Binding NegativePrompt}\" />\n                    </Grid>\n\n                    <Grid RowDefinitions=\"Auto,*\">\n                        <TextBlock Text=\"{x:Static lang:Resources.Label_Model}\" Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                        <SelectableTextBlock Grid.Row=\"1\" Text=\"{Binding ModelName}\" />\n                    </Grid>\n\n                    <Grid RowDefinitions=\"Auto,*\">\n                        <TextBlock Text=\"{x:Static lang:Resources.Label_Sampler}\" Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                        <SelectableTextBlock Grid.Row=\"1\" Text=\"{Binding Sampler}\" />\n                    </Grid>\n\n                    <Grid RowDefinitions=\"Auto,*\">\n                        <TextBlock Text=\"{x:Static lang:Resources.Label_Steps}\" Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                        <SelectableTextBlock Grid.Row=\"1\" Text=\"{Binding Steps}\" />\n                    </Grid>\n\n                    <Grid RowDefinitions=\"Auto,*\">\n                        <TextBlock Text=\"{x:Static lang:Resources.Label_CFGScale}\" Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                        <SelectableTextBlock Grid.Row=\"1\" Text=\"{Binding CfgScale}\" />\n                    </Grid>\n\n                    <Grid RowDefinitions=\"Auto,*\">\n                        <TextBlock Text=\"{x:Static lang:Resources.Label_Seed}\" Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                        <SelectableTextBlock Grid.Row=\"1\" Text=\"{Binding Seed}\" />\n                    </Grid>\n                </StackPanel>\n            </Grid>\n\n        </ui:TeachingTip>\n\n        <!--  Close button  -->\n        <Grid\n            Grid.Row=\"0\"\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Top\">\n            <Button\n                Margin=\"0,8,8,0\"\n                Padding=\"8\"\n                icons:Attached.Icon=\"fa-solid fa-xmark\"\n                Classes=\"transparent-full\"\n                Command=\"{Binding OnCloseButtonClick}\" />\n        </Grid>\n\n        <!--  Footer  -->\n        <!--<Border\n            Grid.Row=\"1\"\n            MinHeight=\"20\"\n            Background=\"Transparent\"\n            VerticalAlignment=\"Bottom\"\n            Opacity=\"0.9\">\n            <UniformGrid Margin=\"4\" Columns=\"3\">\n                <TextBlock\n                    HorizontalAlignment=\"Left\"\n                    FontSize=\"13\"\n                    Text=\"{Binding ImageSizeText}\" />\n                <TextBlock\n                    HorizontalAlignment=\"Center\"\n                    FontSize=\"13\"\n                    Text=\"{Binding FileNameText}\"\n                    TextAlignment=\"Center\" />\n                <TextBlock\n                    HorizontalAlignment=\"Right\"\n                    FontSize=\"13\"\n                    Text=\"{Binding FileSizeText}\" />\n            </UniformGrid>\n        </Border>-->\n        <ExperimentalAcrylicBorder\n            Grid.Row=\"1\"\n            Padding=\"4,0,4,0\"\n            VerticalAlignment=\"Bottom\"\n            Material=\"{StaticResource OpaqueDarkAcrylicMaterial}\">\n            <UniformGrid Margin=\"4\" Columns=\"3\">\n                <TextBlock\n                    HorizontalAlignment=\"Left\"\n                    FontSize=\"13\"\n                    Text=\"{Binding ImageSizeText}\" />\n                <TextBlock\n                    HorizontalAlignment=\"Center\"\n                    FontSize=\"13\"\n                    Text=\"{Binding FileNameText}\"\n                    TextAlignment=\"Center\" />\n                <TextBlock\n                    HorizontalAlignment=\"Right\"\n                    FontSize=\"13\"\n                    Text=\"{Binding FileSizeText}\" />\n            </UniformGrid>\n        </ExperimentalAcrylicBorder>\n\n        <!--  The preview tracker  -->\n        <!--<Image\n            MinHeight=\"100\"\n            MinWidth=\"100\"\n            RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n            Source=\"{Binding #MainImageBox.TrackerImage}\"\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Bottom\"/>-->\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/ImageViewerDialog.axaml.cs",
    "content": "﻿using Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Mixins;\nusing Avalonia.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<ImageViewerDialog>]\npublic partial class ImageViewerDialog : UserControlBase\n{\n    public static readonly StyledProperty<bool> IsFooterEnabledProperty = AvaloniaProperty.Register<\n        ImageViewerDialog,\n        bool\n    >(\"IsFooterEnabled\");\n\n    /// <summary>\n    /// Whether the footer with file name / size will be shown.\n    /// </summary>\n    public bool IsFooterEnabled\n    {\n        get => GetValue(IsFooterEnabledProperty);\n        set => SetValue(IsFooterEnabledProperty, value);\n    }\n\n    public ImageViewerDialog()\n    {\n        InitializeComponent();\n        PressedMixin.Attach<Label>();\n    }\n\n    private void InfoButton_OnTapped(object? sender, TappedEventArgs e)\n    {\n        InfoTeachingTip.IsOpen ^= true;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/InferenceConnectionHelpDialog.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n                          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                          xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n                          xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n                          xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n                          xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n                          xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n                          xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n                          xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n                          xmlns:vmDialog=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n                          xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n                          d:DataContext=\"{x:Static mocks:DesignData.InferenceConnectionHelpViewModel}\"\n                          x:DataType=\"vmDialog:InferenceConnectionHelpViewModel\"\n                          mc:Ignorable=\"d\" d:DesignWidth=\"500\" d:DesignHeight=\"350\"\n                          x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.InferenceConnectionHelpDialog\">\n    <Grid RowDefinitions=\"*\">\n        <StackPanel Spacing=\"8\">\n            <TextBlock \n                Theme=\"{DynamicResource SubtitleTextBlockStyle}\"\n                Text=\"Welcome to Inference\"/>\n            \n            <TextBlock \n                Theme=\"{DynamicResource BodyTextBlockStyle}\"\n                Text=\"ComfyUI must be running to use Inference\"/>\n            \n            <ui:InfoBar\n                Severity=\"Informational\"\n                VerticalAlignment=\"Center\"\n                IsClosable=\"False\"\n                IsOpen=\"{Binding IsInstallMode}\"\n                IsVisible=\"{Binding IsInstallMode}\"\n                Margin=\"0,4\"\n                Title=\"Would you like to install ComfyUI now?\"/>\n            \n            <ui:InfoBar\n                Severity=\"Informational\"\n                VerticalAlignment=\"Center\"\n                IsClosable=\"False\"\n                IsOpen=\"{Binding IsLaunchMode}\"\n                IsVisible=\"{Binding IsLaunchMode}\"\n                Margin=\"0,4\"\n                Title=\"Would you like to launch ComfyUI now?\"/>\n            \n            <ComboBox\n                HorizontalAlignment=\"Stretch\"\n                IsVisible=\"{Binding IsLaunchMode}\"\n                ItemsSource=\"{Binding InstalledPackages}\"\n                SelectedItem=\"{Binding SelectedPackage}\">\n                <ComboBox.Styles>\n                    <Style Selector=\"ComboBox /template/ ContentControl#ContentPresenter &gt; StackPanel &gt; TextBlock:nth-child(2)\">\n                        <Setter Property=\"IsVisible\" Value=\"False\" />\n                    </Style>\n                </ComboBox.Styles>\n\n                <ComboBox.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type models:InstalledPackage}\">\n                        <StackPanel Margin=\"10,0,0,0\" VerticalAlignment=\"Top\">\n                            <TextBlock\n                                Name=\"NameTextBlock\"\n                                Margin=\"0,5,0,5\"\n                                Text=\"{Binding DisplayName, Mode=OneWay}\" />\n                            <TextBlock Text=\"{Binding FullPath, Mode=OneWay}\" />\n                        </StackPanel>\n                    </DataTemplate>\n                </ComboBox.ItemTemplate>\n            </ComboBox>\n        </StackPanel>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/InferenceConnectionHelpDialog.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<InferenceConnectionHelpDialog>]\npublic partial class InferenceConnectionHelpDialog : UserControlBase\n{\n    public InferenceConnectionHelpDialog()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/LaunchOptionsDialog.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n                          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                          xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n                          xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n                          xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n                          xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n                          xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n                          xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n                          xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n                          xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n                          xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n                          d:DataContext=\"{x:Static mocks:DesignData.LaunchOptionsViewModel}\"\n                          x:DataType=\"dialogs:LaunchOptionsViewModel\"\n                          mc:Ignorable=\"d\" d:DesignWidth=\"500\" d:DesignHeight=\"650\"\n                          x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.LaunchOptionsDialog\">\n    <controls:UserControlBase.Resources>\n\n        <converters:LaunchOptionConverter x:Key=\"LaunchOptionConverter\" />\n        <converters:LaunchOptionIntDoubleConverter x:Key=\"LaunchOptionIntDoubleConverter\" />\n        <converters:ValueConverterGroup x:Key=\"LaunchOptionIntToStringConverter\">\n            <converters:LaunchOptionConverter />\n            <converters:LaunchOptionIntDoubleConverter />\n        </converters:ValueConverterGroup>\n                \n    </controls:UserControlBase.Resources>\n    \n    <Grid MinWidth=\"400\" RowDefinitions=\"Auto,*\" Margin=\"8\">\n        <StackPanel\n            HorizontalAlignment=\"Stretch\"\n            Spacing=\"4\"\n            Margin=\"0,0,0,16\"\n            Orientation=\"Vertical\">\n            <!--  Title  -->\n            <TextBlock\n                FontSize=\"24\"\n                FontWeight=\"Bold\"\n                Margin=\"16\"\n                Text=\"{Binding Title}\"\n                TextWrapping=\"Wrap\" />\n            <!--  Search box  -->\n            <TextBox\n                HorizontalAlignment=\"Stretch\"\n                Margin=\"8,0\"\n                Watermark=\"{x:Static lang:Resources.Label_SearchEllipsis}\"\n                Text=\"{Binding SearchText, Mode=TwoWay}\"\n                VerticalAlignment=\"Top\"\n                IsVisible=\"{Binding IsSearchBoxEnabled}\"\n                x:Name=\"SearchBox\">\n                <TextBox.InnerRightContent>\n                    <ui:SymbolIcon Symbol=\"Find\" Margin=\"0,0,8,0\" />\n                </TextBox.InnerRightContent>\n            </TextBox>\n        </StackPanel>\n        \n        <!--  Option Cards  -->\n        <ScrollViewer Grid.Row=\"1\" VerticalScrollBarVisibility=\"Auto\">\n            <ItemsControl\n                VerticalAlignment=\"Stretch\"\n                HorizontalAlignment=\"Stretch\"\n                Padding=\"8\"\n                ItemsSource=\"{Binding FilteredCards}\">\n                \n                <ItemsControl.ItemsPanel>\n                    <ItemsPanelTemplate>\n                        <StackPanel Spacing=\"4\" />\n                    </ItemsPanelTemplate>\n                </ItemsControl.ItemsPanel>\n                \n                <ItemsControl.DataTemplates>\n                    <controls:LaunchOptionCardTemplateSelector>\n                        <!--  Int type card (textboxes)  -->\n                        <DataTemplate x:DataType=\"models:LaunchOptionCard\" x:Key=\"{x:Static models:LaunchOptionType.Int}\">\n                            <controls:Card>\n                                <StackPanel\n                                    HorizontalAlignment=\"Stretch\"\n                                    Margin=\"8,0,8,0\"\n                                    Orientation=\"Vertical\">\n                                    <TextBlock\n                                        FontSize=\"16\"\n                                        FontWeight=\"Bold\"\n                                        Margin=\"0,8\"\n                                        Text=\"{Binding Title}\"\n                                        TextWrapping=\"Wrap\" />\n                                    <ItemsControl ItemsSource=\"{Binding Options}\">\n                                        <ItemsControl.ItemTemplate>\n                                            <DataTemplate>\n                                                <StackPanel HorizontalAlignment=\"Stretch\" Orientation=\"Vertical\">\n                                                    <Label Content=\"{Binding Name}\" />\n                                                    <ui:NumberBox\n                                                        HorizontalAlignment=\"Stretch\"\n                                                        Margin=\"8\"\n                                                        PlaceholderText=\"{Binding DefaultValue, Mode=OneWay, Converter={StaticResource LaunchOptionConverter}}\"\n                                                        SpinButtonPlacementMode=\"Compact\"\n                                                        ValidationMode=\"Disabled\"\n                                                        Value=\"{Binding OptionValue, Converter={StaticResource LaunchOptionIntDoubleConverter}, Mode=TwoWay}\"\n                                                        VerticalAlignment=\"Stretch\" />\n                                                </StackPanel>\n                                            </DataTemplate>\n                                        </ItemsControl.ItemTemplate>\n                                    </ItemsControl>\n                                </StackPanel>\n                            </controls:Card>\n                        </DataTemplate>\n                        \n                        <!--  String type card (textboxes)  -->\n                        <DataTemplate DataType=\"{x:Type models:LaunchOptionCard}\" x:Key=\"{x:Static models:LaunchOptionType.String}\">\n                            <controls:Card Margin=\"0,8\">\n                                <StackPanel\n                                    HorizontalAlignment=\"Stretch\"\n                                    Margin=\"8,0,8,0\"\n                                    Orientation=\"Vertical\">\n                                    <TextBlock\n                                        FontSize=\"16\"\n                                        FontWeight=\"Bold\"\n                                        Margin=\"0,8\"\n                                        Text=\"{Binding Title}\"\n                                        TextWrapping=\"Wrap\" />\n                                    <ItemsControl ItemsSource=\"{Binding Options}\">\n                                        <ItemsControl.ItemTemplate>\n                                            <DataTemplate>\n                                                <StackPanel HorizontalAlignment=\"Stretch\" Orientation=\"Vertical\">\n                                                    <Label Content=\"{Binding Name}\" />\n                                                    <!--PlaceholderEnabled=\"{Binding HasDefaultValue}\"-->\n                                                    <TextBox\n                                                        HorizontalAlignment=\"Stretch\"\n                                                        Margin=\"8\"\n                                                        Watermark=\"{Binding DefaultValue}\"\n                                                        Text=\"{Binding OptionValue, Converter={StaticResource LaunchOptionConverter}}\"\n                                                        VerticalAlignment=\"Stretch\" />\n                                                </StackPanel>\n                                            </DataTemplate>\n                                        </ItemsControl.ItemTemplate>\n                                    </ItemsControl>\n                                </StackPanel>\n                            </controls:Card>\n                        </DataTemplate>\n                    \n                        <!--  Bool type card (checkboxes)  -->\n                        <DataTemplate DataType=\"{x:Type models:LaunchOptionCard}\" x:Key=\"{x:Static models:LaunchOptionType.Bool}\">\n                            <controls:Card Margin=\"0,8\">\n                                <StackPanel\n                                    HorizontalAlignment=\"Left\"\n                                    Margin=\"8,0,8,0\"\n                                    Orientation=\"Vertical\">\n                                    <TextBlock\n                                        FontSize=\"16\"\n                                        FontWeight=\"Bold\"\n                                        Margin=\"0,8\"\n                                        Text=\"{Binding Title}\"\n                                        TextWrapping=\"Wrap\" />\n                                    <StackPanel Orientation=\"Horizontal\">\n                                        <ItemsControl ItemsSource=\"{Binding Options}\">\n                                            <ItemsControl.ItemTemplate>\n                                                <DataTemplate>\n                                                    <CheckBox \n                                                        Content=\"{Binding Name}\" \n                                                        IsChecked=\"{Binding OptionValue, Converter={StaticResource LaunchOptionConverter}}\" />\n                                                </DataTemplate>\n                                            </ItemsControl.ItemTemplate>\n                                        </ItemsControl>\n                                    </StackPanel>\n                                </StackPanel>\n                            </controls:Card>\n                        </DataTemplate>\n                    </controls:LaunchOptionCardTemplateSelector>\n                </ItemsControl.DataTemplates>\n            </ItemsControl>\n        </ScrollViewer>\n\n    </Grid>\n    \n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/LaunchOptionsDialog.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<LaunchOptionsDialog>]\npublic partial class LaunchOptionsDialog : UserControlBase\n{\n    public LaunchOptionsDialog()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.LykosLoginDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:ctxt=\"clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mdxaml=\"https://github.com/whistyun/Markdown.Avalonia.Tight\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:sm=\"clr-namespace:StabilityMatrix.Avalonia\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    MaxWidth=\"400\"\n    d:DataContext=\"{x:Static mocks:DesignData.LykosLoginViewModel}\"\n    d:DesignHeight=\"350\"\n    d:DesignWidth=\"400\"\n    x:DataType=\"vmDialogs:LykosLoginViewModel\"\n    Focusable=\"True\"\n    mc:Ignorable=\"d\">\n\n    <sg:SpacedGrid MinWidth=\"400\" MinHeight=\"300\">\n        <!--<TextBlock Text=\"Log in or sign up\" Theme=\"{DynamicResource SubtitleTextBlockStyle}\" />-->\n        <TabControl SelectedIndex=\"{Binding IsSignupMode}\">\n            <TabItem FontSize=\"20\" Header=\"Login\">\n\n                <sg:SpacedGrid\n                    Margin=\"-4,32,-4,8\"\n                    RowDefinitions=\"Auto,Auto,Auto,*,*\"\n                    RowSpacing=\"8\">\n                    <!--  Username  -->\n                    <TextBox\n                        Grid.Row=\"0\"\n                        Text=\"{Binding Email}\"\n                        UseFloatingWatermark=\"True\"\n                        Watermark=\"{x:Static lang:Resources.Label_Email}\" />\n                    <!--  Password  -->\n                    <MaskedTextBox\n                        Grid.Row=\"1\"\n                        PasswordChar=\"*\"\n                        Text=\"{Binding Password}\"\n                        Watermark=\"{x:Static lang:Resources.Label_Password}\" />\n\n                    <ui:InfoBar\n                        Title=\"{Binding LoginError.Message}\"\n                        Grid.Row=\"2\"\n                        IsOpen=\"{Binding LoginError, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                        Message=\"{Binding LoginError.Details}\"\n                        Severity=\"Error\" />\n\n                    <controls:HyperlinkIconButton\n                        Grid.Row=\"3\"\n                        VerticalAlignment=\"Bottom\"\n                        Content=\"Forgot Password?\"\n                        NavigateUri=\"{x:Static sm:Assets.LykosForgotPasswordUrl}\" />\n\n                    <controls:HyperlinkIconButton\n                        Grid.Row=\"4\"\n                        VerticalAlignment=\"Bottom\"\n                        Command=\"{Binding GoogleOAuthButtonClickCommand}\"\n                        Content=\"Sign in with Google\" />\n                </sg:SpacedGrid>\n\n            </TabItem>\n            <TabItem FontSize=\"20\" Header=\"Sign up\">\n\n                <sg:SpacedGrid\n                    Margin=\"-4,32,-4,8\"\n                    RowDefinitions=\"Auto,Auto,Auto,Auto,Auto,Auto\"\n                    RowSpacing=\"8\">\n                    <!--  Email  -->\n                    <TextBox\n                        Grid.Row=\"0\"\n                        Text=\"{Binding Email}\"\n                        UseFloatingWatermark=\"True\"\n                        Watermark=\"{x:Static lang:Resources.Label_Email}\" />\n                    <!--  Username  -->\n                    <TextBox\n                        Grid.Row=\"1\"\n                        Text=\"{Binding Username}\"\n                        UseFloatingWatermark=\"True\"\n                        Watermark=\"{x:Static lang:Resources.Label_Username}\" />\n                    <!--  Password  -->\n                    <TextBox\n                        Grid.Row=\"2\"\n                        PasswordChar=\"*\"\n                        Text=\"{Binding Password}\"\n                        UseFloatingWatermark=\"True\"\n                        Watermark=\"{x:Static lang:Resources.Label_Password}\" />\n                    <!--  Confirm Password  -->\n                    <TextBox\n                        Grid.Row=\"3\"\n                        PasswordChar=\"*\"\n                        Text=\"{Binding ConfirmPassword}\"\n                        UseFloatingWatermark=\"True\"\n                        Watermark=\"{x:Static lang:Resources.Label_ConfirmPassword}\" />\n\n                    <ui:InfoBar\n                        Title=\"{Binding SignupError.Message}\"\n                        Grid.Row=\"4\"\n                        IsOpen=\"{Binding SignupError, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                        Message=\"{Binding SignupError.Details}\"\n                        Severity=\"Error\" />\n\n                    <controls:BetterMarkdownScrollViewer\n                        Grid.Row=\"5\"\n                        MaxWidth=\"380\"\n                        Margin=\"4,0\"\n                        VerticalAlignment=\"Bottom\"\n                        Markdown=\"{Binding SignupFooterMarkdown}\"\n                        TextElement.Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\">\n                        <controls:BetterMarkdownScrollViewer.Styles>\n                            <Style Selector=\"ctxt|CHyperlink\">\n                                <Style.Setters>\n                                    <Setter Property=\"IsUnderline\" Value=\"False\" />\n                                </Style.Setters>\n                            </Style>\n                            <Style Selector=\"ctxt|CHyperlink:pointerover\">\n                                <Setter Property=\"IsUnderline\" Value=\"True\" />\n                                <Setter Property=\"Foreground\" Value=\"{StaticResource ThemeEldenRingOrangeColor}\" />\n                            </Style>\n                        </controls:BetterMarkdownScrollViewer.Styles>\n                    </controls:BetterMarkdownScrollViewer>\n                </sg:SpacedGrid>\n\n            </TabItem>\n        </TabControl>\n    </sg:SpacedGrid>\n\n\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<LykosLoginDialog>]\npublic partial class LykosLoginDialog : UserControlBase\n{\n    public LykosLoginDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/MaskEditorDialog.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n                          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                          xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n                          xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n                          xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n                          xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n                          xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n                          xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n                          d:DataContext=\"{x:Static mocks:DesignData.MaskEditorViewModel}\"\n                          x:DataType=\"vmDialogs:MaskEditorViewModel\"\n                          mc:Ignorable=\"d\" d:DesignWidth=\"800\" d:DesignHeight=\"450\"\n                          x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.MaskEditorDialog\">\n    <sg:SpacedGrid RowDefinitions=\"Auto,*\" Margin=\"0,8\">\n        <controls:Card>\n            <Grid ColumnDefinitions=\"*,Auto\" Margin=\"8,0\">\n                <ToggleSwitch\n                    IsChecked=\"{Binding IsMaskEnabled}\"\n                    OnContent=\"Enabled\" OffContent=\"Disabled\" Grid.Column=\"0\" HorizontalAlignment=\"Left\"/>\n                <StackPanel \n                    Grid.Column=\"1\"\n                    Orientation=\"Horizontal\" Spacing=\"16\">\n                    <Button Content=\"Load Mask\" Command=\"{Binding DebugSelectFileLoadMaskCommand}\" />\n                    <Button Content=\"Save Mask\" Command=\"{Binding DebugSelectFileSaveMaskCommand}\" />\n                    <ToggleButton \n                        Content=\"Use Image Alpha Channel as Mask\" \n                        IsVisible=\"False\"\n                        IsChecked=\"{Binding UseImageAlphaAsMask}\"/>\n                </StackPanel>\n            </Grid>\n        </controls:Card>\n\n        <controls:Card Padding=\"8,4,8,8\" Grid.Row=\"1\">\n            <controls:PaintCanvas DataContext=\"{Binding PaintCanvasViewModel, Mode=OneWay}\" />\n        </controls:Card>\n\n    </sg:SpacedGrid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/MaskEditorDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<MaskEditorDialog>]\npublic partial class MaskEditorDialog : UserControlBase\n{\n    public MaskEditorDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/ModelMetadataEditorDialog.axaml",
    "content": "﻿<controls:DropTargetUserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.ModelMetadataEditorDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:api=\"clr-namespace:StabilityMatrix.Core.Models.Api;assembly=StabilityMatrix.Core\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:helpers=\"clr-namespace:StabilityMatrix.Avalonia.Helpers\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    d:DataContext=\"{x:Static designData:DesignData.MetadataEditorDialogViewModel}\"\n    d:DesignHeight=\"700\"\n    d:DesignWidth=\"600\"\n    x:DataType=\"dialogs:ModelMetadataEditorDialogViewModel\"\n    mc:Ignorable=\"d\">\n\n    <UserControl.Resources>\n        <converters:EnumStringConverter x:Key=\"EnumStringConverter\" />\n    </UserControl.Resources>\n\n    <ScrollViewer Padding=\"8\">\n        <Grid\n            VerticalAlignment=\"Top\"\n            ColumnDefinitions=\"Auto,*\"\n            RowDefinitions=\"Auto,Auto,100,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto\">\n            <TextBlock\n                Grid.Row=\"0\"\n                Grid.ColumnSpan=\"2\"\n                Margin=\"0,16\"\n                HorizontalAlignment=\"Center\"\n                FontSize=\"24\"\n                FontWeight=\"SemiBold\"\n                Text=\"{x:Static lang:Resources.Label_EditModelMetadata}\" />\n\n            <TextBlock\n                Grid.Row=\"1\"\n                Grid.Column=\"0\"\n                Margin=\"0,0,10,0\"\n                VerticalAlignment=\"Center\"\n                Text=\"{x:Static lang:Resources.Label_DisplayName}\" />\n            <TextBox\n                Grid.Row=\"1\"\n                Grid.Column=\"1\"\n                Text=\"{Binding ModelName}\" />\n\n            <TextBlock\n                Grid.Row=\"2\"\n                Grid.Column=\"0\"\n                Margin=\"0,10,10,0\"\n                VerticalAlignment=\"Top\"\n                Text=\"{x:Static lang:Resources.Label_ModelDescription}\" />\n            <TextBox\n                Grid.Row=\"2\"\n                Grid.Column=\"1\"\n                Margin=\"0,10,0,0\"\n                AcceptsReturn=\"True\"\n                Text=\"{Binding ModelDescription}\"\n                TextWrapping=\"Wrap\" />\n\n            <GridSplitter\n                Grid.Row=\"3\"\n                Grid.Column=\"1\"\n                MaxWidth=\"50\"\n                Margin=\"0,8,0,0\"\n                VerticalAlignment=\"Center\"\n                Background=\"White\"\n                BorderThickness=\"1\"\n                CornerRadius=\"4\"\n                Opacity=\"0.5\" />\n\n            <TextBlock\n                Grid.Row=\"4\"\n                Grid.Column=\"0\"\n                Margin=\"0,10,10,0\"\n                VerticalAlignment=\"Center\"\n                Text=\"{x:Static lang:Resources.Label_NSFW}\" />\n            <ToggleSwitch\n                Grid.Row=\"4\"\n                Grid.Column=\"1\"\n                Margin=\"0,10,0,0\"\n                IsChecked=\"{Binding IsNsfw}\"\n                OffContent=\"{x:Static lang:Resources.Label_No}\"\n                OnContent=\"{x:Static lang:Resources.Action_Yes}\" />\n\n            <TextBlock\n                Grid.Row=\"5\"\n                Grid.Column=\"0\"\n                Margin=\"0,10,10,0\"\n                VerticalAlignment=\"Top\"\n                Text=\"{x:Static lang:Resources.Label_Tags}\" />\n            <TextBox\n                Grid.Row=\"5\"\n                Grid.Column=\"1\"\n                Height=\"100\"\n                Margin=\"0,10,0,0\"\n                AcceptsReturn=\"True\"\n                Text=\"{Binding Tags}\"\n                TextWrapping=\"Wrap\" />\n\n            <TextBlock\n                Grid.Row=\"6\"\n                Grid.Column=\"0\"\n                Margin=\"0,10,10,0\"\n                VerticalAlignment=\"Center\"\n                Text=\"{x:Static lang:Resources.Label_ModelType}\" />\n            <controls:BetterComboBox\n                Grid.Row=\"6\"\n                Grid.Column=\"1\"\n                Margin=\"0,10,0,0\"\n                ItemsSource=\"{x:Static helpers:EnumHelpers.MetadataEditorCivitModelTypes}\"\n                SelectedItem=\"{Binding ModelType}\" />\n\n            <TextBlock\n                Grid.Row=\"7\"\n                Grid.Column=\"0\"\n                Margin=\"0,10,10,0\"\n                VerticalAlignment=\"Center\"\n                Text=\"{x:Static lang:Resources.Label_VersionName}\" />\n            <TextBox\n                Grid.Row=\"7\"\n                Grid.Column=\"1\"\n                Margin=\"0,10,0,0\"\n                Text=\"{Binding VersionName}\" />\n\n            <TextBlock\n                Grid.Row=\"8\"\n                Grid.Column=\"0\"\n                Margin=\"0,10,10,0\"\n                VerticalAlignment=\"Center\"\n                Text=\"{x:Static lang:Resources.Label_BaseModel}\" />\n            <controls:BetterComboBox\n                Grid.Row=\"8\"\n                Grid.Column=\"1\"\n                Margin=\"0,10,0,0\"\n                ItemsSource=\"{Binding BaseModelTypes}\"\n                SelectedItem=\"{Binding BaseModelType}\">\n                <controls:BetterComboBox.ItemTemplate>\n                    <DataTemplate x:DataType=\"api:CivitBaseModelType\">\n                        <TextBlock Text=\"{Binding ., Converter={StaticResource EnumStringConverter}}\" />\n                    </DataTemplate>\n                </controls:BetterComboBox.ItemTemplate>\n            </controls:BetterComboBox>\n\n            <TextBlock\n                Grid.Row=\"9\"\n                Grid.Column=\"0\"\n                Margin=\"0,10,10,0\"\n                VerticalAlignment=\"Top\"\n                Text=\"{x:Static lang:Resources.Label_TrainedWords}\" />\n            <TextBox\n                Grid.Row=\"9\"\n                Grid.Column=\"1\"\n                Height=\"100\"\n                Margin=\"0,10,0,0\"\n                AcceptsReturn=\"True\"\n                Text=\"{Binding TrainedWords}\"\n                TextWrapping=\"Wrap\" />\n\n            <TextBlock\n                Grid.Row=\"10\"\n                Grid.Column=\"0\"\n                Margin=\"0,10,10,0\"\n                VerticalAlignment=\"Top\"\n                IsVisible=\"{Binding ShowInferenceDefaults}\"\n                Text=\"Inference Defaults\" />\n\n            <ToggleSwitch\n                Grid.Row=\"10\"\n                Grid.Column=\"1\"\n                IsChecked=\"{Binding IsInferenceDefaultsEnabled}\"\n                IsVisible=\"{Binding ShowInferenceDefaults}\" />\n\n            <controls:SamplerCard\n                Grid.Row=\"11\"\n                Grid.Column=\"1\"\n                Margin=\"0,10,0,0\"\n                DataContext=\"{Binding SamplerCardViewModel}\">\n                <controls:SamplerCard.IsVisible>\n                    <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                        <Binding Path=\"$parent[Grid].((dialogs:ModelMetadataEditorDialogViewModel)DataContext).IsInferenceDefaultsEnabled\" />\n                        <Binding Path=\"$parent[Grid].((dialogs:ModelMetadataEditorDialogViewModel)DataContext).ShowInferenceDefaults\" />\n                    </MultiBinding>\n                </controls:SamplerCard.IsVisible>\n            </controls:SamplerCard>\n\n            <TextBlock\n                Grid.Row=\"12\"\n                Grid.Column=\"0\"\n                Margin=\"0,10,10,0\"\n                VerticalAlignment=\"Top\"\n                Text=\"{x:Static lang:Resources.Label_PreviewImage}\" />\n            <StackPanel\n                Grid.Row=\"12\"\n                Grid.Column=\"1\"\n                Margin=\"0,10,0,0\"\n                Orientation=\"Vertical\"\n                Spacing=\"10\">\n                <controls:BetterAdvancedImage\n                    Height=\"200\"\n                    DragDrop.AllowDrop=\"True\"\n                    Source=\"{Binding ThumbnailFilePath}\"\n                    Stretch=\"Uniform\" />\n                <Grid ColumnDefinitions=\"*,Auto\">\n                    <TextBox Grid.Column=\"0\" Text=\"{Binding ThumbnailFilePath}\" />\n                    <Button\n                        Grid.Column=\"1\"\n                        Margin=\"10,0,0,0\"\n                        Command=\"{Binding OpenFilePickerDialogCommand}\">\n                        <ui:SymbolIcon Symbol=\"OpenFolder\" />\n                    </Button>\n                </Grid>\n            </StackPanel>\n        </Grid>\n    </ScrollViewer>\n</controls:DropTargetUserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/ModelMetadataEditorDialog.axaml.cs",
    "content": "﻿using Avalonia.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<ModelMetadataEditorDialog>]\npublic partial class ModelMetadataEditorDialog : DropTargetUserControlBase\n{\n    public ModelMetadataEditorDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/NewOneClickInstallDialog.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n             xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n             xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n             xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n             xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n             xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n             xmlns:packages=\"clr-namespace:StabilityMatrix.Core.Models.Packages;assembly=StabilityMatrix.Core\"\n             xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n             mc:Ignorable=\"d\" d:DesignWidth=\"700\" d:DesignHeight=\"725\"\n             x:DataType=\"dialogs:NewOneClickInstallViewModel\"\n             d:DataContext=\"{x:Static mocks:DesignData.NewOneClickInstallViewModel}\"\n             x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.NewOneClickInstallDialog\">\n    \n    <controls:UserControlBase.Resources>\n        <input:StandardUICommand x:Key=\"InstallPackageCommand\"\n                                 Command=\"{Binding InstallPackageCommand}\" />\n\n        <DataTemplate x:DataType=\"packages:BasePackage\" x:Key=\"PackageTemplate\">\n            <Button Padding=\"0\"\n                    HorizontalAlignment=\"Stretch\"\n                    HorizontalContentAlignment=\"Stretch\"\n                    Margin=\"4\"\n                    Classes=\"transparent-full\"\n                    CornerRadius=\"8\"\n                    Command=\"{StaticResource InstallPackageCommand}\"\n                    CommandParameter=\"{Binding }\">\n                <controls:Card Padding=\"8, 0\">\n\n                    <Grid ColumnDefinitions=\"Auto, *, Auto\">\n                        <controls:BetterAdvancedImage\n                            Grid.Column=\"0\"\n                            MaxHeight=\"96\"\n                            MaxWidth=\"96\"\n                            Stretch=\"UniformToFill\"\n                            CornerRadius=\"4\"\n                            Source=\"{Binding PreviewImageUri}\" />\n                        <StackPanel Grid.Column=\"1\" Orientation=\"Vertical\">\n                        <TextBlock Text=\"{Binding DisplayName}\"\n                                   FontWeight=\"Light\"\n                                   Margin=\"16,8,0,0\"\n                                   FontSize=\"20\" />\n                        \n                        <TextBlock Text=\"{Binding ByAuthor}\"\n                                   FontWeight=\"Light\"\n                                   Margin=\"16,0,0,0\"\n                                   FontSize=\"13\" />\n\n                        <TextBlock TextWrapping=\"Wrap\"\n                                   Text=\"{Binding Blurb}\"\n                                   FontWeight=\"Light\"\n                                   Margin=\"16,8,0,8\"\n                                   FontSize=\"14\" />\n                        \n                        <controls:Card Tag=\"{Binding InstallerSortOrder}\"\n                                       HorizontalAlignment=\"Left\"\n                                       Padding=\"4\"\n                                       Margin=\"16, 0, 0, 8\"\n                                       VerticalAlignment=\"Top\">\n\n                                        <controls:Card.Styles>\n                                            <Style Selector=\"controls|Card[Tag=ReallyRecommended]\">\n                                                <Setter Property=\"Background\"\n                                                        Value=\"{DynamicResource ThemeGreenColorTransparent}\" />\n                                                <Setter Property=\"BorderBrush\"\n                                                        Value=\"{DynamicResource ThemeGreenColorTransparent}\" />\n                                                <Style\n                                                    Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                                    <Setter Property=\"Foreground\"\n                                                            Value=\"{DynamicResource ButtonForeground}\" />\n                                                </Style>\n                                                <Setter Property=\"Content\">\n                                                    <Template>\n                                                        <TextBlock\n                                                            FontWeight=\"Medium\"\n                                                            HorizontalAlignment=\"Center\"\n                                                            Text=\"Recommended\"\n                                                            TextAlignment=\"Center\"\n                                                            VerticalAlignment=\"Center\" />\n                                                    </Template>\n                                                </Setter>\n                                            </Style>\n                                            <Style Selector=\"controls|Card[Tag=Recommended]\">\n                                                <Setter Property=\"Background\"\n                                                        Value=\"{DynamicResource ThemeGreenColorTransparent}\" />\n                                                <Setter Property=\"BorderBrush\"\n                                                        Value=\"{DynamicResource ThemeGreenColorTransparent}\" />\n                                                <Style\n                                                    Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                                    <Setter Property=\"Foreground\"\n                                                            Value=\"{DynamicResource ButtonForeground}\" />\n                                                </Style>\n                                                <Setter Property=\"Content\">\n                                                    <Template>\n                                                        <TextBlock\n                                                            FontWeight=\"Medium\"\n                                                            HorizontalAlignment=\"Center\"\n                                                            Text=\"Recommended\"\n                                                            TextAlignment=\"Center\"\n                                                            VerticalAlignment=\"Center\" />\n                                                    </Template>\n                                                </Setter>\n                                            </Style>\n                                            <Style Selector=\"controls|Card[Tag=Simple]\">\n                                                <Setter Property=\"Background\"\n                                                        Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                                                <Setter Property=\"BorderBrush\"\n                                                        Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                                                <Setter Property=\"Content\">\n                                                    <Template>\n                                                        <TextBlock\n                                                            FontWeight=\"Medium\"\n                                                            HorizontalAlignment=\"Center\"\n                                                            Text=\"Simple\"\n                                                            TextAlignment=\"Center\"\n                                                            VerticalAlignment=\"Center\" />\n                                                    </Template>\n                                                </Setter>\n                                            </Style>\n                                            <Style Selector=\"controls|Card[Tag=Advanced]\">\n                                                <Setter Property=\"Background\"\n                                                        Value=\"{DynamicResource ThemeOrangeColor}\" />\n                                                <Setter Property=\"BorderBrush\"\n                                                        Value=\"{DynamicResource ThemeOrangeColor}\" />\n                                                <Setter Property=\"Foreground\"\n                                                        Value=\"Black\" />\n                                                <Setter Property=\"Content\">\n                                                    <Template>\n                                                        <TextBlock\n                                                            FontWeight=\"Medium\"\n                                                            HorizontalAlignment=\"Center\"\n                                                            Text=\"Advanced\"\n                                                            TextAlignment=\"Center\"\n                                                            VerticalAlignment=\"Center\" />\n                                                    </Template>\n                                                </Setter>\n                                            </Style>\n                                            <Style Selector=\"controls|Card[Tag=InferenceCompatible]\">\n                                                <Setter Property=\"Background\"\n                                                        Value=\"{DynamicResource ThemePurpleColor}\" />\n                                                <Setter Property=\"BorderBrush\"\n                                                        Value=\"{DynamicResource ThemePurpleColor}\" />\n                                                <Setter Property=\"Foreground\"\n                                                        Value=\"White\" />\n                                                <Setter Property=\"Content\">\n                                                    <Template>\n                                                        <TextBlock\n                                                            FontWeight=\"Medium\"\n                                                            HorizontalAlignment=\"Center\"\n                                                            Text=\"Inference Compatible\"\n                                                            TextAlignment=\"Center\"\n                                                            VerticalAlignment=\"Center\" />\n                                                    </Template>\n                                                </Setter>\n                                            </Style>\n                                            <Style Selector=\"controls|Card\">\n                                                <Setter Property=\"Background\"\n                                                        Value=\"{DynamicResource ThemeDarkRedColor}\" />\n                                                <Setter Property=\"BorderBrush\"\n                                                        Value=\"{DynamicResource ThemeDarkRedColor}\" />\n                                                <Setter Property=\"Content\">\n                                                    <Template>\n                                                        <TextBlock\n                                                            FontWeight=\"Medium\"\n                                                            HorizontalAlignment=\"Center\"\n                                                            Text=\"Expert\"\n                                                            TextAlignment=\"Center\"\n                                                            VerticalAlignment=\"Center\" />\n                                                    </Template>\n                                                </Setter>\n                                            </Style>\n                                        </controls:Card.Styles>\n                                    </controls:Card>\n                        </StackPanel>\n\n                        <ui:SymbolIcon Grid.Column=\"2\"\n                                              HorizontalAlignment=\"Right\"\n                                              VerticalAlignment=\"Center\"\n                                              FontSize=\"24\"\n                                              Symbol=\"ChevronRight\" />\n                    </Grid>\n                </controls:Card>\n            </Button>\n        </DataTemplate>\n    </controls:UserControlBase.Resources>\n    \n    <StackPanel Orientation=\"Vertical\" Margin=\"8\">\n        <TextBlock Text=\"{x:Static lang:Resources.Text_WelcomeToStabilityMatrix}\"\n                   TextWrapping=\"Wrap\"\n                   Margin=\"8,0,8,0\"\n                   VerticalAlignment=\"Center\" HorizontalAlignment=\"Center\"\n                   FontSize=\"36\" FontWeight=\"Light\" />\n        <TextBlock Text=\"{x:Static lang:Resources.Text_OneClickInstaller_SubHeader}\"\n                   TextWrapping=\"Wrap\" TextAlignment=\"Center\"\n                   VerticalAlignment=\"Top\" HorizontalAlignment=\"Center\"\n                   FontSize=\"16\" FontWeight=\"Light\" Margin=\"8\"/>\n\n        <ScrollViewer Height=\"575\"\n                      Padding=\"8, 0\">\n            <ItemsRepeater ItemsSource=\"{Binding ShownPackages}\"\n                           Name=\"PackagesRepeater\">\n                <ItemsRepeater.Layout>\n                    <StackLayout Orientation=\"Vertical\" />\n                </ItemsRepeater.Layout>\n                <ItemsRepeater.ItemTemplate>\n                    <StaticResource ResourceKey=\"PackageTemplate\" />\n                </ItemsRepeater.ItemTemplate>\n            </ItemsRepeater>\n        </ScrollViewer>\n        \n        <ui:TeachingTip\n            Name=\"InferenceTeachingTip\"\n            MinWidth=\"100\"\n            Title=\"Use ComfyUI with Inference\"\n            Subtitle=\"A new built-in native Stable Diffusion experience, powered by ComfyUI\"\n            ActionButtonContent=\"{x:Static lang:Resources.Action_Install}\"\n            ActionButtonCommand=\"{Binding InstallComfyForInferenceCommand}\"\n            CloseButtonContent=\"{x:Static lang:Resources.Action_Close}\"\n            PreferredPlacement=\"RightTop\"\n            Margin=\"8,0,0,0\"\n            PlacementMargin=\"0,0,0,0\"\n            TailVisibility=\"Auto\">\n            <ui:TeachingTip.HeroContent>\n                <controls:BetterAdvancedImage\n                    RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n                    Source=\"https://cdn.lykos.ai/static/sc-inference-drag-load.gif\"/>\n            </ui:TeachingTip.HeroContent>\n        </ui:TeachingTip>\n        \n        <Grid ColumnDefinitions=\"Auto, *\" Margin=\"0,4\">\n            <ToggleSwitch IsChecked=\"{Binding ShowIncompatiblePackages}\"\n                          Margin=\"6, 0\"\n                          OffContent=\"Show All Packages\"\n                          OnContent=\"Show All Packages\"/>\n            <Button Grid.Column=\"0\"\n                    Grid.ColumnSpan=\"2\"\n                    HorizontalAlignment=\"Center\"\n                    Content=\"{x:Static lang:Resources.Label_SkipSetup}\"\n                    Command=\"{Binding OnCloseButtonClick}\"/>\n        </Grid>\n    </StackPanel>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/NewOneClickInstallDialog.axaml.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing Avalonia.Controls;\nusing Avalonia.Interactivity;\nusing FluentAvalonia.UI.Controls;\nusing KGySoft.CoreLibraries;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Core.Models.Packages;\nusing ItemsRepeater = Avalonia.Controls.ItemsRepeater;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\npublic partial class NewOneClickInstallDialog : UserControlBase\n{\n    public NewOneClickInstallDialog()\n    {\n        InitializeComponent();\n    }\n\n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        base.OnLoaded(e);\n\n        var teachingTip =\n            this.FindControl<TeachingTip>(\"InferenceTeachingTip\")\n            ?? throw new InvalidOperationException(\"TeachingTip not found\");\n\n        teachingTip.ActionButtonClick += (_, _) =>\n        {\n            teachingTip.IsOpen = false;\n        };\n\n        // Find ComfyUI listbox item\n        var listBox = this.FindControl<ItemsRepeater>(\"PackagesRepeater\");\n\n        // Find ComfyUI listbox item\n        if (listBox?.ItemsSource?.Cast<BasePackage>().FirstOrDefault(p => p is ComfyUI) is { } comfy)\n        {\n            var comfyItem = listBox.TryGetElement(listBox?.ItemsSource?.IndexOf(comfy) ?? 0);\n\n            // comfyItem!.IsSelected = true;\n\n            teachingTip.Target = comfyItem;\n            teachingTip.IsOpen = true;\n            teachingTip.CloseButtonCommand = null;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n                          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                          xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n                          xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n                          xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n                          xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n                          xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n                          xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n                          xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n                          xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n                          xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n                          xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n                          d:DataContext=\"{x:Static mocks:DesignData.OAuthConnectViewModel}\"\n                          x:DataType=\"vmDialogs:OAuthConnectViewModel\"\n                          mc:Ignorable=\"d\" d:DesignWidth=\"500\" d:DesignHeight=\"400\"\n                          x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.OAuthConnectDialog\">\n    \n    <sg:SpacedGrid RowDefinitions=\"Auto,Auto,Auto\" Margin=\"8,4\" RowSpacing=\"12\">\n        <sg:SpacedGrid.Styles>\n            <Style Selector=\"HyperlinkButton > TextBlock\">\n                <Setter Property=\"TextWrapping\" Value=\"WrapWithOverflow\"/>\n            </Style>\n        </sg:SpacedGrid.Styles>\n        \n        <HyperlinkButton \n            Padding=\"6,0\"\n            Content=\"{Binding Url}\"\n            NavigateUri=\"{Binding Url}\"/>\n            \n        <TextBlock\n            Grid.Row=\"1\"\n            Margin=\"6,0\"\n            TextWrapping=\"WrapWithOverflow\"\n            Text=\"{Binding Description}\"/>\n            \n        <ProgressBar\n            IsIndeterminate=\"True\"\n            Grid.Row=\"2\"/>\n    </sg:SpacedGrid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<OAuthConnectDialog>]\npublic partial class OAuthConnectDialog : UserControlBase\n{\n    public OAuthConnectDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/OAuthDeviceAuthDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.OAuthDeviceAuthDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:helpers=\"clr-namespace:StabilityMatrix.Avalonia.Helpers\"\n    xmlns:icExt=\"clr-namespace:FluentIcons.Avalonia.Fluent.MarkupExtensions;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    d:DataContext=\"{x:Static mocks:DesignData.OAuthDeviceAuthViewModel}\"\n    d:DesignHeight=\"400\"\n    d:DesignWidth=\"500\"\n    x:DataType=\"vmDialogs:OAuthDeviceAuthViewModel\"\n    mc:Ignorable=\"d\">\n\n    <sg:SpacedGrid\n        Margin=\"8,4\"\n        ColumnDefinitions=\"*,Auto\"\n        RowDefinitions=\"Auto,Auto,Auto,Auto,Auto\"\n        RowSpacing=\"8\">\n        <TextBlock\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            Text=\"{Binding Description}\"\n            TextWrapping=\"Wrap\" />\n\n        <controls:HyperlinkIconButton\n            Grid.Row=\"1\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            Content=\"{Binding VerificationUri}\"\n            NavigateUri=\"{Binding VerificationUri}\" />\n\n        <TextBox\n            Grid.Row=\"2\"\n            Grid.Column=\"0\"\n            Margin=\"16,0\"\n            IsReadOnly=\"True\"\n            Text=\"{Binding UserCode}\">\n            <TextBox.InnerRightContent>\n                <Button\n                    Classes=\"transparent-full\"\n                    Command=\"{x:Static helpers:ClipboardCommands.CopyTextCommand}\"\n                    CommandParameter=\"{Binding UserCode}\"\n                    Content=\"{icExt:SymbolIcon Symbol=Copy}\" />\n            </TextBox.InnerRightContent>\n        </TextBox>\n\n        <TextBlock\n            Grid.Row=\"3\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            Margin=\"8,8,0,0\"\n            HorizontalAlignment=\"Center\"\n            IsVisible=\"{Binding IsLoading}\"\n            Text=\"Authenticating...\"\n            TextAlignment=\"Center\" />\n\n        <ProgressBar\n            Grid.Row=\"4\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            Margin=\"8\"\n            IsEnabled=\"{Binding IsLoading}\"\n            IsIndeterminate=\"True\"\n            IsVisible=\"{Binding IsLoading}\" />\n    </sg:SpacedGrid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/OAuthDeviceAuthDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<OAuthDeviceAuthDialog>]\npublic partial class OAuthDeviceAuthDialog : UserControlBase\n{\n    public OAuthDeviceAuthDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/OAuthLoginDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.OAuthLoginDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    d:DataContext=\"{x:Static mocks:DesignData.OAuthLoginViewModel}\"\n    d:DesignHeight=\"400\"\n    d:DesignWidth=\"500\"\n    x:DataType=\"vmDialogs:OAuthLoginViewModel\"\n    mc:Ignorable=\"d\">\n\n    <Grid Margin=\"8,4\" RowDefinitions=\"Auto,Auto\">\n        <Grid.Styles>\n            <Style Selector=\"HyperlinkButton &gt; TextBlock\">\n                <Setter Property=\"TextWrapping\" Value=\"Wrap\" />\n            </Style>\n        </Grid.Styles>\n\n        <!--  Content  -->\n        <StackPanel\n            Grid.Row=\"0\"\n            IsEnabled=\"{Binding !IsLoading}\"\n            IsVisible=\"{Binding !IsLoading}\"\n            Spacing=\"16\">\n            <HyperlinkButton\n                Padding=\"6,0\"\n                Content=\"{Binding Url}\"\n                NavigateUri=\"{Binding Url}\" />\n\n            <TextBlock\n                Margin=\"6,0\"\n                Text=\"{Binding Description}\"\n                TextWrapping=\"WrapWithOverflow\" />\n\n            <TextBlock\n                Margin=\"6,0\"\n                Text=\"{Binding AppLinkInstructions}\"\n                TextWrapping=\"WrapWithOverflow\" />\n        </StackPanel>\n\n        <!--  Loading  -->\n        <StackPanel\n            Grid.Row=\"0\"\n            Margin=\"16,64\"\n            IsEnabled=\"{Binding IsLoading}\"\n            IsVisible=\"{Binding IsLoading}\"\n            Spacing=\"16\">\n            <Border\n                HorizontalAlignment=\"Center\"\n                VerticalAlignment=\"Center\"\n                Background=\"White\"\n                CornerRadius=\"24\">\n                <!--<Image\n                    VerticalAlignment=\"Center\"\n                    Margin=\"12,12\"\n                    Height=\"24\"\n                    Width=\"24\"\n                    Source=\"{Binding IconUri}\"/>-->\n                <!--<Svg\n                    VerticalAlignment=\"Center\"\n                    Margin=\"12,12\"\n                    Height=\"24\"\n                    Width=\"24\"\n                    Path=\"/Assets/brands-google-oauth-icon.svg\"/>-->\n                <Svg\n                    Width=\"24\"\n                    Height=\"24\"\n                    Margin=\"12,12\"\n                    VerticalAlignment=\"Center\"\n                    Path=\"{Binding IconUri}\" />\n            </Border>\n        </StackPanel>\n\n        <ProgressBar\n            Grid.Row=\"1\"\n            Margin=\"0,16,0,0\"\n            IsEnabled=\"{Binding IsLoading}\"\n            IsIndeterminate=\"True\"\n            IsVisible=\"{Binding IsLoading}\" />\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/OAuthLoginDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<OAuthLoginDialog>]\npublic partial class OAuthLoginDialog : UserControlBase\n{\n    public OAuthLoginDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/OneClickInstallDialog.axaml",
    "content": "﻿<UserControl xmlns=\"https://github.com/avaloniaui\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n             xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n             xmlns:designData=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n             xmlns:packages=\"clr-namespace:StabilityMatrix.Core.Models.Packages;assembly=StabilityMatrix.Core\"\n             xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n             xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n             xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n             mc:Ignorable=\"d\" d:DesignWidth=\"700\" d:DesignHeight=\"700\"\n             x:DataType=\"dialogs:OneClickInstallViewModel\"\n             d:DataContext=\"{x:Static designData:DesignData.OneClickInstallViewModel}\"\n             x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.OneClickInstallDialog\">\n    \n    <Grid MaxHeight=\"900\" \n          RowDefinitions=\"Auto,Auto,Auto,Auto,Auto,*\" \n          ColumnDefinitions=\"*,Auto\">\n\n        <Grid.Transitions>\n            <Transitions>\n                <DoubleTransition Property=\"Width\"\n                                  Duration=\"00:00:00.25\"\n                                  Easing=\"0,0 0,1\" />\n                <DoubleTransition Property=\"Height\"\n                                  Duration=\"00:00:00.25\"\n                                  Easing=\"0,0 0,1\" />\n            </Transitions>\n        </Grid.Transitions>\n        \n        <ui:TeachingTip\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            Name=\"InferenceTeachingTip\"\n            MinWidth=\"100\"\n            Title=\"Use ComfyUI with Inference\"\n            Subtitle=\"A new built-in native Stable Diffusion experience, powered by ComfyUI\"\n            ActionButtonContent=\"{x:Static lang:Resources.Action_Install}\"\n            ActionButtonCommand=\"{Binding InstallComfyForInferenceCommand}\"\n            CloseButtonContent=\"{x:Static lang:Resources.Action_Close}\"\n            PreferredPlacement=\"RightTop\"\n            Margin=\"8,0,0,0\"\n            PlacementMargin=\"0,0,0,0\"\n            TailVisibility=\"Auto\">\n            <ui:TeachingTip.HeroContent>\n                <controls:BetterAdvancedImage\n                    RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n                    Source=\"https://cdn.lykos.ai/static/sc-inference-drag-load.gif\"/>\n            </ui:TeachingTip.HeroContent>\n        </ui:TeachingTip>\n        \n        <StackPanel Grid.Row=\"0\" Grid.Column=\"0\" Grid.ColumnSpan=\"2\">\n            <TextBlock Text=\"{Binding HeaderText, FallbackValue=Installing Stable Diffusion WebUI...}\"\n                       TextWrapping=\"Wrap\"\n                       Margin=\"8,0,8,0\"\n                       VerticalAlignment=\"Center\" HorizontalAlignment=\"Center\"\n                       FontSize=\"36\" FontWeight=\"Light\" />\n            <TextBlock Text=\"{Binding SubHeaderText, FallbackValue=Installing git...}\"\n                       TextWrapping=\"Wrap\" TextAlignment=\"Center\"\n                       VerticalAlignment=\"Top\" HorizontalAlignment=\"Center\"\n                       FontSize=\"16\" FontWeight=\"Light\" Margin=\"8\"/>\n            <Separator Margin=\"16, 4\" Background=\"#AAAAAA\"\n                       IsVisible=\"{Binding ShowInstallButton}\"/>\n        </StackPanel>\n        \n        <StackPanel Grid.Row=\"1\" Grid.Column=\"0\"\n                    IsVisible=\"{Binding ShowInstallButton}\">\n            <TextBlock Text=\"{Binding SelectedPackage.DisplayName}\"\n                       FontSize=\"24\"\n                       Margin=\"16, 16, 0, 4\"/>\n            <TextBlock Text=\"{Binding SelectedPackage.Blurb}\"\n                       TextWrapping=\"Wrap\"\n                       Margin=\"16, 0, 0, 4\"/>\n            <TextBlock Text=\"{Binding SelectedPackage.Disclaimer}\"\n                       Margin=\"16, 0, 0, 0\"\n                       TextWrapping=\"Wrap\"\n                       Foreground=\"OrangeRed\"\n                       IsVisible=\"{Binding SelectedPackage.Disclaimer, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"/>\n            <ItemsRepeater Margin=\"16, 0, 0, 0\"\n                           ItemsSource=\"{Binding SelectedPackage.AvailableTorchIndices}\">\n                <ItemsRepeater.Layout>\n                    <StackLayout Orientation=\"Horizontal\" />\n                </ItemsRepeater.Layout>\n                <ItemsRepeater.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type models:TorchIndex}\">\n                        <controls:Card\n                            Tag=\"{Binding }\"\n                            HorizontalAlignment=\"Left\"\n                            Padding=\"4\"\n                            Margin=\"0,8,8,0\"\n                            VerticalAlignment=\"Top\">\n\n                            <controls:Card.Styles>\n                                <Style Selector=\"controls|Card[Tag=Cuda]\">\n                                    <Setter Property=\"Background\" Value=\"{DynamicResource ThemeGreenColorTransparent}\" />\n                                    <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeGreenColorTransparent}\" />\n                                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                                    </Style>\n                                    <Setter Property=\"Content\">\n                                        <Template>\n                                            <TextBlock\n                                                FontWeight=\"Medium\"\n                                                HorizontalAlignment=\"Center\"\n                                                Text=\"NVIDIA\"\n                                                TextAlignment=\"Center\"\n                                                VerticalAlignment=\"Center\" />\n                                        </Template>\n                                    </Setter>\n                                </Style>\n                                <Style Selector=\"controls|Card[Tag=Rocm]\">\n                                    <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n                                    <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n                                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                                    </Style>\n                                    <Setter Property=\"Content\">\n                                        <Template>\n                                            <TextBlock\n                                                FontWeight=\"Medium\"\n                                                HorizontalAlignment=\"Center\"\n                                                Text=\"AMD (Linux)\"\n                                                TextAlignment=\"Center\"\n                                                VerticalAlignment=\"Center\"\n                                                ToolTip.Tip=\"For AMD GPUs that support ROCm on Linux\" />\n                                        </Template>\n                                    </Setter>\n                                </Style>\n                                <Style Selector=\"controls|Card[Tag=DirectMl]\">\n                                    <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                                    <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                                    <Setter Property=\"Content\">\n                                        <Template>\n                                            <TextBlock\n                                                FontWeight=\"Medium\"\n                                                HorizontalAlignment=\"Center\"\n                                                Text=\"DirectML\"\n                                                TextAlignment=\"Center\"\n                                                VerticalAlignment=\"Center\"\n                                                ToolTip.Tip=\"For any DirectX compatible GPU on Windows\" />\n                                        </Template>\n                                    </Setter>\n                                </Style>\n                                <Style Selector=\"controls|Card[Tag=Mps]\">\n                                    <Setter Property=\"Background\" Value=\"White\" />\n                                    <Setter Property=\"BorderBrush\" Value=\"White\" />\n                                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                                    </Style>\n                                    <Setter Property=\"Content\">\n                                        <Template>\n                                            <TextBlock\n                                                FontWeight=\"Medium\"\n                                                HorizontalAlignment=\"Center\"\n                                                Text=\"macOS\"\n                                                TextAlignment=\"Center\"\n                                                Foreground=\"Black\"\n                                                VerticalAlignment=\"Center\" />\n                                        </Template>\n                                    </Setter>\n                                </Style>\n                                <Style Selector=\"controls|Card[Tag=Zluda]\">\n                                    <Setter Property=\"Background\"\n                                            Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n                                    <Setter Property=\"BorderBrush\"\n                                            Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n                                    <Style\n                                        Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                        <Setter Property=\"Foreground\"\n                                                Value=\"{DynamicResource ButtonForeground}\" />\n                                    </Style>\n                                    <Setter Property=\"Content\">\n                                        <Template>\n                                            <TextBlock\n                                                FontWeight=\"Medium\"\n                                                HorizontalAlignment=\"Center\"\n                                                Text=\"ZLUDA\"\n                                                TextAlignment=\"Center\"\n                                                VerticalAlignment=\"Center\"\n                                                ToolTip.Tip=\"For AMD GPUs that support ZLUDA\" />\n                                        </Template>\n                                    </Setter>\n                                </Style>\n                                <Style Selector=\"controls|Card[Tag=Cpu]\">\n                                    <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueGreyColor}\" />\n                                    <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeBlueGreyColor}\" />\n                                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                                    </Style>\n                                    <Setter Property=\"Content\">\n                                        <Template>\n                                            <TextBlock\n                                                FontWeight=\"Medium\"\n                                                HorizontalAlignment=\"Center\"\n                                                Text=\"CPU\"\n                                                TextAlignment=\"Center\"\n                                                VerticalAlignment=\"Center\" />\n                                        </Template>\n                                    </Setter>\n                                </Style>\n                            </controls:Card.Styles>\n                        </controls:Card>\n                    </DataTemplate>\n                </ItemsRepeater.ItemTemplate>\n            </ItemsRepeater>\n            <controls:BetterAdvancedImage Source=\"{Binding SelectedPackage.PreviewImageUri}\"\n                                          HorizontalAlignment=\"Left\"\n                                          Height=\"300\"\n                                          Width=\"400\"\n                                          Stretch=\"UniformToFill\"\n                                          Margin=\"16, 8\"/>\n        </StackPanel>\n        \n        <TextBlock Grid.Row=\"2\" Grid.Column=\"0\"\n                   Text=\"{Binding SubSubHeaderText, FallbackValue=Installing git...}\"\n                   TextWrapping=\"Wrap\" TextAlignment=\"Center\"\n                   VerticalAlignment=\"Top\" HorizontalAlignment=\"Center\"\n                   FontSize=\"10\" Margin=\"8\"/>\n\n        <ProgressBar Maximum=\"100\" \n                     Grid.Row=\"3\" Grid.Column=\"0\" VerticalAlignment=\"Center\"\n                     Value=\"{Binding OneClickInstallProgress}\"\n                     IsVisible=\"{Binding IsProgressBarVisible}\"\n                     IsIndeterminate=\"{Binding IsIndeterminate}\"\n                     Margin=\"16\" >\n            <ProgressBar.Transitions>\n                <Transitions>\n                    <DoubleTransition Property=\"Value\" Duration=\"00:00:00.150\">\n                        <DoubleTransition.Easing>\n                            <SineEaseInOut/>\n                        </DoubleTransition.Easing>\n                    </DoubleTransition>\n                </Transitions>\n            </ProgressBar.Transitions>\n        </ProgressBar>\n        \n        <StackPanel Grid.Row=\"4\" Grid.Column=\"0\" Grid.ColumnSpan=\"2\" \n                    Orientation=\"Vertical\"\n                    IsVisible=\"{Binding ShowInstallButton}\"\n                    HorizontalAlignment=\"Center\">\n            <Button \n                    Content=\"{x:Static lang:Resources.Action_Install}\"\n                    Command=\"{Binding InstallCommand}\"\n                    FontSize=\"32\"\n                    HorizontalAlignment=\"Center\"\n                    Classes=\"success\"\n                    Margin=\"8\"\n                    Padding=\"16, 8, 16, 8\" />\n            <Button Command=\"{Binding ToggleAdvancedModeCommand}\"\n                    Content=\"{x:Static lang:Resources.Label_SkipSetup}\"\n                    FontSize=\"16\"\n                    HorizontalAlignment=\"Center\"\n                    Padding=\"16, 8, 16, 8\"/>\n        </StackPanel>\n        \n        <CheckBox Grid.Column=\"1\"\n                  Grid.Row=\"2\"\n                  VerticalAlignment=\"Bottom\"\n                  Content=\"Show All Packages\"\n                  IsVisible=\"{Binding ShowInstallButton}\"\n                  HorizontalAlignment=\"Center\"\n                  IsChecked=\"{Binding ShowIncompatiblePackages}\"\n                  ToolTip.Tip=\"Enabling &quot;Show All Packages&quot; will include packages that may not be compatible with your system and may run very slowly as a result.\"/>\n\n        \n        <ScrollViewer Grid.Column=\"1\" Grid.Row=\"1\"\n                      MaxHeight=\"400\"\n                      VerticalScrollBarVisibility=\"Visible\"\n                      Margin=\"0, 8\">\n            <ListBox \n                Name=\"PackagesListBox\"\n                Margin=\"8,0\"\n                IsVisible=\"{Binding ShowInstallButton}\"\n                ItemsSource=\"{Binding AllPackages}\"\n                SelectedItem=\"{Binding SelectedPackage}\">\n                <ListBox.Template>\n                    <ControlTemplate>\n                        <ItemsPresenter/>\n                    </ControlTemplate>\n                </ListBox.Template>\n                <ListBox.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type packages:BasePackage}\">\n                        <Grid ColumnDefinitions=\"*,Auto\">\n                            <StackPanel Margin=\"8\">\n                                <TextBlock Text=\"{Binding DisplayName}\"/>\n                                <TextBlock Text=\"{Binding ByAuthor}\"/>\n                            </StackPanel>\n                            <ui:InfoBadge\n                                IsVisible=\"{Binding IsInferenceCompatible}\"\n                                Grid.Column=\"1\"\n                                Margin=\"8,0,0,0\"\n                                Classes=\"Success Dot\" \n                                VerticalAlignment=\"Center\" />\n                        </Grid>\n                    </DataTemplate>\n                </ListBox.ItemTemplate>\n            </ListBox>\n        </ScrollViewer>\n    </Grid>\n    \n</UserControl>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/OneClickInstallDialog.axaml.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Interactivity;\nusing FluentAvalonia.UI.Controls;\nusing StabilityMatrix.Core.Models.Packages;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\npublic partial class OneClickInstallDialog : UserControl\n{\n    public OneClickInstallDialog()\n    {\n        InitializeComponent();\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n    }\n\n    /// <inheritdoc />\n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        base.OnLoaded(e);\n\n        var teachingTip =\n            this.FindControl<TeachingTip>(\"InferenceTeachingTip\")\n            ?? throw new InvalidOperationException(\"TeachingTip not found\");\n\n        teachingTip.ActionButtonClick += (_, _) =>\n        {\n            teachingTip.IsOpen = false;\n        };\n\n        // Find ComfyUI listbox item\n        var listBox = this.FindControl<ListBox>(\"PackagesListBox\");\n\n        // Find ComfyUI listbox item\n        if (listBox?.Items.Cast<BasePackage>().FirstOrDefault(p => p is ComfyUI) is { } comfy)\n        {\n            var comfyItem = listBox.ContainerFromItem(comfy) as ListBoxItem;\n\n            // comfyItem!.IsSelected = true;\n\n            teachingTip.Target = comfyItem;\n            teachingTip.IsOpen = true;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/OpenArtWorkflowDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.OpenArtWorkflowDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:labs=\"clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n    d:DataContext=\"{x:Static designData:DesignData.OpenArtWorkflowViewModel}\"\n    d:DesignHeight=\"650\"\n    d:DesignWidth=\"600\"\n    x:DataType=\"dialogs:OpenArtWorkflowViewModel\"\n    mc:Ignorable=\"d\">\n    <Grid\n        Width=\"600\"\n        HorizontalAlignment=\"Stretch\"\n        ColumnDefinitions=\"*, 2*\"\n        RowDefinitions=\"Auto, Auto, Auto, Auto, Auto\">\n        <TextBlock\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            Margin=\"8,8,0,4\"\n            FontSize=\"20\"\n            TextAlignment=\"Left\"\n            ToolTip.Tip=\"{Binding Workflow.Name}\">\n            <Run Text=\"{Binding Workflow.Name}\" />\n            <Run Text=\"- by\" />\n            <Run Text=\"{Binding Workflow.Creator.Name}\" />\n        </TextBlock>\n\n        <vendorLabs:BetterAsyncImage\n            Grid.Row=\"2\"\n            Grid.Column=\"0\"\n            Height=\"300\"\n            Margin=\"8\"\n            CornerRadius=\"8\"\n            Source=\"{Binding Workflow.Thumbnails[0].Url}\"\n            Stretch=\"UniformToFill\" />\n\n        <controls:Card\n            Grid.Row=\"2\"\n            Grid.Column=\"1\"\n            Margin=\"8\"\n            VerticalAlignment=\"Top\">\n            <ScrollViewer MaxHeight=\"270\">\n                <TextBlock\n                    Margin=\"4\"\n                    Text=\"{Binding PrunedDescription}\"\n                    TextWrapping=\"Wrap\" />\n            </ScrollViewer>\n        </controls:Card>\n\n        <Expander\n            Grid.Row=\"3\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            Margin=\"8,8\"\n            ExpandDirection=\"Down\"\n            Header=\"{x:Static lang:Resources.Label_NodeDetails}\">\n            <ScrollViewer MaxHeight=\"225\">\n                <ItemsControl ItemsSource=\"{Binding CustomNodes}\">\n                    <ItemsControl.ItemsPanel>\n                        <ItemsPanelTemplate>\n                            <StackPanel Orientation=\"Vertical\" Spacing=\"4\" />\n                        </ItemsPanelTemplate>\n                    </ItemsControl.ItemsPanel>\n                    <ItemsControl.ItemTemplate>\n                        <DataTemplate DataType=\"{x:Type models:OpenArtCustomNode}\">\n                            <StackPanel Orientation=\"Vertical\">\n                                <StackPanel Orientation=\"Horizontal\">\n                                    <TextBlock\n                                        FontSize=\"16\"\n                                        FontWeight=\"SemiBold\"\n                                        Text=\"{Binding Title}\" />\n                                    <avalonia:Icon\n                                        Margin=\"4\"\n                                        VerticalAlignment=\"Center\"\n                                        Foreground=\"Lime\"\n                                        IsVisible=\"{Binding IsInstalled}\"\n                                        Value=\"fa-solid fa-circle-check\" />\n                                </StackPanel>\n                                <ItemsControl Margin=\"0,4\" ItemsSource=\"{Binding Children}\">\n                                    <ItemsControl.ItemsPanel>\n                                        <ItemsPanelTemplate>\n                                            <StackPanel Orientation=\"Vertical\" Spacing=\"4\" />\n                                        </ItemsPanelTemplate>\n                                    </ItemsControl.ItemsPanel>\n                                    <ItemsControl.ItemTemplate>\n                                        <DataTemplate DataType=\"{x:Type system:String}\">\n                                            <StackPanel Orientation=\"Vertical\">\n                                                <TextBlock Margin=\"4,0,0,0\" Text=\"{Binding ., StringFormat={} - {0}}\" />\n                                            </StackPanel>\n                                        </DataTemplate>\n                                    </ItemsControl.ItemTemplate>\n                                </ItemsControl>\n                            </StackPanel>\n                        </DataTemplate>\n                    </ItemsControl.ItemTemplate>\n                </ItemsControl>\n            </ScrollViewer>\n        </Expander>\n\n        <Expander\n            Grid.Row=\"4\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            Margin=\"8\"\n            Header=\"Options\">\n            <StackPanel Spacing=\"4\">\n                <ui:SettingsExpanderItem Content=\"Install Required Nodes\">\n                    <ui:SettingsExpanderItem.Footer>\n                        <CheckBox IsChecked=\"{Binding InstallRequiredNodes}\"\n                                  IsEnabled=\"{Binding AvailablePackages.Count}\"/>\n                    </ui:SettingsExpanderItem.Footer>\n                </ui:SettingsExpanderItem>\n                <ui:SettingsExpanderItem Content=\"Target Package\">\n                    <ui:SettingsExpanderItem.Footer>\n                        <ComboBox \n                            DisplayMemberBinding=\"{Binding DisplayName}\"\n                            ItemsSource=\"{Binding AvailablePackages}\" \n                            SelectedItem=\"{Binding SelectedPackage}\"/>\n                    </ui:SettingsExpanderItem.Footer>\n                </ui:SettingsExpanderItem>\n            </StackPanel>\n        </Expander>\n\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/OpenArtWorkflowDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<OpenArtWorkflowDialog>]\npublic partial class OpenArtWorkflowDialog : UserControlBase\n{\n    public OpenArtWorkflowDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/OpenModelDbModelDetailsDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.OpenModelDbModelDetailsDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:fluent=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models.Api.OpenModelsDb;assembly=StabilityMatrix.Core\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    d:DataContext=\"{x:Static mocks:DesignData.OpenModelDbModelDetailsViewModel}\"\n    d:DesignHeight=\"800\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"vmDialogs:OpenModelDbModelDetailsViewModel\"\n    mc:Ignorable=\"d\">\n    <controls:UserControlBase.Resources>\n        <input:StandardUICommand x:Key=\"ImportCommand\" Command=\"{Binding ImportCommand}\" />\n    </controls:UserControlBase.Resources>\n\n    <controls:UserControlBase.Styles>\n        <Style Selector=\"vendorLabs|BetterAsyncImage\">\n            <Setter Property=\"ImageCache\" Value=\"{x:Static controls:ImageLoaders.OpenModelDbImageCache}\" />\n            <Setter Property=\"IsCacheEnabled\" Value=\"True\" />\n        </Style>\n    </controls:UserControlBase.Styles>\n\n    <Grid\n        Margin=\"8\"\n        ColumnDefinitions=\"*,Auto,*\"\n        RowDefinitions=\"Auto, Auto, Auto, Auto\">\n\n        <Carousel\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            ItemsSource=\"{Binding ImageUris}\"\n            Theme=\"{DynamicResource FullCarousel}\">\n            <Carousel.ItemTemplate>\n                <DataTemplate DataType=\"{x:Type system:Uri}\">\n                    <vendorLabs:BetterAsyncImage\n                        Height=\"300\"\n                        Margin=\"8\"\n                        HorizontalAlignment=\"Center\"\n                        VerticalAlignment=\"Top\"\n                        Source=\"{Binding}\"\n                        Stretch=\"UniformToFill\" />\n                </DataTemplate>\n            </Carousel.ItemTemplate>\n        </Carousel>\n\n        <!--<Border\n            Grid.Row=\"0\"\n            Grid.Column=\"1\"\n            MaxHeight=\"360\"\n            Background=\"#33000000\"\n            CornerRadius=\"4\" />-->\n\n        <Border\n            Grid.Row=\"0\"\n            Grid.Column=\"2\"\n            MaxHeight=\"360\"\n            Margin=\"8,0\"\n            Background=\"#33000000\"\n            CornerRadius=\"4\" />\n\n        <ListBox\n            Grid.Row=\"0\"\n            Grid.Column=\"2\"\n            MaxHeight=\"360\"\n            Margin=\"8,8,8,0\"\n            VerticalAlignment=\"Top\"\n            Background=\"#AA000000\"\n            ItemsSource=\"{Binding Resources}\"\n            SelectedItem=\"{Binding SelectedResource}\"\n            SelectionMode=\"AlwaysSelected\">\n            <ListBox.Template>\n                <ControlTemplate>\n                    <ScrollViewer>\n                        <ItemsPresenter Margin=\"8\" />\n                    </ScrollViewer>\n                </ControlTemplate>\n            </ListBox.Template>\n            <ListBox.ItemTemplate>\n                <DataTemplate DataType=\"{x:Type vmDialogs:OpenModelDbModelDetailsViewModel+ModelResourceViewModel}\">\n                    <Grid\n                        Margin=\"0,4,0,4\"\n                        ColumnDefinitions=\"Auto, *\"\n                        RowDefinitions=\"Auto, Auto\">\n                        <Grid.ContextFlyout>\n                            <ui:FAMenuFlyout>\n                                <ui:MenuFlyoutItem\n                                    Command=\"{StaticResource ImportCommand}\"\n                                    CommandParameter=\"{Binding Resource}\"\n                                    IconSource=\"Download\"\n                                    IsVisible=\"{Binding IsInstalled}\"\n                                    Text=\"{x:Static lang:Resources.Action_Import}\" />\n                            </ui:FAMenuFlyout>\n                        </Grid.ContextFlyout>\n                        <fluent:SymbolIcon\n                            Grid.Row=\"0\"\n                            Grid.RowSpan=\"2\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,0,8,0\"\n                            VerticalAlignment=\"Center\"\n                            Foreground=\"Lime\"\n                            IsVisible=\"{Binding IsInstalled}\"\n                            Symbol=\"Checkmark\" />\n                        <TextBlock\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,4,0,4\"\n                            Text=\"{Binding DisplayName}\"\n                            TextWrapping=\"Wrap\" />\n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,4,0,4\"\n                            FontSize=\"11\"\n                            Text=\"{Binding Resource.Size, Converter={x:Static converters:FileSizeConverters.HumanizeFileSizeConverter}}\"\n                            TextWrapping=\"Wrap\" />\n                    </Grid>\n                </DataTemplate>\n            </ListBox.ItemTemplate>\n        </ListBox>\n\n        <Expander\n            Grid.Row=\"1\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"3\"\n            Margin=\"8,8\"\n            ExpandDirection=\"Down\"\n            Header=\"{x:Static lang:Resources.Label_ModelDescription}\"\n            IsExpanded=\"True\">\n            <ScrollViewer MaxHeight=\"300\">\n                <controls:MarkdownViewer Text=\"{Binding Model.Description}\" />\n            </ScrollViewer>\n        </Expander>\n\n        <StackPanel\n            Grid.Row=\"2\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"3\"\n            Margin=\"0,8,0,12\"\n            HorizontalAlignment=\"Center\"\n            IsVisible=\"{Binding !SelectedResource.IsInstalled}\"\n            Orientation=\"Vertical\">\n            <TextBlock\n                Margin=\"0,4\"\n                HorizontalAlignment=\"Center\"\n                Text=\"{x:Static lang:Resources.Label_SelectDownloadLocation}\"\n                TextAlignment=\"Center\" />\n            <ComboBox\n                MinWidth=\"300\"\n                HorizontalAlignment=\"Center\"\n                ItemsSource=\"{Binding AvailableInstallLocations}\"\n                SelectedItem=\"{Binding SelectedInstallLocation}\" />\n            <StackPanel\n                Margin=\"0,4\"\n                HorizontalAlignment=\"Center\"\n                IsVisible=\"{Binding IsCustomSelected}\"\n                Orientation=\"Horizontal\">\n                <TextBox MinWidth=\"260\" Text=\"{Binding CustomInstallLocation}\" />\n                <Button\n                    Margin=\"4,0,0,0\"\n                    VerticalAlignment=\"Stretch\"\n                    Command=\"{Binding SelectCustomFolder}\">\n                    <ui:SymbolIcon Symbol=\"OpenFolder\" />\n                </Button>\n            </StackPanel>\n            <TextBlock\n                Foreground=\"{DynamicResource ThemeRedColor}\"\n                IsVisible=\"{Binding ShowEmptyPathWarning}\"\n                Text=\"{x:Static lang:Resources.Error_PleaseSelectDownloadLocation}\"\n                TextAlignment=\"Center\" />\n        </StackPanel>\n\n        <StackPanel\n            Grid.Row=\"3\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"3\"\n            Margin=\"0,8,0,8\"\n            HorizontalAlignment=\"Center\"\n            Orientation=\"Horizontal\">\n            <Button\n                Margin=\"8,0\"\n                HorizontalAlignment=\"Center\"\n                Command=\"{Binding OnCloseButtonClick}\"\n                Content=\"{x:Static lang:Resources.Action_Cancel}\" />\n\n            <Button\n                Margin=\"8,0\"\n                Classes=\"accent\"\n                Command=\"{Binding ImportCommand}\"\n                CommandParameter=\"{Binding SelectedResource}\"\n                Content=\"{x:Static lang:Resources.Action_Import}\"\n                IsVisible=\"{Binding !SelectedResource.IsInstalled, TargetNullValue=True, FallbackValue=True}\" />\n\n            <Button\n                Margin=\"8,0\"\n                Classes=\"danger\"\n                Command=\"{Binding DeleteModelCommand}\"\n                Content=\"{x:Static lang:Resources.Action_Delete}\"\n                IsVisible=\"{Binding SelectedResource.IsInstalled, TargetNullValue=False, FallbackValue=False}\" />\n        </StackPanel>\n\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/OpenModelDbModelDetailsDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<OpenModelDbModelDetailsDialog>]\npublic partial class OpenModelDbModelDetailsDialog : UserControlBase\n{\n    public OpenModelDbModelDetailsDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/PackageImportDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.PackageImportDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:python=\"clr-namespace:StabilityMatrix.Core.Python;assembly=StabilityMatrix.Core\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    d:DataContext=\"{x:Static mocks:DesignData.PackageImportViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"vmDialogs:PackageImportViewModel\"\n    mc:Ignorable=\"d\">\n    <StackPanel Margin=\"8\" Spacing=\"8\">\n        <ui:SettingsExpander Header=\"{x:Static lang:Resources.Label_PackageType}\">\n            <ui:SettingsExpander.Footer>\n                <ui:FAComboBox\n                    HorizontalAlignment=\"Stretch\"\n                    DisplayMemberBinding=\"{Binding DisplayName}\"\n                    IsEnabled=\"{Binding CanSelectBasePackage}\"\n                    ItemsSource=\"{Binding AvailablePackages}\"\n                    SelectedItem=\"{Binding SelectedBasePackage}\" />\n            </ui:SettingsExpander.Footer>\n        </ui:SettingsExpander>\n\n        <ui:SettingsExpander Header=\"{x:Static lang:Resources.Label_VersionType}\" IsVisible=\"{Binding IsReleaseModeAvailable}\">\n            <ui:SettingsExpander.Footer>\n                <ToggleSwitch\n                    IsChecked=\"{Binding IsReleaseMode}\"\n                    IsEnabled=\"{Binding IsReleaseModeAvailable}\"\n                    OffContent=\"{x:Static lang:Resources.Label_Branches}\"\n                    OnContent=\"{x:Static lang:Resources.Label_Releases}\" />\n            </ui:SettingsExpander.Footer>\n        </ui:SettingsExpander>\n\n        <ui:SettingsExpander Header=\"{x:Static lang:Resources.Label_Version}\">\n            <ui:SettingsExpander.Footer>\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"8\">\n                    <ui:FAComboBox\n                        DisplayMemberBinding=\"{Binding TagName}\"\n                        ItemsSource=\"{Binding AvailableVersions}\"\n                        SelectedItem=\"{Binding SelectedVersion}\" />\n                    <ui:FAComboBox\n                        DisplayMemberBinding=\"{Binding Sha}\"\n                        IsVisible=\"{Binding !IsReleaseMode}\"\n                        ItemsSource=\"{Binding AvailableCommits}\"\n                        SelectedItem=\"{Binding SelectedCommit}\" />\n                </StackPanel>\n            </ui:SettingsExpander.Footer>\n        </ui:SettingsExpander>\n        <ui:SettingsExpander Header=\"{x:Static lang:Resources.Label_Commit}\" IsVisible=\"{Binding ShowCustomCommitSha}\">\n            <ui:SettingsExpander.Footer>\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"8\">\n                    <TextBox MinWidth=\"250\" Text=\"{Binding CustomCommitSha, Mode=TwoWay}\" />\n                </StackPanel>\n            </ui:SettingsExpander.Footer>\n        </ui:SettingsExpander>\n\n        <ui:SettingsExpander Header=\"Python Version\" IsVisible=\"{Binding ShowPythonVersionSelection}\">\n            <ui:SettingsExpander.Footer>\n                <controls:BetterComboBox\n                    HorizontalAlignment=\"Stretch\"\n                    ItemsSource=\"{Binding AvailablePythonVersions}\"\n                    SelectedItem=\"{Binding SelectedPythonVersion}\">\n                    <controls:BetterComboBox.ItemTemplate>\n                        <DataTemplate DataType=\"{x:Type python:UvPythonInfo}\">\n                            <Grid ColumnDefinitions=\"*, Auto\" RowDefinitions=\"Auto, Auto\">\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"0\"\n                                    Margin=\"8,4,0,4\"\n                                    FontSize=\"18\"\n                                    FontWeight=\"Light\"\n                                    Text=\"{Binding Version}\" />\n                                <avalonia:Icon\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"1\"\n                                    Foreground=\"{DynamicResource ThemeGreenColor}\"\n                                    IsVisible=\"{Binding IsInstalled}\"\n                                    Value=\"fa-solid fa-check\" />\n                                <TextBlock\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"2\"\n                                    Margin=\"8,4,0,4\"\n                                    FontSize=\"12\"\n                                    Foreground=\"{DynamicResource TextControlForegroundDisabled}\"\n                                    Text=\"{Binding Key}\" />\n                            </Grid>\n                        </DataTemplate>\n                    </controls:BetterComboBox.ItemTemplate>\n                    <controls:BetterComboBox.SelectionBoxItemTemplate>\n                        <DataTemplate DataType=\"{x:Type python:UvPythonInfo}\">\n                            <TextBlock Text=\"{Binding Version}\" />\n                        </DataTemplate>\n                    </controls:BetterComboBox.SelectionBoxItemTemplate>\n                </controls:BetterComboBox>\n            </ui:SettingsExpander.Footer>\n        </ui:SettingsExpander>\n    </StackPanel>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/PackageImportDialog.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<PackageImportDialog>]\npublic partial class PackageImportDialog : UserControlBase\n{\n    public PackageImportDialog()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.PackageModificationDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:avaloniaEdit=\"https://github.com/avaloniaui/avaloniaedit\"\n    xmlns:base=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Base\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:controls1=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    d:DataContext=\"{x:Static mocks:DesignData.PackageInstallProgressItemViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"base:ContentDialogProgressViewModelBase\"\n    mc:Ignorable=\"d\">\n    <Grid Margin=\"8\" RowDefinitions=\"Auto, Auto, Auto, *, Auto, Auto\">\n\n        <Button\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Top\"\n            avalonia:Attached.Icon=\"fa-solid fa-xmark\"\n            Classes=\"transparent-full\"\n            Command=\"{Binding OnCloseButtonClick}\"\n            FontSize=\"20\"\n            IsVisible=\"{Binding !HideCloseButton}\"\n            ToolTip.Tip=\"{x:Static lang:Resources.Action_Close}\" />\n\n        <TextBlock\n            Grid.Row=\"0\"\n            Margin=\"4\"\n            HorizontalAlignment=\"Stretch\"\n            FontSize=\"24\"\n            FontWeight=\"Light\"\n            Text=\"{Binding Text, FallbackValue='Package Modification'}\"\n            TextAlignment=\"Center\"\n            TextWrapping=\"WrapWithOverflow\" />\n\n        <ProgressBar\n            Grid.Row=\"1\"\n            Margin=\"32,8,32,8\"\n            IsIndeterminate=\"{Binding IsIndeterminate, FallbackValue=True}\"\n            Value=\"{Binding Value}\" />\n\n        <controls1:CommandBar\n            Grid.Row=\"2\"\n            Margin=\"8,0,0,0\"\n            HorizontalAlignment=\"Left\"\n            DefaultLabelPosition=\"Right\">\n            <controls1:CommandBar.PrimaryCommands>\n                <controls1:CommandBarToggleButton\n                    Margin=\"8,0,0,0\"\n                    VerticalAlignment=\"Center\"\n                    IsChecked=\"{Binding AutoScrollToBottom}\"\n                    IsVisible=\"{Binding !HideCloseButton}\"\n                    Label=\"{x:Static lang:Resources.Label_ToggleAutoScrolling}\"\n                    ToolTip.Tip=\"{x:Static lang:Resources.Label_AutoScrollToEnd}\">\n                    <controls1:CommandBarToggleButton.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-solid fa-arrow-down-wide-short\" />\n                    </controls1:CommandBarToggleButton.IconSource>\n                </controls1:CommandBarToggleButton>\n\n                <controls1:CommandBarToggleButton\n                    Margin=\"8,0,0,0\"\n                    VerticalAlignment=\"Center\"\n                    IsChecked=\"{Binding CloseWhenFinished}\"\n                    Label=\"{x:Static lang:Resources.Label_CloseDialogWhenFinished}\"\n                    ToolTip.Tip=\"{x:Static lang:Resources.Label_CloseDialogWhenFinished}\">\n                    <controls1:CommandBarToggleButton.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-solid fa-square-xmark\" />\n                    </controls1:CommandBarToggleButton.IconSource>\n                </controls1:CommandBarToggleButton>\n\n            </controls1:CommandBar.PrimaryCommands>\n        </controls1:CommandBar>\n\n        <avaloniaEdit:TextEditor\n            x:Name=\"Console\"\n            Grid.Row=\"3\"\n            MaxHeight=\"400\"\n            Margin=\"8\"\n            DataContext=\"{Binding Console}\"\n            Document=\"{Binding Document}\"\n            FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n            IsReadOnly=\"True\"\n            LineNumbersForeground=\"DarkSlateGray\"\n            ShowLineNumbers=\"True\"\n            VerticalScrollBarVisibility=\"Auto\"\n            WordWrap=\"True\">\n            <avaloniaEdit:TextEditor.ContextFlyout>\n                <MenuFlyout>\n                    <MenuItem\n                        Command=\"{Binding CopySelectionCommand}\"\n                        CommandParameter=\"{Binding ., ElementName=Console}\"\n                        Header=\"{x:Static lang:Resources.Action_Copy}\">\n                        <MenuItem.Icon>\n                            <avalonia:Icon Value=\"fa-solid fa-copy\" />\n                        </MenuItem.Icon>\n                    </MenuItem>\n                    <MenuItem\n                        Command=\"{Binding SelectAllCommand}\"\n                        CommandParameter=\"{Binding ., ElementName=Console}\"\n                        Header=\"{x:Static lang:Resources.Action_SelectAll}\"\n                        IsEnabled=\"{Binding CanSelectAll, ElementName=Console}\">\n                        <MenuItem.Icon>\n                            <avalonia:Icon Value=\"fa-solid fa-table-cells\" />\n                        </MenuItem.Icon>\n                    </MenuItem>\n                    <MenuItem\n                        Command=\"{Binding SearchWithGoogleCommand}\"\n                        CommandParameter=\"{Binding ., ElementName=Console}\"\n                        Header=\"Search with Google\">\n                        <MenuItem.Icon>\n                            <avalonia:Icon Value=\"fa-brands fa-google\" />\n                        </MenuItem.Icon>\n                    </MenuItem>\n                    <MenuItem\n                        Command=\"{Binding SearchWithChatGptCommand}\"\n                        CommandParameter=\"{Binding ., ElementName=Console}\"\n                        Header=\"Search with ChatGPT\"\n                        Icon=\"{StaticResource OpenAiIcon}\" />\n                </MenuFlyout>\n            </avaloniaEdit:TextEditor.ContextFlyout>\n        </avaloniaEdit:TextEditor>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml.cs",
    "content": "﻿using System;\nusing Avalonia.Controls;\nusing Avalonia.Media;\nusing Avalonia.Threading;\nusing AvaloniaEdit;\nusing AvaloniaEdit.TextMate;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Core.Helper;\nusing TextMateSharp.Grammars;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\npublic partial class PackageModificationDialog : UserControlBase\n{\n    public PackageModificationDialog()\n    {\n        InitializeComponent();\n\n        var editor = this.FindControl<TextEditor>(\"Console\");\n        if (editor is not null)\n        {\n            var options = new RegistryOptions(ThemeName.DarkPlus);\n\n            // Config hyperlinks\n            editor.TextArea.Options.EnableHyperlinks = true;\n            editor.TextArea.Options.RequireControlModifierForHyperlinkClick = false;\n            editor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Coral;\n\n            var textMate = editor.InstallTextMate(options);\n            var scope = options.GetScopeByLanguageId(\"log\");\n\n            if (scope is null)\n                throw new InvalidOperationException(\"Scope is null\");\n\n            textMate.SetGrammar(scope);\n            textMate.SetTheme(options.LoadTheme(ThemeName.DarkPlus));\n\n            editor.Options.ShowBoxForControlCharacters = false;\n        }\n\n        EventManager.Instance.ScrollToBottomRequested += (_, _) =>\n        {\n            Dispatcher\n                .UIThread\n                .Invoke(() =>\n                {\n                    var editor = this.FindControl<TextEditor>(\"Console\");\n                    if (editor?.Document == null)\n                        return;\n                    var line = Math.Max(editor.Document.LineCount - 1, 1);\n                    editor.ScrollToLine(line);\n                });\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/PropertyGridDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.PropertyGridDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:componentModel=\"clr-namespace:System.ComponentModel;assembly=System.ObjectModel\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    d:DataContext=\"{x:Static mocks:DesignData.PropertyGridViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"600\"\n    x:DataType=\"vmDialogs:PropertyGridViewModel\"\n    mc:Ignorable=\"d\">\n    <Grid>\n        <ItemsControl x:Name=\"MainItemsControl\" ItemsSource=\"{Binding SelectedObjectItemsSource, Mode=OneWay}\">\n            <ItemsControl.ItemTemplate>\n                <DataTemplate DataType=\"componentModel:INotifyPropertyChanged\">\n                    <controls:BetterPropertyGrid\n                        AllowFilter=\"False\"\n                        AllowQuickFilter=\"False\"\n                        DataContext=\"{Binding Mode=OneWay}\"\n                        ExcludedCategories=\"{Binding $parent[ItemsControl].((vmDialogs:PropertyGridViewModel)DataContext).ExcludeCategories, Mode=OneTime, FallbackValue=''}\"\n                        IncludedCategories=\"{Binding $parent[ItemsControl].((vmDialogs:PropertyGridViewModel)DataContext).IncludeCategories, Mode=OneTime, FallbackValue=''}\"\n                        ShowStyle=\"{Binding $parent[ItemsControl].((vmDialogs:PropertyGridViewModel)DataContext).ShowStyle, Mode=OneTime, FallbackValue=Alphabetic}\"\n                        ShowTitle=\"False\" />\n                </DataTemplate>\n            </ItemsControl.ItemTemplate>\n        </ItemsControl>\n\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/PropertyGridDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<PropertyGridDialog>]\npublic partial class PropertyGridDialog : UserControlBase\n{\n    public PropertyGridDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/PythonPackageSpecifiersDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.PythonPackageSpecifiersDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:e=\"clr-namespace:StabilityMatrix.Avalonia.MarkupExtensions\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:python=\"clr-namespace:StabilityMatrix.Core.Python;assembly=StabilityMatrix.Core\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    d:DataContext=\"{x:Static mocks:DesignData.PythonPackageSpecifiersViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"dialogs:PythonPackageSpecifiersViewModel\"\n    Focusable=\"True\"\n    mc:Ignorable=\"d\">\n\n    <controls:UserControlBase.Styles>\n        <Style Selector=\"DataGridRow:not(:selected) /template/ Rectangle#BackgroundRectangle\">\n            <Setter Property=\"Fill\" Value=\"{DynamicResource ComboBoxItemBackgroundSelected}\" />\n        </Style>\n        <Style Selector=\"DataGridRow:selected /template/ Rectangle#BackgroundRectangle\">\n            <Setter Property=\"Fill\" Value=\"{DynamicResource ComboBoxItemBackgroundPointerOver}\" />\n        </Style>\n    </controls:UserControlBase.Styles>\n\n    <controls:UserControlBase.Resources>\n        <DataTemplate x:Key=\"DataGridCellDividerTemplate\">\n            <Rectangle\n                HorizontalAlignment=\"Stretch\"\n                VerticalAlignment=\"Stretch\"\n                Fill=\"#08ffffff\" />\n        </DataTemplate>\n    </controls:UserControlBase.Resources>\n\n    <Grid\n        MinHeight=\"300\"\n        Margin=\"8\"\n        RowDefinitions=\"Auto,Auto,*\">\n        <StackPanel\n            HorizontalAlignment=\"Stretch\"\n            Orientation=\"Vertical\"\n            Spacing=\"8\">\n            <!--  Title  -->\n            <TextBlock\n                Margin=\"16,16,16,8\"\n                Theme=\"{DynamicResource TitleTextBlockStyle}\"\n                IsVisible=\"{Binding Title, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                Text=\"{Binding Title}\"\n                TextWrapping=\"Wrap\" />\n            <!--  Description  -->\n            <ui:InfoBar\n                Margin=\"8,0\"\n                Severity=\"Informational\"\n                Title=\"{Binding Description}\"\n                IsOpen=\"{Binding Description, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                IsClosable=\"False\">\n                <ui:InfoBar.ActionButton>\n                    <controls:HyperlinkIconButton \n                        Content=\"{Binding HelpLinkLabel}\"\n                        NavigateUri=\"{Binding HelpLinkUri}\"/>\n                </ui:InfoBar.ActionButton>\n            </ui:InfoBar>\n        </StackPanel>\n\n        <Panel Grid.Row=\"1\">\n            <ui:CommandBar>\n                <ui:CommandBar.PrimaryCommands>\n                    <ui:CommandBarButton\n                        Width=\"45\"\n                        Height=\"50\"\n                        Command=\"{Binding AddRowCommand}\"\n                        IconSource=\"Add\"\n                        Label=\"{x:Static lang:Resources.Action_Save}\" />\n                    <ui:CommandBarButton\n                        Width=\"45\"\n                        Height=\"50\"\n                        Command=\"{Binding RemoveSelectedRowCommand}\"\n                        CommandParameter=\"{Binding #PipOverridesGrid.SelectedIndex}\"\n                        IconSource=\"Remove\"\n                        Label=\"{x:Static lang:Resources.Action_Remove}\">\n                        <ui:CommandBarButton.IsEnabled>\n                            <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                <Binding Path=\"!SpecifiersView.IsEmpty\" />\n                                <Binding\n                                    Converter=\"{x:Static ObjectConverters.IsNotNull}\"\n                                    ElementName=\"PipOverridesGrid\"\n                                    Path=\"SelectedItem\" />\n                            </MultiBinding>\n                        </ui:CommandBarButton.IsEnabled>\n                    </ui:CommandBarButton>\n                    <ui:CommandBarSeparator />\n                    <ui:CommandBarButton\n                        Width=\"45\"\n                        Height=\"50\"\n                        FontSize=\"5\"\n                        IconSource=\"Copy\"\n                        Label=\"Bold\" />\n                </ui:CommandBar.PrimaryCommands>\n            </ui:CommandBar>\n        </Panel>\n\n        <DataGrid\n            x:Name=\"PipOverridesGrid\"\n            Grid.Row=\"2\"\n            MinHeight=\"200\"\n            ItemsSource=\"{Binding SpecifiersView}\">\n            <DataGrid.Columns>\n                <DataGridTemplateColumn\n                    Width=\"5*\"\n                    Header=\"Action\">\n                    <DataTemplate x:DataType=\"models:PythonPackageSpecifiersItem\">\n                        <ComboBox\n                            HorizontalAlignment=\"Stretch\"\n                            ItemsSource=\"{e:EnumValues NoDefault=True,\n                                                       x:TypeArguments=python:PipPackageSpecifierOverrideAction}\"\n                            SelectedItem=\"{Binding Action}\" />\n                    </DataTemplate>\n                </DataGridTemplateColumn>\n                <DataGridTemplateColumn\n                    Width=\"1\"\n                    MinWidth=\"0\"\n                    CellTemplate=\"{StaticResource DataGridCellDividerTemplate}\" />\n                <DataGridTextColumn\n                    Width=\"5*\"\n                    x:DataType=\"models:PythonPackageSpecifiersItem\"\n                    Binding=\"{Binding Name}\"\n                    Header=\"{x:Static lang:Resources.Label_EnvVarsTable_Name}\" />\n                <DataGridTemplateColumn\n                    Width=\"1\"\n                    MinWidth=\"0\"\n                    CellTemplate=\"{StaticResource DataGridCellDividerTemplate}\" />\n                <DataGridTemplateColumn\n                    Width=\"5*\"\n                    Header=\"Constraint\">\n                    <DataTemplate x:DataType=\"models:PythonPackageSpecifiersItem\">\n                        <ComboBox\n                            HorizontalAlignment=\"Stretch\"\n                            ItemsSource=\"{x:Static python:PipPackageSpecifier.ConstraintOptions}\"\n                            SelectedItem=\"{Binding Constraint}\" />\n                    </DataTemplate>\n                </DataGridTemplateColumn>\n                <DataGridTemplateColumn\n                    Width=\"1\"\n                    MinWidth=\"0\"\n                    CellTemplate=\"{StaticResource DataGridCellDividerTemplate}\" />\n                <DataGridTextColumn\n                    Width=\"5*\"\n                    x:DataType=\"models:PythonPackageSpecifiersItem\"\n                    Binding=\"{Binding Version}\"\n                    Header=\"{x:Static lang:Resources.Label_Version}\" />\n                <!--<DataGridTemplateColumn\n                    Width=\"5*\"\n                    Header=\"{x:Static lang:Resources.Label_Version}\">\n                    <DataTemplate x:DataType=\"models:PythonPackageSpecifiersItem\">\n                        <TextBox\n                            HorizontalAlignment=\"Stretch\"\n                            Text=\"{Binding Version}\"\n                            />\n                    </DataTemplate>\n                </DataGridTemplateColumn>-->\n            </DataGrid.Columns>\n        </DataGrid>\n\n    </Grid>\n\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/PythonPackageSpecifiersDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<PythonPackageSpecifiersDialog>]\npublic partial class PythonPackageSpecifiersDialog : UserControlBase\n{\n    public PythonPackageSpecifiersDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/PythonPackagesDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.PythonPackagesDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    d:DataContext=\"{x:Static mocks:DesignData.PythonPackagesViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"vmDialogs:PythonPackagesViewModel\"\n    mc:Ignorable=\"d\">\n\n    <sg:SpacedGrid\n        MinHeight=\"300\"\n        Margin=\"8\"\n        ColumnDefinitions=\"Auto,Auto,*\"\n        RowDefinitions=\"Auto,Auto,Auto,*\">\n        <!--  Title  -->\n        <!--<TextBlock\n            Margin=\"16,16,16,0\"\n            FontSize=\"24\"\n            FontWeight=\"Bold\"\n            Text=\"{x:Static lang:Resources.Label_PythonPackages}\"\n            TextWrapping=\"Wrap\" />-->\n\n        <!--  Close button  -->\n        <!--<Button\n            Grid.Row=\"1\"\n            Grid.Column=\"2\"\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Top\">\n            <ui:SymbolIcon\n                Foreground=\"LightGreen\"\n                Symbol=\"\"\n                ToolTip.Tip=\"{x:Static lang:Resources.Label_ConnectedModel}\" />\n        </Button>-->\n\n        <ui:CommandBar Grid.Row=\"1\" Grid.Column=\"0\">\n            <ui:CommandBar.Styles>\n                <Style Selector=\"ui|CommandBarButton\">\n                    <Setter Property=\"Padding\" Value=\"4\" />\n                    <Setter Property=\"Width\" Value=\"35\" />\n                    <Setter Property=\"Height\" Value=\"45\" />\n                </Style>\n            </ui:CommandBar.Styles>\n            <ui:CommandBar.PrimaryCommands>\n                <ui:CommandBarButton\n                    Command=\"{Binding InstallPackageCommand}\"\n                    IconSource=\"Add\"\n                    Label=\"{x:Static lang:Resources.Action_AddPackage}\" />\n                <ui:CommandBarButton\n                    Command=\"{Binding UninstallSelectedPackageCommand}\"\n                    IconSource=\"Remove\"\n                    IsEnabled=\"{Binding SelectedPackage, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                    Label=\"{x:Static lang:Resources.Action_Uninstall}\" />\n                <ui:CommandBarSeparator />\n                <ui:CommandBarButton\n                    Command=\"{Binding RefreshBackgroundCommand}\"\n                    IconSource=\"Refresh\"\n                    IsEnabled=\"{Binding SelectedPackage, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                    Label=\"{x:Static lang:Resources.Action_Refresh}\" />\n            </ui:CommandBar.PrimaryCommands>\n        </ui:CommandBar>\n\n        <TextBox\n            Grid.Row=\"2\"\n            Grid.Column=\"0\"\n            Margin=\"0,0,0,8\"\n            Classes=\"search\"\n            Text=\"{Binding SearchQuery, Mode=TwoWay}\"\n            Watermark=\"{x:Static lang:Resources.Action_Search}\" />\n\n        <controls:Card\n            Grid.Row=\"3\"\n            Grid.Column=\"0\"\n            MinWidth=\"270\"\n            Padding=\"0\"\n            VerticalAlignment=\"Stretch\"\n            VerticalContentAlignment=\"Stretch\"\n            Classes=\"transparent\">\n            <ScrollViewer IsVisible=\"{Binding !IsLoading}\">\n                <ListBox\n                    ItemsSource=\"{Binding Packages}\"\n                    SelectedItem=\"{Binding SelectedPackage}\"\n                    SelectionMode=\"AlwaysSelected\">\n                    <ListBox.ItemTemplate>\n                        <DataTemplate DataType=\"vmDialogs:PythonPackagesItemViewModel\">\n                            <sg:SpacedGrid Margin=\"0,4\" ColumnDefinitions=\"Auto,Auto\">\n                                <TextBlock\n                                    VerticalAlignment=\"Center\"\n                                    Text=\"{Binding Package.Name}\"\n                                    Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                                <TextBlock\n                                    Grid.Column=\"1\"\n                                    VerticalAlignment=\"Center\"\n                                    FontSize=\"13\"\n                                    Foreground=\"{StaticResource ThemeLightGreyColor}\"\n                                    Text=\"{Binding Package.Version}\"\n                                    TextTrimming=\"CharacterEllipsis\" />\n                            </sg:SpacedGrid>\n                        </DataTemplate>\n                    </ListBox.ItemTemplate>\n                </ListBox>\n            </ScrollViewer>\n        </controls:Card>\n\n        <ProgressBar\n            Grid.Row=\"3\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"3\"\n            Margin=\"32,16\"\n            IsEnabled=\"{Binding IsLoading}\"\n            IsIndeterminate=\"True\"\n            IsVisible=\"{Binding IsLoading}\" />\n\n        <GridSplitter\n            Grid.Row=\"0\"\n            Grid.RowSpan=\"4\"\n            Grid.Column=\"1\"\n            Width=\"8\"\n            IsEnabled=\"False\" />\n\n        <TransitioningContentControl\n            Grid.Row=\"1\"\n            Grid.RowSpan=\"3\"\n            Grid.Column=\"2\"\n            Margin=\"8,0\"\n            HorizontalAlignment=\"Stretch\"\n            HorizontalContentAlignment=\"Stretch\"\n            Content=\"{Binding SelectedPackage}\">\n\n            <TransitioningContentControl.Resources>\n                <input:StandardUICommand x:Key=\"ModifySelectedPackageCommand\" Command=\"{Binding ModifySelectedPackageCommand}\" />\n            </TransitioningContentControl.Resources>\n\n            <TransitioningContentControl.ContentTemplate>\n                <DataTemplate DataType=\"{x:Type vmDialogs:PythonPackagesItemViewModel}\">\n                    <sg:SpacedGrid RowDefinitions=\"Auto,Auto,*\" RowSpacing=\"16\">\n                        <TextBlock Text=\"{Binding Package.Name}\" Theme=\"{DynamicResource TitleTextBlockStyle}\" />\n\n                        <StackPanel\n                            Grid.Row=\"1\"\n                            Orientation=\"Horizontal\"\n                            Spacing=\"4\">\n                            <ComboBox ItemsSource=\"{Binding AvailableVersions}\" SelectedItem=\"{Binding SelectedVersion}\" />\n\n                            <!--  Upgrade button  -->\n                            <Button\n                                Padding=\"5,5\"\n                                Command=\"{StaticResource ModifySelectedPackageCommand}\"\n                                CommandParameter=\"{Binding}\"\n                                IsVisible=\"{Binding CanUpgrade}\"\n                                ToolTip.Tip=\"{x:Static lang:Resources.Action_Upgrade}\">\n                                <fluentIcons:SymbolIcon\n                                    VerticalAlignment=\"Center\"\n                                    FontSize=\"18\"\n                                    Foreground=\"#3592c4\"\n                                    Symbol=\"ArrowCircleUpRight\" />\n                            </Button>\n\n                            <!--  Downgrade button  -->\n                            <Button\n                                Padding=\"5,5\"\n                                Command=\"{StaticResource ModifySelectedPackageCommand}\"\n                                CommandParameter=\"{Binding}\"\n                                IsVisible=\"{Binding CanDowngrade}\"\n                                ToolTip.Tip=\"{x:Static lang:Resources.Action_Downgrade}\">\n                                <fluentIcons:SymbolIcon\n                                    VerticalAlignment=\"Center\"\n                                    FontSize=\"18\"\n                                    Symbol=\"ArrowCircleDownRight\" />\n                            </Button>\n\n                        </StackPanel>\n\n\n                        <!--  Loaded content  -->\n                        <StackPanel\n                            Grid.Row=\"2\"\n                            MaxWidth=\"200\"\n                            HorizontalAlignment=\"Left\"\n                            IsVisible=\"{Binding !IsLoading}\">\n                            <TextBlock Text=\"{Binding PipShowResult.Author}\" TextWrapping=\"WrapWithOverflow\" />\n                            <TextBlock Text=\"{Binding PipShowResult.License}\" TextWrapping=\"WrapWithOverflow\" />\n                            <HyperlinkButton NavigateUri=\"{Binding PipShowResult.HomePage}\" />\n                            <TextBlock Text=\"{Binding PipShowResult.Summary}\" TextWrapping=\"WrapWithOverflow\" />\n                        </StackPanel>\n\n                        <!--  Loading indicator  -->\n                        <ProgressBar\n                            Grid.Row=\"2\"\n                            Margin=\"32,16\"\n                            IsEnabled=\"{Binding IsLoading}\"\n                            IsIndeterminate=\"True\"\n                            IsVisible=\"{Binding IsLoading}\" />\n                    </sg:SpacedGrid>\n                </DataTemplate>\n            </TransitioningContentControl.ContentTemplate>\n        </TransitioningContentControl>\n\n        <!--<Carousel\n            Grid.Row=\"2\"\n            Grid.Column=\"2\"\n                  ItemsSource=\"{Binding Packages}\"\n            AutoScrollToSelectedItem=\"True\"\n                  SelectedItem=\"{Binding SelectedPackage}\">\n            <Carousel.PageTransition>\n                <CompositePageTransition>\n                    <PageSlide Orientation=\"Vertical\" Duration=\"0.167\" >\n                        ~1~<PageSlide.SlideInEasing>\n                            <SineEaseIn />\n                        </PageSlide.SlideInEasing>\n                        <PageSlide.SlideOutEasing>\n                            <SineEaseOut />\n                        </PageSlide.SlideOutEasing>@1@\n                    </PageSlide>\n                    ~1~<CrossFade Duration=\"0.167\" />@1@\n                </CompositePageTransition>\n            </Carousel.PageTransition>\n            <Carousel.ItemTemplate>\n                <DataTemplate DataType=\"{x:Type python:PipPackageInfo}\">\n                    <sg:SpacedGrid>\n                        <TextBlock Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" Text=\"{Binding Name}\"/>\n                    </sg:SpacedGrid>\n                </DataTemplate>\n            </Carousel.ItemTemplate>\n        </Carousel>-->\n\n    </sg:SpacedGrid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/PythonPackagesDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<PythonPackagesDialog>]\npublic partial class PythonPackagesDialog : UserControlBase\n{\n    public PythonPackagesDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/RecommendedModelsDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.RecommendedModelsDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:asyncImageLoader=\"clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n    d:DataContext=\"{x:Static mocks:DesignData.RecommendedModelsViewModel}\"\n    d:DesignHeight=\"725\"\n    d:DesignWidth=\"700\"\n    x:DataType=\"dialogs:RecommendedModelsViewModel\"\n    mc:Ignorable=\"d\">\n    <controls:UserControlBase.Styles>\n        <Style Selector=\"Button#RootButton\">\n            <Style Selector=\"^ vendorLabs|BetterAsyncImage\">\n                <Setter Property=\"Transitions\">\n                    <Transitions>\n                        <TransformOperationsTransition Property=\"RenderTransform\" Duration=\"0:0:0.237\">\n                            <TransformOperationsTransition.Easing>\n                                <QuadraticEaseInOut />\n                            </TransformOperationsTransition.Easing>\n                        </TransformOperationsTransition>\n                    </Transitions>\n                </Setter>\n            </Style>\n\n            <Style Selector=\"^:pointerover\">\n                <Setter Property=\"Cursor\" Value=\"Hand\" />\n                <Style Selector=\"^ vendorLabs|BetterAsyncImage\">\n                    <Setter Property=\"CornerRadius\" Value=\"12\" />\n                    <Setter Property=\"RenderTransform\" Value=\"scale(1.03, 1.03)\" />\n                </Style>\n                <Style Selector=\"^ Border#ModelCardBottom\">\n                    <Setter Property=\"Background\" Value=\"#CC000000\" />\n                </Style>\n            </Style>\n\n            <Style Selector=\"^:not(:pointerover)\">\n                <Setter Property=\"Cursor\" Value=\"Arrow\" />\n                <Style Selector=\"^ vendorLabs|BetterAsyncImage\">\n                    <Setter Property=\"CornerRadius\" Value=\"8\" />\n                    <Setter Property=\"RenderTransform\" Value=\"scale(1, 1)\" />\n                </Style>\n                <Style Selector=\"^ Border#ModelCardBottom\">\n                    <Setter Property=\"Background\" Value=\"#99000000\" />\n                </Style>\n            </Style>\n        </Style>\n    </controls:UserControlBase.Styles>\n\n    <controls:UserControlBase.Resources>\n        <DataTemplate x:Key=\"PackageTemplate\" x:DataType=\"dialogs:RecommendedModelItemViewModel\">\n            <Button\n                Name=\"RootButton\"\n                Margin=\"4\"\n                Padding=\"-4\"\n                HorizontalAlignment=\"Stretch\"\n                HorizontalContentAlignment=\"Stretch\"\n                Classes=\"transparent-full\"\n                Command=\"{Binding ToggleSelectionCommand}\"\n                CornerRadius=\"8\">\n                <controls:Card Margin=\"0\" Padding=\"0\">\n\n                    <Grid RowDefinitions=\"Auto, *, Auto\">\n                        <CheckBox\n                            Margin=\"0,4,-8,0\"\n                            HorizontalAlignment=\"Right\"\n                            VerticalAlignment=\"Top\"\n                            CornerRadius=\"100\"\n                            IsChecked=\"{Binding IsSelected}\"\n                            ZIndex=\"1\">\n                            <CheckBox.RenderTransform>\n                                <ScaleTransform ScaleX=\"1.5\" ScaleY=\"1.5\" />\n                            </CheckBox.RenderTransform>\n                        </CheckBox>\n                        <vendorLabs:BetterAsyncImage\n                            Grid.Row=\"0\"\n                            Grid.RowSpan=\"3\"\n                            Width=\"300\"\n                            Height=\"300\"\n                            CornerRadius=\"8\"\n                            Source=\"{Binding ThumbnailUrl}\"\n                            Stretch=\"UniformToFill\" />\n                        <Border\n                            Name=\"ModelCardBottom\"\n                            Grid.Row=\"2\"\n                            Padding=\"8\"\n                            HorizontalAlignment=\"Stretch\"\n                            VerticalAlignment=\"Stretch\"\n                            Background=\"#DD000000\"\n                            CornerRadius=\"0,0,8,8\"\n                            ZIndex=\"1\">\n                            <StackPanel Orientation=\"Vertical\">\n                                <TextBlock\n                                    VerticalAlignment=\"Center\"\n                                    FontSize=\"14\"\n                                    Text=\"{Binding CivitModel.Name}\" />\n                                <TextBlock\n                                    VerticalAlignment=\"Center\"\n                                    FontSize=\"12\"\n                                    Text=\"{Binding Author}\" />\n                            </StackPanel>\n                        </Border>\n                    </Grid>\n                </controls:Card>\n            </Button>\n        </DataTemplate>\n    </controls:UserControlBase.Resources>\n    <StackPanel>\n        <TextBlock\n            Margin=\"8,8\"\n            FontSize=\"26\"\n            Text=\"{x:Static lang:Resources.Label_RecommendedModels}\"\n            TextAlignment=\"Left\" />\n\n        <TextBlock\n            Margin=\"8,0,0,8\"\n            Text=\"{x:Static lang:Resources.Label_RecommendedModelsSubText}\"\n            TextAlignment=\"Left\"\n            TextWrapping=\"WrapWithOverflow\" />\n\n        <controls:ProgressRing\n            Width=\"96\"\n            Height=\"96\"\n            Margin=\"8\"\n            IsIndeterminate=\"True\"\n            IsVisible=\"{Binding IsLoading}\" />\n\n        <ScrollViewer\n            Margin=\"4,8,4,8\"\n            HorizontalScrollBarVisibility=\"Disabled\"\n            IsVisible=\"{Binding !IsLoading}\"\n            VerticalScrollBarVisibility=\"Auto\">\n            <ItemsControl ItemTemplate=\"{StaticResource PackageTemplate}\" ItemsSource=\"{Binding RecommendedModels}\">\n                <ItemsControl.ItemsPanel>\n                    <ItemsPanelTemplate>\n                        <WrapPanel HorizontalAlignment=\"Center\" Orientation=\"Horizontal\" />\n                    </ItemsPanelTemplate>\n                </ItemsControl.ItemsPanel>\n            </ItemsControl>\n        </ScrollViewer>\n    </StackPanel>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/RecommendedModelsDialog.axaml.cs",
    "content": "﻿using StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\npublic partial class RecommendedModelsDialog : UserControlBase\n{\n    public RecommendedModelsDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/SafetensorMetadataDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.SafetensorMetadataDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    d:DataContext=\"{x:Static mocks:DesignData.SafetensorMetadataViewModel}\"\n    d:DesignHeight=\"550\"\n    d:DesignWidth=\"700\"\n    x:DataType=\"dialogs:SafetensorMetadataViewModel\"\n    mc:Ignorable=\"d\">\n    <Grid RowDefinitions=\"Auto,Auto,Auto\">\n        <TextBlock\n            Margin=\"8\"\n            HorizontalAlignment=\"Center\"\n            FontSize=\"24\"\n            FontWeight=\"SemiBold\"\n            Text=\"Safetensor Metadata\" />\n        <TextBlock\n            Grid.Row=\"1\"\n            Margin=\"8\"\n            HorizontalAlignment=\"Center\"\n            FontSize=\"16\"\n            FontWeight=\"SemiBold\"\n            Text=\"{Binding ModelName}\" />\n\n        <TextBlock\n            Grid.Row=\"2\"\n            Margin=\"10,20\"\n            HorizontalAlignment=\"Center\"\n            FontSize=\"16\"\n            FontStyle=\"Italic\"\n            IsVisible=\"{Binding Metadata, Converter={x:Static ObjectConverters.IsNull}}\"\n            Text=\"No Metadata\" />\n\n        <Grid\n            Grid.Row=\"2\"\n            IsVisible=\"{Binding Metadata, Converter={x:Static ObjectConverters.IsNotNull}}\"\n            RowDefinitions=\"Auto,Auto,Auto,Auto\">\n\n            <!--  List of tags  -->\n            <TextBlock\n                Grid.Row=\"0\"\n                Margin=\"8\"\n                HorizontalAlignment=\"Left\"\n                FontSize=\"16\"\n                FontWeight=\"SemiBold\"\n                IsVisible=\"{Binding Metadata.TagFrequency, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                Text=\"Trained Tags\" />\n\n            <ui:ItemsRepeater\n                Grid.Row=\"1\"\n                Margin=\"8\"\n                IsVisible=\"{Binding Metadata.TagFrequency, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                ItemsSource=\"{Binding Metadata.TagFrequency}\">\n                <ui:ItemsRepeater.Layout>\n                    <ui:FlowLayout\n                        MinColumnSpacing=\"4\"\n                        MinRowSpacing=\"4\"\n                        Orientation=\"Horizontal\" />\n                </ui:ItemsRepeater.Layout>\n                <ui:ItemsRepeater.ItemTemplate>\n                    <DataTemplate x:DataType=\"models:SafetensorMetadata+Tag\">\n                        <Button\n                            Command=\"{Binding $parent[ui:ItemsRepeater].((dialogs:SafetensorMetadataViewModel)DataContext).CopyTagToClipboardCommand}\"\n                            CommandParameter=\"{Binding Name}\"\n                            Cursor=\"Hand\">\n                            <StackPanel Orientation=\"Horizontal\">\n                                <TextBlock FontWeight=\"SemiBold\" Text=\"{Binding Name}\" />\n                                <TextBlock Margin=\"5,0,0,0\" Text=\"{Binding Frequency}\" />\n                            </StackPanel>\n                        </Button>\n                    </DataTemplate>\n                </ui:ItemsRepeater.ItemTemplate>\n            </ui:ItemsRepeater>\n\n            <!--  All other metadata  -->\n            <TextBlock\n                Grid.Row=\"2\"\n                Margin=\"8\"\n                FontSize=\"16\"\n                FontWeight=\"SemiBold\"\n                Text=\"Other Metadata\" />\n            <TextBlock\n                Grid.Row=\"3\"\n                Margin=\"8\"\n                FontSize=\"16\"\n                FontStyle=\"Italic\"\n                IsVisible=\"{Binding !Metadata.OtherMetadata.Count}\"\n                Text=\"No Other Metadata\" />\n            <ui:ItemsRepeater\n                Grid.Row=\"3\"\n                Margin=\"8\"\n                IsVisible=\"{Binding !!Metadata.OtherMetadata.Count}\"\n                ItemsSource=\"{Binding Metadata.OtherMetadata}\">\n                <ui:ItemsRepeater.ItemTemplate>\n                    <DataTemplate x:DataType=\"models:SafetensorMetadata+Metadata\">\n                        <StackPanel Margin=\"5\" Orientation=\"Vertical\">\n                            <TextBlock FontWeight=\"SemiBold\" Text=\"{Binding Name}\" />\n                            <TextBlock Text=\"{Binding Value}\" />\n                        </StackPanel>\n                    </DataTemplate>\n                </ui:ItemsRepeater.ItemTemplate>\n            </ui:ItemsRepeater>\n        </Grid>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/SafetensorMetadataDialog.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<SafetensorMetadataDialog>]\npublic partial class SafetensorMetadataDialog : UserControlBase\n{\n    public SafetensorMetadataDialog()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n                          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                          xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n                          xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n                          xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n                          xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n                          xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n                          xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n                          xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n                          d:DataContext=\"{x:Static mocks:DesignData.SelectDataDirectoryViewModel}\"\n                          x:DataType=\"dialogs:SelectDataDirectoryViewModel\"\n                          x:CompileBindings=\"True\"\n                          mc:Ignorable=\"d\" d:DesignWidth=\"800\" d:DesignHeight=\"550\"\n                          x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.SelectDataDirectoryDialog\">\n    \n    <Grid RowDefinitions=\"Auto,Auto,Auto,*,*\"\n          Margin=\"16,0,16,16\"\n          MinHeight=\"450\"\n          MaxWidth=\"700\" >\n        \n        <StackPanel Orientation=\"Vertical\">\n            <TextBlock Text=\"{x:Static lang:Resources.Text_WelcomeToStabilityMatrix}\"\n                       TextWrapping=\"Wrap\"\n                       Margin=\"8,0,8,0\"\n                       VerticalAlignment=\"Center\" HorizontalAlignment=\"Center\"\n                       FontSize=\"36\" FontWeight=\"Light\" />\n            <TextBlock Text=\"{x:Static lang:Resources.Label_PleaseSelectDataDirectory}\"\n                       TextWrapping=\"Wrap\"\n                       Margin=\"8,4,8,0\"\n                       VerticalAlignment=\"Center\" HorizontalAlignment=\"Center\"\n                       FontSize=\"20\" FontWeight=\"Light\" />\n            <TextBlock\n                Text=\"{x:Static lang:Resources.Label_DataDirectoryExplanation}\"\n                TextWrapping=\"Wrap\"\n                Foreground=\"LightGray\"\n                FontSize=\"14\"\n                FontWeight=\"Light\"\n                TextAlignment=\"Center\"\n                Margin=\"0,8,0,0\" />\n        </StackPanel>\n        \n\n        <StackPanel Grid.Row=\"1\" Margin=\"0,0,0,16\">\n            <Label\n                Content=\"{x:Static lang:Resources.Label_DataDirectory}\"\n                FontSize=\"13\"\n                Margin=\"0,16,0,0\" />\n            \n            <Grid ColumnDefinitions=\"*,Auto\">\n\n                <TextBox\n                    Height=\"36\"\n                    IsEnabled=\"{Binding !IsPortableMode}\"\n                    Margin=\"0,0,8,0\"\n                    Watermark=\"{Binding DefaultInstallLocation}\"\n                    Text=\"{Binding DataDirectory}\"\n                    VerticalAlignment=\"Stretch\" />\n\n                <Button\n                    Command=\"{Binding ShowFolderBrowserDialogCommand}\"\n                    Grid.Column=\"1\"\n                    Height=\"36\"\n                    HorizontalAlignment=\"Stretch\"\n                    IsEnabled=\"{Binding !IsPortableMode}\">\n                    <ui:SymbolIcon Symbol=\"OpenFolder\" />\n                </Button>\n            </Grid>\n\n            <TextBlock\n                Text=\"{x:Static lang:Resources.Label_FatWarning}\"\n                TextWrapping=\"Wrap\"\n                Foreground=\"Yellow\"\n                IsVisible=\"{Binding ShowFatWarning}\"\n                FontSize=\"14\"\n                Margin=\"0,8,0,0\" />\n            \n            <TextBlock\n                Text=\"{x:Static lang:Resources.Warning_PleaseExtractFirst}\"\n                TextWrapping=\"Wrap\"\n                TextAlignment=\"Center\"\n                Foreground=\"Red\"\n                FontSize=\"28\"\n                IsVisible=\"{Binding IsInTempFolder}\"\n                Margin=\"0,8,0,0\" />\n        </StackPanel>\n        \n        <StackPanel Grid.Row=\"2\" Orientation=\"Vertical\">\n            <CheckBox\n                Content=\"{x:Static lang:Resources.Label_PortableMode}\"\n                IsChecked=\"{Binding IsPortableMode, Mode=TwoWay}\"\n                Margin=\"0,8,0,0\" />\n\n            <ui:InfoBar\n                IsClosable=\"False\"\n                IsOpen=\"True\"\n                FontSize=\"13\"\n                Margin=\"0,8,0,0\"\n                Padding=\"16\"\n                Title=\"{x:Static lang:Resources.Label_PortableModeExplanation}\" />\n        </StackPanel>\n\n        <!--  Indicator of existing or new data directory  -->\n        <StackPanel\n            Grid.Row=\"3\"\n            HorizontalAlignment=\"Center\"\n            Margin=\"8,0,8,0\"\n            Orientation=\"Horizontal\"\n            IsVisible=\"{Binding IsStatusBadgeVisible}\">\n            <controls:RefreshBadge DataContext=\"{Binding ValidatorRefreshBadge}\" />\n            <TextBlock\n                FontSize=\"14\"\n                Text=\"{Binding DirectoryStatusText}\"\n                VerticalAlignment=\"Center\" />\n        </StackPanel>\n\n        <UniformGrid Grid.Row=\"4\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Bottom\"\n                     Margin=\"0,8,0,0\">\n            <Button\n                Content=\"{x:Static lang:Resources.Action_Cancel}\"\n                IsDefault=\"True\"\n                Command=\"{Binding OnCloseButtonClick}\"\n                FontSize=\"16\"\n                HorizontalAlignment=\"Center\"\n                Padding=\"16,8\" />\n            <Button\n                Content=\"{x:Static lang:Resources.Action_Continue}\"\n                IsDefault=\"True\"\n                Command=\"{Binding OnPrimaryButtonClick}\"\n                FontSize=\"16\"\n                Classes=\"success\"\n                HorizontalAlignment=\"Center\"\n                IsEnabled=\"{Binding IsDirectoryValid}\"\n                Padding=\"16,8\" />\n        </UniformGrid>\n        \n    </Grid>\n    \n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<SelectDataDirectoryDialog>]\npublic partial class SelectDataDirectoryDialog : UserControlBase\n{\n    public SelectDataDirectoryDialog()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml",
    "content": "﻿<UserControl\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.SelectModelVersionDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:controls1=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:fluentAvalonia=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    d:DataContext=\"{x:Static designData:DesignData.SelectModelVersionViewModel}\"\n    d:DesignHeight=\"850\"\n    d:DesignWidth=\"700\"\n    x:DataType=\"dialogs:SelectModelVersionViewModel\"\n    mc:Ignorable=\"d\">\n\n    <Grid\n        MinWidth=\"700\"\n        MinHeight=\"450\"\n        Margin=\"8\"\n        ColumnDefinitions=\"*,Auto,*\"\n        RowDefinitions=\"Auto, Auto, *, Auto\">\n\n        <Grid.Resources>\n            <input:StandardUICommand x:Key=\"ImportCommand\" Command=\"{Binding Import}\" />\n        </Grid.Resources>\n\n        <Grid\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            RowDefinitions=\"Auto, Auto, Auto\">\n            <Carousel\n                Grid.Row=\"0\"\n                ItemsSource=\"{Binding ImageUrls}\"\n                SelectedIndex=\"{Binding SelectedImageIndex}\">\n                <Carousel.PageTransition>\n                    <CompositePageTransition>\n                        <PageSlide Orientation=\"Horizontal\" Duration=\"0.167\">\n                            <PageSlide.SlideInEasing>\n                                <SineEaseIn />\n                            </PageSlide.SlideInEasing>\n                            <PageSlide.SlideOutEasing>\n                                <SineEaseOut />\n                            </PageSlide.SlideOutEasing>\n                        </PageSlide>\n                        <CrossFade Duration=\"0.167\" />\n                    </CompositePageTransition>\n                </Carousel.PageTransition>\n                <Carousel.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type models:ImageSource}\">\n                        <controls:BetterImage\n                            Height=\"300\"\n                            Margin=\"8\"\n                            HorizontalAlignment=\"Center\"\n                            VerticalAlignment=\"Top\"\n                            Source=\"{Binding BitmapAsync^}\"\n                            Stretch=\"UniformToFill\" />\n                    </DataTemplate>\n                </Carousel.ItemTemplate>\n            </Carousel>\n\n            <StackPanel\n                Grid.Row=\"1\"\n                Margin=\"0,0,0,8\"\n                HorizontalAlignment=\"Center\"\n                VerticalAlignment=\"Bottom\"\n                Orientation=\"Horizontal\">\n                <TextBlock Text=\"{Binding DisplayedPageNumber}\" />\n                <TextBlock Text=\"/\" />\n                <TextBlock Text=\"{Binding ImageUrls.Count}\" />\n            </StackPanel>\n\n            <StackPanel\n                Grid.Row=\"2\"\n                HorizontalAlignment=\"Center\"\n                VerticalAlignment=\"Bottom\"\n                Orientation=\"Horizontal\">\n                <Button\n                    Margin=\"0,0,8,0\"\n                    Command=\"{Binding PreviousImage}\"\n                    IsEnabled=\"{Binding CanGoToPreviousImage}\"\n                    ToolTip.Placement=\"Top\"\n                    ToolTip.Tip=\"{x:Static lang:Resources.Label_PreviousImage}\">\n                    <avalonia:Icon Value=\"fa-solid fa-caret-left\" />\n                </Button>\n                <Button\n                    Command=\"{Binding NextImage}\"\n                    IsEnabled=\"{Binding CanGoToNextImage}\"\n                    ToolTip.Placement=\"Top\"\n                    ToolTip.Tip=\"{x:Static lang:Resources.Label_NextImage}\">\n                    <avalonia:Icon Value=\"fa-solid fa-caret-right\" />\n                </Button>\n            </StackPanel>\n        </Grid>\n\n        <Border\n            Grid.Row=\"0\"\n            Grid.Column=\"1\"\n            MaxHeight=\"360\"\n            Background=\"#33000000\"\n            CornerRadius=\"4\" />\n        <ListBox\n            Grid.Row=\"0\"\n            Grid.Column=\"1\"\n            MaxHeight=\"360\"\n            Margin=\"0,8,0,8\"\n            VerticalAlignment=\"Top\"\n            ItemsSource=\"{Binding Versions}\"\n            SelectedItem=\"{Binding SelectedVersionViewModel}\">\n            <ListBox.ItemTemplate>\n                <DataTemplate DataType=\"{x:Type dialogs:ModelVersionViewModel}\">\n                    <Grid\n                        MinWidth=\"128\"\n                        Margin=\"0,4,0,4\"\n                        ColumnDefinitions=\"Auto, Auto\"\n                        RowDefinitions=\"Auto, Auto, Auto\">\n                        <fluentAvalonia:SymbolIcon\n                            Grid.Row=\"0\"\n                            Grid.RowSpan=\"2\"\n                            Grid.Column=\"0\"\n                            VerticalAlignment=\"Center\"\n                            Foreground=\"Lime\"\n                            IsVisible=\"{Binding IsInstalled}\"\n                            Symbol=\"Checkmark\" />\n                        <TextBlock\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"4,0\"\n                            Padding=\"4\"\n                            Text=\"{Binding ModelVersion.Name}\" />\n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Margin=\"6,0,0,0\"\n                            Padding=\"4,0,4,4\"\n                            FontSize=\"11\"\n                            Text=\"{Binding ModelVersion.BaseModel}\" />\n                        <TextBlock\n                            Grid.Row=\"2\"\n                            Grid.Column=\"1\"\n                            Margin=\"10,4\"\n                            Foreground=\"Orange\"\n                            IsVisible=\"{Binding ModelVersion.IsEarlyAccess}\">\n                            <avalonia:Icon\n                                Margin=\"0,4,2,0\"\n                                HorizontalAlignment=\"Left\"\n                                Value=\"fa-regular fa-credit-card\" />\n                            <Run FontSize=\"12\" Text=\"Early Access\" />\n                        </TextBlock>\n\n                    </Grid>\n                </DataTemplate>\n            </ListBox.ItemTemplate>\n            <ListBox.Template>\n                <ControlTemplate>\n                    <ScrollViewer>\n                        <ItemsPresenter Margin=\"8\" />\n                    </ScrollViewer>\n                </ControlTemplate>\n            </ListBox.Template>\n        </ListBox>\n\n        <Border\n            Grid.Row=\"0\"\n            Grid.Column=\"2\"\n            MaxHeight=\"360\"\n            Margin=\"8,0\"\n            Background=\"#33000000\"\n            CornerRadius=\"4\" />\n        <ListBox\n            Grid.Row=\"0\"\n            Grid.Column=\"2\"\n            MaxHeight=\"360\"\n            Margin=\"8,8,8,0\"\n            VerticalAlignment=\"Top\"\n            Background=\"#AA000000\"\n            ItemsSource=\"{Binding SelectedVersionViewModel}\"\n            SelectedItem=\"{Binding SelectedFile}\">\n            <ListBox.Template>\n                <ControlTemplate>\n                    <ScrollViewer>\n                        <ItemsPresenter Margin=\"8\" />\n                    </ScrollViewer>\n                </ControlTemplate>\n            </ListBox.Template>\n            <ListBox.ItemTemplate>\n                <DataTemplate DataType=\"{x:Type dialogs:CivitFileViewModel}\">\n                    <Grid\n                        Margin=\"0,4,0,4\"\n                        ColumnDefinitions=\"Auto, *\"\n                        RowDefinitions=\"Auto, Auto\">\n                        <Grid.ContextFlyout>\n                            <controls1:FAMenuFlyout>\n                                <controls1:MenuFlyoutItem\n                                    Command=\"{StaticResource ImportCommand}\"\n                                    IconSource=\"Download\"\n                                    IsVisible=\"{Binding IsInstalled}\"\n                                    Text=\"{x:Static lang:Resources.Action_Import}\" />\n                            </controls1:FAMenuFlyout>\n                        </Grid.ContextFlyout>\n                        <fluentAvalonia:SymbolIcon\n                            Grid.Row=\"0\"\n                            Grid.RowSpan=\"2\"\n                            Grid.Column=\"0\"\n                            Margin=\"0,0,8,0\"\n                            VerticalAlignment=\"Center\"\n                            Foreground=\"Lime\"\n                            IsVisible=\"{Binding IsInstalled}\"\n                            Symbol=\"Checkmark\" />\n                        <TextBlock\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,4,0,4\"\n                            Text=\"{Binding CivitFile.DisplayName}\"\n                            TextWrapping=\"Wrap\" />\n                        <TextBlock\n                            Grid.Row=\"1\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,4,0,4\"\n                            FontSize=\"11\"\n                            TextWrapping=\"Wrap\">\n                            <Run Text=\"{Binding CivitFile.Metadata.Format}\" />\n                            <Run Text=\"-\" />\n                            <Run Text=\"{Binding CivitFile.Metadata.Size}\" />\n                            <Run Text=\"-\" />\n                            <Run Text=\"{Binding CivitFile.Metadata.Fp}\" />\n                            <Run Text=\"-\" />\n                            <Run Text=\"{Binding CivitFile.FullFilesSize, Mode=OneWay}\" />\n                        </TextBlock>\n                    </Grid>\n                </DataTemplate>\n            </ListBox.ItemTemplate>\n        </ListBox>\n\n        <StackPanel\n            Grid.Row=\"1\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"3\"\n            Margin=\"0,8\"\n            HorizontalAlignment=\"Center\"\n            IsVisible=\"{Binding !SelectedFile.IsInstalled}\"\n            Orientation=\"Vertical\">\n            <TextBlock\n                Margin=\"0,4\"\n                HorizontalAlignment=\"Center\"\n                Text=\"{x:Static lang:Resources.Label_SelectDownloadLocation}\"\n                TextAlignment=\"Center\" />\n            <ComboBox\n                MinWidth=\"300\"\n                HorizontalAlignment=\"Center\"\n                ItemsSource=\"{Binding AvailableInstallLocations}\"\n                SelectedItem=\"{Binding SelectedInstallLocation}\" />\n            <StackPanel\n                Margin=\"0,4\"\n                HorizontalAlignment=\"Center\"\n                IsVisible=\"{Binding IsCustomSelected}\"\n                Orientation=\"Horizontal\">\n                <TextBox MinWidth=\"260\" Text=\"{Binding CustomInstallLocation}\" />\n                <Button\n                    Margin=\"4,0,0,0\"\n                    VerticalAlignment=\"Stretch\"\n                    Command=\"{Binding SelectCustomFolder}\">\n                    <controls1:SymbolIcon Symbol=\"OpenFolder\" />\n                </Button>\n            </StackPanel>\n            <TextBlock\n                Foreground=\"{DynamicResource ThemeRedColor}\"\n                IsVisible=\"{Binding ShowEmptyPathWarning}\"\n                Text=\"{x:Static lang:Resources.Error_PleaseSelectDownloadLocation}\"\n                TextAlignment=\"Center\" />\n        </StackPanel>\n\n        <Expander\n            Grid.Row=\"2\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"3\"\n            Margin=\"8,8\"\n            ExpandDirection=\"Down\"\n            Header=\"{x:Static lang:Resources.Label_ModelDescription}\">\n            <ScrollViewer MaxHeight=\"300\">\n                <controls:MarkdownViewer Html=\"{Binding Description}\" />\n            </ScrollViewer>\n        </Expander>\n\n        <StackPanel\n            Grid.Row=\"3\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"3\"\n            Margin=\"0,8,0,0\"\n            HorizontalAlignment=\"Center\"\n            Orientation=\"Horizontal\">\n            <Button\n                Margin=\"8,0\"\n                HorizontalAlignment=\"Center\"\n                Command=\"{Binding Cancel}\"\n                Content=\"{x:Static lang:Resources.Action_Cancel}\" />\n\n            <Button\n                Margin=\"8,0\"\n                Classes=\"accent\"\n                Command=\"{Binding Import}\"\n                Content=\"{x:Static lang:Resources.Action_Import}\"\n                IsEnabled=\"{Binding IsImportEnabled}\"\n                IsVisible=\"{Binding !SelectedFile.IsInstalled, TargetNullValue=True, FallbackValue=True}\"\n                ToolTip.ShowOnDisabled=\"True\"\n                ToolTip.Tip=\"{Binding ImportTooltip}\" />\n\n            <Button\n                Margin=\"8,0\"\n                Classes=\"danger\"\n                Command=\"{Binding Delete}\"\n                Content=\"{x:Static lang:Resources.Action_Delete}\"\n                IsVisible=\"{Binding SelectedFile.IsInstalled, TargetNullValue=False, FallbackValue=False}\" />\n        </StackPanel>\n\n    </Grid>\n\n</UserControl>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml.cs",
    "content": "﻿using Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\npublic partial class SelectModelVersionDialog : UserControl\n{\n    public SelectModelVersionDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/SponsorshipPromptDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.SponsorshipPromptDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:ctxt=\"clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:icon=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:iconExt=\"clr-namespace:FluentIcons.Avalonia.MarkupExtensions;assembly=FluentIcons.Avalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mde=\"clr-namespace:Markdown.Avalonia.Extensions;assembly=Markdown.Avalonia\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:sm=\"clr-namespace:StabilityMatrix.Avalonia\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:uip=\"using:FluentAvalonia.UI.Controls.Primitives\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    xmlns:vmDialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    d:DataContext=\"{x:Static mocks:DesignData.SponsorshipPromptViewModel}\"\n    d:DesignHeight=\"400\"\n    d:DesignWidth=\"600\"\n    x:DataType=\"vmDialogs:SponsorshipPromptViewModel\"\n    mc:Ignorable=\"d\">\n\n    <controls:UserControlBase.Resources>\n        <SolidColorBrush x:Key=\"BrushB0\" Color=\"#FFFFFFFF\" />\n        <DrawingImage x:Key=\"BrandsPatreonSymbolWhite\">\n            <DrawingGroup>\n                <GeometryDrawing Brush=\"{DynamicResource BrushB0}\" Geometry=\"F1 M1033.05 324.45C1032.86 186.55 925.46 73.53 799.45 32.75C642.97 -17.89 436.59 -10.55 287.17 59.95C106.07 145.41 49.18 332.61 47.06 519.31C45.32 672.81 60.64 1077.1 288.68 1079.98C458.12 1082.13 483.35 863.8 561.75 758.65C617.53 683.84 689.35 662.71 777.76 640.83C929.71 603.22 1033.27 483.3 1033.05 324.45Z\" />\n            </DrawingGroup>\n        </DrawingImage>\n    </controls:UserControlBase.Resources>\n\n    <controls:UserControlBase.Styles>\n        <Style Selector=\"controls|BetterMarkdownScrollViewer\">\n            <!--<Style Selector=\"^ctxt|CHyperlink\">\n                <Style.Setters>\n                    <Setter Property=\"IsUnderline\" Value=\"False\" />\n                </Style.Setters>\n            </Style>\n            <Style Selector=\"^ctxt|CHyperlink:pointerover\">\n                <Setter Property=\"IsUnderline\" Value=\"True\" />\n                <Setter Property=\"Foreground\" Value=\"{StaticResource ThemeEldenRingOrangeColor}\" />\n            </Style>-->\n            <Style Selector=\"^.Markdown_Avalonia_MarkdownViewer TextBlock\">\n                <Style.Setters>\n                    <Setter Property=\"Margin\" Value=\"0, 7, 0, 14\" />\n                    <Setter Property=\"FontSize\" Value=\"14\" />\n                </Style.Setters>\n            </Style>\n        </Style>\n        <Style Selector=\"controls|BetterMarkdownScrollViewer &gt; ctxt|CTextBlock\">\n            <Style.Setters>\n                <Setter Property=\"SelectionBrush\" Value=\"{DynamicResource TextControlSelectionHighlightColor}\" />\n                <Setter Property=\"FontSize\" Value=\"24\" />\n                <Setter Property=\"Margin\" Value=\"0,5\" />\n            </Style.Setters>\n        </Style>\n        <Style Selector=\"controls|BetterMarkdownScrollViewer &gt; TextBlock\">\n            <Style.Setters>\n                <Setter Property=\"Margin\" Value=\"0, 7, 0, 14\" />\n                <Setter Property=\"FontSize\" Value=\"24\" />\n            </Style.Setters>\n        </Style>\n    </controls:UserControlBase.Styles>\n\n    <StackPanel Margin=\"8,4\" Spacing=\"4\">\n        <StackPanel\n            Margin=\"8\"\n            Orientation=\"Horizontal\"\n            Spacing=\"8\">\n            <icon:Icon\n                Margin=\"0,2,0,0\"\n                FontSize=\"18\"\n                Foreground=\"#f97315\"\n                Value=\"fa-solid fa-heart\" />\n            <!--<fluentIcons:SymbolIcon\n                FontSize=\"28\"\n                Foreground=\"#f97315\"\n                IconVariant=\"Filled\"\n                Symbol=\"Heart\" />-->\n            <TextBlock\n                Text=\"{Binding Title}\"\n                TextWrapping=\"Wrap\"\n                Theme=\"{StaticResource SubtitleTextBlockStyle}\" />\n\n        </StackPanel>\n\n        <controls:BetterMarkdownScrollViewer\n            Margin=\"4,0\"\n            VerticalAlignment=\"Bottom\"\n            IsVisible=\"{Binding IsExistingSupporter}\"\n            Markdown=\"{Binding ExistingSupporterPreamble}\" />\n\n        <!--  TextElement.Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"  -->\n        <controls:BetterMarkdownScrollViewer\n            Margin=\"4,0\"\n            VerticalAlignment=\"Bottom\"\n            Markdown=\"{Binding FeatureText}\" />\n\n        <ui:InfoBar\n            Title=\"{x:Static lang:Resources.Sponsorship_ConnectPatreonPrompt}\"\n            IsClosable=\"False\"\n            IsOpen=\"{Binding !IsPatreonConnected}\">\n            <ui:InfoBar.IconSource>\n                <ui:ImageIconSource Source=\"{StaticResource BrandsPatreonSymbolWhite}\" />\n            </ui:InfoBar.IconSource>\n            <ui:InfoBar.ActionButton>\n                <Button Command=\"{Binding NavigateToAccountSettingsCommand}\" Content=\"{x:Static lang:Resources.Label_AccountSettings}\" />\n            </ui:InfoBar.ActionButton>\n        </ui:InfoBar>\n\n        <!--<ui:InfoBar\n            Title=\"Not a supporter yet, or need to upgrade? Your contribution helps us keep improving Stability Matrix!\"\n            IsOpen=\"True\"\n            IsClosable=\"False\">\n            <ui:InfoBar.IconSource>\n                <ui:ImageIconSource Source=\"{StaticResource BrandsPatreonSymbolWhite}\" />\n            </ui:InfoBar.IconSource>\n            <ui:InfoBar.ActionButton>\n                <controls:HyperlinkIconButton Content=\"View Support Options\"/>\n            </ui:InfoBar.ActionButton>\n        </ui:InfoBar>-->\n\n        <!--<ui:SettingsExpander\n            TextBlock.TextWrapping=\"Wrap\"\n            Description=\"Link your Patreon account within Stability Matrix to activate your benefits\"\n            Header=\"Already a Patreon supporter at this level?\">\n            <ui:SettingsExpander.IconSource>\n                <ui:ImageIconSource\n                    Source=\"{StaticResource BrandsPatreonSymbolWhite}\" />\n            </ui:SettingsExpander.IconSource>\n            <ui:SettingsExpander.Footer>\n                <Button Content=\"Account Settings\"/>\n            </ui:SettingsExpander.Footer>\n        </ui:SettingsExpander>-->\n\n        <!--<TextBlock Text=\"Not a supporter yet, or need to upgrade?\"></TextBlock>\n\n        <TextBlock Text=\"Your contribution helps us keep improving Stability Matrix!\"></TextBlock>\n\n        <ui:SettingsExpander\n            TextBlock.TextWrapping=\"Wrap\"\n            Description=\"Your contribution helps us keep improving Stability Matrix!\"\n            Header=\"Not a supporter yet, or need to upgrade?\">\n            <ui:SettingsExpander.IconSource>\n                <fluentIcons:SymbolIconSource\n                    IconVariant=\"Filled\"\n                    Symbol=\"Star\" />\n            </ui:SettingsExpander.IconSource>\n            <ui:SettingsExpander.Footer>\n                <Button>\n                    <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                        <fluentIcons:SymbolIcon Symbol=\"Open\" IconVariant=\"Light\" />\n                        <TextBlock Text=\"View Support Options\"/>\n                    </StackPanel>\n                </Button>\n            </ui:SettingsExpander.Footer>\n        </ui:SettingsExpander>\n\n        <controls:SettingsAccountLinkExpander\n            Header=\"Patreon\"\n            HeaderTargetUri=\"{x:Static sm:Assets.PatreonUrl}\"\n            IsConnected=\"False\"\n            IsEnabled=\"True\"\n            OffDescription=\"Access Preview and Dev release channels for auto-updates\">\n            <controls:SettingsAccountLinkExpander.IconSource>\n                <ui:ImageIconSource Source=\"{StaticResource BrandsPatreonSymbolWhite}\" />\n            </controls:SettingsAccountLinkExpander.IconSource>\n        </controls:SettingsAccountLinkExpander>-->\n\n\n\n    </StackPanel>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/SponsorshipPromptDialog.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<SponsorshipPromptDialog>]\npublic partial class SponsorshipPromptDialog : UserControlBase\n{\n    public SponsorshipPromptDialog()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Dialogs.UpdateDialog\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mdxaml=\"https://github.com/whistyun/Markdown.Avalonia.Tight\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    d:DataContext=\"{x:Static mocks:DesignData.UpdateViewModel}\"\n    d:DesignHeight=\"550\"\n    d:DesignWidth=\"700\"\n    x:DataType=\"dialogs:UpdateViewModel\"\n    mc:Ignorable=\"d\">\n    <Grid RowDefinitions=\"Auto,Auto,Auto,Auto,*,Auto,Auto\">\n\n        <!--<TitleBar Background=\"{ui:ThemeResource ApplicationBackgroundBrush}\">\n            <TitleBar.Header>\n                <TextBlock Margin=\"16,8\" Text=\"Stability Matrix - Update Available\" />\n            </TitleBar.Header>\n        </TitleBar>-->\n\n        <TextBlock\n            Grid.Row=\"0\"\n            Margin=\"0,16,0,0\"\n            HorizontalAlignment=\"Center\"\n            FontSize=\"28\"\n            FontWeight=\"Thin\"\n            Text=\"{x:Static lang:Resources.Label_NewVersionAvailable}\" />\n\n        <StackPanel\n            Grid.Row=\"1\"\n            Margin=\"0,4\"\n            HorizontalAlignment=\"Center\"\n            IsVisible=\"{Binding UpdateText, Converter={x:Static StringConverters.IsNullOrEmpty}}\"\n            Orientation=\"Horizontal\">\n            <TextBlock\n                FontSize=\"18\"\n                Foreground=\"{StaticResource ThemeGreyColor}\"\n                Text=\"{Binding CurrentVersionText}\" />\n            <fluentIcons:SymbolIcon\n                Margin=\"8,0\"\n                VerticalAlignment=\"Center\"\n                FontSize=\"15\"\n                IconVariant=\"Filled\"\n                Symbol=\"ArrowRight\" />\n            <TextBlock FontSize=\"18\" Text=\"{Binding NewVersionText}\" />\n        </StackPanel>\n\n        <TextBlock\n            Grid.Row=\"1\"\n            Margin=\"16,32,16,0\"\n            HorizontalAlignment=\"Center\"\n            FontSize=\"18\"\n            IsVisible=\"{Binding UpdateText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n            Text=\"{Binding UpdateText}\"\n            TextAlignment=\"Center\"\n            TextWrapping=\"Wrap\" />\n\n        <TextBlock\n            Grid.Row=\"2\"\n            Margin=\"32,16,32,0\"\n            FontSize=\"16\"\n            IsVisible=\"{Binding !ShowProgressBar}\"\n            Text=\"{x:Static lang:Resources.Label_ReleaseNotes}\" />\n\n        <ProgressBar\n            Grid.Row=\"3\"\n            Height=\"200\"\n            Margin=\"32\"\n            IsIndeterminate=\"{Binding IsProgressIndeterminate}\"\n            IsVisible=\"{Binding ShowProgressBar}\"\n            Value=\"{Binding ProgressValue}\" />\n\n        <Grid\n            Grid.Row=\"4\"\n            Margin=\"8\"\n            IsVisible=\"{Binding !ShowProgressBar}\">\n            <Border\n                Margin=\"32,16\"\n                Background=\"{DynamicResource ButtonBackgroundPressed}\"\n                CornerRadius=\"8\">\n                <controls:BetterMarkdownScrollViewer Margin=\"16\" Markdown=\"{Binding ReleaseNotes, Mode=OneWay}\" />\n            </Border>\n        </Grid>\n\n        <ui:InfoBar\n            Title=\"{x:Static lang:Resources.Text_AppWillRelaunchAfterUpdate}\"\n            Grid.Row=\"5\"\n            Margin=\"64,0,64,16\"\n            IsClosable=\"False\"\n            IsOpen=\"True\" />\n\n        <StackPanel\n            Grid.Row=\"6\"\n            Margin=\"0,0,0,16\"\n            HorizontalAlignment=\"Center\"\n            Orientation=\"Horizontal\">\n            <Button\n                Margin=\"0,0,8,0\"\n                Classes=\"info\"\n                Command=\"{Binding OnCloseButtonClick}\"\n                Content=\"{x:Static lang:Resources.Action_RemindMeLater}\"\n                FontSize=\"15\"\n                IsEnabled=\"{Binding !InstallUpdateCommand.IsRunning}\" />\n\n            <Button\n                Margin=\"8,0,0,0\"\n                Classes=\"success\"\n                Command=\"{Binding InstallUpdateCommand}\"\n                Content=\"{x:Static lang:Resources.Action_InstallNow}\"\n                FontSize=\"15\" />\n        </StackPanel>\n\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Dialogs/UpdateDialog.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Dialogs;\n\n[RegisterTransient<UpdateDialog>]\npublic partial class UpdateDialog : UserControlBase\n{\n    public UpdateDialog()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/FirstLaunchSetupWindow.axaml",
    "content": "﻿<controls:AppWindowBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.FirstLaunchSetupWindow\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:hardwareInfo=\"clr-namespace:StabilityMatrix.Core.Helper.HardwareInfo;assembly=StabilityMatrix.Core\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    Title=\"Stability Matrix\"\n    Width=\"800\"\n    Height=\"550\"\n    d:DataContext=\"{x:Static mocks:DesignData.FirstLaunchSetupViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"vm:FirstLaunchSetupViewModel\"\n    CornerRadius=\"4\"\n    Icon=\"/Assets/Icon.ico\"\n    WindowStartupLocation=\"CenterScreen\"\n    mc:Ignorable=\"d\">\n\n    <Grid ColumnDefinitions=\"0.8*,*\">\n        <StackPanel\n            Margin=\"64\"\n            VerticalAlignment=\"Center\"\n            Orientation=\"Vertical\">\n            <controls:BetterAdvancedImage Width=\"128\" Source=\"/Assets/Icon.png\" />\n        </StackPanel>\n        <Grid Grid.Column=\"1\">\n            <Grid Margin=\"0,16,32,32\" RowDefinitions=\"0.6*,0.4*\">\n                <!--  Texts  -->\n                <StackPanel Margin=\"32,64,0,0\">\n                    <TextBlock FontSize=\"24\" Text=\"{x:Static lang:Resources.Label_LetsGetStarted}\" />\n                    <!--  Loading icon and text  -->\n                    <Grid Margin=\"0,8,0,0\" ColumnDefinitions=\"0.15*,*\">\n                        <controls:RefreshBadge VerticalAlignment=\"Center\" DataContext=\"{Binding CheckHardwareBadge}\" />\n                        <TextBlock\n                            Grid.Column=\"1\"\n                            Margin=\"8,8\"\n                            VerticalAlignment=\"Center\"\n                            FontSize=\"14\"\n                            Text=\"{Binding CheckHardwareBadge.CurrentToolTip}\"\n                            TextWrapping=\"WrapWithOverflow\" />\n                    </Grid>\n\n                    <!--\n                        Gpu info\n                        IsVisible=\"{Binding GpuInfoText,\n                        Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                    -->\n                    <Grid\n                        Margin=\"8,8,0,0\"\n                        HorizontalAlignment=\"Left\"\n                        ColumnDefinitions=\"*,Auto\">\n                        <Grid.IsVisible>\n                            <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                <Binding Converter=\"{x:Static StringConverters.IsNotNullOrEmpty}\" Path=\"GpuInfoText\" />\n                                <Binding Path=\"!SelectDifferentGpu\" />\n                            </MultiBinding>\n                        </Grid.IsVisible>\n                        <Ellipse\n                            Width=\"8\"\n                            Height=\"8\"\n                            Margin=\"8,0\"\n                            HorizontalAlignment=\"Left\"\n                            Fill=\"{DynamicResource TextFillColorPrimaryBrush}\" />\n                        <TextBlock\n                            Grid.Column=\"1\"\n                            HorizontalAlignment=\"Left\"\n                            FontSize=\"14\"\n                            Text=\"{Binding GpuInfoText}\"\n                            TextWrapping=\"Wrap\" />\n                    </Grid>\n\n                    <HyperlinkButton\n                        Margin=\"0,8\"\n                        Command=\"{Binding ToggleManualGpuCommand}\"\n                        Content=\"Select a different default GPU\"\n                        FontSize=\"11\" />\n\n                    <controls:BetterComboBox\n                        HorizontalAlignment=\"Stretch\"\n                        IsVisible=\"{Binding SelectDifferentGpu}\"\n                        ItemsSource=\"{Binding GpuInfoCollection}\"\n                        SelectedItem=\"{Binding SelectedGpu}\">\n                        <controls:BetterComboBox.ItemTemplate>\n                            <DataTemplate DataType=\"hardwareInfo:GpuInfo\">\n                                <TextBlock Text=\"{Binding Name}\" />\n                            </DataTemplate>\n                        </controls:BetterComboBox.ItemTemplate>\n                    </controls:BetterComboBox>\n\n                    <TextBlock\n                        Margin=\"8,4\"\n                        IsVisible=\"{Binding SelectDifferentGpu}\"\n                        Text=\"{Binding YouCanChangeThis}\"\n                        TextWrapping=\"Wrap\" />\n\n                </StackPanel>\n                <!--  Checkbox  -->\n                <!-- <Hyperlink Command=\"{Binding OpenLicenseLinkCommand}\" NavigateUri=\"\">License Agreement.</Hyperlink> -->\n\n                <Grid\n                    Grid.Row=\"1\"\n                    HorizontalAlignment=\"Center\"\n                    ColumnDefinitions=\"*,Auto\">\n                    <CheckBox\n                        Grid.Column=\"1\"\n                        Margin=\"16,0,0,0\"\n                        HorizontalAlignment=\"Left\"\n                        IsChecked=\"{Binding EulaAccepted, Mode=TwoWay}\">\n                        <Grid VerticalAlignment=\"Center\" ColumnDefinitions=\"*,Auto\">\n                            <TextBlock\n                                VerticalAlignment=\"Top\"\n                                FontSize=\"14\"\n                                Text=\"{x:Static lang:Resources.Label_ReadAndAgree}\" />\n\n                            <HyperlinkButton\n                                Grid.Column=\"1\"\n                                Margin=\"1,0,0,0\"\n                                Padding=\"0\"\n                                VerticalAlignment=\"Top\"\n                                Classes=\"transparent\"\n                                Content=\"{x:Static lang:Resources.Label_LicenseAgreement}\"\n                                NavigateUri=\"https://lykos.ai/matrix/license\" />\n                        </Grid>\n                    </CheckBox>\n                </Grid>\n            </Grid>\n            <!--  Buttons  -->\n            <UniformGrid\n                MaxWidth=\"170\"\n                Margin=\"32,8\"\n                HorizontalAlignment=\"Right\"\n                VerticalAlignment=\"Bottom\">\n                <Button\n                    Classes=\"danger\"\n                    Click=\"QuitButton_OnClick\"\n                    Content=\"{x:Static lang:Resources.Action_Quit}\" />\n                <Button\n                    Classes=\"info\"\n                    Click=\"ContinueButton_OnClick\"\n                    Content=\"{x:Static lang:Resources.Action_Continue}\"\n                    IsEnabled=\"{Binding EulaAccepted, Mode=OneWay}\" />\n            </UniformGrid>\n\n        </Grid>\n    </Grid>\n\n</controls:AppWindowBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/FirstLaunchSetupWindow.axaml.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing Avalonia.Interactivity;\nusing Avalonia.Markup.Xaml;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<FirstLaunchSetupWindow>]\npublic partial class FirstLaunchSetupWindow : AppWindowBase\n{\n    public ContentDialogResult Result { get; private set; }\n\n    public FirstLaunchSetupWindow()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n\n    [SuppressMessage(\"ReSharper\", \"UnusedParameter.Local\")]\n    private void ContinueButton_OnClick(object? sender, RoutedEventArgs e)\n    {\n        Result = ContentDialogResult.Primary;\n        Close();\n    }\n\n    [SuppressMessage(\"ReSharper\", \"UnusedParameter.Local\")]\n    private void QuitButton_OnClick(object? sender, RoutedEventArgs e)\n    {\n        Result = ContentDialogResult.None;\n        Close();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/HuggingFacePage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.HuggingFacePage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:checkpointBrowser=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:helpers=\"clr-namespace:StabilityMatrix.Avalonia.Helpers\"\n    xmlns:huggingFacePage=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.HuggingFacePage\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    d:DataContext=\"{x:Static mocks:DesignData.HuggingFacePageViewModel}\"\n    d:DesignHeight=\"650\"\n    d:DesignWidth=\"800\"\n    x:CompileBindings=\"True\"\n    x:DataType=\"checkpointBrowser:HuggingFacePageViewModel\"\n    Focusable=\"True\"\n    mc:Ignorable=\"d\">\n    <Grid\n        Margin=\"8\"\n        HorizontalAlignment=\"Stretch\"\n        RowDefinitions=\"Auto,Auto,*\">\n        <ui:CommandBar\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            Margin=\"0,-1,0,0\"\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Center\"\n            VerticalContentAlignment=\"Center\"\n            DefaultLabelPosition=\"Right\">\n            <ui:CommandBar.PrimaryCommands>\n                <ui:CommandBarButton\n                    Command=\"{Binding ImportSelectedCommand}\"\n                    Foreground=\"{DynamicResource AccentButtonBackground}\"\n                    IconSource=\"Download\"\n                    IsEnabled=\"{Binding !!NumSelected}\"\n                    Label=\"{x:Static lang:Resources.Action_Import}\" />\n                <ui:CommandBarSeparator />\n                <ui:CommandBarButton\n                    Command=\"{Binding Refresh}\"\n                    IconSource=\"Refresh\"\n                    Label=\"{x:Static lang:Resources.Action_Refresh}\" />\n                <ui:CommandBarButton\n                    Command=\"{Binding SelectAll}\"\n                    IconSource=\"SelectAll\"\n                    Label=\"{x:Static lang:Resources.Action_SelectAll}\" />\n                <ui:CommandBarButton\n                    Command=\"{Binding ClearSelection}\"\n                    IconSource=\"ClearSelection\"\n                    IsEnabled=\"{Binding !!NumSelected}\"\n                    Label=\"{x:Static lang:Resources.Action_ClearSelection}\" />\n                <ui:CommandBarSeparator IsVisible=\"{Binding !!NumSelected}\" />\n            </ui:CommandBar.PrimaryCommands>\n        </ui:CommandBar>\n\n        <StackPanel\n            Grid.Row=\"1\"\n            Grid.Column=\"0\"\n            Margin=\"0,8\"\n            VerticalAlignment=\"Bottom\"\n            Orientation=\"Vertical\">\n            <TextBlock\n                Margin=\"0,4\"\n                HorizontalAlignment=\"Stretch\"\n                IsVisible=\"{Binding !!TotalProgress.Total}\"\n                Text=\"{Binding DownloadPercentText}\"\n                TextAlignment=\"Center\" />\n            <ProgressBar\n                IsIndeterminate=\"False\"\n                IsVisible=\"{Binding !!TotalProgress.Total}\"\n                Maximum=\"{Binding TotalProgress.Total}\"\n                Value=\"{Binding TotalProgress.Current}\" />\n        </StackPanel>\n        <ScrollViewer Grid.Row=\"2\">\n            <ItemsRepeater ItemsSource=\"{Binding Categories}\">\n                <ItemsRepeater.Layout>\n                    <StackLayout Orientation=\"Vertical\" />\n                </ItemsRepeater.Layout>\n                <ItemsRepeater.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type huggingFacePage:CategoryViewModel}\">\n                        <Expander Margin=\"0,0,0,8\" IsExpanded=\"True\">\n                            <Expander.Header>\n                                <StackPanel Orientation=\"Horizontal\">\n                                    <CheckBox Content=\"{Binding Title}\" IsChecked=\"{Binding IsChecked}\" />\n                                </StackPanel>\n                            </Expander.Header>\n                            <ItemsRepeater Margin=\"8,0\" ItemsSource=\"{Binding Items}\">\n                                <ItemsRepeater.Layout>\n                                    <WrapLayout\n                                        HorizontalSpacing=\"8\"\n                                        Orientation=\"Horizontal\"\n                                        VerticalSpacing=\"8\" />\n                                </ItemsRepeater.Layout>\n                                <ItemsRepeater.ItemTemplate>\n                                    <DataTemplate DataType=\"{x:Type huggingFacePage:HuggingfaceItemViewModel}\">\n                                        <Button\n                                            MinWidth=\"300\"\n                                            MaxWidth=\"300\"\n                                            HorizontalAlignment=\"Stretch\"\n                                            HorizontalContentAlignment=\"Stretch\"\n                                            Command=\"{Binding ToggleSelectedCommand}\"\n                                            CornerRadius=\"8\"\n                                            IsEnabled=\"{Binding !Exists}\">\n                                            <Button.ContextFlyout>\n                                                <MenuFlyout>\n                                                    <MenuItem\n                                                        Command=\"{x:Static helpers:IOCommands.OpenUrlCommand}\"\n                                                        CommandParameter=\"{Binding RepoUrl}\"\n                                                        Header=\"{x:Static lang:Resources.Action_OpenOnHuggingFace}\">\n                                                        <MenuItem.Icon>\n                                                            <ui:SymbolIcon Symbol=\"Open\" />\n                                                        </MenuItem.Icon>\n                                                    </MenuItem>\n                                                    <MenuItem\n                                                        Command=\"{x:Static helpers:IOCommands.OpenUrlCommand}\"\n                                                        CommandParameter=\"{Binding LicenseUrl}\"\n                                                        Header=\"{x:Static lang:Resources.Label_License}\">\n                                                        <MenuItem.Icon>\n                                                            <ui:SymbolIcon Symbol=\"Document\" />\n                                                        </MenuItem.Icon>\n                                                    </MenuItem>\n                                                </MenuFlyout>\n                                            </Button.ContextFlyout>\n                                            <Grid ColumnDefinitions=\"*, Auto, Auto\">\n                                                <CheckBox VerticalAlignment=\"Center\" IsChecked=\"{Binding IsSelected}\">\n                                                    <TextBlock\n                                                        MaxWidth=\"225\"\n                                                        Text=\"{Binding Item.ModelName}\"\n                                                        TextTrimming=\"CharacterEllipsis\"\n                                                        TextWrapping=\"NoWrap\"\n                                                        ToolTip.Tip=\"{Binding Item.ModelName}\" />\n                                                </CheckBox>\n                                                <avalonia:Icon\n                                                    Grid.Column=\"1\"\n                                                    Margin=\"0,0,4,0\"\n                                                    IsVisible=\"{Binding Item.LoginRequired}\"\n                                                    ToolTip.Tip=\"{x:Static lang:Resources.Label_LoginRequired}\"\n                                                    Value=\"fa-solid fa-lock\" />\n                                                <Button\n                                                    Grid.Column=\"2\"\n                                                    Width=\"24\"\n                                                    Margin=\"0,4,4,0\"\n                                                    Padding=\"4\"\n                                                    HorizontalAlignment=\"Right\"\n                                                    VerticalAlignment=\"Top\"\n                                                    HorizontalContentAlignment=\"Right\"\n                                                    VerticalContentAlignment=\"Top\"\n                                                    BorderThickness=\"0\"\n                                                    Classes=\"transparent\"\n                                                    IsEnabled=\"{Binding !Exists}\">\n                                                    <ui:SymbolIcon FontSize=\"18\" Symbol=\"MoreVertical\" />\n                                                    <Button.Flyout>\n                                                        <MenuFlyout>\n                                                            <MenuItem\n                                                                Command=\"{x:Static helpers:IOCommands.OpenUrlCommand}\"\n                                                                CommandParameter=\"{Binding RepoUrl}\"\n                                                                Header=\"{x:Static lang:Resources.Action_OpenOnHuggingFace}\">\n                                                                <MenuItem.Icon>\n                                                                    <ui:SymbolIcon Symbol=\"Open\" />\n                                                                </MenuItem.Icon>\n                                                            </MenuItem>\n                                                            <MenuItem\n                                                                Command=\"{x:Static helpers:IOCommands.OpenUrlCommand}\"\n                                                                CommandParameter=\"{Binding LicenseUrl}\"\n                                                                Header=\"{x:Static lang:Resources.Label_License}\">\n                                                                <MenuItem.Icon>\n                                                                    <ui:SymbolIcon Symbol=\"List\" />\n                                                                </MenuItem.Icon>\n                                                            </MenuItem>\n                                                        </MenuFlyout>\n                                                    </Button.Flyout>\n                                                </Button>\n                                            </Grid>\n                                        </Button>\n                                    </DataTemplate>\n                                </ItemsRepeater.ItemTemplate>\n                            </ItemsRepeater>\n                        </Expander>\n                    </DataTemplate>\n                </ItemsRepeater.ItemTemplate>\n            </ItemsRepeater>\n        </ScrollViewer>\n\n        <ui:InfoBar\n            Grid.Row=\"2\"\n            Margin=\"0,0,8,8\"\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Bottom\"\n            HorizontalContentAlignment=\"Right\"\n            Background=\"#AA000000\"\n            CloseButtonCommand=\"{Binding ClearSelection}\"\n            CornerRadius=\"8\"\n            IsIconVisible=\"False\"\n            IsOpen=\"{Binding !!NumSelected}\">\n            <ui:InfoBar.Title>\n                <MultiBinding StringFormat=\"{}{0} Selected\">\n                    <Binding Path=\"NumSelected\" />\n                </MultiBinding>\n            </ui:InfoBar.Title>\n            <ui:InfoBar.ActionButton>\n                <Button\n                    VerticalAlignment=\"Center\"\n                    Classes=\"accent\"\n                    Command=\"{Binding ImportSelectedCommand}\"\n                    Content=\"{x:Static lang:Resources.Action_Import}\" />\n            </ui:InfoBar.ActionButton>\n        </ui:InfoBar>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/HuggingFacePage.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<HuggingFacePage>]\npublic partial class HuggingFacePage : UserControlBase\n{\n    public HuggingFacePage()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Inference/InferenceImageToImageView.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Inference.InferenceImageToImageView\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dock=\"clr-namespace:StabilityMatrix.Avalonia.Controls.Dock\"\n    xmlns:icons=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    xmlns:inference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:modelsInference=\"clr-namespace:StabilityMatrix.Avalonia.Models.Inference\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    xmlns:vmInference=\"using:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    d:DataContext=\"{x:Static mocks:DesignData.InferenceImageToImageViewModel}\"\n    d:DesignHeight=\"800\"\n    d:DesignWidth=\"1000\"\n    x:DataType=\"inference:InferenceImageToImageViewModel\"\n    Focusable=\"True\"\n    mc:Ignorable=\"d\">\n\n    <Grid Margin=\"2,0,2,8\">\n        <DockControl\n            x:Name=\"Dock\"\n            InitializeFactory=\"True\"\n            InitializeLayout=\"True\">\n            <DockControl.Factory>\n                <Factory />\n            </DockControl.Factory>\n\n            <RootDock\n                x:Name=\"Root\"\n                DefaultDockable=\"{Binding #MainLayout}\"\n                Id=\"Root\"\n                IsCollapsable=\"False\">\n\n                <ProportionalDock\n                    x:Name=\"MainLayout\"\n                    Id=\"MainLayout\"\n                    Orientation=\"Horizontal\">\n\n                    <!--  Left Pane  -->\n                    <ToolDock\n                        x:Name=\"ConfigPane\"\n                        Alignment=\"Left\"\n                        Id=\"ConfigPane\"\n                        Proportion=\"0.2\">\n                        <Tool\n                            x:Name=\"ConfigTool\"\n                            Title=\"Config\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ConfigTool\">\n                            <controls:StackCard DataContext=\"{ReflectionBinding ElementName=Dock, Path=DataContext.StackCardViewModel}\" Opacity=\"1\" />\n                        </Tool>\n                    </ToolDock>\n\n                    <ProportionalDockSplitter x:Name=\"Splitter1\" Id=\"Splitter1\" />\n\n                    <!--  Prompt Pane  -->\n                    <ProportionalDock\n                        x:Name=\"CenterProportionalDock\"\n                        Id=\"CenterProportionalDock\"\n                        Orientation=\"Vertical\">\n\n                        <!--  Center Top (Image)  -->\n\n                        <ToolDock\n                            x:Name=\"ImagePane\"\n                            Alignment=\"Left\"\n                            Id=\"ImagePane\"\n                            Proportion=\"0.25\">\n                            <Tool\n                                x:Name=\"ImageTool\"\n                                Title=\"Image\"\n                                x:DataType=\"Tool\"\n                                CanClose=\"False\"\n                                Id=\"ImageTool\">\n\n                                <!--  Prompt and buttons  -->\n                                <Grid\n                                    x:CompileBindings=\"False\"\n                                    DataContext=\"{Binding ElementName=Dock, Path=DataContext}\"\n                                    Opacity=\"1\"\n                                    RowDefinitions=\"*\">\n                                    <!--  Prompt card  -->\n                                    <controls:SelectImageCard \n                                        x:CompileBindings=\"False\" \n                                        DataContext=\"{Binding SelectImageCardViewModel}\" />\n                                </Grid>\n\n                            </Tool>\n                        </ToolDock>\n\n                        <ProportionalDockSplitter />\n\n                        <!--  Center Bottom (Prompt)  -->\n                        <ToolDock\n                            x:Name=\"PromptPane\"\n                            Alignment=\"Left\"\n                            Id=\"PromptPane\"\n                            Proportion=\"0.25\">\n                            <Tool\n                                x:Name=\"PromptTool\"\n                                Title=\"Prompt\"\n                                x:DataType=\"Tool\"\n                                CanClose=\"False\"\n                                Id=\"PromptTool\">\n\n                                <!--  Prompt and buttons  -->\n                                <Grid\n                                    x:CompileBindings=\"False\"\n                                    DataContext=\"{Binding ElementName=Dock, Path=DataContext}\"\n                                    Opacity=\"1\"\n                                    RowDefinitions=\"*,Auto,Auto\">\n                                    <!--  Prompt card  -->\n                                    <controls:PromptCard DataContext=\"{Binding PromptCardViewModel}\" />\n\n                                    <!--  Generate Buttons  -->\n                                    <StackPanel Grid.Row=\"2\" Margin=\"0,6,0,0\">\n                                        <controls:Card Padding=\"8\">\n                                            <Grid\n                                                HorizontalAlignment=\"Center\"\n                                                ColumnDefinitions=\"Auto,*,Auto\"\n                                                RowDefinitions=\"Auto,*\">\n                                                <Grid.Styles>\n                                                    <Style Selector=\"Button\">\n                                                        <Setter Property=\"MinHeight\" Value=\"32\" />\n                                                    </Style>\n                                                </Grid.Styles>\n                                                <Button\n                                                    Grid.Row=\"1\"\n                                                    Margin=\"4,0\"\n                                                    Padding=\"12,8\"\n                                                    HorizontalAlignment=\"Left\"\n                                                    icons:Attached.Icon=\"fa-solid fa-magnifying-glass-plus\"\n                                                    Classes=\"transparent\"\n                                                    Command=\"{Binding GenerateImageCommand}\"\n                                                    CommandParameter=\"{x:Static modelsInference:GenerateFlags.HiresFixAndUseCurrentSeed}\"\n                                                    ToolTip.Tip=\"Generate with Hires Scaling\" />\n\n                                                <!--  Main Generate Button  -->\n                                                <Button\n                                                    x:Name=\"GenerateButton\"\n                                                    Grid.Row=\"1\"\n                                                    Grid.Column=\"1\"\n                                                    Width=\"130\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    Classes=\"accent\"\n                                                    ToolTip.Tip=\"{Binding $self.HotKey}\"\n                                                    HotKey=\"Ctrl+Enter\"\n                                                    Command=\"{Binding GenerateImageCommand}\"\n                                                    CommandParameter=\"{x:Static modelsInference:GenerateFlags.None}\"\n                                                    IsVisible=\"{Binding !GenerateImageCommand.CanBeCanceled}\">\n                                                    <Panel>\n                                                        <TextBlock IsVisible=\"{Binding #GenerateButton.IsEnabled}\" Text=\"Generate Image\" />\n                                                        <controls:ProgressRing IsIndeterminate=\"True\" IsVisible=\"{Binding !#GenerateButton.IsEnabled}\" />\n                                                    </Panel>\n                                                </Button>\n                                                <!--  Cancel Button  -->\n                                                <Button\n                                                    x:Name=\"CancelButton\"\n                                                    Grid.Row=\"1\"\n                                                    Grid.Column=\"1\"\n                                                    Width=\"130\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    icons:Attached.Icon=\"fa-solid fa-stop\"\n                                                    Command=\"{Binding GenerateImageCancelCommand}\"\n                                                    IsVisible=\"{Binding GenerateImageCommand.CanBeCanceled}\" />\n\n                                                <Button\n                                                    Grid.Row=\"1\"\n                                                    Grid.Column=\"2\"\n                                                    Margin=\"4,0\"\n                                                    Padding=\"12,8\"\n                                                    HorizontalAlignment=\"Left\"\n                                                    icons:Attached.Icon=\"fa-solid fa-repeat\"\n                                                    Classes=\"transparent\"\n                                                    Command=\"{Binding GenerateImageCommand}\"\n                                                    CommandParameter=\"{x:Static modelsInference:GenerateFlags.UseCurrentSeed}\"\n                                                    ToolTip.Tip=\"Generate with current seed\" />\n                                            </Grid>\n                                        </controls:Card>\n                                    </StackPanel>\n                                </Grid>\n\n                            </Tool>\n                        </ToolDock>\n\n                    </ProportionalDock>\n\n\n                    <ProportionalDockSplitter x:Name=\"Splitter2\" Id=\"Splitter2\" />\n\n                    <!--  Middle Right Pane  -->\n                    <ToolDock\n                        x:Name=\"ImageGalleryPane\"\n                        Alignment=\"Right\"\n                        Id=\"ImageGalleryPane\"\n                        Proportion=\"0.35\">\n                        <Tool\n                            x:Name=\"ImageGalleryTool\"\n                            Title=\"Image Output\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ImageGalleryTool\">\n\n                            <Grid x:CompileBindings=\"False\" DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                <controls:ImageGalleryCard Grid.Row=\"0\" DataContext=\"{Binding ImageGalleryCardViewModel}\" />\n\n                                <StackPanel\n                                    Margin=\"2,1,2,4\"\n                                    VerticalAlignment=\"Top\"\n                                    DataContext=\"{Binding OutputProgress}\"\n                                    Spacing=\"4\">\n                                    <ProgressBar\n                                        IsIndeterminate=\"{Binding IsIndeterminate}\"\n                                        IsVisible=\"{Binding IsProgressVisible}\"\n                                        Maximum=\"{Binding Maximum}\"\n                                        Value=\"{Binding Value}\" />\n\n                                    <TextBlock\n                                        Margin=\"0,10,0,0\"\n                                        IsVisible=\"{Binding IsTextVisible}\"\n                                        Text=\"{Binding Text}\"\n                                        TextAlignment=\"Center\" />\n                                </StackPanel>\n                            </Grid>\n                        </Tool>\n                    </ToolDock>\n\n                    <ProportionalDockSplitter x:Name=\"Splitter3\" Id=\"Splitter3\" />\n\n                    <!--  Right Pane  -->\n                    <ToolDock\n                        x:Name=\"ImageFolderPane\"\n                        Alignment=\"Right\"\n                        Id=\"ImageFolderPane\"\n                        Proportion=\"0.1\">\n                        <Tool\n                            x:Name=\"ImageFolderTool\"\n                            Title=\"Gallery\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ImageFolderTool\">\n\n                            <Grid x:CompileBindings=\"False\" DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                <controls:ImageFolderCard DataContext=\"{Binding ImageFolderCardViewModel}\" />\n                            </Grid>\n                        </Tool>\n                    </ToolDock>\n\n                </ProportionalDock>\n            </RootDock>\n\n        </DockControl>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Inference/InferenceImageToImageView.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls.Dock;\n\nnamespace StabilityMatrix.Avalonia.Views.Inference;\n\n[RegisterTransient<InferenceImageToImageView>]\npublic partial class InferenceImageToImageView : DockUserControlBase\n{\n    public InferenceImageToImageView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml",
    "content": "﻿<dock:DockUserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Inference.InferenceImageToVideoView\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:icons=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:vmInference=\"using:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:dock=\"clr-namespace:StabilityMatrix.Avalonia.Controls.Dock\"\n    xmlns:modelsInference=\"clr-namespace:StabilityMatrix.Avalonia.Models.Inference\"\n    xmlns:gif=\"clr-namespace:Avalonia.Gif;assembly=Avalonia.Gif\"\n    d:DataContext=\"{x:Static mocks:DesignData.InferenceImageToVideoViewModel}\"\n    d:DesignHeight=\"800\"\n    d:DesignWidth=\"1000\"\n    x:DataType=\"vmInference:InferenceImageToVideoViewModel\"\n    Focusable=\"True\"\n    mc:Ignorable=\"d\">\n\n    <Grid Margin=\"2,0,2,8\">\n        <DockControl\n            x:Name=\"Dock\"\n            InitializeFactory=\"True\"\n            InitializeLayout=\"True\">\n            <DockControl.Factory>\n                <Factory />\n            </DockControl.Factory>\n\n            <RootDock\n                x:Name=\"Root\"\n                DefaultDockable=\"{Binding #MainLayout}\"\n                Id=\"Root\"\n                IsCollapsable=\"False\">\n\n                <ProportionalDock\n                    x:Name=\"MainLayout\"\n                    Id=\"MainLayout\"\n                    Orientation=\"Horizontal\">\n                    <!--  Left Pane  -->\n                    <ProportionalDock\n                        Proportion=\"0.3\"\n                        Orientation=\"Vertical\">\n                        <ToolDock\n                            x:Name=\"SelectImagePane\"\n                            Alignment=\"Left\"\n                            Id=\"ConfigPane\"\n                            Proportion=\"0.3\">\n                            <Tool\n                                x:Name=\"SelectImageTool\"\n                                Title=\"Image Input\"\n                                x:DataType=\"Tool\"\n                                Id=\"ConfigTool\"\n                                CanClose=\"False\">\n                                <controls:SelectImageCard \n                                    DataContext=\"{Binding #Dock.((vmInference:InferenceImageToVideoViewModel)DataContext).SelectImageCardViewModel}\" />\n                            </Tool>\n                        </ToolDock>\n                        <ToolDock\n                            x:Name=\"ConfigPane\"\n                            Alignment=\"Left\"\n                            Id=\"ConfigPane\"\n                            Proportion=\"0.6\">\n                            <Tool\n                                x:Name=\"ConfigTool\"\n                                Title=\"Config\"\n                                x:DataType=\"Tool\"\n                                Id=\"ConfigTool\"\n                                CanClose=\"False\">\n                                <controls:StackCard\n                                    Opacity=\"1\"\n                                    DataContext=\"{ReflectionBinding ElementName=Dock, Path=DataContext.StackCardViewModel}\" />\n                            </Tool>\n                        </ToolDock>\n                    </ProportionalDock>\n                    <ProportionalDockSplitter x:Name=\"Splitter1\" Id=\"Splitter1\" />\n\n                    <!--  Middle Pane  -->\n                    <ProportionalDock\n                        Proportion=\"0.5\"\n                        Orientation=\"Vertical\">\n                        <ToolDock\n                            x:Name=\"ImageGalleryPane\"\n                            Alignment=\"Right\"\n                            Id=\"ImageGalleryPane\"\n                            Proportion=\"0.9\">\n                            <Tool\n                                x:Name=\"ImageGalleryTool\"\n                                Title=\"Image Output\"\n                                x:DataType=\"Tool\"\n                                Id=\"ImageGalleryTool\"\n                                CanClose=\"False\">\n\n                                <Grid\n                                    x:CompileBindings=\"False\"\n                                    DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                    <controls:ImageGalleryCard\n                                        Grid.Row=\"0\"\n                                        DataContext=\"{Binding ImageGalleryCardViewModel}\"/>\n                                    \n                                    <StackPanel\n                                        DataContext=\"{Binding OutputProgress}\"\n                                        Margin=\"2,1,2,4\"\n                                        Spacing=\"4\"\n                                        VerticalAlignment=\"Top\">\n                                        <ProgressBar\n                                            IsVisible=\"{Binding IsProgressVisible}\"\n                                            IsIndeterminate=\"{Binding IsIndeterminate}\"\n                                            Maximum=\"{Binding Maximum}\"\n                                            Value=\"{Binding Value}\" />\n\n                                        <TextBlock\n                                            Margin=\"0,10,0,0\"\n                                            IsVisible=\"{Binding IsTextVisible}\"\n                                            TextAlignment=\"Center\"\n                                            Text=\"{Binding Text}\" />\n                                    </StackPanel>\n                                </Grid>\n                            </Tool>\n                        </ToolDock>\n                        <ToolDock x:Name=\"GeneratePane\"\n                                  Alignment=\"Top\"\n                                  Id=\"ConfigPane\"\n                                  Proportion=\"0.1\">\n                            <Tool\n                                x:Name=\"GenerateTool\"\n                                Title=\"Generate\"\n                                x:DataType=\"Tool\"\n                                Id=\"GenerateTool\"\n                                CanClose=\"False\">\n                                <!--  Generate Buttons  -->\n                                <StackPanel Margin=\"0,6,0,0\"\n                                            x:CompileBindings=\"False\"\n                                            DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                    <controls:Card Padding=\"8\">\n                                        <Grid\n                                            HorizontalAlignment=\"Center\"\n                                            ColumnDefinitions=\"Auto,*,Auto\"\n                                            RowDefinitions=\"Auto,*\">\n                                            <Grid.Styles>\n                                                <Style Selector=\"Button\">\n                                                    <Setter Property=\"MinHeight\" Value=\"32\" />\n                                                </Style>\n                                            </Grid.Styles>\n\n                                            <!--  Main Generate Button  -->\n                                            <Button\n                                                x:Name=\"GenerateButton\"\n                                                Grid.Row=\"1\"\n                                                Grid.Column=\"1\"\n                                                Width=\"130\"\n                                                HorizontalAlignment=\"Stretch\"\n                                                Classes=\"accent\"\n                                                Command=\"{Binding GenerateImageCommand}\"\n                                                CommandParameter=\"{x:Static modelsInference:GenerateFlags.None}\"\n                                                IsVisible=\"{Binding !GenerateImageCommand.CanBeCanceled}\">\n                                                <Panel>\n                                                    <TextBlock IsVisible=\"{Binding #GenerateButton.IsEnabled}\"\n                                                               Text=\"Generate Image\" />\n                                                    <controls:ProgressRing IsIndeterminate=\"True\"\n                                                                           IsVisible=\"{Binding !#GenerateButton.IsEnabled}\" />\n                                                </Panel>\n                                            </Button>\n                                            <!--  Cancel Button  -->\n                                            <Button\n                                                x:Name=\"CancelButton\"\n                                                Grid.Row=\"1\"\n                                                Grid.Column=\"1\"\n                                                Width=\"130\"\n                                                HorizontalAlignment=\"Stretch\"\n                                                icons:Attached.Icon=\"fa-solid fa-stop\"\n                                                Command=\"{Binding GenerateImageCancelCommand}\"\n                                                IsVisible=\"{Binding GenerateImageCommand.CanBeCanceled}\" />\n\n                                            <Button\n                                                Grid.Row=\"1\"\n                                                Grid.Column=\"2\"\n                                                Margin=\"4,0\"\n                                                Padding=\"12,8\"\n                                                HorizontalAlignment=\"Left\"\n                                                icons:Attached.Icon=\"fa-solid fa-repeat\"\n                                                Classes=\"transparent\"\n                                                Command=\"{Binding GenerateImageCommand}\"\n                                                CommandParameter=\"{x:Static modelsInference:GenerateFlags.UseCurrentSeed}\"\n                                                ToolTip.Tip=\"Generate with current seed\" />\n                                        </Grid>\n                                    </controls:Card>\n                                </StackPanel>\n                            </Tool>\n                        </ToolDock>\n                    </ProportionalDock>\n\n                    <ProportionalDockSplitter x:Name=\"Splitter3\" Id=\"Splitter3\" />\n\n                    <!--  Right Pane  -->\n                    <ToolDock\n                        x:Name=\"ImageFolderPane\"\n                        Alignment=\"Right\"\n                        Id=\"ImageFolderPane\"\n                        Proportion=\"0.2\">\n                        <Tool\n                            x:Name=\"ImageFolderTool\"\n                            x:DataType=\"Tool\"\n                            Title=\"Gallery\"\n                            Id=\"ImageFolderTool\"\n                            CanClose=\"False\">\n\n                            <Grid\n                                x:CompileBindings=\"False\"\n                                DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                <controls:ImageFolderCard\n                                    DataContext=\"{Binding ImageFolderCardViewModel}\" />\n                            </Grid>\n                        </Tool>\n                    </ToolDock>\n\n                </ProportionalDock>\n            </RootDock>\n\n        </DockControl>\n    </Grid>\n</dock:DockUserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Inference/InferenceImageToVideoView.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls.Dock;\n\nnamespace StabilityMatrix.Avalonia.Views.Inference;\n\n[RegisterTransient<InferenceImageToVideoView>]\npublic partial class InferenceImageToVideoView : DockUserControlBase\n{\n    public InferenceImageToVideoView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Inference/InferenceImageUpscaleView.axaml",
    "content": "﻿<dock:DockUserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Inference.InferenceImageUpscaleView\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dock=\"clr-namespace:StabilityMatrix.Avalonia.Controls.Dock\"\n    xmlns:icons=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:modelsInference=\"clr-namespace:StabilityMatrix.Avalonia.Models.Inference\"\n    xmlns:vmInference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    d:DataContext=\"{x:Static mocks:DesignData.InferenceImageUpscaleViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"vmInference:InferenceImageUpscaleViewModel\"\n    mc:Ignorable=\"d\">\n\n    <Grid Margin=\"2,0,2,8\">\n        <DockControl\n            x:Name=\"Dock\"\n            InitializeFactory=\"True\"\n            InitializeLayout=\"True\">\n            <DockControl.Factory>\n                <Factory />\n            </DockControl.Factory>\n\n            <RootDock\n                x:Name=\"Root\"\n                DefaultDockable=\"{Binding #MainLayout}\"\n                Id=\"Root\"\n                IsCollapsable=\"False\">\n\n                <ProportionalDock\n                    x:Name=\"MainLayout\"\n                    Id=\"MainLayout\"\n                    Orientation=\"Horizontal\">\n\n                    <!--  Left Pane  -->\n                    <ProportionalDock \n                        x:Name=\"LeftProportionalDock\" \n                        Id=\"LeftProportionalDock\" \n                        Orientation=\"Vertical\"\n                        Proportion=\"0.2\">\n\n                        <!-- Left Upper (Image Source) Pane -->\n                        <ToolDock\n                            x:Name=\"SourceImagePane\"\n                            Alignment=\"Top\"\n                            Id=\"SourceImagePane\"\n                            Proportion=\"0.5\">\n                            <Tool\n                                x:Name=\"SourceImageTool\"\n                                Title=\"Image Input\"\n                                x:DataType=\"Tool\"\n                                CanClose=\"False\"\n                                Id=\"SourceImageTool\">\n                                <controls:SelectImageCard x:CompileBindings=\"False\" DataContext=\"{Binding ElementName=Dock, Path=DataContext.SelectImageCardViewModel}\" />\n                            </Tool>\n                        </ToolDock>\n                        \n                        <ProportionalDockSplitter Id=\"LeftProportionalDockSplitter\" />\n                        \n                        <!--  Left Lower (Config) Pane  -->\n                        <ToolDock\n                            x:Name=\"ConfigPane\"\n                            Alignment=\"Bottom\"\n                            Id=\"ConfigPane\"\n                            Proportion=\"0.5\">\n                            <Tool\n                                x:Name=\"ConfigTool\"\n                                Title=\"Config\"\n                                x:DataType=\"Tool\"\n                                CanClose=\"False\"\n                                Id=\"ConfigTool\">\n\n                                <!--  Config and buttons  -->\n                                <Grid\n                                    x:CompileBindings=\"False\"\n                                    DataContext=\"{Binding ElementName=Dock, Path=DataContext}\"\n                                    Opacity=\"1\"\n                                    RowDefinitions=\"*,Auto,Auto\">\n                                    <!--  Config options  -->\n                                    <controls:StackCard DataContext=\"{ReflectionBinding ElementName=Dock, Path=DataContext.StackCardViewModel}\" Opacity=\"1\" />\n\n                                    <!--  Generate Buttons  -->\n                                    <StackPanel Grid.Row=\"2\" Margin=\"0,6,0,0\">\n                                        <controls:Card Padding=\"8\">\n                                            <Grid\n                                                HorizontalAlignment=\"Center\"\n                                                ColumnDefinitions=\"Auto,*,Auto\"\n                                                RowDefinitions=\"Auto,*\">\n                                                <!--  Main Generate Button  -->\n                                                <Button\n                                                    x:Name=\"GenerateButton\"\n                                                    Grid.Row=\"1\"\n                                                    Grid.Column=\"1\"\n                                                    Width=\"130\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    Classes=\"accent\"\n                                                    HotKey=\"Ctrl+Enter\"\n                                                    ToolTip.Tip=\"{Binding $self.HotKey}\"\n                                                    Command=\"{Binding GenerateImageCommand}\"\n                                                    CommandParameter=\"{x:Static modelsInference:GenerateFlags.None}\"\n                                                    IsVisible=\"{Binding !GenerateImageCommand.CanBeCanceled}\">\n                                                    <Panel>\n                                                        <TextBlock IsVisible=\"{Binding #GenerateButton.IsEnabled}\" Text=\"Generate Image\" />\n                                                        <controls:ProgressRing IsIndeterminate=\"True\" IsVisible=\"{Binding !#GenerateButton.IsEnabled}\" />\n                                                    </Panel>\n                                                </Button>\n                                                <!--  Cancel Button  -->\n                                                <Button\n                                                    x:Name=\"CancelButton\"\n                                                    Grid.Row=\"1\"\n                                                    Grid.Column=\"1\"\n                                                    Width=\"130\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    icons:Attached.Icon=\"fa-solid fa-stop\"\n                                                    Command=\"{Binding GenerateImageCancelCommand}\"\n                                                    IsVisible=\"{Binding GenerateImageCommand.CanBeCanceled}\" />\n                                            </Grid>\n                                        </controls:Card>\n                                    </StackPanel>\n                                </Grid>\n                            </Tool>\n                        </ToolDock>\n\n                    </ProportionalDock>\n\n                    <ProportionalDockSplitter x:Name=\"LeftSplitter\" Id=\"LeftSplitter\" />\n\n                    <!--  Middle Right Pane  -->\n                    <ToolDock\n                        x:Name=\"ImageGalleryPane\"\n                        Alignment=\"Right\"\n                        Id=\"ImageGalleryPane\"\n                        Proportion=\"0.6\">\n                        <Tool\n                            x:Name=\"ImageGalleryTool\"\n                            Title=\"Output Image\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ImageGalleryTool\">\n\n                            <Grid x:CompileBindings=\"False\" DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                <StackPanel\n                                    Margin=\"2,1,2,4\"\n                                    VerticalAlignment=\"Top\"\n                                    DataContext=\"{Binding OutputProgress}\"\n                                    Spacing=\"4\">\n                                    <ProgressBar\n                                        IsVisible=\"{Binding IsProgressVisible}\"\n                                        IsIndeterminate=\"{Binding IsIndeterminate}\"\n                                        Maximum=\"{Binding Maximum}\"\n                                        Value=\"{Binding Value}\" />\n                                    <TextBlock\n                                        IsVisible=\"{Binding IsTextVisible}\"\n                                        Text=\"{Binding Text}\"\n                                        TextAlignment=\"Center\" />\n                                </StackPanel>\n\n                                <controls:ImageGalleryCard Grid.Row=\"0\" DataContext=\"{Binding ImageGalleryCardViewModel}\" />\n                            </Grid>\n                        </Tool>\n                    </ToolDock>\n\n                    <ProportionalDockSplitter x:Name=\"RightSplitter\" Id=\"RightSplitter\" />\n\n                    <!--  Right Pane  -->\n                    <ToolDock\n                        x:Name=\"ImageFolderPane\"\n                        Alignment=\"Right\"\n                        Id=\"ImageFolderPane\"\n                        Proportion=\"0.1\">\n                        <Tool\n                            x:Name=\"ImageFolderTool\"\n                            Title=\"Gallery\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ImageFolderTool\">\n\n                            <Grid x:CompileBindings=\"False\" DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                <controls:ImageFolderCard DataContext=\"{Binding ImageFolderCardViewModel}\" />\n                            </Grid>\n                        </Tool>\n                    </ToolDock>\n\n                </ProportionalDock>\n            </RootDock>\n\n        </DockControl>\n    </Grid>\n\n</dock:DockUserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Inference/InferenceImageUpscaleView.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls.Dock;\n\nnamespace StabilityMatrix.Avalonia.Views.Inference;\n\n[RegisterTransient<InferenceImageUpscaleView>]\npublic partial class InferenceImageUpscaleView : DockUserControlBase\n{\n    public InferenceImageUpscaleView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Inference/InferenceTextToImageView.axaml",
    "content": "﻿<dock:DockUserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Inference.InferenceTextToImageView\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dock=\"clr-namespace:StabilityMatrix.Avalonia.Controls.Dock\"\n    xmlns:icons=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:modelsInference=\"clr-namespace:StabilityMatrix.Avalonia.Models.Inference\"\n    xmlns:vmInference=\"using:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    d:DataContext=\"{x:Static mocks:DesignData.InferenceTextToImageViewModel}\"\n    d:DesignHeight=\"800\"\n    d:DesignWidth=\"1000\"\n    x:DataType=\"vmInference:InferenceTextToImageViewModel\"\n    Focusable=\"True\"\n    mc:Ignorable=\"d\">\n\n    <Grid Margin=\"2,0,2,8\">\n        <DockControl\n            x:Name=\"Dock\"\n            InitializeFactory=\"True\"\n            InitializeLayout=\"True\">\n            <DockControl.Factory>\n                <Factory />\n            </DockControl.Factory>\n\n            <RootDock\n                x:Name=\"Root\"\n                DefaultDockable=\"{Binding #MainLayout}\"\n                Id=\"Root\"\n                IsCollapsable=\"False\">\n\n                <ProportionalDock\n                    x:Name=\"MainLayout\"\n                    Id=\"MainLayout\"\n                    Orientation=\"Horizontal\">\n\n                    <!--  Left Pane  -->\n                    <ToolDock\n                        x:Name=\"ConfigPane\"\n                        Alignment=\"Left\"\n                        Id=\"ConfigPane\"\n                        Proportion=\"0.2\">\n                        <Tool\n                            x:Name=\"ConfigTool\"\n                            Title=\"Config\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ConfigTool\">\n                            <controls:StackCard DataContext=\"{ReflectionBinding ElementName=Dock, Path=DataContext.StackCardViewModel}\" Opacity=\"1\" />\n                        </Tool>\n                    </ToolDock>\n\n                    <ProportionalDockSplitter x:Name=\"Splitter1\" Id=\"Splitter1\" />\n\n                    <ProportionalDock\n                        x:Name=\"PromptLayout\"\n                        Id=\"PromptLayout\"\n                        Orientation=\"Vertical\">\n                        <!--  Prompt Pane  -->\n                        <ToolDock\n                            x:Name=\"PromptPane\"\n                            Alignment=\"Left\"\n                            Id=\"PromptPane\"\n                            Proportion=\"0.9\">\n                            <Tool\n                                x:Name=\"PromptTool\"\n                                Title=\"Prompt\"\n                                x:DataType=\"Tool\"\n                                CanClose=\"False\"\n                                Id=\"PromptTool\">\n\n                                <!--  Prompt and buttons  -->\n                                <Grid\n                                    x:CompileBindings=\"False\"\n                                    DataContext=\"{Binding ElementName=Dock, Path=DataContext}\"\n                                    Opacity=\"1\"\n                                    RowDefinitions=\"*,Auto\">\n                                    <!--  Prompt card  -->\n                                    <controls:PromptCard VerticalAlignment=\"Stretch\" DataContext=\"{Binding PromptCardViewModel}\" />\n                                </Grid>\n                            </Tool>\n                        </ToolDock>\n\n                        <!--  Generate Dock  -->\n                        <ToolDock\n                            x:Name=\"GeneratePane\"\n                            Alignment=\"Left\"\n                            Id=\"PromptPane\"\n                            Proportion=\"0.1\">\n                            <Tool\n                                x:Name=\"GenerateTool\"\n                                Title=\"Generate\"\n                                x:DataType=\"Tool\"\n                                CanClose=\"False\"\n                                Id=\"GenerateTool\">\n                                <!--  Generate Buttons  -->\n\n                                <controls:Card\n                                    Margin=\"0,6,0,0\"\n                                    Padding=\"8\"\n                                    x:CompileBindings=\"False\"\n                                    DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                    <Grid\n                                        HorizontalAlignment=\"Center\"\n                                        ColumnDefinitions=\"Auto,*,Auto\"\n                                        RowDefinitions=\"Auto,*\">\n                                        <Grid.Styles>\n                                            <Style Selector=\"Button\">\n                                                <Setter Property=\"MinHeight\" Value=\"32\" />\n                                            </Style>\n                                        </Grid.Styles>\n                                        <Button\n                                            Grid.Row=\"1\"\n                                            Margin=\"4,0\"\n                                            Padding=\"12,8\"\n                                            HorizontalAlignment=\"Left\"\n                                            icons:Attached.Icon=\"fa-solid fa-magnifying-glass-plus\"\n                                            Classes=\"transparent\"\n                                            Command=\"{Binding GenerateImageCommand}\"\n                                            CommandParameter=\"{x:Static modelsInference:GenerateFlags.HiresFixAndUseCurrentSeed}\"\n                                            ToolTip.Tip=\"Generate with Hires Scaling\" />\n\n                                        <!--  Main Generate Button  -->\n                                        <Button\n                                            x:Name=\"GenerateButton\"\n                                            Grid.Row=\"1\"\n                                            Grid.Column=\"1\"\n                                            Width=\"130\"\n                                            HorizontalAlignment=\"Stretch\"\n                                            Classes=\"accent\"\n                                            Command=\"{Binding GenerateImageCommand}\"\n                                            CommandParameter=\"{x:Static modelsInference:GenerateFlags.None}\"\n                                            HotKey=\"Ctrl+Enter\"\n                                            IsVisible=\"{Binding !GenerateImageCommand.CanBeCanceled}\"\n                                            ToolTip.Tip=\"{Binding $self.HotKey}\">\n                                            <Panel>\n                                                <TextBlock IsVisible=\"{Binding #GenerateButton.IsEnabled}\" Text=\"Generate Image\" />\n                                                <controls:ProgressRing IsIndeterminate=\"True\" IsVisible=\"{Binding !#GenerateButton.IsEnabled}\" />\n                                            </Panel>\n                                        </Button>\n                                        <!--  Cancel Button  -->\n                                        <Button\n                                            x:Name=\"CancelButton\"\n                                            Grid.Row=\"1\"\n                                            Grid.Column=\"1\"\n                                            Width=\"130\"\n                                            HorizontalAlignment=\"Stretch\"\n                                            icons:Attached.Icon=\"fa-solid fa-stop\"\n                                            Command=\"{Binding GenerateImageCancelCommand}\"\n                                            IsVisible=\"{Binding GenerateImageCommand.CanBeCanceled}\" />\n\n                                        <Button\n                                            Grid.Row=\"1\"\n                                            Grid.Column=\"2\"\n                                            Margin=\"4,0\"\n                                            Padding=\"12,8\"\n                                            HorizontalAlignment=\"Left\"\n                                            icons:Attached.Icon=\"fa-solid fa-repeat\"\n                                            Classes=\"transparent\"\n                                            Command=\"{Binding GenerateImageCommand}\"\n                                            CommandParameter=\"{x:Static modelsInference:GenerateFlags.UseCurrentSeed}\"\n                                            ToolTip.Tip=\"Generate with current seed\" />\n                                    </Grid>\n                                </controls:Card>\n                            </Tool>\n                        </ToolDock>\n                    </ProportionalDock>\n\n                    <ProportionalDockSplitter x:Name=\"Splitter2\" Id=\"Splitter2\" />\n\n                    <!--  Middle Right Pane  -->\n                    <ToolDock\n                        x:Name=\"ImageGalleryPane\"\n                        Alignment=\"Right\"\n                        Id=\"ImageGalleryPane\"\n                        Proportion=\"0.35\">\n                        <Tool\n                            x:Name=\"ImageGalleryTool\"\n                            Title=\"Image Output\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ImageGalleryTool\">\n\n                            <Grid x:CompileBindings=\"False\" DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                <controls:ImageGalleryCard Grid.Row=\"0\" DataContext=\"{Binding ImageGalleryCardViewModel}\" />\n\n                                <StackPanel\n                                    Margin=\"2,1,2,4\"\n                                    VerticalAlignment=\"Top\"\n                                    DataContext=\"{Binding OutputProgress}\"\n                                    Spacing=\"4\">\n                                    <ProgressBar\n                                        IsIndeterminate=\"{Binding IsIndeterminate}\"\n                                        IsVisible=\"{Binding IsProgressVisible}\"\n                                        Maximum=\"{Binding Maximum}\"\n                                        Value=\"{Binding Value}\" />\n\n                                    <TextBlock\n                                        Margin=\"0,10,0,0\"\n                                        IsVisible=\"{Binding IsTextVisible}\"\n                                        Text=\"{Binding Text}\"\n                                        TextAlignment=\"Center\" />\n                                </StackPanel>\n                            </Grid>\n                        </Tool>\n                    </ToolDock>\n\n                    <ProportionalDockSplitter x:Name=\"Splitter3\" Id=\"Splitter3\" />\n\n                    <!--  Right Pane  -->\n                    <ToolDock\n                        x:Name=\"ImageFolderPane\"\n                        Alignment=\"Right\"\n                        Id=\"ImageFolderPane\"\n                        Proportion=\"0.1\">\n                        <Tool\n                            x:Name=\"ImageFolderTool\"\n                            Title=\"Gallery\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ImageFolderTool\">\n\n                            <Grid x:CompileBindings=\"False\" DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                <controls:ImageFolderCard DataContext=\"{Binding ImageFolderCardViewModel}\" />\n                            </Grid>\n                        </Tool>\n                    </ToolDock>\n\n\n                    <!--  bottom pane  -->\n                    <!--  <ToolDock  -->\n                    <!--  x:Name=\"LoraGallery\"  -->\n                    <!--  Alignment=\"Bottom\"  -->\n                    <!--  CanClose=\"False\"  -->\n                    <!--  Id=\"LoraGallery\"  -->\n                    <!--  IsCollapsable=\"True\"  -->\n                    <!--  IsExpanded=\"False\">  -->\n                    <!--  <Tool  -->\n                    <!--  x:Name=\"LoraGalleryTool\"  -->\n                    <!--  Title=\"Lora Gallery\"  -->\n                    <!--  x:DataType=\"Tool\"  -->\n                    <!--  CanClose=\"True\"  -->\n                    <!--  Id=\"LoraGalleryTool\" />  -->\n                    <!-- </ToolDock> -->\n\n                </ProportionalDock>\n            </RootDock>\n\n        </DockControl>\n    </Grid>\n</dock:DockUserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Inference/InferenceTextToImageView.axaml.cs",
    "content": "﻿using Avalonia;\nusing Avalonia.Controls;\nusing Dock.Avalonia.Controls;\nusing Dock.Model.Avalonia.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls.Dock;\n\nnamespace StabilityMatrix.Avalonia.Views.Inference;\n\n[RegisterTransient<InferenceTextToImageView>]\npublic partial class InferenceTextToImageView : DockUserControlBase\n{\n    private bool hasMovedLoraDock = false;\n\n    public InferenceTextToImageView()\n    {\n        InitializeComponent();\n    }\n\n    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)\n    {\n        base.OnAttachedToVisualTree(e);\n\n        // TODO: this\n        // get LoraGallery ToolDock\n        // var dock = this.FindControl<DockControl>(\"Dock\");\n        // var dockable = this.Find<Tool>(\"LoraGalleryTool\");\n        // if (dockable == null || hasMovedLoraDock)\n        //     return;\n        //\n        // dock?.Factory?.PinDockable(dockable);\n        // hasMovedLoraDock = true;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Inference/InferenceWanImageToVideoView.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Inference.InferenceWanImageToVideoView\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:inference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:modelsInference=\"clr-namespace:StabilityMatrix.Avalonia.Models.Inference\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    d:DataContext=\"{x:Static mocks:DesignData.InferenceWanImageToVideoViewModel}\"\n    d:DesignHeight=\"750\"\n    d:DesignWidth=\"1100\"\n    x:DataType=\"inference:InferenceWanImageToVideoViewModel\"\n    mc:Ignorable=\"d\">\n    <Grid Margin=\"2,0,2,8\">\n        <DockControl\n            x:Name=\"Dock\"\n            InitializeFactory=\"True\"\n            InitializeLayout=\"True\">\n            <DockControl.Factory>\n                <Factory />\n            </DockControl.Factory>\n\n            <RootDock\n                x:Name=\"Root\"\n                DefaultDockable=\"{Binding #MainLayout}\"\n                Id=\"Root\"\n                IsCollapsable=\"False\">\n\n                <ProportionalDock\n                    x:Name=\"MainLayout\"\n                    Id=\"MainLayout\"\n                    Orientation=\"Horizontal\">\n\n                    <!--  Left Pane  -->\n                    <ToolDock\n                        x:Name=\"ConfigPane\"\n                        Alignment=\"Left\"\n                        Id=\"ConfigPane\"\n                        Proportion=\"0.2\">\n                        <Tool\n                            x:Name=\"ConfigTool\"\n                            Title=\"Config\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ConfigTool\">\n                            <controls:StackCard DataContext=\"{ReflectionBinding ElementName=Dock, Path=DataContext.StackCardViewModel}\" Opacity=\"1\" />\n                        </Tool>\n                    </ToolDock>\n\n                    <ProportionalDockSplitter x:Name=\"Splitter1\" Id=\"Splitter1\" />\n\n                    <!--  Prompt Pane  -->\n                    <ProportionalDock\n                        x:Name=\"CenterProportionalDock\"\n                        Id=\"CenterProportionalDock\"\n                        Orientation=\"Vertical\">\n\n                        <!--  Center Top (Image)  -->\n\n                        <ToolDock\n                            x:Name=\"ImagePane\"\n                            Alignment=\"Left\"\n                            Id=\"ImagePane\"\n                            Proportion=\"0.25\">\n                            <Tool\n                                x:Name=\"ImageTool\"\n                                Title=\"Image\"\n                                x:DataType=\"Tool\"\n                                CanClose=\"False\"\n                                Id=\"ImageTool\">\n\n                                <!--  Prompt and buttons  -->\n                                <Grid\n                                    x:CompileBindings=\"False\"\n                                    DataContext=\"{Binding ElementName=Dock, Path=DataContext}\"\n                                    Opacity=\"1\"\n                                    RowDefinitions=\"*\">\n                                    <!--  Prompt card  -->\n                                    <controls:SelectImageCard x:CompileBindings=\"False\" DataContext=\"{Binding SelectImageCardViewModel}\" />\n                                </Grid>\n\n                            </Tool>\n                        </ToolDock>\n\n                        <ProportionalDockSplitter />\n\n                        <!--  Center Bottom (Prompt)  -->\n                        <ToolDock\n                            x:Name=\"PromptPane\"\n                            Alignment=\"Left\"\n                            Id=\"PromptPane\"\n                            Proportion=\"0.25\">\n                            <Tool\n                                x:Name=\"PromptTool\"\n                                Title=\"Prompt\"\n                                x:DataType=\"Tool\"\n                                CanClose=\"False\"\n                                Id=\"PromptTool\">\n\n                                <!--  Prompt and buttons  -->\n                                <Grid\n                                    x:CompileBindings=\"False\"\n                                    DataContext=\"{Binding ElementName=Dock, Path=DataContext}\"\n                                    Opacity=\"1\"\n                                    RowDefinitions=\"*,Auto,Auto\">\n                                    <!--  Prompt card  -->\n                                    <controls:PromptCard DataContext=\"{Binding PromptCardViewModel}\" />\n\n                                    <!--  Generate Buttons  -->\n                                    <StackPanel Grid.Row=\"2\" Margin=\"0,6,0,0\">\n                                        <controls:Card Padding=\"8\">\n                                            <Grid\n                                                HorizontalAlignment=\"Center\"\n                                                ColumnDefinitions=\"Auto,*,Auto\"\n                                                RowDefinitions=\"Auto,*\">\n                                                <Grid.Styles>\n                                                    <Style Selector=\"Button\">\n                                                        <Setter Property=\"MinHeight\" Value=\"32\" />\n                                                    </Style>\n                                                </Grid.Styles>\n                                                <Button\n                                                    Grid.Row=\"1\"\n                                                    Margin=\"4,0\"\n                                                    Padding=\"12,8\"\n                                                    HorizontalAlignment=\"Left\"\n                                                    avalonia:Attached.Icon=\"fa-solid fa-magnifying-glass-plus\"\n                                                    Classes=\"transparent\"\n                                                    Command=\"{Binding GenerateImageCommand}\"\n                                                    CommandParameter=\"{x:Static modelsInference:GenerateFlags.HiresFixAndUseCurrentSeed}\"\n                                                    ToolTip.Tip=\"Generate with Hires Scaling\" />\n\n                                                <!--  Main Generate Button  -->\n                                                <Button\n                                                    x:Name=\"GenerateButton\"\n                                                    Grid.Row=\"1\"\n                                                    Grid.Column=\"1\"\n                                                    Width=\"130\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    Classes=\"accent\"\n                                                    Command=\"{Binding GenerateImageCommand}\"\n                                                    CommandParameter=\"{x:Static modelsInference:GenerateFlags.None}\"\n                                                    HotKey=\"Ctrl+Enter\"\n                                                    IsVisible=\"{Binding !GenerateImageCommand.CanBeCanceled}\"\n                                                    ToolTip.Tip=\"{Binding $self.HotKey}\">\n                                                    <Panel>\n                                                        <TextBlock IsVisible=\"{Binding #GenerateButton.IsEnabled}\" Text=\"Generate Image\" />\n                                                        <controls:ProgressRing IsIndeterminate=\"True\" IsVisible=\"{Binding !#GenerateButton.IsEnabled}\" />\n                                                    </Panel>\n                                                </Button>\n                                                <!--  Cancel Button  -->\n                                                <Button\n                                                    x:Name=\"CancelButton\"\n                                                    Grid.Row=\"1\"\n                                                    Grid.Column=\"1\"\n                                                    Width=\"130\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    avalonia:Attached.Icon=\"fa-solid fa-stop\"\n                                                    Command=\"{Binding GenerateImageCancelCommand}\"\n                                                    IsVisible=\"{Binding GenerateImageCommand.CanBeCanceled}\" />\n\n                                                <Button\n                                                    Grid.Row=\"1\"\n                                                    Grid.Column=\"2\"\n                                                    Margin=\"4,0\"\n                                                    Padding=\"12,8\"\n                                                    HorizontalAlignment=\"Left\"\n                                                    avalonia:Attached.Icon=\"fa-solid fa-repeat\"\n                                                    Classes=\"transparent\"\n                                                    Command=\"{Binding GenerateImageCommand}\"\n                                                    CommandParameter=\"{x:Static modelsInference:GenerateFlags.UseCurrentSeed}\"\n                                                    ToolTip.Tip=\"Generate with current seed\" />\n                                            </Grid>\n                                        </controls:Card>\n                                    </StackPanel>\n                                </Grid>\n\n                            </Tool>\n                        </ToolDock>\n\n                    </ProportionalDock>\n\n\n                    <ProportionalDockSplitter x:Name=\"Splitter2\" Id=\"Splitter2\" />\n\n                    <!--  Middle Right Pane  -->\n                    <ToolDock\n                        x:Name=\"ImageGalleryPane\"\n                        Alignment=\"Right\"\n                        Id=\"ImageGalleryPane\"\n                        Proportion=\"0.35\">\n                        <Tool\n                            x:Name=\"ImageGalleryTool\"\n                            Title=\"Image Output\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ImageGalleryTool\">\n\n                            <Grid x:CompileBindings=\"False\" DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                <controls:ImageGalleryCard Grid.Row=\"0\" DataContext=\"{Binding ImageGalleryCardViewModel}\" />\n\n                                <StackPanel\n                                    Margin=\"2,1,2,4\"\n                                    VerticalAlignment=\"Top\"\n                                    DataContext=\"{Binding OutputProgress}\"\n                                    Spacing=\"4\">\n                                    <ProgressBar\n                                        IsIndeterminate=\"{Binding IsIndeterminate}\"\n                                        IsVisible=\"{Binding IsProgressVisible}\"\n                                        Maximum=\"{Binding Maximum}\"\n                                        Value=\"{Binding Value}\" />\n\n                                    <TextBlock\n                                        Margin=\"0,10,0,0\"\n                                        IsVisible=\"{Binding IsTextVisible}\"\n                                        Text=\"{Binding Text}\"\n                                        TextAlignment=\"Center\" />\n                                </StackPanel>\n                            </Grid>\n                        </Tool>\n                    </ToolDock>\n\n                    <ProportionalDockSplitter x:Name=\"Splitter3\" Id=\"Splitter3\" />\n\n                    <!--  Right Pane  -->\n                    <ToolDock\n                        x:Name=\"ImageFolderPane\"\n                        Alignment=\"Right\"\n                        Id=\"ImageFolderPane\"\n                        Proportion=\"0.1\">\n                        <Tool\n                            x:Name=\"ImageFolderTool\"\n                            Title=\"Gallery\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ImageFolderTool\">\n\n                            <Grid x:CompileBindings=\"False\" DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                <controls:ImageFolderCard DataContext=\"{Binding ImageFolderCardViewModel}\" />\n                            </Grid>\n                        </Tool>\n                    </ToolDock>\n\n                </ProportionalDock>\n            </RootDock>\n\n        </DockControl>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Inference/InferenceWanImageToVideoView.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls.Dock;\n\nnamespace StabilityMatrix.Avalonia.Views.Inference;\n\n[RegisterTransient<InferenceWanImageToVideoView>]\npublic partial class InferenceWanImageToVideoView : DockUserControlBase\n{\n    public InferenceWanImageToVideoView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Inference/InferenceWanTextToVideoView.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Inference.InferenceWanTextToVideoView\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:inference=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:modelsInference=\"clr-namespace:StabilityMatrix.Avalonia.Models.Inference\"\n    d:DataContext=\"{x:Static mocks:DesignData.InferenceWanTextToVideoViewModel}\"\n    d:DesignHeight=\"750\"\n    d:DesignWidth=\"1100\"\n    x:DataType=\"inference:InferenceWanTextToVideoViewModel\"\n    mc:Ignorable=\"d\">\n    <Grid Margin=\"2,0,2,8\">\n        <DockControl\n            x:Name=\"Dock\"\n            InitializeFactory=\"True\"\n            InitializeLayout=\"True\">\n            <DockControl.Factory>\n                <Factory />\n            </DockControl.Factory>\n\n            <RootDock\n                x:Name=\"Root\"\n                DefaultDockable=\"{Binding #MainLayout}\"\n                Id=\"Root\"\n                IsCollapsable=\"False\">\n\n                <ProportionalDock\n                    x:Name=\"MainLayout\"\n                    Id=\"MainLayout\"\n                    Orientation=\"Horizontal\">\n\n                    <!--  Left Pane  -->\n                    <ToolDock\n                        x:Name=\"ConfigPane\"\n                        Alignment=\"Left\"\n                        Id=\"ConfigPane\"\n                        Proportion=\"0.2\">\n                        <Tool\n                            x:Name=\"ConfigTool\"\n                            Title=\"Config\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ConfigTool\">\n                            <controls:StackCard DataContext=\"{ReflectionBinding ElementName=Dock, Path=DataContext.StackCardViewModel}\" Opacity=\"1\" />\n                        </Tool>\n                    </ToolDock>\n\n                    <ProportionalDockSplitter x:Name=\"Splitter1\" Id=\"Splitter1\" />\n\n                    <!--  Prompt Pane  -->\n                    <ProportionalDock\n                        x:Name=\"CenterProportionalDock\"\n                        Id=\"CenterProportionalDock\"\n                        Orientation=\"Vertical\">\n\n                        <ToolDock\n                            x:Name=\"PromptPane\"\n                            Alignment=\"Left\"\n                            Id=\"PromptPane\"\n                            Proportion=\"0.25\">\n                            <Tool\n                                x:Name=\"PromptTool\"\n                                Title=\"Prompt\"\n                                x:DataType=\"Tool\"\n                                CanClose=\"False\"\n                                Id=\"PromptTool\">\n\n                                <!--  Prompt and buttons  -->\n                                <Grid\n                                    x:CompileBindings=\"False\"\n                                    DataContext=\"{Binding ElementName=Dock, Path=DataContext}\"\n                                    Opacity=\"1\"\n                                    RowDefinitions=\"*,Auto,Auto\">\n                                    <!--  Prompt card  -->\n                                    <controls:PromptCard DataContext=\"{Binding PromptCardViewModel}\" />\n\n                                    <!--  Generate Buttons  -->\n                                    <StackPanel Grid.Row=\"2\" Margin=\"0,6,0,0\">\n                                        <controls:Card Padding=\"8\">\n                                            <Grid\n                                                HorizontalAlignment=\"Center\"\n                                                ColumnDefinitions=\"Auto,*,Auto\"\n                                                RowDefinitions=\"Auto,*\">\n                                                <Grid.Styles>\n                                                    <Style Selector=\"Button\">\n                                                        <Setter Property=\"MinHeight\" Value=\"32\" />\n                                                    </Style>\n                                                </Grid.Styles>\n                                                <Button\n                                                    Grid.Row=\"1\"\n                                                    Margin=\"4,0\"\n                                                    Padding=\"12,8\"\n                                                    HorizontalAlignment=\"Left\"\n                                                    avalonia:Attached.Icon=\"fa-solid fa-magnifying-glass-plus\"\n                                                    Classes=\"transparent\"\n                                                    Command=\"{Binding GenerateImageCommand}\"\n                                                    CommandParameter=\"{x:Static modelsInference:GenerateFlags.HiresFixAndUseCurrentSeed}\"\n                                                    ToolTip.Tip=\"Generate with Hires Scaling\" />\n\n                                                <!--  Main Generate Button  -->\n                                                <Button\n                                                    x:Name=\"GenerateButton\"\n                                                    Grid.Row=\"1\"\n                                                    Grid.Column=\"1\"\n                                                    Width=\"130\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    Classes=\"accent\"\n                                                    Command=\"{Binding GenerateImageCommand}\"\n                                                    CommandParameter=\"{x:Static modelsInference:GenerateFlags.None}\"\n                                                    HotKey=\"Ctrl+Enter\"\n                                                    IsVisible=\"{Binding !GenerateImageCommand.CanBeCanceled}\"\n                                                    ToolTip.Tip=\"{Binding $self.HotKey}\">\n                                                    <Panel>\n                                                        <TextBlock IsVisible=\"{Binding #GenerateButton.IsEnabled}\" Text=\"Generate Image\" />\n                                                        <controls:ProgressRing IsIndeterminate=\"True\" IsVisible=\"{Binding !#GenerateButton.IsEnabled}\" />\n                                                    </Panel>\n                                                </Button>\n                                                <!--  Cancel Button  -->\n                                                <Button\n                                                    x:Name=\"CancelButton\"\n                                                    Grid.Row=\"1\"\n                                                    Grid.Column=\"1\"\n                                                    Width=\"130\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    avalonia:Attached.Icon=\"fa-solid fa-stop\"\n                                                    Command=\"{Binding GenerateImageCancelCommand}\"\n                                                    IsVisible=\"{Binding GenerateImageCommand.CanBeCanceled}\" />\n\n                                                <Button\n                                                    Grid.Row=\"1\"\n                                                    Grid.Column=\"2\"\n                                                    Margin=\"4,0\"\n                                                    Padding=\"12,8\"\n                                                    HorizontalAlignment=\"Left\"\n                                                    avalonia:Attached.Icon=\"fa-solid fa-repeat\"\n                                                    Classes=\"transparent\"\n                                                    Command=\"{Binding GenerateImageCommand}\"\n                                                    CommandParameter=\"{x:Static modelsInference:GenerateFlags.UseCurrentSeed}\"\n                                                    ToolTip.Tip=\"Generate with current seed\" />\n                                            </Grid>\n                                        </controls:Card>\n                                    </StackPanel>\n                                </Grid>\n\n                            </Tool>\n                        </ToolDock>\n\n                    </ProportionalDock>\n\n\n                    <ProportionalDockSplitter x:Name=\"Splitter2\" Id=\"Splitter2\" />\n\n                    <!--  Middle Right Pane  -->\n                    <ToolDock\n                        x:Name=\"ImageGalleryPane\"\n                        Alignment=\"Right\"\n                        Id=\"ImageGalleryPane\"\n                        Proportion=\"0.35\">\n                        <Tool\n                            x:Name=\"ImageGalleryTool\"\n                            Title=\"Image Output\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ImageGalleryTool\">\n\n                            <Grid x:CompileBindings=\"False\" DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                <controls:ImageGalleryCard Grid.Row=\"0\" DataContext=\"{Binding ImageGalleryCardViewModel}\" />\n\n                                <StackPanel\n                                    Margin=\"2,1,2,4\"\n                                    VerticalAlignment=\"Top\"\n                                    DataContext=\"{Binding OutputProgress}\"\n                                    Spacing=\"4\">\n                                    <ProgressBar\n                                        IsIndeterminate=\"{Binding IsIndeterminate}\"\n                                        IsVisible=\"{Binding IsProgressVisible}\"\n                                        Maximum=\"{Binding Maximum}\"\n                                        Value=\"{Binding Value}\" />\n\n                                    <TextBlock\n                                        Margin=\"0,10,0,0\"\n                                        IsVisible=\"{Binding IsTextVisible}\"\n                                        Text=\"{Binding Text}\"\n                                        TextAlignment=\"Center\" />\n                                </StackPanel>\n                            </Grid>\n                        </Tool>\n                    </ToolDock>\n\n                    <ProportionalDockSplitter x:Name=\"Splitter3\" Id=\"Splitter3\" />\n\n                    <!--  Right Pane  -->\n                    <ToolDock\n                        x:Name=\"ImageFolderPane\"\n                        Alignment=\"Right\"\n                        Id=\"ImageFolderPane\"\n                        Proportion=\"0.1\">\n                        <Tool\n                            x:Name=\"ImageFolderTool\"\n                            Title=\"Gallery\"\n                            x:DataType=\"Tool\"\n                            CanClose=\"False\"\n                            Id=\"ImageFolderTool\">\n\n                            <Grid x:CompileBindings=\"False\" DataContext=\"{Binding ElementName=Dock, Path=DataContext}\">\n                                <controls:ImageFolderCard DataContext=\"{Binding ImageFolderCardViewModel}\" />\n                            </Grid>\n                        </Tool>\n                    </ToolDock>\n\n                </ProportionalDock>\n            </RootDock>\n\n        </DockControl>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Inference/InferenceWanTextToVideoView.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls.Dock;\n\nnamespace StabilityMatrix.Avalonia.Views.Inference;\n\n[RegisterTransient<InferenceWanTextToVideoView>]\npublic partial class InferenceWanTextToVideoView : DockUserControlBase\n{\n    public InferenceWanTextToVideoView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/InferencePage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.InferencePage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:base=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Base\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:fluentIcons=\"using:FluentIcons.Avalonia.Fluent\"\n    xmlns:icons=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    xmlns:inference=\"clr-namespace:StabilityMatrix.Core.Models.Inference;assembly=StabilityMatrix.Core\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    xmlns:vmInference=\"using:StabilityMatrix.Avalonia.ViewModels.Inference\"\n    d:DataContext=\"{x:Static mocks:DesignData.InferenceViewModel}\"\n    d:DesignHeight=\"650\"\n    d:DesignWidth=\"1000\"\n    x:DataType=\"vm:InferenceViewModel\"\n    mc:Ignorable=\"d\">\n\n    <controls:UserControlBase.KeyBindings>\n        <KeyBinding Command=\"{Binding MenuOpenProjectCommand}\" Gesture=\"Ctrl+O\" />\n        <KeyBinding Command=\"{Binding MenuSaveCommand}\" Gesture=\"Ctrl+S\" />\n        <KeyBinding Command=\"{Binding MenuSaveAsCommand}\" Gesture=\"Ctrl+Shift+S\" />\n        <KeyBinding Command=\"{Binding GoForwardTabWithLoopingCommand}\" Gesture=\"Ctrl+Tab\" />\n        <KeyBinding Command=\"{Binding GoBackwardsTabWithLoopingCommand}\" Gesture=\"Ctrl+Shift+Tab\" />\n    </controls:UserControlBase.KeyBindings>\n\n    <controls:UserControlBase.Resources>\n        <converters:UriStringConverter x:Key=\"UriStringConverter\" />\n        <ui:CommandBarFlyout x:Key=\"AddTabFlyout\" Placement=\"Right\">\n            <!-- Note: unlike a regular CommandBar, primary items can be set as the xml content and don't need\n                   to be wrapped in a <ui:CommandBarFlyout.PrimaryCommands> tag\n            -->\n            <!--<ui:CommandBarButton Label=\"Text to Image\" ToolTip.Tip=\"Text to Image\">\n                <ui:CommandBarButton.IconSource>\n                    <controls:FASymbolIconSource Symbol=\"fa-solid fa-bolt\"/>\n                </ui:CommandBarButton.IconSource>\n            </ui:CommandBarButton>\n            <ui:CommandBarButton Label=\"Save\" ToolTip.Tip=\"Save\" />\n            <ui:CommandBarButton Label=\"Delete\" ToolTip.Tip=\"Delete\" />-->\n            <ui:CommandBarFlyout.SecondaryCommands>\n                <ui:CommandBarButton\n                    Click=\"AddTabMenu_OnClick\"\n                    IconSource=\"FullScreenMaximize\"\n                    Label=\"Text to Image\"\n                    Tag=\"{x:Static inference:InferenceProjectType.TextToImage}\"\n                    ToolTip.Tip=\"Text to Image\" />\n                <ui:CommandBarButton\n                    Click=\"AddTabMenu_OnClick\"\n                    IconSource=\"ImageCopy\"\n                    Label=\"Image to Image\"\n                    Tag=\"{x:Static inference:InferenceProjectType.ImageToImage}\"\n                    ToolTip.Tip=\"Image to Image\" />\n                <ui:CommandBarButton\n                    IconSource=\"ImageEdit\"\n                    IsEnabled=\"False\"\n                    IsVisible=\"False\"\n                    Label=\"Inpaint\"\n                    ToolTip.Tip=\"Not yet implemented, coming soon!\" />\n                <ui:CommandBarButton\n                    Click=\"AddTabMenu_OnClick\"\n                    Label=\"Upscale\"\n                    Tag=\"{x:Static inference:InferenceProjectType.Upscale}\"\n                    ToolTip.Tip=\"Upscale\">\n                    <ui:CommandBarButton.IconSource>\n                        <fluentIcons:SymbolIconSource FontSize=\"10\" Symbol=\"ResizeImage\" />\n                    </ui:CommandBarButton.IconSource>\n                </ui:CommandBarButton>\n                <ui:CommandBarButton\n                    Click=\"AddTabMenu_WanTextToVideo_OnClick\"\n                    Label=\"Wan Text to Video\"\n                    ToolTip.Tip=\"Wan Text to Video\">\n                    <ui:CommandBarButton.IconSource>\n                        <fluentIcons:SymbolIconSource FontSize=\"10\" Symbol=\"Video\" />\n                    </ui:CommandBarButton.IconSource>\n                </ui:CommandBarButton>\n                <ui:CommandBarButton\n                    Click=\"AddTabMenu_WanImgToVideo_OnClick\"\n                    Label=\"Wan Image to Video\"\n                    ToolTip.Tip=\"Wan Image to Video\">\n                    <ui:CommandBarButton.IconSource>\n                        <fluentIcons:SymbolIconSource FontSize=\"10\" Symbol=\"VideoAdd\" />\n                    </ui:CommandBarButton.IconSource>\n                </ui:CommandBarButton>\n                <ui:CommandBarButton\n                    Click=\"AddTabMenu_ImgToVideo_OnClick\"\n                    Label=\"SVD Image to Video\"\n                    ToolTip.Tip=\"SVD Image to Video\">\n                    <ui:CommandBarButton.IconSource>\n                        <fluentIcons:SymbolIconSource FontSize=\"10\" Symbol=\"VideoRecording\" />\n                    </ui:CommandBarButton.IconSource>\n                </ui:CommandBarButton>\n            </ui:CommandBarFlyout.SecondaryCommands>\n        </ui:CommandBarFlyout>\n    </controls:UserControlBase.Resources>\n\n    <Grid>\n        <ui:TabView\n            x:Name=\"TabView\"\n            HorizontalAlignment=\"Stretch\"\n            VerticalAlignment=\"Stretch\"\n            AddTabButtonClick=\"TabView_OnAddTabButtonClick\"\n            CanDragTabs=\"True\"\n            CanReorderTabs=\"True\"\n            CloseButtonOverlayMode=\"Auto\"\n            SelectedIndex=\"{Binding SelectedTabIndex}\"\n            SelectedItem=\"{Binding SelectedTab}\"\n            TabCloseRequested=\"TabView_OnTabCloseRequested\"\n            TabItems=\"{Binding Tabs}\"\n            TabWidthMode=\"SizeToContent\">\n            <ui:TabView.TabItemTemplate>\n                <DataTemplate DataType=\"{x:Type base:InferenceTabViewModelBase}\">\n                    <ui:TabViewItem\n                        Content=\"{Binding FallbackValue=''}\"\n                        Header=\"{Binding TabTitle}\"\n                        IconSource=\"Document\" />\n                </DataTemplate>\n            </ui:TabView.TabItemTemplate>\n            <ui:TabView.TabStripFooter>\n                <StackPanel\n                    HorizontalAlignment=\"Right\"\n                    Orientation=\"Horizontal\"\n                    Spacing=\"2\">\n\n                    <!--  Connecting Progress  -->\n                    <controls:ProgressRing\n                        Margin=\"0,0,4,0\"\n                        BorderThickness=\"4\"\n                        IsIndeterminate=\"True\">\n                        <controls:ProgressRing.IsVisible>\n                            <MultiBinding Converter=\"{x:Static BoolConverters.Or}\">\n                                <Binding Path=\"ClientManager.IsConnecting\" />\n                                <Binding Path=\"IsWaitingForConnection\" />\n                            </MultiBinding>\n                        </controls:ProgressRing.IsVisible>\n                    </controls:ProgressRing>\n                    <!--  Connecting Progress Text  -->\n                    <TextBlock\n                        VerticalAlignment=\"Center\"\n                        IsVisible=\"{Binding ClientManager.IsConnecting}\"\n                        Text=\"{x:Static lang:Resources.Label_ConnectingEllipsis}\" />\n                    <!--  Waiting to connect progress text  -->\n                    <TextBlock VerticalAlignment=\"Center\" Text=\"{x:Static lang:Resources.Label_WaitingToConnectEllipsis}\">\n                        <Button.IsVisible>\n                            <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                <Binding Path=\"!ClientManager.IsConnecting\" />\n                                <Binding Path=\"IsWaitingForConnection\" />\n                            </MultiBinding>\n                        </Button.IsVisible>\n                    </TextBlock>\n\n                    <!--  Active connect (when Comfy is running)  -->\n                    <Button\n                        Padding=\"12,4\"\n                        Classes=\"success\"\n                        Command=\"{Binding ConnectCommand}\">\n                        <Button.IsVisible>\n                            <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                <Binding Path=\"ClientManager.CanUserConnect\" />\n                                <Binding Path=\"IsComfyRunning\" />\n                                <Binding Path=\"!IsWaitingForConnection\" />\n                            </MultiBinding>\n                        </Button.IsVisible>\n                        <StackPanel Orientation=\"Horizontal\">\n                            <TextBlock Text=\"{x:Static lang:Resources.Action_Connect}\" />\n                        </StackPanel>\n                    </Button>\n\n                    <!--  Connect help  -->\n                    <Button Padding=\"12,4\" Command=\"{Binding ShowConnectionHelpCommand}\">\n                        <Button.IsVisible>\n                            <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                <Binding Path=\"ClientManager.CanUserConnect\" />\n                                <Binding Path=\"!IsComfyRunning\" />\n                                <Binding Path=\"!IsWaitingForConnection\" />\n                            </MultiBinding>\n                        </Button.IsVisible>\n                        <StackPanel Orientation=\"Horizontal\">\n                            <icons:Icon\n                                Margin=\"0,0,8,0\"\n                                FontSize=\"14\"\n                                Value=\"fa-solid fa-circle-question\" />\n                            <TextBlock Text=\"{x:Static lang:Resources.Action_Launch}\" />\n                        </StackPanel>\n                    </Button>\n\n                    <!--  Successful connection icon  -->\n                    <Button Classes=\"transparent-full\">\n\n                        <Button.IsVisible>\n                            <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                <Binding Path=\"ClientManager.IsConnected\" />\n                                <Binding Path=\"!ClientManager.IsConnecting\" />\n                                <Binding Path=\"!IsWaitingForConnection\" />\n                            </MultiBinding>\n                        </Button.IsVisible>\n\n                        <fluentIcons:SymbolIcon\n                            VerticalAlignment=\"Center\"\n                            FontSize=\"18\"\n                            Foreground=\"{DynamicResource ThemeGreyColor}\"\n                            IconVariant=\"Filled\"\n                            Symbol=\"PlugConnected\" />\n\n                        <Button.Flyout>\n                            <ui:FAMenuFlyout Placement=\"BottomEdgeAlignedRight\">\n                                <ui:MenuFlyoutItem\n                                    Command=\"{Binding DisconnectCommand}\"\n                                    IsEnabled=\"False\"\n                                    Text=\"{Binding ClientManager.Client.BaseAddress, Converter={StaticResource UriStringConverter}, FallbackValue=''}\" />\n                                <ui:MenuFlyoutSeparator />\n                                <ui:MenuFlyoutItem Command=\"{Binding DisconnectCommand}\" Text=\"Disconnect\">\n                                    <ui:MenuFlyoutItem.IconSource>\n                                        <fluentIcons:SymbolIconSource Symbol=\"PlugDisconnected\" />\n                                    </ui:MenuFlyoutItem.IconSource>\n                                </ui:MenuFlyoutItem>\n                            </ui:FAMenuFlyout>\n                        </Button.Flyout>\n                    </Button>\n\n                    <!--  Menu  -->\n                    <Button Classes=\"transparent-full\">\n                        <ui:SymbolIcon FontSize=\"18\" Symbol=\"MoreVertical\" />\n                        <Button.Flyout>\n                            <ui:FAMenuFlyout Placement=\"BottomEdgeAlignedRight\">\n                                <ui:MenuFlyoutItem\n                                    Command=\"{Binding MenuOpenProjectCommand}\"\n                                    IconSource=\"OpenFolder\"\n                                    InputGesture=\"Ctrl+O\"\n                                    Text=\"{x:Static lang:Resources.Action_OpenProjectEllipsis}\" />\n                                <ui:MenuFlyoutSeparator />\n                                <ui:MenuFlyoutItem\n                                    Command=\"{Binding MenuSaveCommand}\"\n                                    IconSource=\"Save\"\n                                    InputGesture=\"Ctrl+S\"\n                                    Text=\"{x:Static lang:Resources.Action_Save}\" />\n                                <ui:MenuFlyoutItem\n                                    Command=\"{Binding MenuSaveAsCommand}\"\n                                    InputGesture=\"Ctrl+Shift+S\"\n                                    Text=\"{x:Static lang:Resources.Action_SaveAsEllipsis}\" />\n\n                                <ui:MenuFlyoutSeparator />\n\n                                <ui:MenuFlyoutItem\n                                    Command=\"{Binding SelectedTab.RestoreDefaultViewStateCommand, FallbackValue={x:Null}}\"\n                                    IconSource=\"CalendarReply\"\n                                    Text=\"{x:Static lang:Resources.Action_RestoreDefaultLayout}\" />\n\n                                <!--  Debug mode menus  -->\n                                <ui:MenuFlyoutSeparator IsVisible=\"{Binding SharedState.IsDebugMode}\" />\n\n                                <ui:MenuFlyoutItem\n                                    Command=\"{Binding SelectedTab.DebugSaveViewStateCommand, FallbackValue={x:Null}}\"\n                                    IsVisible=\"{Binding SharedState.IsDebugMode}\"\n                                    Text=\"Show Dock State\" />\n\n                                <ui:MenuFlyoutItem\n                                    Command=\"{Binding SelectedTab.DebugLoadViewStateCommand, FallbackValue={x:Null}}\"\n                                    IsVisible=\"{Binding SharedState.IsDebugMode}\"\n                                    Text=\"Load Dock State\" />\n\n                            </ui:FAMenuFlyout>\n                        </Button.Flyout>\n                    </Button>\n                </StackPanel>\n            </ui:TabView.TabStripFooter>\n        </ui:TabView>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/InferencePage.axaml.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Templates;\nusing Avalonia.Interactivity;\nusing FluentAvalonia.UI.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels;\nusing StabilityMatrix.Core.Models.Inference;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<InferencePage>]\npublic partial class InferencePage : UserControlBase\n{\n    private Button? _addButton;\n    private Button AddButton =>\n        _addButton ??= this.FindControl<TabView>(\"TabView\")!\n            .GetTemplateChildren()\n            .OfType<Button>()\n            .First(p => p.Name == \"AddButton\");\n\n    private readonly CommandBarFlyout addTabFlyout;\n\n    public InferencePage()\n    {\n        InitializeComponent();\n\n        addTabFlyout =\n            Resources[\"AddTabFlyout\"] as CommandBarFlyout\n            ?? throw new NullReferenceException(\"AddTabFlyout not found\");\n    }\n\n    private void TabView_OnTabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args)\n    {\n        (DataContext as InferenceViewModel)?.OnTabCloseRequested(args);\n    }\n\n    private void TabView_OnAddTabButtonClick(TabView sender, EventArgs args)\n    {\n        ShowAddTabMenu(false);\n    }\n\n    private void ShowAddTabMenu(bool isTransient)\n    {\n        addTabFlyout.ShowMode = isTransient ? FlyoutShowMode.Transient : FlyoutShowMode.Standard;\n\n        addTabFlyout.ShowAt(AddButton);\n    }\n\n    private void AddTabMenu_OnClick(object? sender, RoutedEventArgs e)\n    {\n        if (sender is Control { Tag: InferenceProjectType type })\n        {\n            (DataContext as InferenceViewModel)!.AddTabCommand.Execute(type);\n        }\n    }\n\n    private void AddTabMenu_ImgToVideo_OnClick(object? sender, RoutedEventArgs e)\n    {\n        (DataContext as InferenceViewModel)!.AddTabCommand.Execute(InferenceProjectType.ImageToVideo);\n    }\n\n    private void AddTabMenu_WanTextToVideo_OnClick(object? sender, RoutedEventArgs e)\n    {\n        (DataContext as InferenceViewModel)!.AddTabCommand.Execute(InferenceProjectType.WanTextToVideo);\n    }\n\n    private void AddTabMenu_WanImgToVideo_OnClick(object? sender, RoutedEventArgs e)\n    {\n        (DataContext as InferenceViewModel)!.AddTabCommand.Execute(InferenceProjectType.WanImageToVideo);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.InstalledWorkflowsPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:asyncImageLoader=\"clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:controls1=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:fluent=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:helpers=\"clr-namespace:StabilityMatrix.Avalonia.Helpers\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:labs=\"clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:openArt=\"clr-namespace:StabilityMatrix.Core.Models.Api.OpenArt\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    d:DataContext=\"{x:Static designData:DesignData.InstalledWorkflowsViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"viewModels:InstalledWorkflowsViewModel\"\n    mc:Ignorable=\"d\">\n    <UserControl.Styles>\n        <Style Selector=\"Border#HoverBorder\">\n            <Setter Property=\"Transitions\">\n                <Transitions>\n                    <BoxShadowsTransition Property=\"BoxShadow\" Duration=\"0:0:0.237\" />\n                </Transitions>\n            </Setter>\n\n            <Style Selector=\"^ labs|AsyncImage\">\n                <Setter Property=\"Transitions\">\n                    <Transitions>\n                        <TransformOperationsTransition Property=\"RenderTransform\" Duration=\"0:0:0.237\">\n                            <TransformOperationsTransition.Easing>\n                                <QuadraticEaseInOut />\n                            </TransformOperationsTransition.Easing>\n                        </TransformOperationsTransition>\n                    </Transitions>\n                </Setter>\n            </Style>\n\n            <Style Selector=\"^:pointerover\">\n                <Setter Property=\"BoxShadow\" Value=\"0 0 40 0 #60000000\" />\n                <Setter Property=\"Cursor\" Value=\"Hand\" />\n                <Style Selector=\"^ asyncImageLoader|AdvancedImage\">\n                    <Setter Property=\"CornerRadius\" Value=\"12\" />\n                    <Setter Property=\"RenderTransform\" Value=\"scale(1.03, 1.03)\" />\n                </Style>\n                <Style Selector=\"^ Border#ModelCardBottom\">\n                    <Setter Property=\"Background\" Value=\"#CC000000\" />\n                </Style>\n            </Style>\n\n            <Style Selector=\"^:not(:pointerover)\">\n                <Setter Property=\"BoxShadow\" Value=\"0 0 20 0 #60000000\" />\n                <Setter Property=\"Cursor\" Value=\"Arrow\" />\n                <Style Selector=\"^ asyncImageLoader|AdvancedImage\">\n                    <Setter Property=\"CornerRadius\" Value=\"8\" />\n                    <Setter Property=\"RenderTransform\" Value=\"scale(1, 1)\" />\n                </Style>\n                <Style Selector=\"^ Border#ModelCardBottom\">\n                    <Setter Property=\"Background\" Value=\"#99000000\" />\n                </Style>\n            </Style>\n        </Style>\n    </UserControl.Styles>\n\n    <UserControl.Resources>\n        <input:StandardUICommand x:Key=\"OpenInExplorerCommand\" Command=\"{Binding OpenInExplorerCommand}\" />\n        <input:StandardUICommand x:Key=\"OpenOnOpenArtCommand\" Command=\"{Binding OpenOnOpenArtCommand}\" />\n        <input:StandardUICommand x:Key=\"DeleteCommand\" Command=\"{Binding DeleteCommand}\" />\n    </UserControl.Resources>\n\n    <Grid ColumnDefinitions=\"*, Auto\" RowDefinitions=\"Auto, *\">\n        <controls1:CommandBar\n            Grid.Row=\"0\"\n            Grid.Column=\"1\"\n            Margin=\"4,8,4,0\"\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Center\"\n            VerticalContentAlignment=\"Center\"\n            DefaultLabelPosition=\"Right\">\n            <controls1:CommandBar.PrimaryCommands>\n                <controls1:CommandBarButton\n                    VerticalAlignment=\"Center\"\n                    Command=\"{Binding LoadInstalledWorkflowsCommand}\"\n                    IconSource=\"Refresh\"\n                    Label=\"{x:Static lang:Resources.Action_Refresh}\" />\n\n                <controls1:CommandBarSeparator />\n\n                <controls1:CommandBarElementContainer>\n                    <StackPanel Orientation=\"Horizontal\">\n                        <avalonia:Icon\n                            Margin=\"8,0\"\n                            FontSize=\"18\"\n                            Value=\"fa-solid fa-info\" />\n                        <TextBlock VerticalAlignment=\"Center\" Text=\"Drag &amp; drop one of the cards below into ComfyUI to load the workflow\" />\n                    </StackPanel>\n                </controls1:CommandBarElementContainer>\n            </controls1:CommandBar.PrimaryCommands>\n        </controls1:CommandBar>\n\n        <TextBox\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            MinHeight=\"40\"\n            Margin=\"4,8,4,0\"\n            VerticalAlignment=\"Center\"\n            VerticalContentAlignment=\"Center\"\n            Classes=\"search\"\n            FontSize=\"16\"\n            Text=\"{Binding SearchQuery, Mode=TwoWay}\"\n            TextAlignment=\"Left\"\n            Watermark=\"{x:Static lang:Resources.Action_Search}\" />\n\n        <ScrollViewer\n            Grid.Row=\"1\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\">\n            <ItemsRepeater ItemsSource=\"{Binding DisplayedWorkflows}\">\n                <ItemsRepeater.Layout>\n                    <!-- <UniformGridLayout MinColumnSpacing=\"4\" MinRowSpacing=\"4\"/> -->\n                    <UniformGridLayout MinColumnSpacing=\"4\" MinRowSpacing=\"4\" />\n                </ItemsRepeater.Layout>\n                <ItemsRepeater.ItemTemplate>\n                    <DataTemplate x:DataType=\"{x:Type models:OpenArtMetadata}\">\n                        <Border\n                            Name=\"HoverBorder\"\n                            Margin=\"8\"\n                            Padding=\"0\"\n                            BorderThickness=\"0\"\n                            ClipToBounds=\"True\"\n                            CornerRadius=\"8\">\n                            <Interaction.Behaviors>\n                                <BehaviorCollection>\n                                    <controls:BetterContextDragBehavior\n                                        Context=\"{Binding FilePath}\"\n                                        DataFormat=\"Files\"\n                                        HorizontalDragThreshold=\"6\"\n                                        VerticalDragThreshold=\"6\" />\n                                </BehaviorCollection>\n                            </Interaction.Behaviors>\n                            <Border.ContextFlyout>\n                                <MenuFlyout>\n                                    <MenuItem\n                                        Command=\"{StaticResource OpenOnOpenArtCommand}\"\n                                        CommandParameter=\"{Binding}\"\n                                        Header=\"{x:Static lang:Resources.Action_OpenOnOpenArt}\"\n                                        IsVisible=\"{Binding HasMetadata}\">\n                                        <MenuItem.Icon>\n                                            <fluent:SymbolIcon Symbol=\"Open\" />\n                                        </MenuItem.Icon>\n                                    </MenuItem>\n                                    <MenuItem\n                                        Command=\"{StaticResource OpenInExplorerCommand}\"\n                                        CommandParameter=\"{Binding}\"\n                                        Header=\"{x:Static lang:Resources.Action_OpenInExplorer}\">\n                                        <MenuItem.Icon>\n                                            <fluent:SymbolIcon Symbol=\"Folder\" />\n                                        </MenuItem.Icon>\n                                    </MenuItem>\n                                    <MenuItem\n                                        Command=\"{StaticResource DeleteCommand}\"\n                                        CommandParameter=\"{Binding}\"\n                                        Header=\"{x:Static lang:Resources.Action_Delete}\">\n                                        <MenuItem.Icon>\n                                            <fluent:SymbolIcon Symbol=\"Delete\" />\n                                        </MenuItem.Icon>\n                                    </MenuItem>\n                                </MenuFlyout>\n                            </Border.ContextFlyout>\n                            <Button\n                                Name=\"ModelCard\"\n                                Padding=\"0\"\n                                VerticalContentAlignment=\"Top\"\n                                BorderThickness=\"0\"\n                                Classes=\"transparent-full\"\n                                CornerRadius=\"8\">\n                                <Grid RowDefinitions=\"*, Auto\">\n                                    <vendorLabs:BetterAsyncImage\n                                        Grid.Row=\"0\"\n                                        Grid.RowSpan=\"2\"\n                                        Width=\"330\"\n                                        Height=\"400\"\n                                        CornerRadius=\"8\"\n                                        IsVisible=\"{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}\"\n                                        Source=\"{Binding FirstThumbnail}\"\n                                        Stretch=\"UniformToFill\" />\n\n                                    <avalonia:Icon\n                                        Grid.Row=\"0\"\n                                        Grid.RowSpan=\"2\"\n                                        Width=\"330\"\n                                        Height=\"400\"\n                                        CornerRadius=\"8\"\n                                        FontSize=\"100\"\n                                        IsVisible=\"{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNull}, FallbackValue=False}\"\n                                        Value=\"fa-regular fa-file-code\" />\n\n                                    <!--  Username pill card  -->\n                                    <Border\n                                        Grid.Row=\"0\"\n                                        Margin=\"4\"\n                                        HorizontalAlignment=\"Left\"\n                                        VerticalAlignment=\"Bottom\"\n                                        BoxShadow=\"inset 1.2 0 80 1.8 #66000000\"\n                                        CornerRadius=\"16\">\n                                        <Border.Resources>\n                                            <DropShadowEffect\n                                                x:Key=\"TextDropShadowEffect\"\n                                                BlurRadius=\"12\"\n                                                Opacity=\"0.9\"\n                                                Color=\"#FF000000\" />\n                                            <DropShadowEffect\n                                                x:Key=\"ImageDropShadowEffect\"\n                                                BlurRadius=\"12\"\n                                                Opacity=\"0.2\"\n                                                Color=\"#FF000000\" />\n                                        </Border.Resources>\n                                        <Button\n                                            Padding=\"10,4\"\n                                            Classes=\"transparent\"\n                                            Command=\"{x:Static helpers:IOCommands.OpenUrlCommand}\"\n                                            CommandParameter=\"{Binding Workflow.Creator.DevProfileUrl}\"\n                                            CornerRadius=\"16\">\n                                            <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                                                <vendorLabs:BetterAsyncImage\n                                                    Width=\"22\"\n                                                    Height=\"22\"\n                                                    CornerRadius=\"11\"\n                                                    Effect=\"{StaticResource ImageDropShadowEffect}\"\n                                                    IsVisible=\"{Binding Workflow.Creator.Avatar, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                                                    RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n                                                    Source=\"{Binding Workflow.Creator.Avatar}\" />\n                                                <TextBlock\n                                                    VerticalAlignment=\"Center\"\n                                                    Effect=\"{StaticResource TextDropShadowEffect}\"\n                                                    Foreground=\"{DynamicResource TextControlForeground}\"\n                                                    Text=\"{Binding Workflow.Creator.Name}\" />\n                                            </StackPanel>\n                                        </Button>\n                                    </Border>\n\n                                    <Border Name=\"ModelCardBottom\" Grid.Row=\"1\">\n                                        <TextBlock\n                                            MaxWidth=\"315\"\n                                            Margin=\"8,0,8,0\"\n                                            Padding=\"16\"\n                                            HorizontalAlignment=\"Center\"\n                                            VerticalAlignment=\"Center\"\n                                            FontWeight=\"SemiBold\"\n                                            Foreground=\"{DynamicResource TextControlForeground}\"\n                                            LetterSpacing=\"0.33\"\n                                            Text=\"{Binding Workflow.Name}\"\n                                            TextWrapping=\"Wrap\"\n                                            ToolTip.Tip=\"{Binding Workflow.Name}\" />\n                                    </Border>\n                                </Grid>\n                            </Button>\n                        </Border>\n                    </DataTemplate>\n                </ItemsRepeater.ItemTemplate>\n            </ItemsRepeater>\n        </ScrollViewer>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<InstalledWorkflowsPage>]\npublic partial class InstalledWorkflowsPage : UserControlBase\n{\n    public InstalledWorkflowsPage()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/LaunchPageView.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.LaunchPageView\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avaloniaEdit=\"clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:avalonia=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    d:DataContext=\"{x:Static mocks:DesignData.LaunchPageViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"700\"\n    x:CompileBindings=\"True\"\n    x:DataType=\"vm:LaunchPageViewModel\"\n    mc:Ignorable=\"d\">\n    \n    <controls:UserControlBase.Resources>\n        <system:Boolean x:Key=\"True\">True</system:Boolean>\n        <system:Boolean x:Key=\"False\">False</system:Boolean>\n    </controls:UserControlBase.Resources>\n\n    <Grid RowDefinitions=\"Auto,*,Auto\">\n        <Grid ColumnDefinitions=\"Auto,*,Auto,Auto\"\n              Margin=\"0,8,0, 8\">\n            <Grid ColumnDefinitions=\"0.8*,0.2*\" Margin=\"16,8,0,0\">\n                <!-- Use split if extra commands, otherwise normal launch button -->\n                <Grid \n                    x:Name=\"LaunchButtonGrid\"\n                    Grid.Row=\"0\"\n                    Grid.Column=\"0\">\n                    <Button\n                        Width=\"95\"\n                        IsVisible=\"{Binding !SelectedBasePackage.ExtraLaunchCommands.Count, FallbackValue=False}\"\n                        Classes=\"success\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Stretch\"\n                        Command=\"{Binding LaunchCommand}\"\n                        Content=\"{x:Static lang:Resources.Action_Launch}\" />\n                    <SplitButton\n                        Width=\"104\"\n                        IsVisible=\"{Binding !!SelectedBasePackage.ExtraLaunchCommands.Count, FallbackValue=False}\"\n                        Classes=\"success\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Stretch\"\n                        Command=\"{Binding LaunchCommand}\"\n                        Content=\"{x:Static lang:Resources.Action_Launch}\">\n                        <SplitButton.Flyout>\n                            <ui:FAMenuFlyout \n                                Placement=\"Bottom\"\n                                ItemsSource=\"{Binding SelectedPackageExtraCommands}\">\n                                <ui:FAMenuFlyout.ItemTemplate>\n                                    <DataTemplate x:DataType=\"{x:Type system:String}\">\n                                        <!-- ReSharper disable Xaml.PossibleNullReferenceException -->\n                                        <!-- ReSharper disable Xaml.BindingWithContextNotResolved -->\n                                        <ui:MenuFlyoutItem\n                                            Text=\"{Binding}\"\n                                            CommandParameter=\"{Binding}\"\n                                            Command=\"{ReflectionBinding\n                                                    #LaunchButtonGrid.DataContext.LaunchCommand}\"/>\n                                        <!-- ReSharper restore Xaml.BindingWithContextNotResolved -->\n                                        <!-- ReSharper restore Xaml.PossibleNullReferenceException -->\n                                    </DataTemplate>\n                                </ui:FAMenuFlyout.ItemTemplate>\n                            </ui:FAMenuFlyout>\n                        </SplitButton.Flyout>\n                    </SplitButton>\n                </Grid>\n\n                <ui:TeachingTip Grid.Row=\"0\" Grid.Column=\"0\" Name=\"TeachingTip1\"\n                                Target=\"{Binding #LaunchButtonGrid}\"\n                                Title=\"{x:Static lang:Resources.TeachingTip_ClickLaunchToGetStarted}\"\n                                PreferredPlacement=\"BottomRight\"\n                                IsOpen=\"{Binding IsLaunchTeachingTipsOpen}\"/>\n                \n                <Grid \n                    x:Name=\"StopButtonGrid\"\n                    IsVisible=\"{Binding RunningPackage, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                    Grid.Row=\"0\"\n                    Grid.Column=\"0\">\n                    <Button\n                        Width=\"95\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Stretch\"\n                        Classes=\"danger\"\n                        Command=\"{Binding Stop}\"\n                        Content=\"{x:Static lang:Resources.Action_Stop}\"\n                        IsVisible=\"{Binding !SelectedBasePackage.ExtraLaunchCommands.Count, FallbackValue=False}\" />\n                    <Button\n                        Width=\"104\"\n                        HorizontalAlignment=\"Left\"\n                        VerticalAlignment=\"Stretch\"\n                        Classes=\"danger\"\n                        Command=\"{Binding Stop}\"\n                        Content=\"{x:Static lang:Resources.Action_Stop}\"\n                        IsVisible=\"{Binding !!SelectedBasePackage.ExtraLaunchCommands.Count, FallbackValue=False}\" />\n                </Grid>\n                \n                <Button\n                    Grid.Row=\"0\"\n                    Grid.Column=\"1\"\n                    Width=\"48\"\n                    Margin=\"8,0,0,0\"\n                    Command=\"{Binding ConfigCommand}\"\n                    HorizontalAlignment=\"Left\"\n                    VerticalAlignment=\"Stretch\"\n                    FontSize=\"16\">\n                    <ui:SymbolIcon FontSize=\"16\" Symbol=\"Settings\" />\n                </Button>\n            </Grid>\n\n            <ComboBox\n                x:Name=\"SelectPackageComboBox\"\n                Grid.Row=\"0\"\n                Grid.Column=\"1\"\n                Margin=\"8,8,0,0\"\n                HorizontalAlignment=\"Stretch\"\n                VerticalAlignment=\"Top\"\n                IsEnabled=\"{Binding RunningPackage, Converter={x:Static ObjectConverters.IsNull}}\"\n                ItemsSource=\"{Binding InstalledPackages}\"\n                SelectedItem=\"{Binding SelectedPackage}\">\n                <ComboBox.Styles>\n                    <Style Selector=\"ComboBox /template/ ContentControl#ContentPresenter &gt; StackPanel &gt; TextBlock:nth-child(2)\">\n                        <Setter Property=\"IsVisible\" Value=\"False\" />\n                    </Style>\n                </ComboBox.Styles>\n\n                <ComboBox.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type models:InstalledPackage}\">\n                        <StackPanel Margin=\"10,0,0,0\" VerticalAlignment=\"Top\">\n                            <TextBlock\n                                Name=\"NameTextBlock\"\n                                Margin=\"0,5,0,5\"\n                                Text=\"{Binding DisplayName, Mode=OneWay}\" />\n                            <TextBlock Text=\"{Binding FullPath, Mode=OneWay}\" />\n                        </StackPanel>\n                    </DataTemplate>\n                </ComboBox.ItemTemplate>\n            </ComboBox>\n            \n            <!-- Keyboard button to show manual input info bar -->\n            <ToggleButton\n                Grid.Column=\"2\"\n                Width=\"48\"\n                Margin=\"8,8,0,0\"\n                IsChecked=\"{Binding AutoScrollToEnd}\"\n                ToolTip.Tip=\"{x:Static lang:Resources.Label_AutoScrollToEnd}\"\n                HorizontalAlignment=\"Left\"\n                VerticalAlignment=\"Stretch\"\n                FontSize=\"16\">\n                <avalonia:Icon Value=\"fa-solid fa-arrow-down-wide-short\" />\n            </ToggleButton>\n            \n            <ToggleButton\n                Grid.Column=\"3\"\n                Width=\"48\"\n                Margin=\"8,8,16,0\"\n                IsChecked=\"{Binding ShowManualInputPrompt}\"\n                ToolTip.Tip=\"{x:Static lang:Resources.Action_SendInput}\"\n                HorizontalAlignment=\"Left\"\n                VerticalAlignment=\"Stretch\"\n                FontSize=\"16\">\n                <ui:SymbolIcon FontSize=\"18\" Symbol=\"Keyboard\" />\n            </ToggleButton>\n        </Grid>\n\n        <avaloniaEdit:TextEditor\n            x:Name=\"Console\"\n            Grid.Row=\"1\"\n            Margin=\"8,8,16,10\"\n            DataContext=\"{Binding Console}\"\n            Document=\"{Binding Document}\"\n            FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n            IsReadOnly=\"True\"\n            LineNumbersForeground=\"DarkSlateGray\"\n            ShowLineNumbers=\"True\"\n            VerticalScrollBarVisibility=\"Auto\"\n            WordWrap=\"True\" />\n        \n        <Grid Grid.Row=\"1\" ColumnDefinitions=\"0.5*,*\">\n            <StackPanel Grid.Column=\"1\" Margin=\"8\" Spacing=\"4\">\n                <!-- Info bar for manual input -->\n                <ui:InfoBar \n                    Severity=\"Warning\"\n                    Title=\"{x:Static lang:Resources.Label_Input}\"\n                    IsIconVisible=\"False\"\n                    IsClosable=\"False\"\n                    IsOpen=\"{Binding ShowManualInputPrompt, Mode=TwoWay}\" \n                    Margin=\"0\">\n                    <ui:InfoBar.ActionButton>\n                        <Grid ColumnDefinitions=\"*,auto\">\n                            <TextBox Name=\"ManualInputBox\" Margin=\"0,0,8,0\" />\n                            <Button Grid.Column=\"1\" Margin=\"0,0,8,0\" Content=\"{x:Static lang:Resources.Action_Send}\"\n                                    Command=\"{Binding SendManualInputCommand}\"\n                                    CommandParameter=\"{Binding #ManualInputBox.Text}\"/>\n                        </Grid>\n                    </ui:InfoBar.ActionButton>\n                </ui:InfoBar>\n                <!-- Info bar for auto prompts -->\n                <ui:InfoBar \n                    Severity=\"Warning\"\n                    Title=\"{x:Static lang:Resources.Label_InputRequired}\" \n                    IsOpen=\"{Binding ShowConfirmInputPrompt, Mode=TwoWay}\" \n                    Margin=\"0\"\n                    Message=\"{x:Static lang:Resources.Label_ConfirmQuestion}\">\n                    <ui:InfoBar.ActionButton>\n                        <StackPanel Orientation=\"Horizontal\" Spacing=\"8\">\n                            <Button Classes=\"accent\" Content=\"{x:Static lang:Resources.Action_Yes}\" \n                                    Command=\"{Binding SendConfirmInputCommand}\"\n                                    CommandParameter=\"{StaticResource True}\"/>\n                            <Button Content=\"{x:Static lang:Resources.Label_No}\" Command=\"{Binding SendConfirmInputCommand}\"\n                                    CommandParameter=\"{StaticResource False}\"/>\n                        </StackPanel>\n                    </ui:InfoBar.ActionButton>\n                </ui:InfoBar>\n            </StackPanel>\n        </Grid>\n\n        <Button\n            Grid.Row=\"2\"\n            Grid.ColumnSpan=\"2\"\n            Margin=\"24,0,24,8\"\n            HorizontalAlignment=\"Stretch\"\n            Classes=\"success\"\n            Content=\"{x:Static lang:Resources.Action_OpenWebUI}\"\n            Command=\"{Binding OpenWebUi}\"\n            IsVisible=\"{Binding ShowWebUiButton}\"\n            FontSize=\"12\" />\n    </Grid>\n\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/LaunchPageView.axaml.cs",
    "content": "﻿using System;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Interactivity;\nusing Avalonia.Threading;\nusing AvaloniaEdit;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Helpers;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Core.Helper;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<LaunchPageView>]\npublic partial class LaunchPageView : UserControlBase\n{\n    private const int LineOffset = 5;\n\n    public LaunchPageView()\n    {\n        InitializeComponent();\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        TextEditorConfigs.Configure(Console, TextEditorPreset.Console);\n    }\n\n    protected override void OnUnloaded(RoutedEventArgs e)\n    {\n        base.OnUnloaded(e);\n        EventManager.Instance.ScrollToBottomRequested -= OnScrollToBottomRequested;\n    }\n\n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        base.OnLoaded(e);\n        EventManager.Instance.ScrollToBottomRequested += OnScrollToBottomRequested;\n    }\n\n    private void OnScrollToBottomRequested(object? sender, EventArgs e)\n    {\n        Dispatcher.UIThread.Invoke(() =>\n        {\n            var editor = this.FindControl<TextEditor>(\"Console\");\n            if (editor?.Document == null)\n                return;\n            var line = Math.Max(editor.Document.LineCount - LineOffset, 1);\n            editor.ScrollToLine(line);\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/MainWindow.axaml",
    "content": "﻿<controls:AppWindowBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.MainWindow\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:base=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Base\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:local=\"using:StabilityMatrix.Avalonia\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vm=\"using:StabilityMatrix.Avalonia.ViewModels\"\n    Title=\"Stability Matrix\"\n    Width=\"1300\"\n    Height=\"950\"\n    d:DataContext=\"{x:Static mocks:DesignData.MainWindowViewModel}\"\n    d:DesignHeight=\"550\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"vm:MainWindowViewModel\"\n    BackRequested=\"TopLevel_OnBackRequested\"\n    DockProperties.IsDragEnabled=\"True\"\n    DockProperties.IsDropEnabled=\"True\"\n    Icon=\"/Assets/Icon.ico\"\n    mc:Ignorable=\"d\">\n\n    <controls:AppWindowBase.Resources>\n        <SolidColorBrush x:Key=\"BrushB0\" Color=\"#FFFFFFFF\" />\n        <DrawingImage x:Key=\"BrandsPatreonSymbolWhite\">\n            <DrawingGroup>\n                <GeometryDrawing Brush=\"{DynamicResource BrushB0}\" Geometry=\"F1 M1033.05 324.45C1032.86 186.55 925.46 73.53 799.45 32.75C642.97 -17.89 436.59 -10.55 287.17 59.95C106.07 145.41 49.18 332.61 47.06 519.31C45.32 672.81 60.64 1077.1 288.68 1079.98C458.12 1082.13 483.35 863.8 561.75 758.65C617.53 683.84 689.35 662.71 777.76 640.83C929.71 603.22 1033.27 483.3 1033.05 324.45Z\" />\n            </DrawingGroup>\n        </DrawingImage>\n    </controls:AppWindowBase.Resources>\n\n    <Grid RowDefinitions=\"Auto,Auto,*\">\n        <Grid\n            Name=\"TitleBarHost\"\n            Height=\"32\"\n            Background=\"Transparent\"\n            ColumnDefinitions=\"Auto,Auto,*,Auto\">\n            <Image\n                Name=\"WindowIcon\"\n                Width=\"18\"\n                Height=\"18\"\n                Margin=\"12,4\"\n                DockPanel.Dock=\"Left\"\n                IsHitTestVisible=\"False\"\n                RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n                Source=\"/Assets/Icon.ico\">\n                <Image.IsVisible>\n                    <OnPlatform Default=\"False\">\n                        <On Content=\"True\" Options=\"Windows\" />\n                    </OnPlatform>\n                </Image.IsVisible>\n            </Image>\n            <TextBlock\n                Grid.Column=\"1\"\n                VerticalAlignment=\"Center\"\n                FontSize=\"12\"\n                IsHitTestVisible=\"False\"\n                Text=\"{Binding Title, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}\">\n                <TextBlock.IsVisible>\n                    <OnPlatform Default=\"False\">\n                        <On Content=\"True\" Options=\"Windows\" />\n                    </OnPlatform>\n                </TextBlock.IsVisible>\n            </TextBlock>\n\n            <Border Grid.Column=\"2\" Padding=\"6\" />\n        </Grid>\n        <ui:NavigationView\n            Name=\"NavigationView\"\n            Grid.Row=\"1\"\n            Grid.RowSpan=\"2\"\n            BackRequested=\"NavigationView_OnBackRequested\"\n            FooterMenuItemsSource=\"{Binding FooterPages, Mode=OneWay}\"\n            IsBackButtonVisible=\"True\"\n            IsBackEnabled=\"True\"\n            IsPaneOpen=\"{Binding IsPaneOpen, Mode=TwoWay}\"\n            IsSettingsVisible=\"False\"\n            ItemInvoked=\"NavigationView_OnItemInvoked\"\n            MenuItemsSource=\"{Binding Pages, Mode=OneWay}\"\n            OpenPaneLength=\"{Binding PaneWidth}\"\n            PaneDisplayMode=\"Left\"\n            SelectedItem=\"{Binding SelectedCategory}\">\n\n            <ui:NavigationView.MenuItemTemplate>\n                <DataTemplate DataType=\"{x:Type base:PageViewModelBase}\">\n                    <ui:NavigationViewItem\n                        Margin=\"0,0,0,0\"\n                        Content=\"{Binding Title}\"\n                        IconSource=\"{Binding IconSource}\"\n                        Tag=\"{Binding}\" />\n                </DataTemplate>\n            </ui:NavigationView.MenuItemTemplate>\n\n            <ui:NavigationView.PaneFooter>\n                <StackPanel>\n                    <!--  Update button  -->\n                    <ui:NavigationViewItem\n                        Name=\"FooterUpdateItem\"\n                        Content=\"{x:Static lang:Resources.Label_UpdateAvailable}\"\n                        DataContext=\"{Binding UpdateViewModel}\"\n                        IsEnabled=\"{Binding IsUpdateAvailable}\"\n                        IsVisible=\"{Binding IsUpdateAvailable}\"\n                        Tapped=\"FooterUpdateItem_OnTapped\">\n                        <ui:NavigationViewItem.IconSource>\n                            <fluentIcons:SymbolIconSource\n                                FontSize=\"18\"\n                                Foreground=\"LimeGreen\"\n                                IconVariant=\"Filled\"\n                                Symbol=\"ArrowDownload\" />\n                        </ui:NavigationViewItem.IconSource>\n                    </ui:NavigationViewItem>\n\n                    <!--  Patreon  -->\n                    <ui:NavigationViewItem\n                        Name=\"PatreonPatreonItem\"\n                        Content=\"{x:Static lang:Resources.Label_BecomeAPatron}\"\n                        Tapped=\"PatreonPatreonItem_OnTapped\">\n                        <ui:NavigationViewItem.IconSource>\n                            <ui:ImageIconSource Source=\"{StaticResource BrandsPatreonSymbolWhite}\" />\n                        </ui:NavigationViewItem.IconSource>\n                    </ui:NavigationViewItem>\n\n                    <!--  Discord  -->\n                    <ui:NavigationViewItem\n                        Name=\"FooterDiscordItem\"\n                        Content=\"{x:Static lang:Resources.Label_JoinDiscord}\"\n                        Tapped=\"FooterDiscordItem_OnTapped\">\n                        <ui:NavigationViewItem.IconSource>\n                            <controls:FASymbolIconSource Symbol=\"fa-brands fa-discord\" />\n                        </ui:NavigationViewItem.IconSource>\n                    </ui:NavigationViewItem>\n\n                    <!--  Download flyout  -->\n                    <ui:NavigationViewItem\n                        Name=\"FooterDownloadItem\"\n                        Content=\"{x:Static lang:Resources.Label_Downloads}\"\n                        DataContext=\"{Binding ProgressManagerViewModel}\"\n                        IconSource=\"{Binding IconSource}\"\n                        IsVisible=\"{Binding !!ProgressItems.Count}\"\n                        Tapped=\"FooterDownloadItem_OnTapped\">\n                        <ui:NavigationViewItem.ContextFlyout>\n                            <controls:BetterFlyout\n                                Content=\"{Binding}\"\n                                HorizontalScrollBarVisibility=\"Hidden\"\n                                Placement=\"RightEdgeAlignedBottom\"\n                                VerticalScrollBarVisibility=\"Auto\" />\n                        </ui:NavigationViewItem.ContextFlyout>\n                        <ui:NavigationViewItem.InfoBadge>\n                            <ui:InfoBadge IsVisible=\"{Binding !!ProgressItems.Count}\" Value=\"{Binding ProgressItems.Count}\" />\n                        </ui:NavigationViewItem.InfoBadge>\n                    </ui:NavigationViewItem>\n                </StackPanel>\n            </ui:NavigationView.PaneFooter>\n\n            <!--  Since the data context is set dynamically, cannot use compiled bindings  -->\n            <ui:Frame Name=\"FrameView\">\n                <ui:Frame.NavigationPageFactory>\n                    <local:ViewLocator />\n                </ui:Frame.NavigationPageFactory>\n            </ui:Frame>\n\n        </ui:NavigationView>\n        <ui:TeachingTip\n            Name=\"UpdateAvailableTeachingTip\"\n            Title=\"{x:Static lang:Resources.Label_UpdateAvailable}\"\n            Grid.Row=\"1\"\n            Grid.RowSpan=\"2\"\n            PreferredPlacement=\"Right\"\n            Target=\"{Binding #FooterUpdateItem}\" />\n\n        <ui:TeachingTip\n            Name=\"DownloadsTeachingTip\"\n            Title=\"{x:Static lang:Resources.Label_Downloads}\"\n            Grid.Row=\"1\"\n            Grid.RowSpan=\"2\"\n            PreferredPlacement=\"Right\"\n            Target=\"{Binding #FooterDownloadItem}\" />\n    </Grid>\n</controls:AppWindowBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Diagnostics.CodeAnalysis;\nusing System.IO;\nusing System.Linq;\nusing System.Reactive.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing AsyncAwaitBestPractices;\nusing AsyncImageLoader;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Input;\nusing Avalonia.Interactivity;\nusing Avalonia.Media;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Media.Immutable;\nusing Avalonia.Platform;\nusing Avalonia.Styling;\nusing Avalonia.Threading;\nusing FluentAvalonia.Styling;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Media;\nusing FluentAvalonia.UI.Media.Animation;\nusing FluentAvalonia.UI.Windowing;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Models.Update;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\nusing TeachingTip = FluentAvalonia.UI.Controls.TeachingTip;\n#if DEBUG\nusing StabilityMatrix.Avalonia.Extensions;\n#endif\n#if SM_LOG_WINDOW\nusing StabilityMatrix.Avalonia.Diagnostics.Views;\n#endif\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[SuppressMessage(\"ReSharper\", \"UnusedParameter.Local\")]\n[RegisterSingleton<MainWindow>]\npublic partial class MainWindow : AppWindowBase\n{\n    private readonly INotificationService notificationService;\n    private readonly INavigationService<MainWindowViewModel> navigationService;\n    private readonly ISettingsManager settingsManager;\n    private readonly ILogger<MainWindow> logger;\n\n    private FlyoutBase? progressFlyout;\n\n    /*[DesignOnly(true)]\n    [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n    public MainWindow()\n        : this(\n            DesignData.DesignData.Services.GetRequiredService<INotificationService>(),\n            DesignData.DesignData.Services.GetRequiredService<INavigationService<MainWindowViewModel>>(),\n            DesignData.DesignData.Services.GetRequiredService<ISettingsManager>(),\n            DesignData.DesignData.Services.GetRequiredService<ILogger<MainWindow>>()\n        )\n    {\n        if (!Design.IsDesignMode)\n        {\n            throw new InvalidOperationException(\"Design constructor called in non-design mode\");\n        }\n    }*/\n\n    public MainWindow(\n        INotificationService notificationService,\n        INavigationService<MainWindowViewModel> navigationService,\n        ISettingsManager settingsManager,\n        ILogger<MainWindow> logger,\n        Lazy<MainWindowViewModel> lazyViewModel\n    )\n    {\n        this.notificationService = notificationService;\n        this.navigationService = navigationService;\n        this.settingsManager = settingsManager;\n        this.logger = logger;\n\n        InitializeComponent();\n\n#if DEBUG\n        this.AttachDevTools();\n        this.AttachDebugSaveScreenshot();\n#endif\n\n#if SM_LOG_WINDOW\n        LogWindow.Attach(this, App.Services);\n#endif\n        TitleBar.ExtendsContentIntoTitleBar = true;\n        TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex;\n        ExtendClientAreaChromeHints = Program.Args.NoWindowChromeEffects\n            ? ExtendClientAreaChromeHints.NoChrome\n            : ExtendClientAreaChromeHints.PreferSystemChrome;\n\n        // Load window positions\n        if (\n            settingsManager.Settings.WindowSettings is { } windowSettings\n            && !Program.Args.ResetWindowPosition\n        )\n        {\n            Position = new PixelPoint(windowSettings.X, windowSettings.Y);\n            Width = Math.Max(300, windowSettings.Width);\n            Height = Math.Max(300, windowSettings.Height);\n            WindowState = windowSettings.IsMaximized ? WindowState.Maximized : WindowState.Normal;\n        }\n        else\n        {\n            WindowStartupLocation = WindowStartupLocation.CenterScreen;\n        }\n\n        if (Program.Args.IsSplashScreenEnabled)\n        {\n            var appIconStream = Assets.AppIcon.Open();\n            var appIcon = new Bitmap(appIconStream);\n            appIconStream.Dispose();\n\n            SplashScreen = new ApplicationSplashScreen\n            {\n                AppIcon = appIcon,\n                InitApp = cancellationToken =>\n                {\n                    return Dispatcher\n                        .UIThread.InvokeAsync(() => StartupInitialize(lazyViewModel, cancellationToken))\n                        .GetTask();\n                },\n            };\n        }\n        else\n        {\n            StartupInitialize(lazyViewModel);\n        }\n    }\n\n    /// <summary>\n    /// Run startup initialization.\n    /// This runs on the UI thread.\n    /// </summary>\n    private void StartupInitialize(\n        Lazy<MainWindowViewModel> lazyViewModel,\n        CancellationToken cancellationToken = default\n    )\n    {\n        using var _ = CodeTimer.StartDebug();\n\n        Dispatcher.UIThread.VerifyAccess();\n\n        cancellationToken.ThrowIfCancellationRequested();\n\n        navigationService.TypedNavigation += NavigationService_OnTypedNavigation;\n\n        EventManager.Instance.ToggleProgressFlyout += (_, _) => progressFlyout?.Hide();\n        EventManager.Instance.CultureChanged += (_, _) => SetDefaultFonts();\n        EventManager.Instance.UpdateAvailable += OnUpdateAvailable;\n        EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested;\n        EventManager.Instance.DownloadsTeachingTipRequested += InstanceOnDownloadsTeachingTipRequested;\n\n        SetDefaultFonts();\n\n        Observable\n            .FromEventPattern<SizeChangedEventArgs>(this, nameof(SizeChanged))\n            .Where(x => x.EventArgs.NewSize != x.EventArgs.PreviousSize)\n            .Throttle(TimeSpan.FromMilliseconds(100))\n            .Select(x => x.EventArgs.NewSize)\n            .ObserveOn(SynchronizationContext.Current!)\n            .Subscribe(newSize =>\n            {\n                var validWindowPosition = Screens.All.Any(screen => screen.Bounds.Contains(Position));\n\n                settingsManager.Transaction(\n                    s =>\n                    {\n                        var isMaximized = WindowState == WindowState.Maximized;\n                        if (isMaximized && s.WindowSettings != null)\n                        {\n                            s.WindowSettings = s.WindowSettings with { IsMaximized = true };\n                        }\n                        else\n                        {\n                            // idk where these 30 pixels come from, probably title bar height? seems to be windows specific\n                            var newHeight = Compat.IsWindows\n                                ? Math.Max(0, newSize.Height - 30)\n                                : newSize.Height;\n                            s.WindowSettings = new WindowSettings(\n                                newSize.Width,\n                                newHeight,\n                                validWindowPosition ? Position.X : 0,\n                                validWindowPosition ? Position.Y : 0,\n                                WindowState == WindowState.Maximized\n                            );\n                        }\n                    },\n                    ignoreMissingLibraryDir: true\n                );\n            });\n\n        Observable\n            .FromEventPattern<PixelPointEventArgs>(this, nameof(PositionChanged))\n            .Where(x => Screens.All.Any(screen => screen.Bounds.Contains(x.EventArgs.Point)))\n            .Throttle(TimeSpan.FromMilliseconds(100))\n            .Select(x => x.EventArgs.Point)\n            .ObserveOn(SynchronizationContext.Current!)\n            .Subscribe(position =>\n            {\n                settingsManager.Transaction(\n                    s =>\n                    {\n                        var isMaximized = WindowState == WindowState.Maximized;\n                        var validWindowPosition = Screens.All.Any(screen => screen.Bounds.Contains(position));\n\n                        if (isMaximized && s.WindowSettings != null)\n                        {\n                            s.WindowSettings = s.WindowSettings with { IsMaximized = true };\n                        }\n                        else\n                        {\n                            s.WindowSettings = new WindowSettings(\n                                s.WindowSettings?.Width ?? Width,\n                                s.WindowSettings?.Height ?? Height,\n                                validWindowPosition ? position.X : 0,\n                                validWindowPosition ? position.Y : 0,\n                                WindowState == WindowState.Maximized\n                            );\n                        }\n                    },\n                    ignoreMissingLibraryDir: true\n                );\n            });\n\n        using (CodeTimer.StartDebug(\"Load view model\"))\n        {\n            var viewModel = lazyViewModel.Value;\n            DataContext = viewModel;\n        }\n    }\n\n    private void InstanceOnDownloadsTeachingTipRequested(object? sender, EventArgs e)\n    {\n        Dispatcher.UIThread.Post(() =>\n        {\n            if (\n                !settingsManager.Settings.SeenTeachingTips.Contains(\n                    Core.Models.Settings.TeachingTip.DownloadsTip\n                )\n            )\n            {\n                var target = this.FindControl<NavigationViewItem>(\"FooterDownloadItem\")!;\n                var tip = this.FindControl<TeachingTip>(\"DownloadsTeachingTip\")!;\n\n                tip.Target = target;\n                tip.Subtitle = Languages.Resources.TeachingTip_DownloadsExplanation;\n                tip.IsOpen = true;\n            }\n        });\n    }\n\n    private void OnNavigateAndFindCivitModelRequested(object? sender, int e)\n    {\n        navigationService.NavigateTo<CheckpointBrowserViewModel>();\n    }\n\n    /// <inheritdoc />\n    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)\n    {\n        base.OnApplyTemplate(e);\n\n        navigationService.SetFrame(FrameView ?? throw new NullReferenceException(\"Frame not found\"));\n    }\n\n    protected override void OnOpened(EventArgs e)\n    {\n        base.OnOpened(e);\n\n        Application.Current!.ActualThemeVariantChanged += OnActualThemeVariantChanged;\n\n        var theme = ActualThemeVariant;\n        // Enable mica for Windows 11\n        if (IsWindows11 && theme != FluentAvaloniaTheme.HighContrastTheme)\n        {\n            TryEnableMicaEffect();\n        }\n    }\n\n    protected override void OnClosing(WindowClosingEventArgs e)\n    {\n        // Show confirmation if package running\n        var runningPackageService = App.Services.GetRequiredService<RunningPackageService>();\n        if (\n            runningPackageService.RunningPackages.Count > 0\n            && e.CloseReason is WindowCloseReason.WindowClosing\n        )\n        {\n            e.Cancel = true;\n\n            var dialog = CreateExitConfirmDialog();\n            Dispatcher\n                .UIThread.InvokeAsync(async () =>\n                {\n                    if (\n                        (TaskDialogStandardResult)await dialog.ShowAsync(true) == TaskDialogStandardResult.Yes\n                    )\n                    {\n                        App.Services.GetRequiredService<MainWindow>().Hide();\n                        App.Shutdown();\n                    }\n                })\n                .SafeFireAndForget();\n        }\n\n        base.OnClosing(e);\n    }\n\n    private static TaskDialog CreateExitConfirmDialog()\n    {\n        var dialog = DialogHelper.CreateTaskDialog(\n            Languages.Resources.Label_ConfirmExit,\n            Languages.Resources.Label_ConfirmExitDetail\n        );\n\n        dialog.ShowProgressBar = false;\n        dialog.FooterVisibility = TaskDialogFooterVisibility.Never;\n\n        dialog.Buttons = new List<TaskDialogButton>\n        {\n            new(\"Exit\", TaskDialogStandardResult.Yes),\n            TaskDialogButton.CancelButton,\n        };\n        dialog.Buttons[0].IsDefault = true;\n\n        return dialog;\n    }\n\n    /// <inheritdoc />\n    protected override void OnClosed(EventArgs e)\n    {\n        base.OnClosed(e);\n\n        App.Shutdown();\n    }\n\n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        base.OnLoaded(e);\n        // Initialize notification service using this window as the visual root\n        notificationService.Initialize(this);\n\n        // Attach error notification handler for image loader\n        if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)\n        {\n            loader.LoadFailed += OnImageLoadFailed;\n        }\n\n        if (DataContext is not MainWindowViewModel vm)\n            return;\n\n        // Navigate to first page\n        Dispatcher.UIThread.Post(() =>\n            navigationService.NavigateTo(\n                vm.Pages[0],\n                new BetterSlideNavigationTransition { Effect = SlideNavigationTransitionEffect.FromBottom }\n            )\n        );\n\n        // Check show update teaching tip\n        if (vm.UpdateViewModel.IsUpdateAvailable)\n        {\n            OnUpdateAvailable(this, vm.UpdateViewModel.UpdateInfo);\n        }\n    }\n\n    protected override void OnUnloaded(RoutedEventArgs e)\n    {\n        base.OnUnloaded(e);\n\n        // Detach error notification handler for image loader\n        if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)\n        {\n            loader.LoadFailed -= OnImageLoadFailed;\n        }\n    }\n\n    private void NavigationService_OnTypedNavigation(object? sender, TypedNavigationEventArgs e)\n    {\n        var mainViewModel = (MainWindowViewModel)DataContext!;\n\n        mainViewModel.SelectedCategory = mainViewModel\n            .Pages.Concat(mainViewModel.FooterPages)\n            .FirstOrDefault(x => x.GetType() == e.ViewModelType);\n    }\n\n    private void OnUpdateAvailable(object? sender, UpdateInfo? updateInfo)\n    {\n        Dispatcher.UIThread.Post(() =>\n        {\n            if (DataContext is MainWindowViewModel vm && vm.ShouldShowUpdateAvailableTeachingTip(updateInfo))\n            {\n                var target = this.FindControl<NavigationViewItem>(\"FooterUpdateItem\")!;\n                var tip = this.FindControl<TeachingTip>(\"UpdateAvailableTeachingTip\")!;\n\n                tip.Target = target;\n                tip.Subtitle = $\"{Compat.AppVersion.ToDisplayString()} -> {updateInfo.Version}\";\n                tip.IsOpen = true;\n            }\n        });\n    }\n\n    private void SetDefaultFonts()\n    {\n        if (App.Current is not null)\n        {\n            FontFamily = App.Current.GetPlatformDefaultFontFamily();\n        }\n    }\n\n    private void NavigationView_OnItemInvoked(object sender, NavigationViewItemInvokedEventArgs e)\n    {\n        if (e.InvokedItemContainer is NavigationViewItem nvi)\n        {\n            // Skip if this is the currently selected item\n            if (nvi.IsSelected)\n            {\n                return;\n            }\n\n            if (nvi.Tag is null)\n            {\n                throw new InvalidOperationException(\"NavigationViewItem Tag is null\");\n            }\n\n            if (nvi.Tag is not ViewModelBase vm)\n            {\n                throw new InvalidOperationException(\n                    $\"NavigationViewItem Tag must be of type ViewModelBase, not {nvi.Tag?.GetType()}\"\n                );\n            }\n            navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition());\n        }\n    }\n\n    private void OnActualThemeVariantChanged(object? sender, EventArgs e)\n    {\n        if (IsWindows11)\n        {\n            if (ActualThemeVariant != FluentAvaloniaTheme.HighContrastTheme)\n            {\n                TryEnableMicaEffect();\n            }\n            else\n            {\n                ClearValue(BackgroundProperty);\n                ClearValue(TransparencyBackgroundFallbackProperty);\n            }\n        }\n    }\n\n    private void OnImageLoadFailed(object? sender, ImageLoadFailedEventArgs e)\n    {\n        var fileName = Path.GetFileName(e.Url);\n        var displayName = string.IsNullOrEmpty(fileName) ? e.Url : fileName;\n        logger.LogWarning($\"Could not load '{displayName}'\\n({e.Exception.Message})\");\n    }\n\n    private void TryEnableMicaEffect()\n    {\n        TransparencyBackgroundFallback = Brushes.Transparent;\n        TransparencyLevelHint = new[]\n        {\n            WindowTransparencyLevel.Mica,\n            WindowTransparencyLevel.AcrylicBlur,\n            WindowTransparencyLevel.Blur,\n        };\n\n        if (ActualThemeVariant == ThemeVariant.Dark)\n        {\n            var color = this.TryFindResource(\"SolidBackgroundFillColorBase\", ThemeVariant.Dark, out var value)\n                ? (Color2)(Color)value!\n                : new Color2(30, 31, 34);\n\n            color = color.LightenPercent(-0.5f);\n\n            Background = new ImmutableSolidColorBrush(color, 0.72);\n        }\n        else if (ActualThemeVariant == ThemeVariant.Light)\n        {\n            // Similar effect here\n            var color = this.TryFindResource(\n                \"SolidBackgroundFillColorBase\",\n                ThemeVariant.Light,\n                out var value\n            )\n                ? (Color2)(Color)value!\n                : new Color2(243, 243, 243);\n\n            color = color.LightenPercent(0.5f);\n\n            Background = new ImmutableSolidColorBrush(color, 0.9);\n        }\n    }\n\n    private void FooterDownloadItem_OnTapped(object? sender, TappedEventArgs e)\n    {\n        var item = sender as NavigationViewItem;\n        var flyout = item!.ContextFlyout;\n        flyout!.ShowAt(item);\n\n        progressFlyout = flyout;\n    }\n\n    private async void FooterUpdateItem_OnTapped(object? sender, TappedEventArgs e)\n    {\n        // show update window thing\n        if (DataContext is not MainWindowViewModel vm)\n        {\n            throw new NullReferenceException(\"DataContext is not MainWindowViewModel\");\n        }\n\n        await vm.ShowUpdateDialog();\n    }\n\n    private void FooterDiscordItem_OnTapped(object? sender, TappedEventArgs e)\n    {\n        ProcessRunner.OpenUrl(Assets.DiscordServerUrl);\n    }\n\n    private void PatreonPatreonItem_OnTapped(object? sender, TappedEventArgs e)\n    {\n        ProcessRunner.OpenUrl(Assets.PatreonUrl);\n    }\n\n    private void TopLevel_OnBackRequested(object? sender, RoutedEventArgs e)\n    {\n        e.Handled = true;\n        navigationService.GoBack();\n    }\n\n    private void NavigationView_OnBackRequested(object? sender, NavigationViewBackRequestedEventArgs e)\n    {\n        navigationService.GoBack();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n                          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                          xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n                          xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n                          xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n                          xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n                          xmlns:asyncImageLoader=\"clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia\"\n                          xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n                          xmlns:openArt=\"clr-namespace:StabilityMatrix.Core.Models.Api.OpenArt;assembly=StabilityMatrix.Core\"\n                          xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n                          xmlns:helpers=\"clr-namespace:StabilityMatrix.Avalonia.Helpers\"\n                          xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n                          xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n                          xmlns:viewModels=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n                          xmlns:labs=\"clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls\"\n                          xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n                          x:DataType=\"viewModels:OpenArtBrowserViewModel\"\n                          mc:Ignorable=\"d\" d:DesignWidth=\"800\" d:DesignHeight=\"450\"\n                          x:Class=\"StabilityMatrix.Avalonia.Views.OpenArtBrowserPage\">\n    <UserControl.Styles>\n        <Style Selector=\"Border#HoverBorder\">\n            <Setter Property=\"Transitions\">\n                <Transitions>\n                    <BoxShadowsTransition Property=\"BoxShadow\" Duration=\"0:0:0.237\" />\n                </Transitions>\n            </Setter>\n\n            <Style Selector=\"^ labs|AsyncImage\">\n                <Setter Property=\"Transitions\">\n                    <Transitions>\n                        <TransformOperationsTransition Property=\"RenderTransform\"\n                                                       Duration=\"0:0:0.237\">\n                            <TransformOperationsTransition.Easing>\n                                <QuadraticEaseInOut />\n                            </TransformOperationsTransition.Easing>\n                        </TransformOperationsTransition>\n                    </Transitions>\n                </Setter>\n            </Style>\n\n            <Style Selector=\"^:pointerover\">\n                <Setter Property=\"BoxShadow\" Value=\"0 0 40 0 #60000000\" />\n                <Setter Property=\"Cursor\" Value=\"Hand\" />\n                <Style Selector=\"^ labs|AsyncImage\">\n                    <Setter Property=\"CornerRadius\" Value=\"12\" />\n                    <Setter Property=\"RenderTransform\" Value=\"scale(1.03, 1.03)\" />\n                </Style>\n                <Style Selector=\"^ Border#ModelCardBottom\">\n                    <Setter Property=\"Background\" Value=\"#CC000000\" />\n                </Style>\n            </Style>\n\n            <Style Selector=\"^:not(:pointerover)\">\n                <Setter Property=\"BoxShadow\" Value=\"0 0 20 0 #60000000\" />\n                <Setter Property=\"Cursor\" Value=\"Arrow\" />\n                <Style Selector=\"^ labs|AsyncImage\">\n                    <Setter Property=\"CornerRadius\" Value=\"8\" />\n                    <Setter Property=\"RenderTransform\" Value=\"scale(1, 1)\" />\n                </Style>\n                <Style Selector=\"^ Border#ModelCardBottom\">\n                    <Setter Property=\"Background\" Value=\"#99000000\" />\n                </Style>\n            </Style>\n        </Style>\n    </UserControl.Styles>\n\n    <UserControl.Resources>\n        <input:StandardUICommand\n            x:Key=\"OpenModelCommand\"\n            Command=\"{Binding OpenModelCommand}\" />\n        \n        <input:StandardUICommand\n            x:Key=\"OpenOnOpenArtCommand\"\n            Command=\"{Binding OpenOnOpenArtCommand}\" />\n        \n        <input:StandardUICommand\n            x:Key=\"OpenWorkflowCommand\"\n            Command=\"{Binding OpenWorkflowCommand}\" />\n        \n        <converters:KiloFormatterStringConverter x:Key=\"KiloFormatterConverter\" />\n        <DataTemplate x:Key=\"OpenArtWorkflowTemplate\" DataType=\"{x:Type openArt:OpenArtSearchResult}\">\n            <Border\n                Name=\"HoverBorder\"\n                Padding=\"0\"\n                BorderThickness=\"0\"\n                Margin=\"8\"\n                ClipToBounds=\"True\"\n                CornerRadius=\"8\">\n                <Border.ContextFlyout>\n                    <MenuFlyout>\n                        <MenuItem Header=\"{x:Static lang:Resources.Action_OpenOnOpenArt}\"\n                                  Command=\"{StaticResource OpenOnOpenArtCommand}\"\n                                  CommandParameter=\"{Binding }\">\n                            <MenuItem.Icon>\n                                <ui:SymbolIcon Symbol=\"Open\" />\n                            </MenuItem.Icon>\n                        </MenuItem>\n                    </MenuFlyout>\n                </Border.ContextFlyout>\n                <Button\n                    Name=\"ModelCard\"\n                    Classes=\"transparent-full\"\n                    Padding=\"0\"\n                    BorderThickness=\"0\"\n                    VerticalContentAlignment=\"Top\"\n                    CornerRadius=\"8\"\n                    Command=\"{StaticResource OpenWorkflowCommand}\"\n                    CommandParameter=\"{Binding }\">\n                    <Grid RowDefinitions=\"*, Auto\">\n                        <vendorLabs:BetterAsyncImage\n                            Grid.Row=\"0\"\n                            Grid.RowSpan=\"2\"\n                            CornerRadius=\"8\"\n                            Width=\"330\"\n                            Height=\"400\"\n                            Source=\"{Binding Thumbnails[0].Url}\"\n                            Stretch=\"UniformToFill\" />\n\n                        <!-- Username pill card -->\n                        <Border\n                            BoxShadow=\"inset 1.2 0 80 1.8 #66000000\"\n                            CornerRadius=\"16\"\n                            Margin=\"4\"\n                            Grid.Row=\"0\"\n                            ClipToBounds=\"True\"\n                            HorizontalAlignment=\"Left\"\n                            VerticalAlignment=\"Bottom\">\n                            <Border.Resources>\n                                <DropShadowEffect\n                                    x:Key=\"TextDropShadowEffect\"\n                                    BlurRadius=\"12\"\n                                    Color=\"#FF000000\"\n                                    Opacity=\"0.9\" />\n                                <DropShadowEffect\n                                    x:Key=\"ImageDropShadowEffect\"\n                                    BlurRadius=\"12\"\n                                    Color=\"#FF000000\"\n                                    Opacity=\"0.2\" />\n                            </Border.Resources>\n                            <Button\n                                Command=\"{x:Static helpers:IOCommands.OpenUrlCommand}\"\n                                CommandParameter=\"{Binding Creator.DevProfileUrl}\"\n                                CornerRadius=\"16\"\n                                Classes=\"transparent\"\n                                Padding=\"10,4\">\n                                <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                                    <vendorLabs:BetterAsyncImage\n                                        Width=\"22\"\n                                        Height=\"22\"\n                                        ClipToBounds=\"True\"\n                                        Effect=\"{StaticResource ImageDropShadowEffect}\"\n                                        CornerRadius=\"11\"\n                                        RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n                                        IsVisible=\"{Binding Creator.Avatar, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                                        Source=\"{Binding Creator.Avatar}\">\n                                    </vendorLabs:BetterAsyncImage>\n                                    <TextBlock\n                                        VerticalAlignment=\"Center\"\n                                        Foreground=\"{DynamicResource TextControlForeground}\"\n                                        Effect=\"{StaticResource TextDropShadowEffect}\"\n                                        Text=\"{Binding Creator.Name}\" />\n                                </StackPanel>\n                            </Button>\n                        </Border>\n\n                        <Border\n                            Name=\"ModelCardBottom\"\n                            Grid.Row=\"1\">\n                            <Grid ColumnDefinitions=\"*, Auto\" RowDefinitions=\"Auto, Auto, Auto\">\n\n                                <!--\n                                    TextTrimming causing issues with unicode chars until\n                                    https://github.com/AvaloniaUI/Avalonia/pull/13385 is released\n                                -->\n                                <TextBlock\n                                    Grid.ColumnSpan=\"2\"\n                                    MaxWidth=\"250\"\n                                    Margin=\"8,0,8,0\"\n                                    HorizontalAlignment=\"Left\"\n                                    VerticalAlignment=\"Center\"\n                                    FontWeight=\"SemiBold\"\n                                    Foreground=\"{DynamicResource TextControlForeground}\"\n                                    LetterSpacing=\"0.33\"\n                                    Text=\"{Binding Name}\"\n                                    TextWrapping=\"NoWrap\"\n                                    ToolTip.Tip=\"{Binding Name}\" />\n\n                                <StackPanel\n                                    Grid.Row=\"2\"\n                                    Grid.Column=\"0\"\n                                    Orientation=\"Horizontal\">\n\n                                    <controls:StarsRating\n                                        Margin=\"8,8,0,8\"\n                                        Background=\"#66000000\"\n                                        FontSize=\"16\"\n                                        Foreground=\"{DynamicResource ThemeEldenRingOrangeColor}\"\n                                        Value=\"{Binding Stats.Rating}\" />\n                                    <TextBlock\n                                        Margin=\"4,0,0,0\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"{Binding Stats.NumReviews}\"\n                                        TextAlignment=\"Center\" />\n                                </StackPanel>\n\n                                <StackPanel\n                                    Grid.Row=\"2\"\n                                    Grid.Column=\"1\"\n                                    HorizontalAlignment=\"Right\"\n                                    Orientation=\"Horizontal\">\n                                    <avalonia:Icon Value=\"fa-solid fa-heart\" />\n                                    <TextBlock\n                                        Margin=\"4,0\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"{Binding Stats.NumLikes, Converter={StaticResource KiloFormatterConverter}}\" />\n\n                                    <avalonia:Icon Margin=\"4,0\" Value=\"fa-solid fa-download\" />\n                                    <TextBlock\n                                        Margin=\"0,0,4,0\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"{Binding Stats.NumDownloads, Converter={StaticResource KiloFormatterConverter}}\" />\n                                </StackPanel>\n                                <Button\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"1\"\n                                    Width=\"32\"\n                                    Margin=\"0,4,4,0\"\n                                    HorizontalAlignment=\"Right\"\n                                    VerticalAlignment=\"Top\"\n                                    HorizontalContentAlignment=\"Right\"\n                                    VerticalContentAlignment=\"Top\"\n                                    BorderThickness=\"0\"\n                                    Classes=\"transparent\">\n                                    <ui:SymbolIcon FontSize=\"18\" Symbol=\"MoreVertical\" />\n                                    <Button.Flyout>\n                                        <MenuFlyout>\n                                            <MenuItem Command=\"{StaticResource OpenModelCommand}\"\n                                                      CommandParameter=\"{Binding }\"\n                                                      Header=\"{x:Static lang:Resources.Action_OpenOnOpenArt}\">\n                                                <MenuItem.Icon>\n                                                    <ui:SymbolIcon Symbol=\"Open\" />\n                                                </MenuItem.Icon>\n                                            </MenuItem>\n                                        </MenuFlyout>\n                                    </Button.Flyout>\n                                </Button>\n                            </Grid>\n                        </Border>\n                    </Grid>\n                </Button>\n            </Border>\n\n        </DataTemplate>\n    </UserControl.Resources>\n\n    <Grid RowDefinitions=\"Auto, Auto, *, Auto\">\n        <Grid Grid.Row=\"0\" ColumnDefinitions=\"*,Auto\"\n              Margin=\"8\">\n            <TextBox\n                HorizontalAlignment=\"Stretch\"\n                Text=\"{Binding SearchQuery, Mode=TwoWay}\"\n                Watermark=\"{x:Static lang:Resources.Action_Search}\"\n                Classes=\"search\"/>\n\n            <Button\n                Grid.Column=\"1\"\n                Width=\"80\"\n                Margin=\"8,0,8,0\"\n                VerticalAlignment=\"Stretch\"\n                Classes=\"accent\"\n                Command=\"{Binding SearchButtonCommand}\"\n                IsDefault=\"True\">\n                <Grid>\n                    <controls:ProgressRing\n                        MinWidth=\"16\"\n                        MinHeight=\"16\"\n                        VerticalAlignment=\"Center\"\n                        BorderThickness=\"4\"\n                        IsIndeterminate=\"True\"\n                        IsVisible=\"{Binding SearchButtonCommand.IsRunning}\" />\n                    <TextBlock\n                        VerticalAlignment=\"Center\"\n                        IsVisible=\"{Binding !SearchButtonCommand.IsRunning}\"\n                        Text=\"{x:Static lang:Resources.Action_Search}\" />\n                </Grid>\n            </Button>\n        </Grid>\n        \n        <StackPanel Grid.Row=\"1\"\n                    Margin=\"8,0,0,8\" \n                    Orientation=\"Vertical\"\n                    IsVisible=\"{Binding SearchQuery, Converter={x:Static StringConverters.IsNullOrEmpty}}\">\n            <Label Content=\"{x:Static lang:Resources.Label_Sort}\" />\n            <ComboBox\n                MinWidth=\"100\"\n                ItemsSource=\"{Binding AllSortModes}\"\n                SelectedItem=\"{Binding SelectedSortMode}\"/>\n        </StackPanel>\n        \n        <controls:ProgressRing Grid.Row=\"2\"\n                               IsVisible=\"{Binding  IsLoading}\"\n                               IsIndeterminate=\"True\"\n                               Width=\"128\"\n                               Height=\"128\"/>\n\n        <ScrollViewer Grid.Row=\"2\"\n                      ScrollChanged=\"ScrollViewer_OnScrollChanged\"\n                      HorizontalContentAlignment=\"Center\"\n                      IsVisible=\"{Binding !IsLoading}\">\n            <ItemsRepeater ItemsSource=\"{Binding SearchResults}\"\n                           ItemTemplate=\"{StaticResource OpenArtWorkflowTemplate}\">\n                <ItemsRepeater.Layout>\n                    <UniformGridLayout MinColumnSpacing=\"4\" MinRowSpacing=\"4\"/>\n                </ItemsRepeater.Layout>\n            </ItemsRepeater>\n        </ScrollViewer>\n        \n        <StackPanel Grid.Row=\"3\"\n                    HorizontalAlignment=\"Center\"\n                    Margin=\"0,8,0,8\"\n                    Orientation=\"Horizontal\">\n            <Button\n                Margin=\"0,0,8,0\"\n                Command=\"{Binding FirstPageCommand}\"\n                IsEnabled=\"{Binding CanGoBack}\"\n                ToolTip.Tip=\"{x:Static lang:Resources.Label_FirstPage}\">\n                <avalonia:Icon Value=\"fa-solid fa-backward-fast\" />\n            </Button>\n            <Button\n                Margin=\"0,0,16,0\"\n                Command=\"{Binding PreviousPageCommand}\"\n                IsEnabled=\"{Binding CanGoBack}\"\n                ToolTip.Tip=\"{x:Static lang:Resources.Label_PreviousPage}\">\n                <avalonia:Icon Value=\"fa-solid fa-caret-left\" />\n            </Button>\n            <TextBlock Margin=\"8,0,4,0\" TextAlignment=\"Center\"\n                       Text=\"{x:Static lang:Resources.Label_Page}\"\n                       VerticalAlignment=\"Center\"/>\n            <ui:NumberBox Value=\"{Binding DisplayedPageNumber, FallbackValue=1}\"\n                          VerticalAlignment=\"Center\"\n                          SpinButtonPlacementMode=\"Hidden\"\n                          TextAlignment=\"Center\"/>\n            <TextBlock Margin=\"4,0,8,0\" VerticalAlignment=\"Center\">\n                <Run Text=\"/\"/>\n                <Run Text=\"{Binding PageCount, FallbackValue=5}\"/>\n            </TextBlock>\n            <Button\n                Margin=\"16,0,8,0\"\n                Command=\"{Binding NextPageCommand}\"\n                IsEnabled=\"{Binding CanGoForward}\"\n                ToolTip.Tip=\"{x:Static lang:Resources.Label_NextPage}\">\n                <avalonia:Icon Value=\"fa-solid fa-caret-right\" />\n            </Button>\n            <Button\n                Command=\"{Binding LastPageCommand}\"\n                IsEnabled=\"{Binding CanGoToEnd}\"\n                ToolTip.Tip=\"{x:Static lang:Resources.Label_LastPage}\">\n                <avalonia:Icon Value=\"fa-solid fa-forward-fast\" />\n            </Button>\n        </StackPanel>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml.cs",
    "content": "﻿using System;\nusing AsyncAwaitBestPractices;\nusing Avalonia.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<OpenArtBrowserPage>]\npublic partial class OpenArtBrowserPage : UserControlBase\n{\n    private readonly ISettingsManager settingsManager;\n\n    public OpenArtBrowserPage(ISettingsManager settingsManager)\n    {\n        this.settingsManager = settingsManager;\n        InitializeComponent();\n    }\n\n    private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e)\n    {\n        if (sender is not ScrollViewer scrollViewer)\n            return;\n\n        if (scrollViewer.Offset.Y == 0)\n            return;\n\n        var isAtEnd = Math.Abs(scrollViewer.Offset.Y - scrollViewer.ScrollBarMaximum.Y) < 1f;\n\n        if (\n            isAtEnd\n            && settingsManager.Settings.IsWorkflowInfiniteScrollEnabled\n            && DataContext is IInfinitelyScroll scroll\n        )\n        {\n            scroll.LoadNextPageAsync().SafeFireAndForget();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.OpenModelDbBrowserPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:asyncImageLoader=\"clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:helpers=\"clr-namespace:StabilityMatrix.Avalonia.Helpers\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:labs=\"clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:openModelsDb=\"clr-namespace:StabilityMatrix.Core.Models.Api.OpenModelsDb;assembly=StabilityMatrix.Core\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n    xmlns:viewModels=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    xmlns:vmCheckpointBrowser=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser\"\n    d:DataContext=\"{x:Static designData:DesignData.OpenModelDbBrowserViewModel}\"\n    d:DesignHeight=\"650\"\n    d:DesignWidth=\"900\"\n    x:DataType=\"vmCheckpointBrowser:OpenModelDbBrowserViewModel\"\n    mc:Ignorable=\"d\">\n    <UserControl.Styles>\n        <Style Selector=\"Border#HoverBorder\">\n            <Setter Property=\"Transitions\">\n                <Transitions>\n                    <BoxShadowsTransition Property=\"BoxShadow\" Duration=\"0:0:0.237\" />\n                </Transitions>\n            </Setter>\n\n            <Style Selector=\"^ labs|AsyncImage\">\n                <Setter Property=\"Transitions\">\n                    <Transitions>\n                        <TransformOperationsTransition Property=\"RenderTransform\" Duration=\"0:0:0.237\">\n                            <TransformOperationsTransition.Easing>\n                                <QuadraticEaseInOut />\n                            </TransformOperationsTransition.Easing>\n                        </TransformOperationsTransition>\n                    </Transitions>\n                </Setter>\n            </Style>\n\n            <Style Selector=\"^ vendorLabs|BetterAsyncImage\">\n                <Setter Property=\"ImageCache\" Value=\"{x:Static controls:ImageLoaders.OpenModelDbImageCache}\" />\n                <Setter Property=\"IsCacheEnabled\" Value=\"True\" />\n            </Style>\n\n            <Style Selector=\"^:pointerover\">\n                <Setter Property=\"BoxShadow\" Value=\"0 0 40 0 #60000000\" />\n                <Setter Property=\"Cursor\" Value=\"Hand\" />\n                <Style Selector=\"^ labs|AsyncImage\">\n                    <Setter Property=\"CornerRadius\" Value=\"12\" />\n                    <Setter Property=\"RenderTransform\" Value=\"scale(1.03, 1.03)\" />\n                </Style>\n                <Style Selector=\"^ Border#ModelCardBottom\">\n                    <Setter Property=\"Background\" Value=\"#CC000000\" />\n                </Style>\n            </Style>\n\n            <Style Selector=\"^:not(:pointerover)\">\n                <Setter Property=\"BoxShadow\" Value=\"0 0 20 0 #60000000\" />\n                <Setter Property=\"Cursor\" Value=\"Arrow\" />\n                <Style Selector=\"^ labs|AsyncImage\">\n                    <Setter Property=\"CornerRadius\" Value=\"8\" />\n                    <Setter Property=\"RenderTransform\" Value=\"scale(1, 1)\" />\n                </Style>\n                <Style Selector=\"^ Border#ModelCardBottom\">\n                    <Setter Property=\"Background\" Value=\"#99000000\" />\n                </Style>\n            </Style>\n        </Style>\n    </UserControl.Styles>\n\n    <UserControl.Resources>\n        <DropShadowEffect\n            x:Key=\"TextDropShadowEffect\"\n            BlurRadius=\"12\"\n            Opacity=\"0.9\"\n            Color=\"#FF000000\" />\n        <DropShadowEffect\n            x:Key=\"ImageDropShadowEffect\"\n            BlurRadius=\"12\"\n            Opacity=\"0.2\"\n            Color=\"#FF000000\" />\n\n        <converters:KiloFormatterStringConverter x:Key=\"KiloFormatterConverter\" />\n\n        <!--<input:StandardUICommand\n            x:Key=\"OpenModelCommand\"\n            Command=\"{Binding OpenModelCommand}\" />\n        <input:StandardUICommand\n            x:Key=\"OpenWorkflowCommand\"\n            Command=\"{Binding OpenWorkflowCommand}\" />-->\n\n        <input:StandardUICommand x:Key=\"OpenModelCardCommand\" Command=\"{Binding OpenModelCardCommand}\" />\n\n        <DataTemplate x:Key=\"ModelCardTemplate\" DataType=\"{x:Type vmCheckpointBrowser:OpenModelDbBrowserCardViewModel}\">\n            <Border\n                Name=\"HoverBorder\"\n                Margin=\"8\"\n                Padding=\"0\"\n                BorderThickness=\"0\"\n                ClipToBounds=\"True\"\n                CornerRadius=\"8\">\n                <Border.ContextFlyout>\n                    <MenuFlyout>\n                        <MenuItem\n                            Command=\"{x:Static helpers:IOCommands.OpenUriCommand}\"\n                            CommandParameter=\"{Binding ModelUri}\"\n                            Header=\"{x:Static lang:Resources.Action_OpenOnOpenModelDb}\">\n                            <MenuItem.Icon>\n                                <ui:SymbolIcon Symbol=\"Open\" />\n                            </MenuItem.Icon>\n                        </MenuItem>\n                    </MenuFlyout>\n                </Border.ContextFlyout>\n                <!--    -->\n                <Button\n                    Name=\"ModelCard\"\n                    Padding=\"0\"\n                    VerticalContentAlignment=\"Top\"\n                    BorderThickness=\"0\"\n                    Classes=\"transparent-full\"\n                    Command=\"{StaticResource OpenModelCardCommand}\"\n                    CommandParameter=\"{Binding}\"\n                    CornerRadius=\"8\">\n                    <Grid RowDefinitions=\"*, Auto\">\n                        <vendorLabs:BetterAsyncImage\n                            Grid.Row=\"0\"\n                            Grid.RowSpan=\"2\"\n                            MinWidth=\"300\"\n                            MinHeight=\"400\"\n                            CornerRadius=\"8\"\n                            Source=\"{Binding ThumbnailUri}\"\n                            Stretch=\"UniformToFill\" />\n\n                        <!--  Username pill card  -->\n                        <Border\n                            Grid.Row=\"0\"\n                            Margin=\"4\"\n                            HorizontalAlignment=\"Left\"\n                            VerticalAlignment=\"Bottom\"\n                            BoxShadow=\"inset 1.2 0 80 1.8 #66000000\"\n                            ClipToBounds=\"True\"\n                            CornerRadius=\"16\">\n                            <Button\n                                Padding=\"10,4\"\n                                Classes=\"transparent\"\n                                Command=\"{x:Static helpers:IOCommands.OpenUriCommand}\"\n                                CommandParameter=\"{Binding DefaultAuthorProfileUri}\"\n                                CornerRadius=\"16\">\n                                <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                                    <!--<vendorLabs:BetterAsyncImage\n                                        Width=\"22\"\n                                        Height=\"22\"\n                                        ClipToBounds=\"True\"\n                                        CornerRadius=\"11\"\n                                        Effect=\"{StaticResource ImageDropShadowEffect}\"\n                                        IsVisible=\"{Binding Creator.Avatar, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                                        RenderOptions.BitmapInterpolationMode=\"HighQuality\"\n                                        Source=\"{Binding Creator.Avatar}\" />-->\n                                    <TextBlock\n                                        VerticalAlignment=\"Center\"\n                                        Effect=\"{StaticResource TextDropShadowEffect}\"\n                                        Foreground=\"{DynamicResource TextControlForeground}\"\n                                        Text=\"{Binding DefaultAuthor}\" />\n                                </StackPanel>\n                            </Button>\n                        </Border>\n\n                        <Border Name=\"ModelCardBottom\" Grid.Row=\"1\">\n                            <Grid ColumnDefinitions=\"*, Auto\" RowDefinitions=\"Auto, Auto, Auto\">\n                                <!--\n                                    TextTrimming causing issues with unicode chars until\n                                    https://github.com/AvaloniaUI/Avalonia/pull/13385 is released\n                                -->\n                                <TextBlock\n                                    Grid.ColumnSpan=\"2\"\n                                    MaxWidth=\"250\"\n                                    Margin=\"8,0,8,0\"\n                                    HorizontalAlignment=\"Left\"\n                                    VerticalAlignment=\"Center\"\n                                    FontWeight=\"SemiBold\"\n                                    Foreground=\"{DynamicResource TextControlForeground}\"\n                                    LetterSpacing=\"0.33\"\n                                    Text=\"{Binding Model.Name}\"\n                                    TextWrapping=\"NoWrap\"\n                                    ToolTip.Tip=\"{Binding Model.Name}\" />\n\n                                <!--<StackPanel\n                                    Grid.Row=\"2\"\n                                    Grid.Column=\"0\"\n                                    Orientation=\"Horizontal\">\n\n                                    <controls:StarsRating\n                                        Margin=\"8,8,0,8\"\n                                        Background=\"#66000000\"\n                                        FontSize=\"16\"\n                                        Foreground=\"{DynamicResource ThemeEldenRingOrangeColor}\"\n                                        Value=\"{Binding Stats.Rating}\" />\n                                    <TextBlock\n                                        Margin=\"4,0,0,0\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"{Binding Stats.NumReviews}\"\n                                        TextAlignment=\"Center\" />\n                                </StackPanel>-->\n\n                                <!--  Tags  -->\n                                <ui:ItemsRepeater\n                                    Grid.Row=\"2\"\n                                    Grid.Column=\"0\"\n                                    Margin=\"8\"\n                                    HorizontalAlignment=\"Left\"\n                                    ItemsSource=\"{Binding Tags}\">\n                                    <ui:ItemsRepeater.Layout>\n                                        <ui:FlowLayout\n                                            MinColumnSpacing=\"4\"\n                                            MinRowSpacing=\"2\"\n                                            Orientation=\"Horizontal\" />\n                                    </ui:ItemsRepeater.Layout>\n                                    <ui:ItemsRepeater.ItemTemplate>\n                                        <DataTemplate x:DataType=\"openModelsDb:OpenModelDbTag\">\n                                            <Label\n                                                Classes=\"Teal\"\n                                                Content=\"{Binding Name}\"\n                                                Theme=\"{StaticResource TagLabel}\" />\n                                        </DataTemplate>\n                                    </ui:ItemsRepeater.ItemTemplate>\n                                </ui:ItemsRepeater>\n\n                                <StackPanel\n                                    Grid.Row=\"2\"\n                                    Grid.Column=\"1\"\n                                    HorizontalAlignment=\"Right\"\n                                    Orientation=\"Horizontal\">\n                                    <!--<avalonia:Icon Value=\"fa-solid fa-heart\" />-->\n                                    <!--<TextBlock\n                                        Margin=\"4,0\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"{Binding Stats.NumLikes, Converter={StaticResource KiloFormatterConverter}}\" />\n\n                                    <avalonia:Icon Margin=\"4,0\" Value=\"fa-solid fa-download\" />\n                                    <TextBlock\n                                        Margin=\"0,0,4,0\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"{Binding Stats.NumDownloads, Converter={StaticResource KiloFormatterConverter}}\" />-->\n                                </StackPanel>\n                                <Button\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"1\"\n                                    Width=\"32\"\n                                    Margin=\"0,4,4,0\"\n                                    HorizontalAlignment=\"Right\"\n                                    VerticalAlignment=\"Top\"\n                                    HorizontalContentAlignment=\"Right\"\n                                    VerticalContentAlignment=\"Top\"\n                                    BorderThickness=\"0\"\n                                    Classes=\"transparent\">\n                                    <ui:SymbolIcon FontSize=\"18\" Symbol=\"MoreVertical\" />\n                                    <Button.Flyout>\n                                        <MenuFlyout>\n                                            <MenuItem\n                                                Command=\"{StaticResource OpenInBrowserCommand}\"\n                                                CommandParameter=\"{Binding}\"\n                                                Header=\"{x:Static lang:Resources.Action_OpenOnOpenModelDb}\">\n                                                <MenuItem.Icon>\n                                                    <ui:SymbolIcon Symbol=\"Open\" />\n                                                </MenuItem.Icon>\n                                            </MenuItem>\n                                        </MenuFlyout>\n                                    </Button.Flyout>\n                                </Button>\n                            </Grid>\n                        </Border>\n                    </Grid>\n                </Button>\n            </Border>\n\n        </DataTemplate>\n    </UserControl.Resources>\n\n    <Grid RowDefinitions=\"Auto, Auto, *, Auto\">\n        <Grid\n            Grid.Row=\"0\"\n            Margin=\"8\"\n            ColumnDefinitions=\"*,Auto\">\n            <TextBox\n                HorizontalAlignment=\"Stretch\"\n                Classes=\"search\"\n                Text=\"{Binding SearchQuery, Mode=TwoWay}\"\n                Watermark=\"{x:Static lang:Resources.Action_Search}\" />\n\n            <Button\n                Grid.Column=\"1\"\n                Width=\"80\"\n                Margin=\"8,0,8,0\"\n                VerticalAlignment=\"Stretch\"\n                Classes=\"accent\"\n                Command=\"{Binding SearchCommand}\"\n                IsDefault=\"True\">\n                <Grid>\n                    <controls:ProgressRing\n                        MinWidth=\"16\"\n                        MinHeight=\"16\"\n                        VerticalAlignment=\"Center\"\n                        BorderThickness=\"4\"\n                        IsIndeterminate=\"True\"\n                        IsVisible=\"{Binding SearchCommand.IsRunning}\" />\n                    <TextBlock\n                        VerticalAlignment=\"Center\"\n                        IsVisible=\"{Binding !SearchCommand.IsRunning}\"\n                        Text=\"{x:Static lang:Resources.Action_Search}\" />\n                </Grid>\n            </Button>\n        </Grid>\n\n        <StackPanel\n            Grid.Row=\"1\"\n            Margin=\"8,0,0,8\"\n            IsVisible=\"{Binding SearchQuery, Converter={x:Static StringConverters.IsNullOrEmpty}}\"\n            Orientation=\"Vertical\">\n            <Label Content=\"{x:Static lang:Resources.Label_Sort}\" />\n            <controls:BetterComboBox\n                MinWidth=\"100\"\n                ItemsSource=\"{Binding SortOptions}\"\n                SelectedItem=\"{Binding SelectedSortOption}\" />\n        </StackPanel>\n\n        <controls:ProgressRing\n            Grid.Row=\"2\"\n            Width=\"128\"\n            Height=\"128\"\n            IsIndeterminate=\"True\"\n            IsVisible=\"{Binding IsLoading}\" />\n\n        <ScrollViewer\n            Grid.Row=\"2\"\n            HorizontalContentAlignment=\"Center\"\n            IsVisible=\"{Binding !IsLoading}\">\n            <ui:ItemsRepeater ItemTemplate=\"{StaticResource ModelCardTemplate}\" ItemsSource=\"{Binding FilteredModelCards}\">\n                <ui:ItemsRepeater.Layout>\n                    <ui:UniformGridLayout\n                        ItemsStretch=\"Fill\"\n                        MinItemHeight=\"400\"\n                        MinItemWidth=\"300\" />\n                </ui:ItemsRepeater.Layout>\n            </ui:ItemsRepeater>\n        </ScrollViewer>\n\n        <StackPanel\n            Grid.Row=\"3\"\n            Margin=\"0,8,0,8\"\n            HorizontalAlignment=\"Center\"\n            Orientation=\"Horizontal\">\n            <!--<Button\n                Margin=\"0,0,8,0\"\n                Command=\"{Binding FirstPageCommand}\"\n                IsEnabled=\"{Binding CanGoBack}\"\n                ToolTip.Tip=\"{x:Static lang:Resources.Label_FirstPage}\">\n                <avalonia:Icon Value=\"fa-solid fa-backward-fast\" />\n            </Button>\n            <Button\n                Margin=\"0,0,16,0\"\n                Command=\"{Binding PreviousPageCommand}\"\n                IsEnabled=\"{Binding CanGoBack}\"\n                ToolTip.Tip=\"{x:Static lang:Resources.Label_PreviousPage}\">\n                <avalonia:Icon Value=\"fa-solid fa-caret-left\" />\n            </Button>\n            <TextBlock Margin=\"8,0,4,0\" TextAlignment=\"Center\"\n                       Text=\"{x:Static lang:Resources.Label_Page}\"\n                       VerticalAlignment=\"Center\"/>\n            <ui:NumberBox Value=\"{Binding DisplayedPageNumber, FallbackValue=1}\"\n                          VerticalAlignment=\"Center\"\n                          SpinButtonPlacementMode=\"Hidden\"\n                          TextAlignment=\"Center\"/>\n            <TextBlock Margin=\"4,0,8,0\" VerticalAlignment=\"Center\">\n                <Run Text=\"/\"/>\n                <Run Text=\"{Binding PageCount, FallbackValue=5}\"/>\n            </TextBlock>\n            <Button\n                Margin=\"16,0,8,0\"\n                Command=\"{Binding NextPageCommand}\"\n                IsEnabled=\"{Binding CanGoForward}\"\n                ToolTip.Tip=\"{x:Static lang:Resources.Label_NextPage}\">\n                <avalonia:Icon Value=\"fa-solid fa-caret-right\" />\n            </Button>\n            <Button\n                Command=\"{Binding LastPageCommand}\"\n                IsEnabled=\"{Binding CanGoToEnd}\"\n                ToolTip.Tip=\"{x:Static lang:Resources.Label_LastPage}\">\n                <avalonia:Icon Value=\"fa-solid fa-forward-fast\" />\n            </Button>-->\n        </StackPanel>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/OpenModelDbBrowserPage.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.Settings;\nusing StabilityMatrix.Core.Attributes;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<OpenModelDbBrowserPage>]\npublic partial class OpenModelDbBrowserPage : UserControlBase\n{\n    public OpenModelDbBrowserPage()\n    {\n        InitializeComponent();\n    }\n    /*private readonly ISettingsManager settingsManager;\n\n    public OpenModelDbBrowserPage(ISettingsManager settingsManager)\n    {\n        this.settingsManager = settingsManager;\n        InitializeComponent();\n    }\n\n    private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e)\n    {\n        if (sender is not ScrollViewer scrollViewer)\n            return;\n\n        if (scrollViewer.Offset.Y == 0)\n            return;\n\n        var isAtEnd = Math.Abs(scrollViewer.Offset.Y - scrollViewer.ScrollBarMaximum.Y) < 1f;\n\n        if (\n            isAtEnd\n            && settingsManager.Settings.IsWorkflowInfiniteScrollEnabled\n            && DataContext is IInfinitelyScroll scroll\n        )\n        {\n            scroll.LoadNextPageAsync().SafeFireAndForget();\n        }\n    }*/\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/OutputsPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.OutputsPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:behaviors=\"clr-namespace:StabilityMatrix.Avalonia.Behaviors\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:fluentAvalonia=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:outputsPage=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.OutputsPage\"\n    xmlns:scroll=\"clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll\"\n    xmlns:selectableImageCard=\"clr-namespace:StabilityMatrix.Avalonia.Controls.SelectableImageCard\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    d:DataContext=\"{x:Static mocks:DesignData.OutputsPageViewModel}\"\n    d:DesignHeight=\"650\"\n    d:DesignWidth=\"800\"\n    x:CompileBindings=\"True\"\n    x:DataType=\"vm:OutputsPageViewModel\"\n    Focusable=\"True\"\n    mc:Ignorable=\"d\">\n\n    <controls:UserControlBase.Resources>\n        <converters:FileUriConverter x:Key=\"FileUriConverter\" />\n    </controls:UserControlBase.Resources>\n\n    <controls:UserControlBase.Styles>\n        <!--  hide the chevron in the button  -->\n        <Style Selector=\"ui|CommandBarButton:flyout\">\n            <Style Selector=\"^ ui|FontIcon\">\n                <Setter Property=\"Foreground\" Value=\"Transparent\" />\n            </Style>\n        </Style>\n    </controls:UserControlBase.Styles>\n\n    <Grid\n        Margin=\"16\"\n        ColumnDefinitions=\"Auto, *\"\n        RowDefinitions=\"Auto,*\">\n\n        <TreeView\n            Grid.Row=\"1\"\n            Grid.Column=\"0\"\n            Margin=\"0,0,12,0\"\n            IsVisible=\"{Binding ShowFolders}\"\n            ItemsSource=\"{Binding Categories}\"\n            SelectedItem=\"{Binding SelectedCategory}\"\n            SelectionMode=\"AlwaysSelected\">\n            <TreeView.ItemContainerTheme>\n                <ControlTheme\n                    x:DataType=\"models:TreeViewDirectory\"\n                    BasedOn=\"{StaticResource {x:Type TreeViewItem}}\"\n                    TargetType=\"TreeViewItem\">\n                    <Setter Property=\"IsExpanded\" Value=\"{Binding IsExpanded}\" />\n                </ControlTheme>\n            </TreeView.ItemContainerTheme>\n            <TreeView.ItemTemplate>\n                <TreeDataTemplate ItemsSource=\"{Binding SubDirectories}\">\n                    <TextBlock\n                        MaxWidth=\"250\"\n                        Text=\"{Binding Name}\"\n                        TextTrimming=\"CharacterEllipsis\"\n                        TextWrapping=\"NoWrap\"\n                        ToolTip.Tip=\"{Binding Name}\" />\n                </TreeDataTemplate>\n            </TreeView.ItemTemplate>\n        </TreeView>\n\n        <Grid\n            Grid.Row=\"0\"\n            Grid.Column=\"0\"\n            Grid.ColumnSpan=\"2\"\n            Margin=\"0,0,0,16\"\n            ColumnDefinitions=\"Auto,Auto,*,Auto\"\n            RowDefinitions=\"Auto,*\">\n\n            <Grid.Styles>\n                <Style Selector=\"TextBox\">\n                    <Setter Property=\"MinHeight\" Value=\"40\" />\n                </Style>\n                <Style Selector=\"ComboBox\">\n                    <Setter Property=\"MinHeight\" Value=\"40\" />\n                </Style>\n            </Grid.Styles>\n\n            <TextBox\n                Grid.Row=\"1\"\n                Grid.Column=\"2\"\n                MinWidth=\"150\"\n                Margin=\"4,0\"\n                VerticalAlignment=\"Center\"\n                VerticalContentAlignment=\"Center\"\n                KeyDown=\"InputElement_OnKeyDown\"\n                Text=\"{Binding SearchQuery, Mode=TwoWay}\"\n                Watermark=\"{x:Static lang:Resources.Action_Search}\">\n                <TextBox.InnerRightContent>\n                    <Grid>\n                        <Button\n                            Classes=\"transparent-full\"\n                            Command=\"{Binding ClearSearchQuery}\"\n                            IsVisible=\"{Binding SearchQuery.Length}\">\n                            <ui:SymbolIcon Symbol=\"Cancel\" />\n                        </Button>\n                        <ui:SymbolIcon\n                            Margin=\"0,0,10,0\"\n                            FontSize=\"16\"\n                            IsVisible=\"{Binding !SearchQuery.Length}\"\n                            Symbol=\"Find\" />\n                    </Grid>\n                </TextBox.InnerRightContent>\n            </TextBox>\n\n            <ui:CommandBar\n                Grid.Row=\"1\"\n                Grid.Column=\"3\"\n                Margin=\"8,0,0,0\"\n                VerticalAlignment=\"Center\"\n                VerticalContentAlignment=\"Center\"\n                DefaultLabelPosition=\"Right\">\n\n                <ui:CommandBar.PrimaryCommands>\n                    <ui:CommandBarElementContainer>\n                        <TextBlock\n                            VerticalAlignment=\"Center\"\n                            IsVisible=\"{Binding NumItemsSelected}\"\n                            Text=\"{Binding NumImagesSelected}\" />\n                    </ui:CommandBarElementContainer>\n                    <ui:CommandBarButton\n                        Command=\"{Binding DeleteAllSelected}\"\n                        IconSource=\"Delete\"\n                        IsEnabled=\"{Binding NumItemsSelected}\" />\n                    <ui:CommandBarSeparator />\n                    <ui:CommandBarButton\n                        Command=\"{Binding SelectAll}\"\n                        IconSource=\"SelectAll\"\n                        IsEnabled=\"{Binding !IsLoading}\"\n                        Label=\"{x:Static lang:Resources.Action_SelectAll}\" />\n                    <ui:CommandBarButton\n                        Command=\"{Binding ClearSelection}\"\n                        IconSource=\"ClearSelection\"\n                        IsEnabled=\"{Binding NumItemsSelected}\"\n                        Label=\"{x:Static lang:Resources.Action_ClearSelection}\" />\n                    <ui:CommandBarSeparator />\n                    <ui:CommandBarButton\n                        Classes.loading=\"{Binding IsLoading}\"\n                        Command=\"{Binding Refresh}\"\n                        IconSource=\"Refresh\"\n                        IsEnabled=\"{Binding !IsLoading}\"\n                        Label=\"{x:Static lang:Resources.Action_Refresh}\">\n                        <ui:CommandBarButton.Styles>\n                            <Style Selector=\"ui|CommandBarButton:disabled.loading\">\n                                <Style Selector=\"^ ui|SymbolIcon\">\n                                    <Style.Animations>\n                                        <Animation IterationCount=\"INFINITE\" Duration=\"0:0:2\">\n                                            <KeyFrame Cue=\"0%\">\n                                                <Setter Property=\"RotateTransform.Angle\" Value=\"0\" />\n                                            </KeyFrame>\n                                            <KeyFrame Cue=\"100%\">\n                                                <Setter Property=\"RotateTransform.Angle\" Value=\"360\" />\n                                            </KeyFrame>\n                                        </Animation>\n                                    </Style.Animations>\n                                </Style>\n                            </Style>\n                        </ui:CommandBarButton.Styles>\n                    </ui:CommandBarButton>\n                    <ui:CommandBarButton\n                        VerticalAlignment=\"Center\"\n                        IconSource=\"Zoom\"\n                        Label=\"View\">\n                        <ui:CommandBarButton.Flyout>\n                            <Flyout>\n                                <StackPanel Orientation=\"Vertical\">\n                                    <TextBlock\n                                        HorizontalAlignment=\"Center\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"Show\" />\n\n                                    <Separator Margin=\"0,8\" />\n\n                                    <ToggleSwitch\n                                        Margin=\"8,-4\"\n                                        HorizontalAlignment=\"Left\"\n                                        HorizontalContentAlignment=\"Center\"\n                                        IsChecked=\"{Binding ShowFolders}\"\n                                        OffContent=\"Folder Sidebar\"\n                                        OnContent=\"Folder Sidebar\" />\n\n\n                                    <Separator Margin=\"0,8\" />\n\n                                    <TextBlock\n                                        HorizontalAlignment=\"Center\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"Zoom\" />\n                                    <Slider\n                                        MinWidth=\"150\"\n                                        Maximum=\"1.5\"\n                                        Minimum=\"0.6\"\n                                        SmallChange=\"0.05\"\n                                        Value=\"{Binding ResizeFactor, Mode=TwoWay}\" />\n                                    <TextBlock\n                                        Margin=\"0,-4,0,0\"\n                                        HorizontalAlignment=\"Center\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"{Binding ResizeFactor, StringFormat={}{0:F2}x}\" />\n                                </StackPanel>\n                            </Flyout>\n                        </ui:CommandBarButton.Flyout>\n                    </ui:CommandBarButton>\n                </ui:CommandBar.PrimaryCommands>\n\n                <ui:CommandBar.SecondaryCommands>\n                    <ui:CommandBarButton\n                        Command=\"{Binding ConsolidateImages}\"\n                        IconSource=\"MoveToFolder\"\n                        Label=\"{x:Static lang:Resources.Action_Consolidate}\" />\n                </ui:CommandBar.SecondaryCommands>\n            </ui:CommandBar>\n        </Grid>\n\n        <controls:ProgressRing\n            Grid.Row=\"1\"\n            Grid.Column=\"1\"\n            Width=\"200\"\n            Height=\"200\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            IsIndeterminate=\"True\"\n            IsVisible=\"{Binding IsChangingCategory}\" />\n\n        <scroll:BetterScrollViewer\n            Name=\"ImageScrollViewer\"\n            Grid.Row=\"1\"\n            Grid.Column=\"1\">\n            <ItemsRepeater\n                HorizontalAlignment=\"Center\"\n                VerticalAlignment=\"Top\"\n                ItemsSource=\"{Binding Outputs}\">\n                <Interaction.Behaviors>\n                    <behaviors:ResizeBehavior\n                        MaxResizeFactor=\"1.5\"\n                        MinResizeFactor=\"0.6\"\n                        PointerWheelTargetObject=\"{Binding #ImageScrollViewer}\"\n                        ResizeFactor=\"{Binding ResizeFactor}\" />\n                </Interaction.Behaviors>\n                <ItemsRepeater.Layout>\n                    <UniformGridLayout MinColumnSpacing=\"8\" MinRowSpacing=\"8\" />\n                </ItemsRepeater.Layout>\n                <ItemsRepeater.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type outputsPage:OutputImageViewModel}\">\n                        <LayoutTransformControl>\n                            <LayoutTransformControl.LayoutTransform>\n                                <ScaleTransform ScaleX=\"{Binding $parent[scroll:BetterScrollViewer].((vm:OutputsPageViewModel)DataContext).ResizeFactor, FallbackValue=1, TargetNullValue=1}\" ScaleY=\"{Binding $parent[scroll:BetterScrollViewer].((vm:OutputsPageViewModel)DataContext).ResizeFactor, FallbackValue=1, TargetNullValue=1}\" />\n                            </LayoutTransformControl.LayoutTransform>\n                            <selectableImageCard:SelectableImageButton\n                                vendorLabs:BetterAsyncImage.ImageCache=\"{x:Static controls:ImageLoaders.OutputsPageImageCache}\"\n                                Command=\"{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).OnImageClick}\"\n                                CommandParameter=\"{Binding}\"\n                                ImageHeight=\"300\"\n                                ImageWidth=\"300\"\n                                IsSelected=\"{Binding IsSelected}\"\n                                Source=\"{Binding ImageFile.AbsolutePath, Converter={StaticResource FileUriConverter}}\">\n                                <selectableImageCard:SelectableImageButton.ContextFlyout>\n                                    <ui:FAMenuFlyout>\n                                        <ui:MenuFlyoutItem\n                                            Command=\"{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).CopyImage}\"\n                                            CommandParameter=\"{Binding ImageFile.AbsolutePath}\"\n                                            HotKey=\"{x:Null}\"\n                                            IconSource=\"Copy\"\n                                            Text=\"{x:Static lang:Resources.Action_Copy}\" />\n                                        <ui:MenuFlyoutItem\n                                            Command=\"{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).OpenImage}\"\n                                            CommandParameter=\"{Binding ImageFile.AbsolutePath}\"\n                                            HotKey=\"{x:Null}\"\n                                            IconSource=\"Folder\"\n                                            Text=\"{x:Static lang:Resources.Action_OpenInExplorer}\" />\n                                        <ui:MenuFlyoutItem\n                                            Command=\"{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).ShowImageDialog}\"\n                                            CommandParameter=\"{Binding}\"\n                                            HotKey=\"{x:Null}\"\n                                            IconSource=\"Image\"\n                                            IsVisible=\"{Binding !!$parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).NumItemsSelected}\"\n                                            Text=\"{x:Static lang:Resources.Action_OpenInViewer}\" />\n                                        <ui:MenuFlyoutItem\n                                            Command=\"{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).DeleteImage}\"\n                                            CommandParameter=\"{Binding}\"\n                                            HotKey=\"{x:Null}\"\n                                            IconSource=\"Delete\"\n                                            Text=\"{x:Static lang:Resources.Action_Delete}\" />\n\n                                        <ui:MenuFlyoutSeparator IsVisible=\"{Binding ImageFile.GenerationParameters, Converter={x:Static ObjectConverters.IsNotNull}}\" />\n\n                                        <ui:MenuFlyoutSubItem\n                                            IconSource=\"Share\"\n                                            IsVisible=\"{Binding ImageFile.GenerationParameters, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                                            Text=\"{x:Static lang:Resources.Action_SendToInference}\">\n                                            <ui:MenuFlyoutItem\n                                                Command=\"{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).SendToTextToImage}\"\n                                                CommandParameter=\"{Binding}\"\n                                                HotKey=\"{x:Null}\"\n                                                IconSource=\"FullScreenMaximize\"\n                                                Text=\"{x:Static lang:Resources.Label_TextToImage}\" />\n                                            <ui:MenuFlyoutItem\n                                                Command=\"{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).SendToImageToImage}\"\n                                                CommandParameter=\"{Binding}\"\n                                                HotKey=\"{x:Null}\"\n                                                IconSource=\"ImageCopy\"\n                                                Text=\"{x:Static lang:Resources.Label_ImageToImage}\" />\n                                            <ui:MenuFlyoutItem\n                                                Command=\"{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).SendToUpscale}\"\n                                                CommandParameter=\"{Binding}\"\n                                                HotKey=\"{x:Null}\"\n                                                Text=\"{x:Static lang:Resources.Label_Upscale}\">\n                                                <ui:MenuFlyoutItem.IconSource>\n                                                    <fluentAvalonia:SymbolIconSource FontSize=\"10\" Symbol=\"ResizeImage\" />\n                                                </ui:MenuFlyoutItem.IconSource>\n                                            </ui:MenuFlyoutItem>\n                                            <ui:MenuFlyoutItem\n                                                Command=\"{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).SendToImageToVideo}\"\n                                                CommandParameter=\"{Binding}\"\n                                                HotKey=\"{x:Null}\"\n                                                IconSource=\"Video\"\n                                                Text=\"{x:Static lang:Resources.Label_ImageToVideo}\" />\n                                        </ui:MenuFlyoutSubItem>\n                                    </ui:FAMenuFlyout>\n                                </selectableImageCard:SelectableImageButton.ContextFlyout>\n                            </selectableImageCard:SelectableImageButton>\n                        </LayoutTransformControl>\n                    </DataTemplate>\n                </ItemsRepeater.ItemTemplate>\n            </ItemsRepeater>\n        </scroll:BetterScrollViewer>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs",
    "content": "﻿using Avalonia.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<OutputsPage>]\npublic partial class OutputsPage : UserControlBase\n{\n    public OutputsPage()\n    {\n        InitializeComponent();\n    }\n\n    private void InputElement_OnKeyDown(object? sender, KeyEventArgs e)\n    {\n        if (e.Key == Key.Escape && DataContext is OutputsPageViewModel viewModel)\n        {\n            viewModel.ClearSearchQuery();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/PackageManager/MainPackageManagerView.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.PackageManager.MainPackageManagerView\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:generic=\"clr-namespace:System.Collections.Generic;assembly=System.Collections\"\n    xmlns:generic1=\"clr-namespace:System.Collections.Generic;assembly=System.Runtime\"\n    xmlns:icons=\"clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:packageManager=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.PackageManager\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vendorLabs=\"clr-namespace:StabilityMatrix.Avalonia.Controls.VendorLabs\"\n    d:DataContext=\"{x:Static designData:DesignData.MainPackageManagerViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"1000\"\n    x:CompileBindings=\"True\"\n    x:DataType=\"packageManager:MainPackageManagerViewModel\"\n    mc:Ignorable=\"d\">\n\n    <controls:UserControlBase.Resources>\n        <converters:BooleanChoiceMultiConverter x:Key=\"BoolChoiceMultiConverter\" />\n    </controls:UserControlBase.Resources>\n\n    <Grid Margin=\"16\" RowDefinitions=\"Auto,*,Auto\">\n        <ScrollViewer Grid.Row=\"1\">\n            <ui:ItemsRepeater x:Name=\"PackageCardsRepeater\" ItemsSource=\"{Binding PackageCards}\">\n                <ui:ItemsRepeater.Layout>\n                    <ui:UniformGridLayout\n                        ItemsStretch=\"Fill\"\n                        MinColumnSpacing=\"12\"\n                        MinItemWidth=\"400\"\n                        MinRowSpacing=\"12\" />\n                </ui:ItemsRepeater.Layout>\n                <ui:ItemsRepeater.ItemTemplate>\n                    <DataTemplate DataType=\"{x:Type packageManager:PackageCardViewModel}\">\n                        <controls:Card\n                            MinWidth=\"400\"\n                            Padding=\"8\"\n                            CornerRadius=\"8\">\n                            <controls:Card.Background>\n                                <MultiBinding Converter=\"{StaticResource BoolChoiceMultiConverter}\">\n                                    <Binding Path=\"IsRunning\" />\n                                    <DynamicResource ResourceKey=\"ThemeDarkDarkGreenColorBrush\" />\n                                    <DynamicResource ResourceKey=\"ButtonBackground\" />\n                                </MultiBinding>\n                            </controls:Card.Background>\n                            <controls:Card.Resources>\n                                <input:StandardUICommand x:Key=\"LaunchCommand\" Command=\"{Binding Launch}\" />\n                            </controls:Card.Resources>\n                            <Grid ColumnDefinitions=\"Auto, *\">\n                                <vendorLabs:BetterAsyncImage\n                                    Grid.Column=\"0\"\n                                    Width=\"150\"\n                                    Height=\"150\"\n                                    Margin=\"4,8\"\n                                    HorizontalAlignment=\"Center\"\n                                    CornerRadius=\"8\"\n                                    IsVisible=\"{Binding !IsUnknownPackage}\"\n                                    Source=\"{Binding CardImageSource}\"\n                                    Stretch=\"UniformToFill\" />\n                                <Border\n                                    Grid.Column=\"0\"\n                                    Width=\"150\"\n                                    Height=\"150\"\n                                    Margin=\"4,8\"\n                                    HorizontalAlignment=\"Center\"\n                                    Background=\"#202020\"\n                                    CornerRadius=\"8\"\n                                    IsVisible=\"{Binding IsUnknownPackage}\">\n                                    <TextBlock\n                                        VerticalAlignment=\"Center\"\n                                        FontSize=\"18\"\n                                        Text=\"{x:Static lang:Resources.Label_UnknownPackage}\"\n                                        TextAlignment=\"Center\"\n                                        TextWrapping=\"Wrap\" />\n                                </Border>\n                                <Grid\n                                    Grid.Column=\"1\"\n                                    ColumnDefinitions=\"*, Auto\"\n                                    RowDefinitions=\"Auto, Auto, Auto, *\">\n                                    <TextBlock\n                                        Grid.Row=\"0\"\n                                        Grid.Column=\"0\"\n                                        Margin=\"8,0,0,0\"\n                                        FontSize=\"16\"\n                                        FontWeight=\"SemiBold\"\n                                        Text=\"{Binding PackageDisplayName}\"\n                                        TextTrimming=\"CharacterEllipsis\"\n                                        TextWrapping=\"NoWrap\"\n                                        ToolTip.Tip=\"{Binding PackageDisplayName}\" />\n                                    <TextBlock\n                                        Grid.Row=\"1\"\n                                        Grid.Column=\"0\"\n                                        Margin=\"8,0,8,0\"\n                                        VerticalAlignment=\"Center\"\n                                        FontSize=\"13\"\n                                        Foreground=\"{DynamicResource TextControlPlaceholderForeground}\"\n                                        Text=\"{Binding InstalledVersion}\" />\n\n                                    <Button\n                                        Grid.Row=\"0\"\n                                        Grid.Column=\"1\"\n                                        Width=\"24\"\n                                        Margin=\"0,0,0,0\"\n                                        Padding=\"4\"\n                                        HorizontalAlignment=\"Right\"\n                                        VerticalAlignment=\"Top\"\n                                        HorizontalContentAlignment=\"Right\"\n                                        VerticalContentAlignment=\"Top\"\n                                        BorderThickness=\"0\"\n                                        Classes=\"transparent\">\n                                        <ui:SymbolIcon FontSize=\"18\" Symbol=\"MoreVertical\" />\n                                        <Button.Flyout>\n                                            <MenuFlyout Placement=\"BottomEdgeAlignedLeft\">\n                                                <MenuItem Command=\"{Binding OnLoadedAsync}\" Header=\"{x:Static lang:Resources.Action_CheckForUpdates}\">\n                                                    <MenuItem.IsVisible>\n                                                        <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                                            <Binding Path=\"!IsUnknownPackage\" />\n                                                            <Binding Path=\"!IsUpdateAvailable\" />\n                                                            <Binding Path=\"!DontCheckForUpdates\" />\n                                                        </MultiBinding>\n                                                    </MenuItem.IsVisible>\n                                                    <MenuItem.Icon>\n                                                        <ui:SymbolIcon Symbol=\"Refresh\" />\n                                                    </MenuItem.Icon>\n                                                </MenuItem>\n                                                <MenuItem\n                                                    Command=\"{Binding ToggleDontCheckForUpdates}\"\n                                                    Header=\"{x:Static lang:Resources.Label_DisableUpdateCheck}\"\n                                                    IsVisible=\"{Binding !IsUnknownPackage}\">\n                                                    <MenuItem.Icon>\n                                                        <CheckBox\n                                                            Width=\"28\"\n                                                            Height=\"28\"\n                                                            Margin=\"8,0,0,0\"\n                                                            Padding=\"0\"\n                                                            IsChecked=\"{Binding DontCheckForUpdates}\">\n                                                            <CheckBox.RenderTransform>\n                                                                <ScaleTransform ScaleX=\"1.5\" ScaleY=\"1.5\" />\n                                                            </CheckBox.RenderTransform>\n                                                        </CheckBox>\n                                                    </MenuItem.Icon>\n                                                </MenuItem>\n                                                <MenuItem Command=\"{Binding OpenFolder}\" Header=\"{OnPlatform Default={x:Static lang:Resources.Action_OpenInExplorer}, macOS={x:Static lang:Resources.Action_OpenInFinder}}\">\n                                                    <MenuItem.Icon>\n                                                        <ui:SymbolIcon Symbol=\"OpenFolder\" />\n                                                    </MenuItem.Icon>\n                                                </MenuItem>\n                                                <MenuItem\n                                                    Command=\"{Binding OpenOnGitHubCommand}\"\n                                                    Header=\"{x:Static lang:Resources.Action_OpenGithub}\"\n                                                    IsVisible=\"{Binding !IsUnknownPackage}\">\n                                                    <MenuItem.Icon>\n                                                        <icons:Icon Value=\"fa-brands fa-github\" />\n                                                    </MenuItem.Icon>\n                                                </MenuItem>\n                                                <MenuItem Command=\"{Binding RenameCommand}\" Header=\"{x:Static lang:Resources.Action_Rename}\">\n                                                    <MenuItem.IsVisible>\n                                                        <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                                            <Binding Path=\"!IsUnknownPackage\" />\n                                                            <Binding Path=\"!IsRunning\" />\n                                                        </MultiBinding>\n                                                    </MenuItem.IsVisible>\n                                                    <MenuItem.Icon>\n                                                        <ui:SymbolIcon Symbol=\"Rename\" />\n                                                    </MenuItem.Icon>\n                                                </MenuItem>\n\n                                                <Separator IsVisible=\"{Binding !IsUnknownPackage}\" />\n\n                                                <MenuItem\n                                                    Command=\"{Binding OpenPythonPackagesDialogCommand}\"\n                                                    Header=\"{x:Static lang:Resources.Label_PythonPackages}\"\n                                                    IsVisible=\"{Binding UsesVenv}\">\n                                                    <MenuItem.Icon>\n                                                        <icons:Icon Value=\"fa-brands fa-python\" />\n                                                    </MenuItem.Icon>\n                                                </MenuItem>\n                                                <MenuItem\n                                                    Command=\"{Binding OpenPythonDependenciesOverrideDialogCommand}\"\n                                                    Header=\"{x:Static lang:Resources.Label_PythonDependenciesOverride_Title}\"\n                                                    IsVisible=\"{Binding UsesVenv}\">\n                                                    <MenuItem.Icon>\n                                                        <icons:Icon Value=\"fa-solid fa-bandage\" />\n                                                    </MenuItem.Icon>\n                                                </MenuItem>\n                                                <MenuItem\n                                                    Command=\"{Binding OpenExtensionsDialogCommand}\"\n                                                    Header=\"Extensions\"\n                                                    IsVisible=\"{Binding CanUseExtensions}\">\n                                                    <MenuItem.Icon>\n                                                        <icons:Icon Value=\"fa-solid fa-puzzle-piece\" />\n                                                    </MenuItem.Icon>\n                                                </MenuItem>\n\n                                                <MenuItem\n                                                    Command=\"{Binding ChangeVersionCommand}\"\n                                                    Header=\"Change Version\"\n                                                    IsVisible=\"{Binding !IsUnknownPackage}\">\n                                                    <MenuItem.Icon>\n                                                        <icons:Icon Value=\"fa-solid fa-code-compare\" />\n                                                    </MenuItem.Icon>\n                                                </MenuItem>\n\n                                                <Separator IsVisible=\"{Binding CanUseSharedOutput}\" />\n\n                                                <MenuItem Header=\"{x:Static lang:Resources.Label_SharedModelStrategyShort}\" IsVisible=\"{Binding !IsUnknownPackage}\">\n                                                    <MenuItem.Icon>\n                                                        <ui:SymbolIcon Symbol=\"FolderLink\" />\n                                                    </MenuItem.Icon>\n                                                    <!--  ReSharper disable Xaml.RedundantResource  -->\n                                                    <MenuItem\n                                                        Command=\"{Binding ToggleSharedModelSymlink}\"\n                                                        Header=\"Symlink\"\n                                                        IsVisible=\"{Binding CanUseSymlinkMethod}\">\n                                                        <MenuItem.Icon>\n                                                            <CheckBox\n                                                                Width=\"28\"\n                                                                Height=\"28\"\n                                                                Margin=\"8,0,0,0\"\n                                                                Padding=\"0\"\n                                                                IsChecked=\"{Binding IsSharedModelSymlink}\">\n                                                                <CheckBox.RenderTransform>\n                                                                    <ScaleTransform ScaleX=\"1.5\" ScaleY=\"1.5\" />\n                                                                </CheckBox.RenderTransform>\n                                                            </CheckBox>\n                                                        </MenuItem.Icon>\n                                                    </MenuItem>\n                                                    <MenuItem\n                                                        Command=\"{Binding ToggleSharedModelConfig}\"\n                                                        Header=\"Config\"\n                                                        IsVisible=\"{Binding CanUseConfigMethod}\">\n                                                        <MenuItem.Icon>\n                                                            <CheckBox\n                                                                Width=\"28\"\n                                                                Height=\"28\"\n                                                                Margin=\"8,0,0,0\"\n                                                                Padding=\"0\"\n                                                                IsChecked=\"{Binding IsSharedModelConfig}\">\n                                                                <CheckBox.RenderTransform>\n                                                                    <ScaleTransform ScaleX=\"1.5\" ScaleY=\"1.5\" />\n                                                                </CheckBox.RenderTransform>\n                                                            </CheckBox>\n                                                        </MenuItem.Icon>\n                                                    </MenuItem>\n                                                    <MenuItem Command=\"{Binding ToggleSharedModelNone}\" Header=\"None\">\n                                                        <MenuItem.Icon>\n                                                            <CheckBox\n                                                                Width=\"28\"\n                                                                Height=\"28\"\n                                                                Margin=\"8,0,0,0\"\n                                                                Padding=\"0\"\n                                                                IsChecked=\"{Binding IsSharedModelDisabled}\">\n                                                                <CheckBox.RenderTransform>\n                                                                    <ScaleTransform ScaleX=\"1.5\" ScaleY=\"1.5\" />\n                                                                </CheckBox.RenderTransform>\n                                                            </CheckBox>\n                                                        </MenuItem.Icon>\n                                                    </MenuItem>\n                                                    <!--  ReSharper enable Xaml.RedundantResource  -->\n                                                </MenuItem>\n                                                <MenuItem\n                                                    Command=\"{Binding ToggleSharedOutput}\"\n                                                    Header=\"{x:Static lang:Resources.Label_UseSharedOutputFolder}\"\n                                                    IsVisible=\"{Binding CanUseSharedOutput}\">\n                                                    <MenuItem.Icon>\n                                                        <CheckBox\n                                                            Width=\"28\"\n                                                            Height=\"28\"\n                                                            Margin=\"8,0,0,0\"\n                                                            Padding=\"0\"\n                                                            IsChecked=\"{Binding UseSharedOutput}\">\n                                                            <CheckBox.RenderTransform>\n                                                                <ScaleTransform ScaleX=\"1.5\" ScaleY=\"1.5\" />\n                                                            </CheckBox.RenderTransform>\n                                                        </CheckBox>\n                                                    </MenuItem.Icon>\n                                                </MenuItem>\n\n                                                <Separator IsVisible=\"{Binding !IsUnknownPackage}\" />\n\n                                                <!--  Package-specific extra commands section  -->\n                                                <MenuItem\n                                                    Classes=\"SubItems\"\n                                                    Header=\"Package Commands\"\n                                                    IsVisible=\"{Binding ShowExtraCommands}\">\n                                                    <MenuItem.Resources>\n                                                        <input:StandardUICommand x:Key=\"ExecuteExtraCommandCommand\" Command=\"{Binding ExecuteExtraCommandCommand}\" />\n                                                    </MenuItem.Resources>\n\n                                                    <MenuItem.Icon>\n                                                        <icons:Icon Value=\"fa-solid fa-toolbox\" />\n                                                    </MenuItem.Icon>\n\n                                                    <MenuItem.ItemsSource>\n                                                        <Binding Path=\"ExtraCommands\" />\n                                                    </MenuItem.ItemsSource>\n\n                                                    <MenuItem.Styles>\n                                                        <Style x:DataType=\"models:ExtraPackageCommand\" Selector=\"MenuItem.SubItems MenuItem\">\n                                                            <Setter Property=\"Header\" Value=\"{Binding CommandName}\" />\n                                                            <Setter Property=\"Command\" Value=\"{StaticResource ExecuteExtraCommandCommand}\" />\n                                                            <Setter Property=\"CommandParameter\" Value=\"{Binding CommandName}\" />\n                                                        </Style>\n                                                    </MenuItem.Styles>\n                                                </MenuItem>\n\n                                                <Separator IsVisible=\"{Binding ShowExtraCommands}\" />\n\n                                                <MenuItem\n                                                    Command=\"{Binding Uninstall}\"\n                                                    Header=\"{x:Static lang:Resources.Action_Uninstall}\"\n                                                    IsEnabled=\"{Binding !IsRunning}\">\n                                                    <MenuItem.Icon>\n                                                        <ui:SymbolIcon Symbol=\"Delete\" />\n                                                    </MenuItem.Icon>\n                                                </MenuItem>\n                                            </MenuFlyout>\n                                        </Button.Flyout>\n                                    </Button>\n\n                                    <StackPanel\n                                        Grid.Row=\"2\"\n                                        Grid.Column=\"0\"\n                                        Margin=\"4,4,0,0\"\n                                        Orientation=\"Horizontal\">\n                                        <Button\n                                            Margin=\"4,4,4,0\"\n                                            Padding=\"6\"\n                                            Classes=\"transparent\"\n                                            Command=\"{Binding OnLoadedAsync}\"\n                                            ToolTip.Tip=\"{x:Static lang:Resources.Action_CheckForUpdates}\">\n                                            <Button.IsVisible>\n                                                <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                                    <Binding Path=\"!IsUnknownPackage\" />\n                                                    <Binding Path=\"!IsUpdateAvailable\" />\n                                                    <Binding Path=\"!DontCheckForUpdates\" />\n                                                </MultiBinding>\n                                            </Button.IsVisible>\n                                            <ui:SymbolIcon FontSize=\"20\" Symbol=\"Refresh\" />\n                                        </Button>\n\n                                        <Button\n                                            Margin=\"4,4,4,0\"\n                                            Padding=\"4\"\n                                            Classes=\"borderless-success\"\n                                            Command=\"{Binding Update}\"\n                                            IsEnabled=\"{Binding !IsRunning}\"\n                                            ToolTip.ShowOnDisabled=\"True\">\n                                            <ToolTip.Tip>\n                                                <TextBlock>\n                                                    <Run Text=\"{x:Static lang:Resources.Label_UpdateAvailable}\" />\n                                                    <Run Text=\"(\" />\n                                                    <Run Text=\"{Binding UpdateVersion.ReadableVersionString, FallbackValue=Unknown}\" />\n                                                    <Run Text=\")\" />\n                                                </TextBlock>\n                                            </ToolTip.Tip>\n                                            <Button.IsVisible>\n                                                <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                                    <Binding Path=\"!IsUnknownPackage\" />\n                                                    <Binding Path=\"IsUpdateAvailable\" />\n                                                </MultiBinding>\n                                            </Button.IsVisible>\n                                            <StackPanel Orientation=\"Horizontal\">\n                                                <ui:SymbolIcon\n                                                    FontSize=\"24\"\n                                                    IsEnabled=\"{Binding !IsRunning}\"\n                                                    Symbol=\"CloudDownload\">\n                                                    <ui:SymbolIcon.Styles>\n                                                        <Style Selector=\"ui|SymbolIcon\">\n                                                            <Setter Property=\"Foreground\" Value=\"White\" />\n                                                        </Style>\n                                                        <Style Selector=\"ui|SymbolIcon:disabled\">\n                                                            <Setter Property=\"Foreground\" Value=\"Gray\" />\n                                                        </Style>\n                                                    </ui:SymbolIcon.Styles>\n                                                </ui:SymbolIcon>\n                                                <TextBlock\n                                                    Margin=\"8,0,4,0\"\n                                                    VerticalAlignment=\"Center\"\n                                                    FontSize=\"12\"\n                                                    Text=\"{x:Static lang:Resources.Action_Update}\">\n                                                    <TextBlock.Styles>\n                                                        <Style Selector=\"TextBlock\">\n                                                            <Setter Property=\"Foreground\" Value=\"White\" />\n                                                        </Style>\n                                                        <Style Selector=\"TextBlock:disabled\">\n                                                            <Setter Property=\"Foreground\" Value=\"Gray\" />\n                                                        </Style>\n                                                    </TextBlock.Styles>\n                                                </TextBlock>\n                                            </StackPanel>\n                                        </Button>\n                                        <Button\n                                            Margin=\"4,4,4,0\"\n                                            Padding=\"6\"\n                                            Classes=\"transparent\"\n                                            Command=\"{Binding ShowLaunchOptionsCommand}\"\n                                            IsVisible=\"{Binding !IsUnknownPackage}\"\n                                            ToolTip.Tip=\"Launch Options\">\n                                            <ui:SymbolIcon FontSize=\"20\" Symbol=\"SettingsFilled\" />\n                                        </Button>\n                                        <Button\n                                            Margin=\"4,4,4,0\"\n                                            Padding=\"6\"\n                                            Classes=\"transparent\"\n                                            Command=\"{Binding OpenExtensionsDialogCommand}\"\n                                            IsVisible=\"{Binding CanUseExtensions}\"\n                                            ToolTip.Tip=\"Extensions\">\n                                            <icons:Icon\n                                                HorizontalAlignment=\"Center\"\n                                                VerticalAlignment=\"Center\"\n                                                FontSize=\"20\"\n                                                Value=\"fa-solid fa-puzzle-piece\" />\n                                        </Button>\n                                    </StackPanel>\n                                    <UniformGrid\n                                        Grid.Row=\"3\"\n                                        Grid.Column=\"0\"\n                                        Grid.ColumnSpan=\"2\"\n                                        Margin=\"8,8,8,0\"\n                                        VerticalAlignment=\"Bottom\">\n                                        <Button\n                                            Name=\"LaunchButton\"\n                                            HorizontalAlignment=\"Stretch\"\n                                            VerticalAlignment=\"Bottom\"\n                                            Classes=\"accent\"\n                                            Command=\"{Binding Launch}\">\n                                            <Button.IsVisible>\n                                                <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                                    <Binding Path=\"!IsRunning\" />\n                                                    <Binding Path=\"!IsUnknownPackage\" />\n                                                    <Binding Path=\"!ExtraLaunchCommands.Count\" />\n                                                </MultiBinding>\n                                            </Button.IsVisible>\n                                            <StackPanel Margin=\"0,2,0,2\" Orientation=\"Horizontal\">\n                                                <icons:Icon\n                                                    Margin=\"0,0,8,0\"\n                                                    FontSize=\"14\"\n                                                    Value=\"fa-solid fa-rocket\" />\n                                                <TextBlock Text=\"{x:Static lang:Resources.Action_Launch}\" />\n                                            </StackPanel>\n                                        </Button>\n                                        <SplitButton\n                                            Name=\"SplitLaunchButton\"\n                                            HorizontalAlignment=\"Stretch\"\n                                            VerticalAlignment=\"Bottom\"\n                                            Classes=\"accent\"\n                                            Command=\"{Binding Launch}\">\n                                            <Button.IsVisible>\n                                                <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                                    <Binding Path=\"!IsRunning\" />\n                                                    <Binding Path=\"!IsUnknownPackage\" />\n                                                    <Binding Path=\"!!ExtraLaunchCommands.Count\" />\n                                                </MultiBinding>\n                                            </Button.IsVisible>\n                                            <StackPanel\n                                                Margin=\"0,2,0,2\"\n                                                HorizontalAlignment=\"Center\"\n                                                Orientation=\"Horizontal\">\n                                                <icons:Icon\n                                                    Margin=\"0,0,8,0\"\n                                                    FontSize=\"14\"\n                                                    Value=\"fa-solid fa-rocket\" />\n                                                <TextBlock Text=\"{x:Static lang:Resources.Action_Launch}\" />\n                                            </StackPanel>\n                                            <SplitButton.Flyout>\n                                                <ui:FAMenuFlyout ItemsSource=\"{Binding ExtraLaunchCommands}\" Placement=\"BottomEdgeAlignedRight\">\n                                                    <ui:FAMenuFlyout.ItemTemplate>\n                                                        <DataTemplate>\n                                                            <DataTemplate.DataType>\n                                                                <x:Type x:TypeArguments=\"x:String, x:String\" TypeName=\"generic1:KeyValuePair\" />\n                                                            </DataTemplate.DataType>\n                                                            <ui:MenuFlyoutItem\n                                                                Command=\"{StaticResource LaunchCommand}\"\n                                                                CommandParameter=\"{Binding Value}\"\n                                                                Text=\"{Binding Key, StringFormat={}Launch {0}}\" />\n                                                        </DataTemplate>\n                                                    </ui:FAMenuFlyout.ItemTemplate>\n                                                </ui:FAMenuFlyout>\n                                            </SplitButton.Flyout>\n                                        </SplitButton>\n                                        <Button\n                                            HorizontalAlignment=\"Stretch\"\n                                            VerticalAlignment=\"Bottom\"\n                                            Classes=\"borderless-danger\"\n                                            Command=\"{Binding StopCommand}\"\n                                            IsVisible=\"{Binding IsRunning}\">\n                                            <StackPanel Margin=\"0,2\" Orientation=\"Horizontal\">\n                                                <icons:Icon\n                                                    Margin=\"0,0,8,0\"\n                                                    FontSize=\"14\"\n                                                    Value=\"fa-solid fa-stop\" />\n                                                <TextBlock Text=\"{x:Static lang:Resources.Action_Stop}\" />\n                                            </StackPanel>\n                                        </Button>\n                                        <Button\n                                            HorizontalAlignment=\"Stretch\"\n                                            VerticalAlignment=\"Bottom\"\n                                            BorderBrush=\"Transparent\"\n                                            Classes=\"borderless-info\"\n                                            Command=\"{Binding RestartCommand}\"\n                                            IsVisible=\"{Binding IsRunning}\">\n                                            <StackPanel Margin=\"0,2\" Orientation=\"Horizontal\">\n                                                <icons:Icon\n                                                    Margin=\"0,0,8,0\"\n                                                    FontSize=\"14\"\n                                                    Value=\"fa-solid fa-arrow-rotate-left\" />\n                                                <TextBlock Text=\"{x:Static lang:Resources.Action_Restart}\" />\n                                            </StackPanel>\n                                        </Button>\n                                        <Button\n                                            HorizontalAlignment=\"Stretch\"\n                                            VerticalAlignment=\"Bottom\"\n                                            Classes=\"accent\"\n                                            Command=\"{Binding NavToConsole}\"\n                                            IsVisible=\"{Binding IsRunning}\">\n                                            <StackPanel Margin=\"0,2,0,2\" Orientation=\"Horizontal\">\n                                                <icons:Icon\n                                                    Margin=\"0,0,8,0\"\n                                                    FontSize=\"14\"\n                                                    Value=\"fa-solid fa-terminal\" />\n                                                <TextBlock Text=\"{x:Static lang:Resources.Label_Console}\" />\n                                            </StackPanel>\n                                        </Button>\n                                        <Button\n                                            HorizontalAlignment=\"Stretch\"\n                                            VerticalAlignment=\"Bottom\"\n                                            Classes=\"accent\"\n                                            Command=\"{Binding LaunchWebUi}\"\n                                            IsVisible=\"{Binding ShowWebUiButton}\">\n                                            <StackPanel Margin=\"8,2,8,2\" Orientation=\"Horizontal\">\n                                                <icons:Icon\n                                                    Margin=\"0,0,8,0\"\n                                                    FontSize=\"14\"\n                                                    Value=\"fa-solid fa-up-right-from-square\" />\n                                                <TextBlock Text=\"{x:Static lang:Resources.Label_WebUi}\" />\n                                            </StackPanel>\n                                        </Button>\n                                        <Button\n                                            HorizontalAlignment=\"Stretch\"\n                                            VerticalAlignment=\"Bottom\"\n                                            Classes=\"transparent-info\"\n                                            Command=\"{Binding Import}\"\n                                            IsVisible=\"{Binding IsUnknownPackage}\">\n                                            <StackPanel Margin=\"0,2,0,2\" Orientation=\"Horizontal\">\n                                                <icons:Icon\n                                                    Margin=\"0,0,8,0\"\n                                                    FontSize=\"14\"\n                                                    Value=\"fa-solid fa-circle-question\" />\n                                                <TextBlock Text=\"{x:Static lang:Resources.Action_Import}\" />\n                                            </StackPanel>\n                                        </Button>\n                                    </UniformGrid>\n                                </Grid>\n                                <!--  Update overlay  -->\n                                <Border\n                                    Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"2\"\n                                    HorizontalAlignment=\"Stretch\"\n                                    VerticalAlignment=\"Stretch\"\n                                    Background=\"#DD000000\"\n                                    CornerRadius=\"8\"\n                                    IsVisible=\"{Binding IsProgressVisible}\" />\n                                <Grid\n                                    Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"2\"\n                                    HorizontalAlignment=\"Center\"\n                                    VerticalAlignment=\"Center\"\n                                    IsVisible=\"{Binding IsProgressVisible}\"\n                                    RowDefinitions=\"Auto, *\">\n                                    <controls:ProgressRing\n                                        Width=\"120\"\n                                        Height=\"120\"\n                                        HorizontalAlignment=\"Center\"\n                                        VerticalAlignment=\"Center\"\n                                        EndAngle=\"450\"\n                                        IsIndeterminate=\"{Binding IsIndeterminate}\"\n                                        StartAngle=\"90\"\n                                        Value=\"{Binding Value}\" />\n                                    <TextBlock\n                                        Grid.Row=\"1\"\n                                        Width=\"200\"\n                                        Margin=\"8,8,8,0\"\n                                        HorizontalAlignment=\"Center\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"{Binding Text}\"\n                                        TextAlignment=\"Center\"\n                                        TextWrapping=\"Wrap\" />\n                                </Grid>\n                            </Grid>\n                        </controls:Card>\n                    </DataTemplate>\n                </ui:ItemsRepeater.ItemTemplate>\n            </ui:ItemsRepeater>\n        </ScrollViewer>\n\n        <!--  Teaching Tip  -->\n        <ui:TeachingTip\n            Name=\"TeachingTip1\"\n            Title=\"{x:Static lang:Resources.TeachingTip_AddPackageToGetStarted}\"\n            Grid.Row=\"0\"\n            IsOpen=\"{Binding !Packages.Count}\"\n            PreferredPlacement=\"Top\"\n            Target=\"{Binding #AddPackagesButton}\" />\n\n        <ui:TeachingTip\n            Name=\"LaunchTeachingTip\"\n            Title=\"{x:Static lang:Resources.TeachingTip_ClickLaunchToGetStarted}\"\n            Grid.Row=\"0\"\n            MinWidth=\"100\"\n            Margin=\"8,0,0,0\"\n            PlacementMargin=\"0,0,0,0\"\n            PreferredPlacement=\"Bottom\"\n            TailVisibility=\"Auto\" />\n\n        <!--  Add Packages Button  -->\n        <Button\n            Name=\"AddPackagesButton\"\n            Grid.Row=\"2\"\n            Margin=\"0,8,0,0\"\n            HorizontalAlignment=\"Stretch\"\n            VerticalAlignment=\"Bottom\"\n            Classes=\"transparent\"\n            Command=\"{Binding ShowInstallDialog}\">\n            <StackPanel Margin=\"8\" Orientation=\"Horizontal\">\n                <ui:SymbolIcon FontSize=\"18\" Symbol=\"Add\" />\n                <TextBlock Margin=\"4,0,0,0\" Text=\"{x:Static lang:Resources.Action_AddPackage}\" />\n            </StackPanel>\n        </Button>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/PackageManager/MainPackageManagerView.axaml.cs",
    "content": "﻿using System.Linq;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Interactivity;\nusing Avalonia.Markup.Xaml;\nusing Avalonia.Threading;\nusing Avalonia.VisualTree;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Navigation;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Core.Helper;\nusing MainPackageManagerViewModel = StabilityMatrix.Avalonia.ViewModels.PackageManager.MainPackageManagerViewModel;\n\nnamespace StabilityMatrix.Avalonia.Views.PackageManager;\n\n[RegisterSingleton<MainPackageManagerView>]\npublic partial class MainPackageManagerView : UserControlBase\n{\n    public MainPackageManagerView()\n    {\n        InitializeComponent();\n\n        AddHandler(Frame.NavigatedToEvent, OnNavigatedTo, RoutingStrategies.Direct);\n        EventManager.Instance.OneClickInstallFinished += OnOneClickInstallFinished;\n    }\n\n    private void OnOneClickInstallFinished(object? sender, bool skipped)\n    {\n        if (skipped)\n            return;\n\n        Dispatcher.UIThread.Invoke(() =>\n        {\n            var target = this.FindDescendantOfType<UniformGrid>()\n                ?.GetVisualChildren()\n                .OfType<Button>()\n                .FirstOrDefault(x => x is { Name: \"LaunchButton\" });\n\n            if (target == null)\n                return;\n\n            var teachingTip = this.FindControl<TeachingTip>(\"LaunchTeachingTip\");\n            if (teachingTip == null)\n                return;\n\n            teachingTip.Target = target;\n            teachingTip.IsOpen = true;\n        });\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n\n    /// <summary>\n    /// Handle navigation events to this page\n    /// </summary>\n    private void OnNavigatedTo(object? sender, NavigationEventArgs args)\n    {\n        if (args.Parameter is PackageManagerNavigationOptions { OpenInstallerDialog: true } options)\n        {\n            var vm = (MainPackageManagerViewModel)DataContext!;\n            Dispatcher.UIThread.Invoke(() =>\n            {\n                vm.ShowInstallDialog(options.InstallerSelectedPackage);\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/PackageManager/PackageExtensionBrowserView.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.PackageManager.PackageExtensionBrowserView\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:extensions=\"clr-namespace:StabilityMatrix.Core.Models.Packages.Extensions;assembly=StabilityMatrix.Core\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models1=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:packageManager=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.PackageManager\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    d:DataContext=\"{x:Static mocks:DesignData.PackageExtensionBrowserViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"packageManager:PackageExtensionBrowserViewModel\"\n    mc:Ignorable=\"d\">\n    <controls:UserControlBase.Resources>\n        <!--<input:StandardUICommand x:Key=\"InstallExtensionCommand\"\n                                 Command=\"{Binding InstallExtensionCommand}\" />-->\n        <converters:FileUriConverter x:Key=\"FileUriConverter\" />\n\n        <input:StandardUICommand\n            x:Key=\"AddInstalledExtensionsToPackCommand\"\n            Command=\"{Binding AddInstalledExtensionToPackCommand}\"\n            IconSource=\"Add\"\n            Label=\"Add\" />\n\n        <input:StandardUICommand\n            x:Key=\"AddExtensionsToPackCommand\"\n            Command=\"{Binding AddExtensionToPackCommand}\"\n            IconSource=\"Add\"\n            Label=\"Add\" />\n\n        <input:StandardUICommand\n            x:Key=\"DeleteExtensionPackCommand\"\n            Command=\"{Binding DeleteExtensionPackCommand}\"\n            IconSource=\"Delete\"\n            Label=\"Delete\" />\n\n        <input:StandardUICommand\n            x:Key=\"SetExtensionVersionCommand\"\n            Command=\"{Binding SetExtensionVersionCommand}\"\n            IconSource=\"Add\"\n            Label=\"Add\" />\n\n        <DataTemplate x:Key=\"PackageExtensionTemplate\">\n            <DataTemplate.DataType>\n                <x:Type x:TypeArguments=\"extensions:PackageExtension\" TypeName=\"models1:SelectableItem\" />\n            </DataTemplate.DataType>\n            <Button\n                Margin=\"4,0,0,8\"\n                Padding=\"-5\"\n                HorizontalAlignment=\"Stretch\"\n                HorizontalContentAlignment=\"Stretch\"\n                Background=\"Transparent\"\n                BorderBrush=\"Transparent\"\n                Classes=\"transparent-full\"\n                Command=\"{Binding ToggleSelectedCommand}\"\n                CornerRadius=\"8\">\n                <controls:Card\n                    Margin=\"4\"\n                    Padding=\"12,12\"\n                    Classes=\"transparent-full\"\n                    CornerRadius=\"8\">\n\n                    <sg:SpacedGrid ColumnDefinitions=\"Auto,*,Auto\">\n                        <!--<controls:BetterAdvancedImage\n                        Grid.Column=\"0\"\n                        MaxHeight=\"128\"\n                        MaxWidth=\"128\"\n                        Stretch=\"UniformToFill\"\n                        CornerRadius=\"4\"\n                        Source=\"{Binding PreviewImageUri}\" />-->\n                        <StackPanel\n                            Grid.Column=\"1\"\n                            Orientation=\"Vertical\"\n                            Spacing=\"4\">\n                            <TextBlock\n                                FontSize=\"16\"\n                                FontWeight=\"Light\"\n                                Text=\"{Binding Item.Title}\" />\n\n                            <TextBlock\n                                FontSize=\"13\"\n                                FontWeight=\"Light\"\n                                Text=\"{Binding Item.Author}\" />\n\n                            <TextBlock\n                                FontSize=\"13\"\n                                FontWeight=\"Light\"\n                                Foreground=\"{DynamicResource TextFillColorSecondary}\"\n                                Text=\"{Binding Item.Description}\"\n                                TextWrapping=\"Wrap\" />\n\n                            <controls:HyperlinkIconButton\n                                Margin=\"0\"\n                                Padding=\"0,2\"\n                                Content=\"GitHub\"\n                                NavigateUri=\"{Binding Item.Reference}\" />\n                        </StackPanel>\n\n                        <TextBlock\n                            Grid.Column=\"2\"\n                            Margin=\"0,0,16,0\"\n                            VerticalAlignment=\"Center\"\n                            Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                            IsVisible=\"{Binding Item.IsInstalled}\"\n                            Text=\"{Binding Source={x:Static lang:Resources.Label_Installed}, StringFormat='{}({0})'}\" />\n                        <CheckBox\n                            Grid.Column=\"2\"\n                            Margin=\"0,0,16,0\"\n                            IsChecked=\"{Binding IsSelected}\"\n                            IsVisible=\"{Binding !Item.IsInstalled}\" />\n\n                    </sg:SpacedGrid>\n                </controls:Card>\n            </Button>\n        </DataTemplate>\n\n        <DataTemplate x:Key=\"InstalledPackageExtensionTemplate\">\n            <DataTemplate.DataType>\n                <x:Type x:TypeArguments=\"extensions:InstalledPackageExtension\" TypeName=\"models1:SelectableItem\" />\n            </DataTemplate.DataType>\n            <Button\n                Margin=\"4,0,0,8\"\n                Padding=\"-5\"\n                HorizontalAlignment=\"Stretch\"\n                HorizontalContentAlignment=\"Stretch\"\n                Background=\"Transparent\"\n                BorderBrush=\"Transparent\"\n                Classes=\"transparent-full\"\n                Command=\"{Binding ToggleSelectedCommand}\"\n                CornerRadius=\"8\">\n                <controls:Card\n                    Margin=\"4\"\n                    Padding=\"12,12\"\n                    Classes=\"transparent-full\"\n                    CornerRadius=\"8\">\n\n                    <sg:SpacedGrid ColumnDefinitions=\"*,Auto\">\n                        <StackPanel\n                            DataContext=\"{Binding Item}\"\n                            Orientation=\"Vertical\"\n                            Spacing=\"4\">\n                            <TextBlock\n                                FontSize=\"16\"\n                                FontWeight=\"Light\"\n                                Text=\"{Binding Title}\" />\n                            <TextBlock\n                                FontSize=\"13\"\n                                Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                Text=\"{Binding Version, FallbackValue=''}\" />\n                            <TextBlock\n                                FontSize=\"13\"\n                                FontWeight=\"Light\"\n                                Foreground=\"{DynamicResource TextFillColorSecondary}\"\n                                Text=\"{Binding Definition.Description, FallbackValue=''}\"\n                                TextWrapping=\"Wrap\" />\n                            <StackPanel Orientation=\"Horizontal\" Spacing=\"2\">\n                                <controls:HyperlinkIconButton\n                                    Margin=\"0\"\n                                    Padding=\"0,2\"\n                                    Content=\"GitHub\"\n                                    IsEnabled=\"{Binding Definition, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                                    NavigateUri=\"{Binding Definition.Reference, FallbackValue=''}\" />\n\n                                <Ellipse\n                                    Width=\"3\"\n                                    Height=\"3\"\n                                    VerticalAlignment=\"Center\"\n                                    Fill=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                    Opacity=\"0.8\" />\n\n                                <controls:HyperlinkIconButton\n                                    Margin=\"0\"\n                                    Padding=\"0,2\"\n                                    Content=\"{Binding DisplayPath}\"\n                                    IsEnabled=\"{Binding Definition, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                                    NavigateUri=\"{Binding PrimaryPath.FullPath, Converter={StaticResource FileUriConverter}}\" />\n                            </StackPanel>\n                        </StackPanel>\n                        <CheckBox\n                            Grid.Column=\"1\"\n                            Margin=\"0,0,16,0\"\n                            HorizontalAlignment=\"Right\"\n                            IsChecked=\"{Binding IsSelected}\" />\n                    </sg:SpacedGrid>\n                </controls:Card>\n            </Button>\n        </DataTemplate>\n\n        <DataTemplate x:Key=\"ExtensionPackTemplate\">\n            <DataTemplate.DataType>\n                <x:Type x:TypeArguments=\"extensions:SavedPackageExtension\" TypeName=\"models1:SelectableItem\" />\n            </DataTemplate.DataType>\n            <Button\n                Margin=\"4,0,0,8\"\n                Padding=\"-5\"\n                HorizontalAlignment=\"Stretch\"\n                HorizontalContentAlignment=\"Stretch\"\n                Background=\"Transparent\"\n                BorderBrush=\"Transparent\"\n                Classes=\"transparent-full\"\n                Command=\"{Binding ToggleSelectedCommand}\"\n                CornerRadius=\"8\">\n                <controls:Card\n                    Margin=\"4\"\n                    Padding=\"12,12\"\n                    Classes=\"transparent-full\"\n                    CornerRadius=\"8\">\n\n                    <sg:SpacedGrid ColumnDefinitions=\"*,Auto\">\n                        <StackPanel\n                            DataContext=\"{Binding Item}\"\n                            Orientation=\"Vertical\"\n                            Spacing=\"4\">\n                            <TextBlock\n                                FontSize=\"16\"\n                                FontWeight=\"Light\"\n                                Text=\"{Binding PackageExtension.Title}\" />\n                            <TextBlock\n                                FontSize=\"13\"\n                                Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                Text=\"{Binding Version, FallbackValue=''}\" />\n                            <TextBlock\n                                FontSize=\"13\"\n                                FontWeight=\"Light\"\n                                Foreground=\"{DynamicResource TextFillColorSecondary}\"\n                                Text=\"{Binding PackageExtension.Description, FallbackValue=''}\"\n                                TextWrapping=\"Wrap\" />\n\n                            <StackPanel Orientation=\"Horizontal\" Spacing=\"2\">\n                                <controls:HyperlinkIconButton\n                                    Margin=\"0\"\n                                    Padding=\"8\"\n                                    Content=\"GitHub\"\n                                    NavigateUri=\"{Binding PackageExtension.Reference, FallbackValue=''}\" />\n                                <Button\n                                    Margin=\"8,0\"\n                                    Padding=\"8\"\n                                    Classes=\"transparent-full\"\n                                    Command=\"{StaticResource SetExtensionVersionCommand}\"\n                                    CommandParameter=\"{Binding}\"\n                                    Foreground=\"{DynamicResource HyperlinkButtonForeground}\">\n                                    <StackPanel Orientation=\"Horizontal\" Spacing=\"8\">\n                                        <avalonia:Icon Foreground=\"{DynamicResource HyperlinkButtonForeground}\" Value=\"fa-solid fa-code-compare\" />\n                                        <TextBlock Foreground=\"{DynamicResource HyperlinkButtonForeground}\" Text=\"Change Version\" />\n                                    </StackPanel>\n                                </Button>\n                            </StackPanel>\n                        </StackPanel>\n                        <CheckBox\n                            Grid.Column=\"1\"\n                            Margin=\"0,0,16,0\"\n                            HorizontalAlignment=\"Right\"\n                            IsChecked=\"{Binding IsSelected}\" />\n                    </sg:SpacedGrid>\n                </controls:Card>\n            </Button>\n        </DataTemplate>\n    </controls:UserControlBase.Resources>\n\n    <controls:UserControlBase.Styles>\n        <Style Selector=\"ListBox /template/ ScrollViewer#PART_ScrollViewer /template/ ScrollContentPresenter#PART_ContentPresenter\">\n            <Setter Property=\"CornerRadius\" Value=\"8\" />\n        </Style>\n    </controls:UserControlBase.Styles>\n\n    <TabControl SelectionChanged=\"TabControl_OnSelectionChanged\" TabStripPlacement=\"Top\">\n        <TabControl.Items>\n            <TabItem\n                Margin=\"8,0,0,0\"\n                FontSize=\"18\"\n                Header=\"Available Extensions\">\n                <sg:SpacedGrid\n                    Margin=\"8\"\n                    RowDefinitions=\"Auto,*,Auto\"\n                    RowSpacing=\"12\">\n                    <sg:SpacedGrid ColumnDefinitions=\"*,Auto,Auto,Auto\" ColumnSpacing=\"8\">\n                        <TextBox\n                            Margin=\"4,0,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            VerticalAlignment=\"Center\"\n                            Classes=\"search\"\n                            Text=\"{Binding AvailableItemsSearchCollection.Query, Mode=TwoWay}\"\n                            Watermark=\"{x:Static lang:Resources.Action_Search}\" />\n\n                        <!--  Refresh Button  -->\n                        <Button\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Padding=\"16,6\"\n                            HorizontalAlignment=\"Left\"\n                            VerticalAlignment=\"Stretch\"\n                            Command=\"{Binding RefreshCommand}\">\n                            <ui:SymbolIcon FontSize=\"16\" Symbol=\"Refresh\" />\n                        </Button>\n\n                        <!--  Manual Install Button  -->\n                        <Button\n                            Grid.Row=\"0\"\n                            Grid.Column=\"2\"\n                            Padding=\"16,6\"\n                            HorizontalAlignment=\"Left\"\n                            VerticalAlignment=\"Stretch\"\n                            Command=\"{Binding InstallExtensionManualCommand}\">\n                            <ui:SymbolIcon FontSize=\"16\" Symbol=\"Add\" />\n                        </Button>\n\n                        <!--  Settings / Edit Manifests Button  -->\n                        <Button\n                            Grid.Row=\"0\"\n                            Grid.Column=\"3\"\n                            Padding=\"16,6\"\n                            HorizontalAlignment=\"Left\"\n                            VerticalAlignment=\"Stretch\"\n                            Command=\"{Binding OpenExtensionsSettingsDialogCommand}\">\n                            <ui:SymbolIcon FontSize=\"16\" Symbol=\"Settings\" />\n                        </Button>\n                    </sg:SpacedGrid>\n\n                    <TextBlock\n                        Grid.Row=\"1\"\n                        HorizontalAlignment=\"Center\"\n                        IsVisible=\"{Binding ShowNoExtensionsFoundMessage}\"\n                        Text=\"{x:Static lang:Resources.Label_NoExtensionsFound}\" />\n\n                    <ScrollViewer Grid.Row=\"1\" IsVisible=\"{Binding !IsLoading}\">\n                        <ItemsRepeater\n                            DataContext=\"{Binding AvailableItemsSearchCollection}\"\n                            ItemTemplate=\"{StaticResource PackageExtensionTemplate}\"\n                            ItemsSource=\"{Binding FilteredItems}\">\n                            <ItemsRepeater.Layout>\n                                <StackLayout Orientation=\"Vertical\" />\n                            </ItemsRepeater.Layout>\n                        </ItemsRepeater>\n                    </ScrollViewer>\n\n                    <Panel Grid.Row=\"1\" IsVisible=\"{Binding IsLoading}\">\n                        <ProgressBar\n                            Margin=\"32,16\"\n                            IsEnabled=\"{Binding IsLoading}\"\n                            IsIndeterminate=\"True\"\n                            IsVisible=\"{Binding IsLoading}\" />\n                    </Panel>\n\n                    <ui:InfoBar\n                        Grid.Row=\"2\"\n                        HorizontalAlignment=\"Right\"\n                        HorizontalContentAlignment=\"Right\"\n                        CloseButtonCommand=\"{Binding ClearSelection}\"\n                        IsIconVisible=\"False\"\n                        IsOpen=\"{Binding !!SelectedAvailableItems.Count}\">\n                        <ui:InfoBar.Title>\n                            <MultiBinding StringFormat=\"{}{0} Selected\">\n                                <Binding Path=\"SelectedAvailableItems.Count\" />\n                            </MultiBinding>\n                        </ui:InfoBar.Title>\n                        <ui:InfoBar.ActionButton>\n                            <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                                <Button\n                                    Classes=\"accent\"\n                                    Command=\"{Binding InstallSelectedExtensionsCommand}\"\n                                    Content=\"{x:Static lang:Resources.Action_Install}\" />\n\n                                <Button Classes=\"accent\" Content=\"{x:Static lang:Resources.Action_SaveAsEllipsis}\">\n                                    <Button.Flyout>\n                                        <MenuFlyout>\n                                            <MenuItem\n                                                Header=\"{x:Static lang:Resources.Label_AddToExistingPack}\"\n                                                IsVisible=\"{Binding !!ExtensionPacks.Count}\"\n                                                ItemsSource=\"{Binding ExtensionPacks}\">\n                                                <MenuItem.DataTemplates>\n                                                    <DataTemplate DataType=\"extensions:ExtensionPack\">\n                                                        <MenuItem\n                                                            Command=\"{StaticResource AddExtensionsToPackCommand}\"\n                                                            CommandParameter=\"{Binding}\"\n                                                            Header=\"{Binding Name}\" />\n                                                    </DataTemplate>\n                                                </MenuItem.DataTemplates>\n                                            </MenuItem>\n                                            <MenuItem Command=\"{Binding CreateExtensionPackFromAvailableCommand}\" Header=\"{x:Static lang:Resources.Label_NewExtensionPack}\" />\n                                        </MenuFlyout>\n                                    </Button.Flyout>\n                                </Button>\n                            </StackPanel>\n                        </ui:InfoBar.ActionButton>\n                    </ui:InfoBar>\n\n                </sg:SpacedGrid>\n            </TabItem>\n\n            <TabItem\n                Margin=\"0,0,0,0\"\n                FontSize=\"18\"\n                Header=\"Installed Extensions\">\n\n                <sg:SpacedGrid\n                    Margin=\"8\"\n                    RowDefinitions=\"Auto,*,Auto\"\n                    RowSpacing=\"12\">\n                    <sg:SpacedGrid ColumnDefinitions=\"*,Auto\" ColumnSpacing=\"8\">\n                        <TextBox\n                            Margin=\"4,0,0,0\"\n                            HorizontalAlignment=\"Stretch\"\n                            VerticalAlignment=\"Center\"\n                            Classes=\"search\"\n                            DataContext=\"{Binding InstalledItemsSearchCollection}\"\n                            Text=\"{Binding Query, Mode=TwoWay}\"\n                            Watermark=\"{x:Static lang:Resources.Action_Search}\" />\n\n                        <!--  Refresh Button  -->\n                        <Button\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Padding=\"16,6\"\n                            HorizontalAlignment=\"Left\"\n                            VerticalAlignment=\"Stretch\"\n                            Command=\"{Binding RefreshCommand}\">\n                            <ui:SymbolIcon FontSize=\"16\" Symbol=\"Refresh\" />\n                        </Button>\n\n                        <!--  Settings / Edit Manifests Button  -->\n                        <!--<Button\n                            Grid.Row=\"0\"\n                            Grid.Column=\"2\"\n                            Padding=\"16,6\"\n                            HorizontalAlignment=\"Left\"\n                            VerticalAlignment=\"Stretch\">\n                            <ui:SymbolIcon FontSize=\"16\" Symbol=\"Settings\" />\n                        </Button>-->\n                    </sg:SpacedGrid>\n\n                    <ScrollViewer Grid.Row=\"1\" IsVisible=\"{Binding !IsLoading}\">\n                        <ItemsRepeater\n                            DataContext=\"{Binding InstalledItemsSearchCollection}\"\n                            ItemTemplate=\"{StaticResource InstalledPackageExtensionTemplate}\"\n                            ItemsSource=\"{Binding FilteredItems}\">\n                            <ItemsRepeater.Layout>\n                                <StackLayout Orientation=\"Vertical\" />\n                            </ItemsRepeater.Layout>\n                        </ItemsRepeater>\n                    </ScrollViewer>\n\n                    <Panel Grid.Row=\"1\" IsVisible=\"{Binding IsLoading}\">\n                        <ProgressBar\n                            Margin=\"32,16\"\n                            IsEnabled=\"{Binding IsLoading}\"\n                            IsIndeterminate=\"True\"\n                            IsVisible=\"{Binding IsLoading}\" />\n                    </Panel>\n\n                    <ui:InfoBar\n                        Grid.Row=\"2\"\n                        HorizontalAlignment=\"Right\"\n                        HorizontalContentAlignment=\"Right\"\n                        CloseButtonCommand=\"{Binding ClearSelection}\"\n                        IsIconVisible=\"False\"\n                        IsOpen=\"{Binding !!SelectedInstalledItems.Count}\">\n                        <ui:InfoBar.Title>\n                            <MultiBinding StringFormat=\"{}{0} Selected\">\n                                <Binding Path=\"SelectedInstalledItems.Count\" />\n                            </MultiBinding>\n                        </ui:InfoBar.Title>\n                        <ui:InfoBar.ActionButton>\n                            <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                                <Button\n                                    Classes=\"accent\"\n                                    Command=\"{Binding UpdateSelectedExtensionsCommand}\"\n                                    Content=\"{x:Static lang:Resources.Action_Update}\" />\n                                <Button Classes=\"accent\" Content=\"{x:Static lang:Resources.Action_SaveAsEllipsis}\">\n                                    <Button.Flyout>\n                                        <MenuFlyout>\n                                            <MenuItem\n                                                Header=\"{x:Static lang:Resources.Label_AddToExistingPack}\"\n                                                IsVisible=\"{Binding !!ExtensionPacks.Count}\"\n                                                ItemsSource=\"{Binding ExtensionPacks}\">\n                                                <MenuItem.DataTemplates>\n                                                    <DataTemplate DataType=\"extensions:ExtensionPack\">\n                                                        <MenuItem\n                                                            Command=\"{StaticResource AddInstalledExtensionsToPackCommand}\"\n                                                            CommandParameter=\"{Binding}\"\n                                                            Header=\"{Binding Name}\" />\n                                                    </DataTemplate>\n                                                </MenuItem.DataTemplates>\n                                            </MenuItem>\n                                            <MenuItem Command=\"{Binding CreateExtensionPackFromInstalledCommand}\" Header=\"{x:Static lang:Resources.Label_NewExtensionPack}\" />\n                                        </MenuFlyout>\n                                    </Button.Flyout>\n                                </Button>\n                                <Button\n                                    Classes=\"accent\"\n                                    Command=\"{Binding SelectAllInstalledExtensionsCommand}\"\n                                    Content=\"{x:Static lang:Resources.Action_SelectAll}\" />\n                                <Button\n                                    Classes=\"danger\"\n                                    Command=\"{Binding UninstallSelectedExtensionsCommand}\"\n                                    Content=\"{x:Static lang:Resources.Action_Uninstall}\" />\n                            </StackPanel>\n\n                        </ui:InfoBar.ActionButton>\n                    </ui:InfoBar>\n\n                </sg:SpacedGrid>\n\n            </TabItem>\n\n            <TabItem FontSize=\"18\" Header=\"{x:Static lang:Resources.Label_ExtensionPacks}\">\n                <Grid>\n                    <StackPanel IsVisible=\"{Binding !!!ExtensionPacks.Count}\">\n                        <TextBlock\n                            Margin=\"8,32,8,8\"\n                            FontSize=\"28\"\n                            FontWeight=\"SemiLight\"\n                            Text=\"{x:Static lang:Resources.Label_NoExtensionPacksFound}\"\n                            TextAlignment=\"Center\" />\n                        <TextBlock\n                            MaxWidth=\"600\"\n                            Margin=\"0,16,0,0\"\n                            FontSize=\"20\"\n                            FontWeight=\"Light\"\n                            Text=\"{x:Static lang:Resources.Label_CreateExtensionPackExplanation}\"\n                            TextAlignment=\"Center\"\n                            TextWrapping=\"Wrap\" />\n\n                        <TextBlock\n                            Margin=\"0,16,0,0\"\n                            FontSize=\"20\"\n                            FontWeight=\"SemiLight\"\n                            Text=\"{x:Static lang:Resources.Label_OrWithHyphensAround}\"\n                            TextAlignment=\"Center\" />\n\n                        <TextBlock\n                            MaxWidth=\"600\"\n                            Margin=\"0,16,0,0\"\n                            FontSize=\"20\"\n                            FontWeight=\"Light\"\n                            Text=\"{x:Static lang:Resources.Label_ImportExtensionPacksExplanation}\"\n                            TextAlignment=\"Center\"\n                            TextWrapping=\"Wrap\" />\n\n                        <Button\n                            Margin=\"0,32,0,0\"\n                            HorizontalAlignment=\"Center\"\n                            Classes=\"transparent\"\n                            Command=\"{Binding OpenExtensionPackFolderCommand}\"\n                            FontSize=\"16\">\n                            <StackPanel Orientation=\"Horizontal\" Spacing=\"8\">\n                                <ui:SymbolIcon Symbol=\"OpenFolder\" />\n                                <TextBlock FontWeight=\"Light\" Text=\"{x:Static lang:Resources.Label_OpenExtensionPacksFolder}\" />\n                            </StackPanel>\n                        </Button>\n                    </StackPanel>\n                    <Grid\n                        Margin=\"0,8\"\n                        ColumnDefinitions=\"Auto, *\"\n                        IsVisible=\"{Binding !!ExtensionPacks.Count}\"\n                        RowDefinitions=\"Auto, Auto, *\">\n\n                        <Panel\n                            Grid.Row=\"1\"\n                            Grid.Column=\"0\"\n                            Grid.ColumnSpan=\"2\"\n                            IsVisible=\"{Binding AreExtensionPacksLoading}\">\n                            <ProgressBar\n                                Margin=\"32,16\"\n                                IsEnabled=\"{Binding AreExtensionPacksLoading}\"\n                                IsIndeterminate=\"True\"\n                                IsVisible=\"{Binding AreExtensionPacksLoading}\" />\n                        </Panel>\n\n                        <ListBox\n                            Grid.Row=\"0\"\n                            Grid.RowSpan=\"3\"\n                            Grid.Column=\"0\"\n                            Padding=\"8\"\n                            Background=\"#22000000\"\n                            IsVisible=\"{Binding !AreExtensionPacksLoading}\"\n                            ItemsSource=\"{Binding ExtensionPacks}\"\n                            SelectedItem=\"{Binding SelectedExtensionPack}\"\n                            SelectionMode=\"AlwaysSelected\">\n                            <ListBox.ItemTemplate>\n                                <DataTemplate DataType=\"{x:Type extensions:ExtensionPack}\">\n                                    <TextBlock Text=\"{Binding Name}\" />\n                                </DataTemplate>\n                            </ListBox.ItemTemplate>\n                            <ListBox.ContextFlyout>\n                                <MenuFlyout>\n                                    <MenuItem\n                                        Command=\"{StaticResource DeleteExtensionPackCommand}\"\n                                        CommandParameter=\"{Binding SelectedExtensionPack}\"\n                                        Header=\"{x:Static lang:Resources.Action_Delete}\"\n                                        Icon=\"Delete\" />\n                                </MenuFlyout>\n                            </ListBox.ContextFlyout>\n                        </ListBox>\n\n                        <ui:CommandBar\n                            Grid.Row=\"0\"\n                            Grid.Column=\"1\"\n                            Margin=\"6,8\"\n                            DefaultLabelPosition=\"Right\"\n                            IsVisible=\"{Binding !AreExtensionPacksLoading}\">\n                            <ui:CommandBar.PrimaryCommands>\n                                <ui:CommandBarButton\n                                    Classes=\"accent\"\n                                    Command=\"{Binding InstallExtensionPackCommand}\"\n                                    IconSource=\"Download\"\n                                    Label=\"{x:Static lang:Resources.Label_InstallExtensionPack}\" />\n\n                                <ui:CommandBarButton\n                                    Margin=\"8,0,0,0\"\n                                    Command=\"{Binding OpenExtensionPackFolderCommand}\"\n                                    IconSource=\"OpenFolder\"\n                                    Label=\"{OnPlatform Default={x:Static lang:Resources.Action_OpenInExplorer},\n                                                       macOS={x:Static lang:Resources.Action_OpenInFinder}}\" />\n\n                                <ui:CommandBarSeparator Margin=\"8,0\" />\n\n                                <ui:CommandBarButton\n                                    Classes=\"danger\"\n                                    Command=\"{Binding DeleteExtensionPackCommand}\"\n                                    CommandParameter=\"{Binding SelectedExtensionPack}\"\n                                    IconSource=\"Delete\"\n                                    Label=\"{x:Static lang:Resources.Action_Delete}\" />\n                            </ui:CommandBar.PrimaryCommands>\n                        </ui:CommandBar>\n                        <ScrollViewer\n                            Grid.Row=\"1\"\n                            Grid.RowSpan=\"2\"\n                            Grid.Column=\"1\"\n                            Margin=\"8,0\"\n                            IsVisible=\"{Binding !AreExtensionPacksLoading}\">\n                            <ItemsRepeater\n                                DataContext=\"{Binding ExtensionPackExtensionsSearchCollection}\"\n                                ItemTemplate=\"{StaticResource ExtensionPackTemplate}\"\n                                ItemsSource=\"{Binding FilteredItems}\">\n                                <ItemsRepeater.Layout>\n                                    <StackLayout Orientation=\"Vertical\" />\n                                </ItemsRepeater.Layout>\n                            </ItemsRepeater>\n                        </ScrollViewer>\n\n                        <ui:InfoBar\n                            Grid.Row=\"2\"\n                            Grid.Column=\"1\"\n                            Margin=\"0,4\"\n                            HorizontalAlignment=\"Right\"\n                            VerticalAlignment=\"Bottom\"\n                            HorizontalContentAlignment=\"Right\"\n                            CloseButtonCommand=\"{Binding ClearSelection}\"\n                            IsIconVisible=\"False\"\n                            IsOpen=\"{Binding !!SelectedExtensionPackExtensions.Count}\">\n                            <ui:InfoBar.Title>\n                                <MultiBinding StringFormat=\"{}{0} Selected\">\n                                    <Binding Path=\"SelectedExtensionPackExtensions.Count\" />\n                                </MultiBinding>\n                            </ui:InfoBar.Title>\n                            <ui:InfoBar.ActionButton>\n                                <Button\n                                    Classes=\"danger\"\n                                    Command=\"{Binding RemoveExtensionFromPackCommand}\"\n                                    Content=\"{x:Static lang:Resources.Action_Remove}\" />\n                            </ui:InfoBar.ActionButton>\n                        </ui:InfoBar>\n\n                    </Grid>\n                </Grid>\n            </TabItem>\n        </TabControl.Items>\n    </TabControl>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/PackageManager/PackageExtensionBrowserView.axaml.cs",
    "content": "﻿using Avalonia.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.PackageManager;\n\nnamespace StabilityMatrix.Avalonia.Views.PackageManager;\n\n[RegisterTransient<PackageExtensionBrowserView>]\npublic partial class PackageExtensionBrowserView : UserControlBase\n{\n    public PackageExtensionBrowserView()\n    {\n        InitializeComponent();\n    }\n\n    private void TabControl_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)\n    {\n        // Clear selection when switching tabs\n        if (DataContext is PackageExtensionBrowserViewModel vm)\n        {\n            vm.ClearSelection();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallBrowserView.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.PackageManager.PackageInstallBrowserView\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:controls1=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs\"\n    xmlns:input=\"clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:packageManager=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.PackageManager\"\n    xmlns:packages=\"clr-namespace:StabilityMatrix.Core.Models.Packages;assembly=StabilityMatrix.Core\"\n    d:DataContext=\"{x:Static mocks:DesignData.NewInstallerDialogViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"packageManager:PackageInstallBrowserViewModel\"\n    mc:Ignorable=\"d\">\n\n    <controls:UserControlBase.Resources>\n        <input:StandardUICommand x:Key=\"PackageSelected\" Command=\"{Binding OnPackageSelected}\" />\n\n        <DataTemplate x:Key=\"PackageTemplate\" x:DataType=\"packages:BasePackage\">\n            <Button\n                Margin=\"4\"\n                Padding=\"0\"\n                HorizontalAlignment=\"Stretch\"\n                HorizontalContentAlignment=\"Stretch\"\n                Classes=\"transparent-full\"\n                Command=\"{StaticResource PackageSelected}\"\n                CommandParameter=\"{Binding}\"\n                CornerRadius=\"8\">\n                <controls:Card>\n\n                    <Grid ColumnDefinitions=\"Auto, *, Auto\">\n                        <controls:BetterAdvancedImage\n                            Grid.Column=\"0\"\n                            Width=\"128\"\n                            Height=\"128\"\n                            CornerRadius=\"4\"\n                            Source=\"{Binding PreviewImageUri}\"\n                            Stretch=\"UniformToFill\" />\n                        <StackPanel Grid.Column=\"1\" Orientation=\"Vertical\">\n                            <TextBlock\n                                Margin=\"16,0,0,0\"\n                                FontSize=\"20\"\n                                FontWeight=\"Light\"\n                                Text=\"{Binding DisplayName}\" />\n\n                            <TextBlock\n                                Margin=\"16,0,0,0\"\n                                FontSize=\"13\"\n                                FontWeight=\"Light\"\n                                Text=\"{Binding ByAuthor}\" />\n\n                            <TextBlock\n                                Margin=\"16,8,0,8\"\n                                FontSize=\"16\"\n                                FontWeight=\"Light\"\n                                Text=\"{Binding Blurb}\"\n                                TextWrapping=\"Wrap\" />\n\n                            <TextBlock\n                                Margin=\"16,-4,0,4\"\n                                FontSize=\"14\"\n                                FontWeight=\"Light\"\n                                Foreground=\"OrangeRed\"\n                                IsVisible=\"{Binding Disclaimer, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                                Text=\"{Binding Disclaimer}\"\n                                TextWrapping=\"Wrap\" />\n\n                            <ItemsRepeater Margin=\"16,8,0,0\" ItemsSource=\"{Binding AvailableTorchIndices}\">\n                                <ItemsRepeater.Layout>\n                                    <StackLayout Orientation=\"Horizontal\" />\n                                </ItemsRepeater.Layout>\n                                <ItemsRepeater.ItemTemplate>\n                                    <DataTemplate DataType=\"{x:Type models:TorchIndex}\">\n                                        <controls:Card\n                                            Margin=\"0,0,8,0\"\n                                            Padding=\"4\"\n                                            HorizontalAlignment=\"Left\"\n                                            VerticalAlignment=\"Top\"\n                                            FontWeight=\"Light\"\n                                            Tag=\"{Binding}\">\n\n                                            <controls:Card.Styles>\n                                                <Style Selector=\"controls|Card[Tag=Cuda]\">\n                                                    <Setter Property=\"Background\" Value=\"{DynamicResource ThemeGreenColorTransparent}\" />\n                                                    <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeGreenColorTransparent}\" />\n                                                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                                                    </Style>\n                                                    <Setter Property=\"Content\">\n                                                        <Template>\n                                                            <TextBlock\n                                                                HorizontalAlignment=\"Center\"\n                                                                VerticalAlignment=\"Center\"\n                                                                FontWeight=\"Medium\"\n                                                                Text=\"NVIDIA\"\n                                                                TextAlignment=\"Center\" />\n                                                        </Template>\n                                                    </Setter>\n                                                </Style>\n                                                <Style Selector=\"controls|Card[Tag=Rocm]\">\n                                                    <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n                                                    <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkRedColor}\" />\n                                                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                                                    </Style>\n                                                    <Setter Property=\"Content\">\n                                                        <Template>\n                                                            <TextBlock\n                                                                HorizontalAlignment=\"Center\"\n                                                                VerticalAlignment=\"Center\"\n                                                                FontWeight=\"Medium\"\n                                                                Text=\"ROCm\"\n                                                                TextAlignment=\"Center\"\n                                                                ToolTip.Tip=\"For AMD GPUs that support ROCm on Linux\" />\n                                                        </Template>\n                                                    </Setter>\n                                                </Style>\n                                                <Style Selector=\"controls|Card[Tag=DirectMl]\">\n                                                    <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                                                    <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkBlueColor}\" />\n                                                    <Setter Property=\"Content\">\n                                                        <Template>\n                                                            <TextBlock\n                                                                HorizontalAlignment=\"Center\"\n                                                                VerticalAlignment=\"Center\"\n                                                                FontWeight=\"Medium\"\n                                                                Text=\"DirectML\"\n                                                                TextAlignment=\"Center\"\n                                                                ToolTip.Tip=\"For any DirectX compatible GPU on Windows\" />\n                                                        </Template>\n                                                    </Setter>\n                                                </Style>\n                                                <Style Selector=\"controls|Card[Tag=Mps]\">\n                                                    <Setter Property=\"Background\" Value=\"White\" />\n                                                    <Setter Property=\"BorderBrush\" Value=\"White\" />\n                                                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                                                    </Style>\n                                                    <Setter Property=\"Content\">\n                                                        <Template>\n                                                            <TextBlock\n                                                                HorizontalAlignment=\"Center\"\n                                                                VerticalAlignment=\"Center\"\n                                                                FontWeight=\"Medium\"\n                                                                Foreground=\"Black\"\n                                                                Text=\"macOS\"\n                                                                TextAlignment=\"Center\" />\n                                                        </Template>\n                                                    </Setter>\n                                                </Style>\n                                                <Style Selector=\"controls|Card[Tag=Zluda]\">\n                                                    <Setter Property=\"Background\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n                                                    <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeDarkDarkRedColor}\" />\n                                                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                                                    </Style>\n                                                    <Setter Property=\"Content\">\n                                                        <Template>\n                                                            <TextBlock\n                                                                HorizontalAlignment=\"Center\"\n                                                                VerticalAlignment=\"Center\"\n                                                                FontWeight=\"Medium\"\n                                                                Text=\"ZLUDA\"\n                                                                TextAlignment=\"Center\"\n                                                                ToolTip.Tip=\"For AMD GPUs that support ZLUDA\" />\n                                                        </Template>\n                                                    </Setter>\n                                                </Style>\n                                                <Style Selector=\"controls|Card[Tag=Ipex]\">\n                                                    <Setter Property=\"Background\" Value=\"{DynamicResource ThemeLightBlueColor}\" />\n                                                    <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeLightBlueColor}\" />\n                                                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                                                    </Style>\n                                                    <Setter Property=\"Content\">\n                                                        <Template>\n                                                            <TextBlock\n                                                                HorizontalAlignment=\"Center\"\n                                                                VerticalAlignment=\"Center\"\n                                                                FontWeight=\"Medium\"\n                                                                Text=\"IPEX\"\n                                                                TextAlignment=\"Center\"\n                                                                ToolTip.Tip=\"For Intel Arc GPUs\" />\n                                                        </Template>\n                                                    </Setter>\n                                                </Style>\n                                                <Style Selector=\"controls|Card[Tag=Cpu]\">\n                                                    <Setter Property=\"Background\" Value=\"{DynamicResource ThemeBlueGreyColor}\" />\n                                                    <Setter Property=\"BorderBrush\" Value=\"{DynamicResource ThemeBlueGreyColor}\" />\n                                                    <Style Selector=\"^ /template/ ContentPresenter#PART_ContentPresenter\">\n                                                        <Setter Property=\"Foreground\" Value=\"{DynamicResource ButtonForeground}\" />\n                                                    </Style>\n                                                    <Setter Property=\"Content\">\n                                                        <Template>\n                                                            <TextBlock\n                                                                HorizontalAlignment=\"Center\"\n                                                                VerticalAlignment=\"Center\"\n                                                                FontWeight=\"Medium\"\n                                                                Text=\"CPU\"\n                                                                TextAlignment=\"Center\" />\n                                                        </Template>\n                                                    </Setter>\n                                                </Style>\n                                            </controls:Card.Styles>\n                                        </controls:Card>\n                                    </DataTemplate>\n                                </ItemsRepeater.ItemTemplate>\n                            </ItemsRepeater>\n\n                        </StackPanel>\n\n                        <controls1:SymbolIcon\n                            Grid.Column=\"2\"\n                            HorizontalAlignment=\"Right\"\n                            VerticalAlignment=\"Center\"\n                            FontSize=\"24\"\n                            Symbol=\"ChevronRight\" />\n                    </Grid>\n                </controls:Card>\n            </Button>\n        </DataTemplate>\n    </controls:UserControlBase.Resources>\n\n    <TabControl TabStripPlacement=\"Top\">\n        <TabControl.Items>\n            <TabItem Margin=\"8,0,0,0\" Header=\"Inference\">\n                <ScrollViewer Padding=\"8\">\n                    <StackPanel Orientation=\"Vertical\">\n                        <Grid Margin=\"0,0,0,8\" ColumnDefinitions=\"*, Auto\">\n                            <TextBox\n                                Height=\"36\"\n                                Margin=\"4,0,0,0\"\n                                HorizontalAlignment=\"Stretch\"\n                                VerticalAlignment=\"Center\"\n                                FontSize=\"16\"\n                                KeyDown=\"InputElement_OnKeyDown\"\n                                Text=\"{Binding SearchFilter, Mode=TwoWay}\"\n                                Watermark=\"{x:Static lang:Resources.Action_Search}\">\n                                <TextBox.InnerRightContent>\n                                    <Grid>\n                                        <Button\n                                            Classes=\"transparent-full\"\n                                            Command=\"{Binding ClearSearchQuery}\"\n                                            IsVisible=\"{Binding SearchFilter.Length}\">\n                                            <controls1:SymbolIcon Symbol=\"Cancel\" />\n                                        </Button>\n                                        <controls1:SymbolIcon\n                                            Margin=\"0,0,10,0\"\n                                            FontSize=\"16\"\n                                            IsVisible=\"{Binding !SearchFilter.Length}\"\n                                            Symbol=\"Find\" />\n                                    </Grid>\n                                </TextBox.InnerRightContent>\n                            </TextBox>\n                            <ToggleSwitch\n                                Grid.Column=\"1\"\n                                Margin=\"16,0,8,0\"\n                                HorizontalAlignment=\"Left\"\n                                VerticalAlignment=\"Center\"\n                                IsChecked=\"{Binding ShowIncompatiblePackages}\"\n                                OffContent=\"Show All Packages\"\n                                OnContent=\"Show All Packages\"\n                                ToolTip.Tip=\"Enabling &quot;Show All Packages&quot; will include packages that may not be compatible with your system and may run very slowly as a result.\" />\n                        </Grid>\n                        <ItemsRepeater ItemsSource=\"{Binding InferencePackages}\">\n                            <ItemsRepeater.Layout>\n                                <StackLayout Orientation=\"Vertical\" />\n                            </ItemsRepeater.Layout>\n                            <ItemsRepeater.ItemTemplate>\n                                <StaticResource ResourceKey=\"PackageTemplate\" />\n                            </ItemsRepeater.ItemTemplate>\n                        </ItemsRepeater>\n                    </StackPanel>\n                </ScrollViewer>\n            </TabItem>\n            <TabItem Header=\"Training\">\n                <ScrollViewer Padding=\"8\">\n                    <StackPanel Orientation=\"Vertical\">\n                        <Grid Margin=\"0,0,0,8\" ColumnDefinitions=\"*, Auto\">\n                            <TextBox\n                                Height=\"36\"\n                                Margin=\"4,0,0,0\"\n                                HorizontalAlignment=\"Stretch\"\n                                VerticalAlignment=\"Center\"\n                                FontSize=\"16\"\n                                KeyDown=\"InputElement_OnKeyDown\"\n                                Text=\"{Binding SearchFilter, Mode=TwoWay}\"\n                                Watermark=\"{x:Static lang:Resources.Action_Search}\">\n                                <TextBox.InnerRightContent>\n                                    <Grid>\n                                        <Button\n                                            Classes=\"transparent-full\"\n                                            Command=\"{Binding ClearSearchQuery}\"\n                                            IsVisible=\"{Binding SearchFilter.Length}\">\n                                            <controls1:SymbolIcon Symbol=\"Cancel\" />\n                                        </Button>\n                                        <controls1:SymbolIcon\n                                            Margin=\"0,0,10,0\"\n                                            FontSize=\"16\"\n                                            IsVisible=\"{Binding !SearchFilter.Length}\"\n                                            Symbol=\"Find\" />\n                                    </Grid>\n                                </TextBox.InnerRightContent>\n                            </TextBox>\n                            <ToggleSwitch\n                                Grid.Column=\"1\"\n                                Margin=\"16,0,8,0\"\n                                HorizontalAlignment=\"Left\"\n                                VerticalAlignment=\"Center\"\n                                IsChecked=\"{Binding ShowIncompatiblePackages}\"\n                                OffContent=\"Show All Packages\"\n                                OnContent=\"Show All Packages\"\n                                ToolTip.Tip=\"Enabling &quot;Show All Packages&quot; will include packages that may not be compatible with your system and may run very slowly as a result.\" />\n                        </Grid>\n                        <ItemsRepeater ItemsSource=\"{Binding TrainingPackages}\">\n                            <ItemsRepeater.Layout>\n                                <StackLayout Orientation=\"Vertical\" />\n                            </ItemsRepeater.Layout>\n                            <ItemsRepeater.ItemTemplate>\n                                <StaticResource ResourceKey=\"PackageTemplate\" />\n                            </ItemsRepeater.ItemTemplate>\n                        </ItemsRepeater>\n                    </StackPanel>\n                </ScrollViewer>\n            </TabItem>\n            <TabItem Header=\"Legacy\">\n                <ScrollViewer Padding=\"8\">\n                    <StackPanel Orientation=\"Vertical\">\n                        <Grid Margin=\"0,0,0,8\" ColumnDefinitions=\"*, Auto\">\n                            <TextBox\n                                Height=\"36\"\n                                Margin=\"4,0,0,0\"\n                                HorizontalAlignment=\"Stretch\"\n                                VerticalAlignment=\"Center\"\n                                FontSize=\"16\"\n                                KeyDown=\"InputElement_OnKeyDown\"\n                                Text=\"{Binding SearchFilter, Mode=TwoWay}\"\n                                Watermark=\"{x:Static lang:Resources.Action_Search}\">\n                                <TextBox.InnerRightContent>\n                                    <Grid>\n                                        <Button\n                                            Classes=\"transparent-full\"\n                                            Command=\"{Binding ClearSearchQuery}\"\n                                            IsVisible=\"{Binding SearchFilter.Length}\">\n                                            <controls1:SymbolIcon Symbol=\"Cancel\" />\n                                        </Button>\n                                        <controls1:SymbolIcon\n                                            Margin=\"0,0,10,0\"\n                                            FontSize=\"16\"\n                                            IsVisible=\"{Binding !SearchFilter.Length}\"\n                                            Symbol=\"Find\" />\n                                    </Grid>\n                                </TextBox.InnerRightContent>\n                            </TextBox>\n                            <ToggleSwitch\n                                Grid.Column=\"1\"\n                                Margin=\"16,0,8,0\"\n                                HorizontalAlignment=\"Left\"\n                                VerticalAlignment=\"Center\"\n                                IsChecked=\"{Binding ShowIncompatiblePackages}\"\n                                OffContent=\"Show All Packages\"\n                                OnContent=\"Show All Packages\"\n                                ToolTip.Tip=\"Enabling &quot;Show All Packages&quot; will include packages that may not be compatible with your system and may run very slowly as a result.\" />\n                        </Grid>\n                        <ItemsRepeater ItemsSource=\"{Binding LegacyPackages}\">\n                            <ItemsRepeater.Layout>\n                                <StackLayout Orientation=\"Vertical\" />\n                            </ItemsRepeater.Layout>\n                            <ItemsRepeater.ItemTemplate>\n                                <StaticResource ResourceKey=\"PackageTemplate\" />\n                            </ItemsRepeater.ItemTemplate>\n                        </ItemsRepeater>\n                    </StackPanel>\n                </ScrollViewer>\n            </TabItem>\n        </TabControl.Items>\n    </TabControl>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallBrowserView.axaml.cs",
    "content": "﻿using Avalonia.Input;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.ViewModels.PackageManager;\n\nnamespace StabilityMatrix.Avalonia.Views.PackageManager;\n\n[RegisterSingleton<PackageInstallBrowserView>]\npublic partial class PackageInstallBrowserView : UserControlBase\n{\n    public PackageInstallBrowserView()\n    {\n        InitializeComponent();\n    }\n\n    private void InputElement_OnKeyDown(object? sender, KeyEventArgs e)\n    {\n        if (e.Key == Key.Escape && DataContext is PackageInstallBrowserViewModel vm)\n        {\n            vm.ClearSearchQuery();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallDetailView.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.PackageManager.PackageInstallDetailView\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"clr-namespace:FluentIcons.Avalonia;assembly=FluentIcons.Avalonia\"\n    xmlns:avalonia1=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:database=\"clr-namespace:StabilityMatrix.Core.Models.Database;assembly=StabilityMatrix.Core\"\n    xmlns:designData=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:dialogs=\"clr-namespace:StabilityMatrix.Avalonia.Views.Dialogs\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:packageManager=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.PackageManager\"\n    xmlns:python=\"clr-namespace:StabilityMatrix.Core.Python;assembly=StabilityMatrix.Core\"\n    d:DataContext=\"{x:Static designData:DesignData.PackageInstallDetailViewModel}\"\n    d:DesignHeight=\"850\"\n    d:DesignWidth=\"800\"\n    x:CompileBindings=\"True\"\n    x:DataType=\"packageManager:PackageInstallDetailViewModel\"\n    Focusable=\"True\"\n    mc:Ignorable=\"d\">\n\n    <Grid ColumnDefinitions=\"Auto, *\" RowDefinitions=\"Auto, *\">\n        <controls:BetterAdvancedImage\n            Width=\"300\"\n            Height=\"300\"\n            Margin=\"16,16,8,8\"\n            VerticalAlignment=\"Top\"\n            CornerRadius=\"8\"\n            Source=\"{Binding SelectedPackage.PreviewImageUri}\"\n            Stretch=\"UniformToFill\" />\n\n        <StackPanel\n            Grid.Row=\"0\"\n            Grid.Column=\"1\"\n            Margin=\"8\"\n            Orientation=\"Vertical\">\n\n\n            <StackPanel Orientation=\"Horizontal\">\n                <controls:HyperlinkIconButton\n                    Content=\"{Binding SelectedPackage.LicenseType}\"\n                    IsVisible=\"{Binding SelectedPackage.LicenseUrl, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                    NavigateUri=\"{Binding SelectedPackage.LicenseUrl}\" />\n                <controls:HyperlinkIconButton Content=\"{x:Static lang:Resources.Action_OpenGithub}\" NavigateUri=\"{Binding SelectedPackage.GithubUrl}\" />\n            </StackPanel>\n\n            <!--  Version Selector  -->\n            <Label\n                Margin=\"8,4\"\n                Content=\"{x:Static lang:Resources.Label_Version}\"\n                FontSize=\"16\"\n                FontWeight=\"Light\" />\n            <Grid\n                Margin=\"8,0,0,16\"\n                HorizontalAlignment=\"Stretch\"\n                ColumnDefinitions=\"Auto, Auto, *\">\n                <ToggleButton\n                    Grid.Column=\"0\"\n                    Content=\"Branches\"\n                    CornerRadius=\"8,0,0,8\"\n                    IsChecked=\"{Binding !IsReleaseMode, Mode=TwoWay}\"\n                    IsEnabled=\"{Binding ShowBranchMode}\" />\n                <ToggleButton\n                    Grid.Column=\"1\"\n                    Content=\"Releases\"\n                    CornerRadius=\"0,8,8,0\"\n                    IsChecked=\"{Binding IsReleaseMode, Mode=TwoWay}\"\n                    IsEnabled=\"{Binding ShowReleaseMode}\"\n                    ToolTip.ShowOnDisabled=\"True\"\n                    ToolTip.Tip=\"{Binding ReleaseTooltipText}\" />\n\n                <controls:BetterComboBox\n                    Grid.Column=\"2\"\n                    MinWidth=\"250\"\n                    Margin=\"24,0,8,0\"\n                    HorizontalAlignment=\"Stretch\"\n                    VerticalAlignment=\"Stretch\"\n                    HorizontalContentAlignment=\"Stretch\"\n                    FontSize=\"16\"\n                    ItemsSource=\"{Binding AvailableVersions}\"\n                    SelectedItem=\"{Binding SelectedVersion}\">\n                    <controls:BetterComboBox.ItemTemplate>\n                        <DataTemplate DataType=\"{x:Type models:PackageVersion}\">\n                            <TextBlock\n                                Name=\"NameTextBlock\"\n                                VerticalAlignment=\"Center\"\n                                Text=\"{Binding TagName}\" />\n                        </DataTemplate>\n                    </controls:BetterComboBox.ItemTemplate>\n                </controls:BetterComboBox>\n            </Grid>\n\n            <Label\n                Margin=\"8,4\"\n                Content=\"{x:Static lang:Resources.Label_DisplayName}\"\n                FontSize=\"16\"\n                FontWeight=\"Light\"\n                ToolTip.Tip=\"This will also be the folder name\" />\n            <TextBox\n                MinWidth=\"250\"\n                Margin=\"8,4,8,4\"\n                VerticalAlignment=\"Stretch\"\n                VerticalContentAlignment=\"Center\"\n                FontSize=\"16\"\n                FontWeight=\"SemiLight\"\n                Text=\"{Binding InstallName, Mode=TwoWay}\"\n                ToolTip.Tip=\"This will also be the folder name\" />\n            <Label\n                Margin=\"8,0\"\n                Content=\"{Binding FullInstallPath}\"\n                Foreground=\"LightGray\" />\n\n            <StackPanel\n                Margin=\"8,8,8,8\"\n                IsVisible=\"{Binding ShowDuplicateWarning}\"\n                Orientation=\"Horizontal\">\n                <avalonia:SymbolIcon\n                    Margin=\"0,0,8,0\"\n                    Foreground=\"{DynamicResource ThemeRedColor}\"\n                    Symbol=\"Alert\" />\n                <TextBlock\n                    Foreground=\"{DynamicResource ThemeRedColor}\"\n                    TextAlignment=\"Left\"\n                    TextWrapping=\"Wrap\">\n                    <Run Text=\"{x:Static lang:Resources.Label_InstallationWithThisNameExists}\" />\n                    <LineBreak />\n                    <Run Text=\"{x:Static lang:Resources.Label_PleaseChooseDifferentName}\" />\n                </TextBlock>\n            </StackPanel>\n\n            <Expander\n                Margin=\"8\"\n                HorizontalAlignment=\"Stretch\"\n                FontSize=\"16\"\n                Header=\"{x:Static lang:Resources.Label_AdvancedOptions}\">\n                <WrapPanel Margin=\"8,0\">\n                    <StackPanel Margin=\"0,4,24,0\" Orientation=\"Vertical\">\n                        <Label Content=\"{x:Static lang:Resources.Label_SharedModelStrategyShort}\" />\n                        <controls:BetterComboBox\n                            MinWidth=\"150\"\n                            MinHeight=\"38\"\n                            ItemsSource=\"{Binding SelectedPackage.AvailableSharedFolderMethods}\"\n                            SelectedItem=\"{Binding SelectedSharedFolderMethod}\">\n                            <controls:BetterComboBox.ItemTemplate>\n                                <DataTemplate DataType=\"{x:Type models:SharedFolderMethod}\">\n                                    <TextBlock Margin=\"8,4,0,4\" Text=\"{Binding}\" />\n                                </DataTemplate>\n                            </controls:BetterComboBox.ItemTemplate>\n                        </controls:BetterComboBox>\n                    </StackPanel>\n\n                    <StackPanel Margin=\"0,4,24,0\" Orientation=\"Vertical\">\n                        <Label Content=\"PyTorch Index\" IsVisible=\"{Binding ShowTorchIndexOptions}\" />\n                        <controls:BetterComboBox\n                            MinWidth=\"150\"\n                            MinHeight=\"38\"\n                            IsVisible=\"{Binding ShowTorchIndexOptions}\"\n                            ItemsSource=\"{Binding SelectedPackage.AvailableTorchIndices}\"\n                            SelectedItem=\"{Binding SelectedTorchIndex}\">\n                            <controls:BetterComboBox.ItemTemplate>\n                                <DataTemplate DataType=\"{x:Type models:TorchIndex}\">\n                                    <TextBlock Margin=\"8,4,0,4\" Text=\"{Binding}\" />\n                                </DataTemplate>\n                            </controls:BetterComboBox.ItemTemplate>\n                        </controls:BetterComboBox>\n                    </StackPanel>\n\n                    <StackPanel Margin=\"0,4,24,0\" Orientation=\"Vertical\">\n                        <Label Content=\"{x:Static lang:Resources.Label_Commit}\" IsVisible=\"{Binding !IsReleaseMode}\" />\n                        <controls:BetterComboBox\n                            MinWidth=\"150\"\n                            MinHeight=\"38\"\n                            IsVisible=\"{Binding !IsReleaseMode}\"\n                            ItemsSource=\"{Binding AvailableCommits}\"\n                            SelectedItem=\"{Binding SelectedCommit}\">\n                            <controls:BetterComboBox.ItemTemplate>\n                                <DataTemplate DataType=\"{x:Type database:GitCommit}\">\n                                    <TextBlock\n                                        Name=\"NameTextBlock\"\n                                        Margin=\"8,4,0,4\"\n                                        Text=\"{Binding ShortSha}\" />\n                                </DataTemplate>\n                            </controls:BetterComboBox.ItemTemplate>\n                        </controls:BetterComboBox>\n                    </StackPanel>\n\n                    <StackPanel\n                        Margin=\"0,4,24,0\"\n                        IsVisible=\"{Binding !!SelectedPackage.SharedOutputFolders}\"\n                        Orientation=\"Vertical\">\n                        <Label Content=\"{x:Static lang:Resources.Label_UseSharedOutputFolder}\" />\n                        <ToggleSwitch\n                            VerticalAlignment=\"Center\"\n                            VerticalContentAlignment=\"Center\"\n                            IsChecked=\"{Binding IsOutputSharingEnabled}\" />\n                    </StackPanel>\n\n                    <StackPanel Margin=\"0,4,24,0\" Orientation=\"Vertical\">\n                        <Label Content=\"Python Version\" />\n                        <controls:BetterComboBox\n                            MinWidth=\"150\"\n                            MinHeight=\"38\"\n                            ItemsSource=\"{Binding AvailablePythonVersions}\"\n                            SelectedItem=\"{Binding SelectedPythonVersion}\">\n                            <controls:BetterComboBox.ItemTemplate>\n                                <DataTemplate DataType=\"{x:Type python:UvPythonInfo}\">\n                                    <Grid ColumnDefinitions=\"*, Auto\" RowDefinitions=\"Auto, Auto\">\n                                        <TextBlock\n                                            Grid.Row=\"0\"\n                                            Grid.Column=\"0\"\n                                            Margin=\"8,4,0,4\"\n                                            FontSize=\"18\"\n                                            FontWeight=\"Light\"\n                                            Text=\"{Binding Version}\" />\n                                        <avalonia1:Icon\n                                            Grid.Row=\"0\"\n                                            Grid.Column=\"1\"\n                                            Foreground=\"{DynamicResource ThemeGreenColor}\"\n                                            IsVisible=\"{Binding IsInstalled}\"\n                                            Value=\"fa-solid fa-check\" />\n                                        <TextBlock\n                                            Grid.Row=\"1\"\n                                            Grid.Column=\"0\"\n                                            Grid.ColumnSpan=\"2\"\n                                            Margin=\"8,4,0,4\"\n                                            FontSize=\"12\"\n                                            Foreground=\"{DynamicResource TextControlForegroundDisabled}\"\n                                            Text=\"{Binding Key}\" />\n                                    </Grid>\n\n                                </DataTemplate>\n                            </controls:BetterComboBox.ItemTemplate>\n                            <controls:BetterComboBox.SelectionBoxItemTemplate>\n                                <DataTemplate DataType=\"{x:Type python:UvPythonInfo}\">\n                                    <TextBlock Margin=\"8,4,0,4\" Text=\"{Binding Version}\" />\n                                </DataTemplate>\n                            </controls:BetterComboBox.SelectionBoxItemTemplate>\n                        </controls:BetterComboBox>\n                    </StackPanel>\n                </WrapPanel>\n            </Expander>\n\n            <Expander\n                Margin=\"8\"\n                HorizontalAlignment=\"Stretch\"\n                FontSize=\"16\"\n                Header=\"Pip Overrides\">\n                <dialogs:PythonPackageSpecifiersDialog DataContext=\"{Binding PythonPackageSpecifiersViewModel, Mode=OneTime}\" />\n            </Expander>\n\n            <Button\n                Margin=\"8,8,8,8\"\n                HorizontalAlignment=\"Stretch\"\n                VerticalAlignment=\"Stretch\"\n                Classes=\"success\"\n                Command=\"{Binding InstallCommand}\"\n                Content=\"{x:Static lang:Resources.Action_Install}\"\n                FontSize=\"16\"\n                IsEnabled=\"{Binding CanInstall}\" />\n\n        </StackPanel>\n    </Grid>\n\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/PackageManager/PackageInstallDetailView.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.PackageManager;\n\n[RegisterTransient<PackageInstallDetailView>]\npublic partial class PackageInstallDetailView : UserControlBase\n{\n    public PackageInstallDetailView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n                          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n                          xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n                          xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n                          xmlns:viewModels=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n                          xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n                          xmlns:designData=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n                          xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia.BreadcrumbBar\"\n                          xmlns:controls1=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n                          xmlns:avalonia=\"clr-namespace:StabilityMatrix.Avalonia\"\n                          xmlns:base=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Base\"\n                          mc:Ignorable=\"d\" d:DesignWidth=\"800\" d:DesignHeight=\"450\"\n                          x:DataType=\"viewModels:PackageManagerViewModel\"\n                          x:CompileBindings=\"True\"\n                          d:DataContext=\"{x:Static designData:DesignData.PackageManagerViewModel}\"\n                          x:Class=\"StabilityMatrix.Avalonia.Views.PackageManagerPage\">\n\n    <controls:UserControlBase.Resources>\n        <!-- Override styles for BreadcrumbBar -->\n        <!-- ReSharper disable Xaml.RedundantResource -->\n        <x:Double x:Key=\"BreadcrumbBarItemThemeFontSize\">24</x:Double>\n        <x:Double x:Key=\"BreadcrumbBarChevronFontSize\">17</x:Double>\n        <Thickness x:Key=\"BreadcrumbBarChevronPadding\">6,3</Thickness>\n        <FontWeight x:Key=\"BreadcrumbBarItemFontWeight\">Medium</FontWeight>\n        <!-- ReSharper restore Xaml.RedundantResource -->\n    </controls:UserControlBase.Resources>\n    \n    <Grid RowDefinitions=\"Auto,*\">\n        <ui:BreadcrumbBar\n            Grid.Row=\"0\"\n            Margin=\"16,8\"\n            x:Name=\"BreadcrumbBar\"\n            ItemsSource=\"{Binding CurrentPagePath}\">\n            <ui:BreadcrumbBar.ItemTemplate>\n                <DataTemplate x:DataType=\"base:PageViewModelBase\">\n                    <ui:BreadcrumbBarItem Content=\"{Binding Title}\" />\n                </DataTemplate>\n            </ui:BreadcrumbBar.ItemTemplate>\n        </ui:BreadcrumbBar>\n        \n        <controls1:Frame\n            Grid.Row=\"1\"\n            Name=\"FrameView\">\n            <controls1:Frame.NavigationPageFactory>\n                <avalonia:ViewLocator/>\n            </controls1:Frame.NavigationPageFactory>\n        </controls1:Frame>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Linq;\nusing Avalonia.Interactivity;\nusing Avalonia.Threading;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Media.Animation;\nusing FluentAvalonia.UI.Navigation;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.DependencyInjection;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.PackageManager;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<PackageManagerPage>]\npublic partial class PackageManagerPage : UserControlBase, IHandleNavigation\n{\n    private readonly INavigationService<PackageManagerViewModel> packageNavigationService;\n\n    private bool hasLoaded;\n\n    private PackageManagerViewModel ViewModel => (PackageManagerViewModel)DataContext!;\n\n    [DesignOnly(true)]\n    [Obsolete(\"For XAML use only\", true)]\n    public PackageManagerPage()\n        : this(App.Services.GetRequiredService<INavigationService<PackageManagerViewModel>>()) { }\n\n    public PackageManagerPage(INavigationService<PackageManagerViewModel> packageNavigationService)\n    {\n        this.packageNavigationService = packageNavigationService;\n\n        InitializeComponent();\n\n        AddHandler(Frame.NavigatedToEvent, OnNavigatedTo, RoutingStrategies.Direct);\n\n        packageNavigationService.SetFrame(FrameView);\n        packageNavigationService.TypedNavigation += NavigationService_OnTypedNavigation;\n        FrameView.Navigated += FrameView_Navigated;\n        BreadcrumbBar.ItemClicked += BreadcrumbBar_ItemClicked;\n    }\n\n    /// <inheritdoc />\n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        base.OnLoaded(e);\n\n        if (!hasLoaded)\n        {\n            // Initial load, navigate to first page\n            Dispatcher.UIThread.Post(\n                () =>\n                    packageNavigationService.NavigateTo(\n                        ViewModel.SubPages[0],\n                        new SuppressNavigationTransitionInfo()\n                    )\n            );\n\n            hasLoaded = true;\n        }\n    }\n\n    /// <summary>\n    /// Handle navigation events to this page\n    /// </summary>\n    private void OnNavigatedTo(object? sender, NavigationEventArgs args)\n    {\n        if (args.Parameter is PackageManagerNavigationOptions { OpenInstallerDialog: true } options)\n        {\n            var vm = (PackageManagerViewModel)DataContext!;\n\n            Dispatcher.UIThread.Post(\n                () =>\n                {\n                    // Navigate to the installer page\n                    packageNavigationService.NavigateTo<PackageInstallBrowserViewModel>();\n\n                    // Select the package\n                    vm.SubPages.OfType<PackageInstallBrowserViewModel>()\n                        .First()\n                        .OnPackageSelected(options.InstallerSelectedPackage);\n                },\n                DispatcherPriority.Send\n            );\n        }\n    }\n\n    private void NavigationService_OnTypedNavigation(object? sender, TypedNavigationEventArgs e)\n    {\n        ViewModel.CurrentPage =\n            ViewModel.SubPages.FirstOrDefault(x => x.GetType() == e.ViewModelType)\n            ?? e.ViewModel as PageViewModelBase;\n    }\n\n    private void FrameView_Navigated(object? sender, NavigationEventArgs args)\n    {\n        if (args.Content is not PageViewModelBase vm)\n        {\n            return;\n        }\n\n        ViewModel.CurrentPage = vm;\n    }\n\n    private void BreadcrumbBar_ItemClicked(BreadcrumbBar sender, BreadcrumbBarItemClickedEventArgs args)\n    {\n        // Skip if already on same page\n        if (args.Item is not PageViewModelBase viewModel || viewModel == ViewModel.CurrentPage)\n        {\n            return;\n        }\n\n        packageNavigationService.NavigateTo(viewModel, BetterSlideNavigationTransition.PageSlideFromLeft);\n    }\n\n    public bool GoBack()\n    {\n        return packageNavigationService.GoBack();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.ProgressManagerPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:progress=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Progress\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vmBase=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Base\"\n    MaxHeight=\"550\"\n    d:DataContext=\"{x:Static mocks:DesignData.ProgressManagerViewModel}\"\n    d:DesignHeight=\"550\"\n    d:DesignWidth=\"450\"\n    x:DataType=\"progress:ProgressManagerViewModel\"\n    mc:Ignorable=\"d\">\n    <Grid RowDefinitions=\"Auto, *\">\n        <TextBlock\n            Grid.Row=\"0\"\n            VerticalAlignment=\"Center\"\n            FontSize=\"16\"\n            Text=\"Downloads\"\n            TextAlignment=\"Left\"\n            TextDecorations=\"Underline\" />\n        <Button\n            Grid.Row=\"0\"\n            Margin=\"0,0,8,0\"\n            Padding=\"4\"\n            HorizontalAlignment=\"Right\"\n            Classes=\"transparent\"\n            Command=\"{Binding ClearDownloads}\"\n            Content=\"Clear Finished\"\n            IsVisible=\"{Binding !!ProgressItems.Count}\" />\n\n        <ScrollViewer Grid.Row=\"1\" Margin=\"0,4,0,0\">\n            <ItemsControl ItemsSource=\"{Binding ProgressItems, Mode=OneWay}\">\n                <ItemsControl.DataTemplates>\n\n                    <DataTemplate DataType=\"{x:Type vmBase:PausableProgressItemViewModelBase}\">\n                        <Border\n                            Margin=\"4\"\n                            Padding=\"8\"\n                            Background=\"#22000000\"\n                            BorderBrush=\"#33000000\"\n                            BorderThickness=\"2\"\n                            CornerRadius=\"8\">\n                            <Grid ColumnDefinitions=\"Auto,*\" RowDefinitions=\"Auto,Auto\">\n                                <Grid\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"0\"\n                                    Margin=\"0,0,8,0\"\n                                    RowDefinitions=\"Auto, Auto\">\n                                    <TextBlock\n                                        MaxWidth=\"300\"\n                                        HorizontalAlignment=\"Left\"\n                                        Text=\"{Binding Name, Mode=OneWay}\"\n                                        TextTrimming=\"CharacterEllipsis\"\n                                        TextWrapping=\"NoWrap\" />\n\n                                    <!--  non-indeterminate progress  -->\n                                    <StackPanel\n                                        Grid.Row=\"1\"\n                                        IsVisible=\"{Binding !Progress.IsIndeterminate}\"\n                                        Orientation=\"Horizontal\">\n                                        <TextBlock\n                                            MaxWidth=\"200\"\n                                            Margin=\"0,0\"\n                                            Text=\"{Binding Progress.Text, Mode=OneWay}\"\n                                            TextTrimming=\"CharacterEllipsis\"\n                                            TextWrapping=\"NoWrap\" />\n                                        <TextBlock Margin=\"4,0,0,0\" IsVisible=\"{Binding !IsCompleted}\">\n                                            <Run Text=\"{Binding Progress.Value, Mode=OneWay}\" /><Run Text=\"%\" />\n                                            <Run Text=\" - \" />\n                                            <Run Foreground=\"Lime\" Text=\"{Binding Progress.FormattedDownloadSpeed}\" />\n                                        </TextBlock>\n\n                                    </StackPanel>\n\n                                    <!--  indeterminate progress  -->\n                                    <TextBlock\n                                        Grid.Row=\"1\"\n                                        MaxWidth=\"200\"\n                                        Margin=\"0,4\"\n                                        IsVisible=\"{Binding Progress.IsIndeterminate}\"\n                                        Text=\"{Binding Progress.Text, Mode=OneWay}\"\n                                        TextTrimming=\"CharacterEllipsis\"\n                                        TextWrapping=\"NoWrap\" />\n                                </Grid>\n\n\n                                <!--  Buttons  -->\n                                <StackPanel\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"1\"\n                                    HorizontalAlignment=\"Right\"\n                                    Orientation=\"Horizontal\">\n                                    <ToggleButton\n                                        Name=\"PauseResumeToggleButton\"\n                                        Classes=\"transparent-full\"\n                                        Command=\"{Binding TogglePauseResumeCommand}\"\n                                        IsChecked=\"{Binding IsPaused}\"\n                                        IsVisible=\"{Binding CanPauseResume}\">\n                                        <Panel>\n                                            <ui:SymbolIcon IsVisible=\"{Binding ElementName=PauseResumeToggleButton, Path=!IsChecked}\" Symbol=\"Pause\" />\n                                            <ui:SymbolIcon IsVisible=\"{Binding ElementName=PauseResumeToggleButton, Path=IsChecked}\" Symbol=\"Play\" />\n                                        </Panel>\n                                    </ToggleButton>\n\n                                    <Button\n                                        Classes=\"transparent-full\"\n                                        Command=\"{Binding CancelCommand}\"\n                                        IsVisible=\"{Binding CanCancel}\">\n                                        <ui:SymbolIcon Symbol=\"Cancel\" />\n                                    </Button>\n                                </StackPanel>\n\n                                <ProgressBar\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"2\"\n                                    Margin=\"0,8,0,4\"\n                                    IsIndeterminate=\"{Binding Progress.IsIndeterminate}\"\n                                    Value=\"{Binding Progress.Value, Mode=OneWay}\">\n                                    <ProgressBar.Transitions>\n                                        <Transitions>\n                                            <DoubleTransition Property=\"Value\" Duration=\"00:00:00.150\">\n                                                <DoubleTransition.Easing>\n                                                    <SineEaseInOut />\n                                                </DoubleTransition.Easing>\n                                            </DoubleTransition>\n                                        </Transitions>\n                                    </ProgressBar.Transitions>\n                                </ProgressBar>\n                            </Grid>\n                        </Border>\n                    </DataTemplate>\n\n                    <DataTemplate DataType=\"{x:Type progress:ProgressItemViewModel}\">\n                        <Border\n                            Margin=\"4\"\n                            Padding=\"8\"\n                            Background=\"#22000000\"\n                            BorderBrush=\"#33000000\"\n                            BorderThickness=\"2\"\n                            CornerRadius=\"8\">\n                            <Grid ColumnDefinitions=\"*,Auto\" RowDefinitions=\"*,Auto\">\n                                <StackPanel Grid.Row=\"0\" Grid.Column=\"0\">\n                                    <TextBlock Margin=\"0,0\" Text=\"{Binding Name, Mode=OneWay}\" />\n\n                                    <!--  non-indeterminate progress  -->\n                                    <TextBlock\n                                        MaxWidth=\"250\"\n                                        Margin=\"0,0\"\n                                        HorizontalAlignment=\"Left\"\n                                        IsVisible=\"{Binding !Progress.IsIndeterminate}\">\n                                        <Run Text=\"{Binding Progress.Text, Mode=OneWay}\" />\n                                        <Run Text=\"{Binding Progress.Value, Mode=OneWay}\" /><Run Text=\"%\" />\n                                    </TextBlock>\n\n                                    <!--  indeterminate progress  -->\n                                    <TextBlock\n                                        MaxWidth=\"250\"\n                                        Margin=\"0,4\"\n                                        HorizontalAlignment=\"Left\"\n                                        IsVisible=\"{Binding Progress.IsIndeterminate}\"\n                                        Text=\"{Binding Progress.Text, Mode=OneWay}\" />\n                                </StackPanel>\n\n                                <ProgressBar\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"2\"\n                                    Margin=\"0,8,0,4\"\n                                    IsIndeterminate=\"{Binding Progress.IsIndeterminate}\"\n                                    Value=\"{Binding Progress.Value, Mode=OneWay}\">\n                                    <ProgressBar.Transitions>\n                                        <Transitions>\n                                            <DoubleTransition Property=\"Value\" Duration=\"00:00:00.150\">\n                                                <DoubleTransition.Easing>\n                                                    <SineEaseInOut />\n                                                </DoubleTransition.Easing>\n                                            </DoubleTransition>\n                                        </Transitions>\n                                    </ProgressBar.Transitions>\n                                </ProgressBar>\n                            </Grid>\n                        </Border>\n                    </DataTemplate>\n\n                    <DataTemplate DataType=\"{x:Type progress:PackageInstallProgressItemViewModel}\">\n                        <Border\n                            Margin=\"4\"\n                            Padding=\"8\"\n                            Background=\"#22000000\"\n                            BorderBrush=\"#33000000\"\n                            BorderThickness=\"2\"\n                            CornerRadius=\"8\">\n                            <Grid ColumnDefinitions=\"*,Auto\" RowDefinitions=\"*,Auto\">\n\n                                <StackPanel Grid.Row=\"0\" Grid.Column=\"0\">\n                                    <TextBlock\n                                        MaxWidth=\"300\"\n                                        Margin=\"0,0\"\n                                        HorizontalAlignment=\"Left\"\n                                        Text=\"{Binding Progress.Text, Mode=OneWay}\"\n                                        TextTrimming=\"CharacterEllipsis\"\n                                        TextWrapping=\"NoWrap\" />\n\n                                    <!--  non-indeterminate progress  -->\n                                    <TextBlock\n                                        MaxWidth=\"300\"\n                                        Margin=\"0,4\"\n                                        HorizontalAlignment=\"Left\"\n                                        IsVisible=\"{Binding !Progress.IsIndeterminate}\">\n                                        <Run Text=\"{Binding Progress.Description, Mode=OneWay}\" />\n                                        <Run Text=\"{Binding Progress.Value, Mode=OneWay}\" /><Run Text=\"%\" />\n                                    </TextBlock>\n\n                                    <!--  indeterminate progress  -->\n                                    <TextBlock\n                                        MinWidth=\"300\"\n                                        MaxWidth=\"300\"\n                                        Margin=\"0,4\"\n                                        HorizontalAlignment=\"Left\"\n                                        IsVisible=\"{Binding Progress.IsIndeterminate}\"\n                                        Text=\"{Binding Progress.Description, Mode=OneWay}\" />\n                                </StackPanel>\n\n\n                                <!--  Buttons  -->\n                                <Button\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"1\"\n                                    Margin=\"8,0\"\n                                    Classes=\"transparent-full\"\n                                    Command=\"{Binding ShowProgressDialog}\"\n                                    ToolTip.Tip=\"Show Progress Dialog\">\n                                    <ui:SymbolIcon Symbol=\"Open\" />\n                                </Button>\n\n                                <ProgressBar\n                                    Grid.Row=\"1\"\n                                    Grid.Column=\"0\"\n                                    Grid.ColumnSpan=\"2\"\n                                    Margin=\"0,8,0,4\"\n                                    IsIndeterminate=\"{Binding Progress.IsIndeterminate}\"\n                                    Value=\"{Binding Progress.Value, Mode=OneWay}\">\n                                    <ProgressBar.Transitions>\n                                        <Transitions>\n                                            <DoubleTransition Property=\"Value\" Duration=\"00:00:00.150\">\n                                                <DoubleTransition.Easing>\n                                                    <SineEaseInOut />\n                                                </DoubleTransition.Easing>\n                                            </DoubleTransition>\n                                        </Transitions>\n                                    </ProgressBar.Transitions>\n                                </ProgressBar>\n                            </Grid>\n                        </Border>\n                    </DataTemplate>\n                </ItemsControl.DataTemplates>\n            </ItemsControl>\n        </ScrollViewer>\n    </Grid>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<ProgressManagerPage>]\npublic partial class ProgressManagerPage : UserControlBase\n{\n    public ProgressManagerPage()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Settings.AccountSettingsPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:helpers=\"clr-namespace:StabilityMatrix.Avalonia.Helpers\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:sm=\"clr-namespace:StabilityMatrix.Avalonia\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    xmlns:vmSettings=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings\"\n    d:DataContext=\"{x:Static mocks:DesignData.AccountSettingsViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"vmSettings:AccountSettingsViewModel\"\n    mc:Ignorable=\"d\">\n\n    <controls:UserControlBase.Resources>\n        <SolidColorBrush x:Key=\"Brush0\" Color=\"#FF3C3C46\" />\n        <SolidColorBrush x:Key=\"Brush1\" Color=\"#FF3E4B77\" />\n        <SolidColorBrush x:Key=\"Brush4\" Color=\"#FF1375D5\" />\n        <SolidColorBrush x:Key=\"Brush7\" Color=\"#FF1B96E3\" />\n        <SolidColorBrush x:Key=\"Brush13\" Color=\"#FF5486BC\" />\n        <DrawingImage x:Key=\"BrandsLykos\">\n            <DrawingGroup>\n                <GeometryDrawing Brush=\"Transparent\" Geometry=\"F1M0,0L587,0L587,618L0,618z\" />\n                <DrawingGroup>\n                    <DrawingGroup.Transform>\n                        <MatrixTransform Matrix=\"0.99998295,0,0,0.99998295,0,0.24023438\" />\n                    </DrawingGroup.Transform>\n                    <GeometryDrawing Brush=\"{DynamicResource Brush0}\" Geometry=\"F0 M359.12 181.81L290.92 263.57L202.31 422.8L296.41 349.51L341.33 282.47L359.12 181.81Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush1}\" Geometry=\"F0 M354.51 195.18L296.41 349.51L287.33 463.38L402.14 326.47L405.15 227.55L342.3 165.1L354.51 195.18Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush1}\" Geometry=\"F0 M354.51 195.18L373.25 246.84L402.14 326.47L475.55 241.31L506.38 172.37L432.6 170.07L354.51 195.18Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush0}\" Geometry=\"F0 M506.38 172.37L402.14 326.47L431.69 421.7L493.66 289.28L506.38 172.37Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush4}\" Geometry=\"F0 M354.51 195.18L431.14 183.67L506.38 172.37L581.91 114.38L577.7 83.96L459.71 128.22L354.51 195.18Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush1}\" Geometry=\"F0 M354.51 195.18L577.7 83.96L587.01 36.06L555.98 24.72L444.52 90.52L354.51 195.18Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush4}\" Geometry=\"F0 M354.51 195.18L451.44 116.55L551.26 35.59L570.72 16.37L543.04 5.86L469.78 26.31L346.91 82.41L330.57 157.07L354.51 195.18Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush7}\" Geometry=\"F0 M258.82 209.64L283.43 257.76L354.51 195.18L354.73 143.18L354.97 88.91L300.3 145.74L258.82 209.64Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush4}\" Geometry=\"F0 M284.89 201.03L283.82 132.98L245.45 133.94L209.32 211.39L209.53 265.05L202.31 422.8L277.33 274.62L284.89 201.03Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush1}\" Geometry=\"F0 M354.51 195.18L318.27 198.22L284.89 201.03L202.31 422.8L268.99 323.08L354.51 195.18Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush1}\" Geometry=\"F0 M209.53 265.05L225.86 205.45L245.45 133.94L157.16 269.41L209.53 265.05Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush4}\" Geometry=\"F0 M245.45 133.94L301.48 136.45L354.97 88.91L275.66 59.42L139 44.21L245.45 133.94Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush7}\" Geometry=\"F0 M139 44.21L287.77 75.01L354.97 88.91L348.14 53.88L306.32 49.68L139 44.21Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush13}\" Geometry=\"F0 M393.94 11.53L306.32 49.68L354.97 88.91L412.38 57.61L441.91 26.85L393.94 11.53Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush7}\" Geometry=\"F0 M441.91 26.85L354.97 88.91L543.04 5.86L571.25 0L441.91 26.85Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush13}\" Geometry=\"F0 M571.25 0L543.04 5.86L551.26 35.59L587.01 36.06L571.25 0Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush13}\" Geometry=\"F0 M151.49 224.62L33.55 321.2L103.23 316.08L151.49 224.62Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush4}\" Geometry=\"F0 M145.63 400.32L200.67 282.86L0 447.89L145.63 400.32Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush13}\" Geometry=\"F0 M262.66 413.33L132.89 597.28L236.55 530.32L262.66 413.33Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush1}\" Geometry=\"F0 M374.93 617.53L350.75 441.52L396.59 364.19L412.64 459.88L374.93 617.53Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush1}\" Geometry=\"F0 M277.5 507.65L312.09 557.16L324.24 472.27L277.5 507.65Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush1}\" Geometry=\"F0 M185.82 455.66L119.65 488.21L183.32 389.39L185.82 455.66Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush4}\" Geometry=\"F0 M441.13 526.17L473.16 366.86L489.5 454.43L441.13 526.17Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush1}\" Geometry=\"F0 M354.97 88.91L354.97 88.91L245.45 133.94L284.89 201.03L316.79 149.99L354.97 88.91Z\" />\n                    <GeometryDrawing Brush=\"{DynamicResource Brush4}\" Geometry=\"F0 M353.83 211.72L346.35 393L402.14 326.47L373.25 246.84L356.02 199.33L354.51 195.18L353.83 211.72Z\" />\n                </DrawingGroup>\n            </DrawingGroup>\n        </DrawingImage>\n\n        <SolidColorBrush x:Key=\"BrushB0\" Color=\"#FFFFFFFF\" />\n        <DrawingImage x:Key=\"BrandsPatreonSymbolWhite\">\n            <DrawingGroup>\n                <GeometryDrawing Brush=\"{DynamicResource BrushB0}\" Geometry=\"F1 M1033.05 324.45C1032.86 186.55 925.46 73.53 799.45 32.75C642.97 -17.89 436.59 -10.55 287.17 59.95C106.07 145.41 49.18 332.61 47.06 519.31C45.32 672.81 60.64 1077.1 288.68 1079.98C458.12 1082.13 483.35 863.8 561.75 758.65C617.53 683.84 689.35 662.71 777.76 640.83C929.71 603.22 1033.27 483.3 1033.05 324.45Z\" />\n            </DrawingGroup>\n        </DrawingImage>\n    </controls:UserControlBase.Resources>\n\n    <ScrollViewer VerticalScrollBarVisibility=\"Auto\">\n        <StackPanel Margin=\"16,16\" Spacing=\"2\">\n\n            <sg:SpacedGrid Margin=\"0,4,0,16\" ColumnDefinitions=\"*,*\">\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"16\">\n                    <fluentIcons:SymbolIcon\n                        Width=\"64\"\n                        Height=\"64\"\n                        FontSize=\"64\"\n                        IconVariant=\"Filled\"\n                        IsVisible=\"{Binding LykosProfileImageUrl, Converter={x:Static StringConverters.IsNullOrEmpty}}\"\n                        Symbol=\"Person\" />\n                    <Button\n                        Padding=\"2\"\n                        HorizontalAlignment=\"Left\"\n                        Classes=\"transparent-full\"\n                        CornerRadius=\"8\"\n                        IsVisible=\"{Binding LykosProfileImageUrl, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\">\n                        <Button.Flyout>\n                            <ui:FAMenuFlyout>\n                                <ui:MenuFlyoutItem\n                                    Command=\"{x:Static helpers:IOCommands.OpenUrlCommand}\"\n                                    CommandParameter=\"{Binding LykosAccountManageUrl}\"\n                                    HotKey=\"{x:Null}\"\n                                    IconSource=\"Link\"\n                                    Text=\"Manage your Lykos Account\" />\n                                <ui:MenuFlyoutItem\n                                    Command=\"{x:Static helpers:IOCommands.OpenUrlCommand}\"\n                                    CommandParameter=\"http://gravatar.com/emails/\"\n                                    HotKey=\"{x:Null}\"\n                                    IconSource=\"Link\"\n                                    Text=\"Edit Profile Image on Gravatar\" />\n                                <ui:MenuFlyoutSeparator />\n                                <ui:MenuFlyoutItem\n                                    Command=\"{x:Static helpers:ClipboardCommands.CopyTextCommand}\"\n                                    CommandParameter=\"{Binding LykosStatus.Id}\"\n                                    IconSource=\"Copy\"\n                                    Text=\"Copy User ID\" />\n                            </ui:FAMenuFlyout>\n                        </Button.Flyout>\n                        <controls:BetterAdvancedImage\n                            Width=\"64\"\n                            Height=\"64\"\n                            CornerRadius=\"8\"\n                            Source=\"{Binding LykosProfileImageUrl}\" />\n                    </Button>\n\n\n                    <StackPanel>\n                        <TextBlock\n                            Margin=\"-1,0,0,0\"\n                            Text=\"{Binding LykosStatus.DisplayName, FallbackValue=''}\"\n                            Theme=\"{DynamicResource SubtitleTextBlockStyle}\" />\n                        <TextBlock Text=\"{Binding LykosStatus.Email, FallbackValue=''}\" />\n                    </StackPanel>\n                </StackPanel>\n            </sg:SpacedGrid>\n\n            <controls:SettingsAccountLinkExpander\n                ConnectCommand=\"{Binding ConnectLykosCommand}\"\n                DisconnectCommand=\"{Binding DisconnectLykosCommand}\"\n                Header=\"Lykos\"\n                HeaderTargetUri=\"{x:Static sm:Assets.LykosUrl}\"\n                IsConnected=\"{Binding LykosStatus.IsConnected}\"\n                OffDescription=\"Manage connected features in Stability Matrix\">\n                <controls:SettingsAccountLinkExpander.IconSource>\n                    <ui:ImageIconSource Source=\"{StaticResource BrandsLykos}\" />\n                </controls:SettingsAccountLinkExpander.IconSource>\n                <controls:SettingsAccountLinkExpander.IsLoading>\n                    <MultiBinding Converter=\"{x:Static BoolConverters.Or}\">\n                        <Binding Path=\"ConnectLykosCommand.IsRunning\" />\n                        <Binding Path=\"!IsInitialUpdateFinished\" />\n                    </MultiBinding>\n                </controls:SettingsAccountLinkExpander.IsLoading>\n            </controls:SettingsAccountLinkExpander>\n\n            <controls:SettingsAccountLinkExpander\n                ConnectCommand=\"{Binding ConnectPatreonCommand}\"\n                DisconnectCommand=\"{Binding DisconnectPatreonCommand}\"\n                Header=\"Patreon\"\n                HeaderTargetUri=\"{x:Static sm:Assets.PatreonUrl}\"\n                IsConnected=\"{Binding IsPatreonConnected}\"\n                IsEnabled=\"{Binding LykosStatus.IsConnected}\"\n                OffDescription=\"Access Preview and Dev release channels for auto-updates\">\n                <controls:SettingsAccountLinkExpander.IconSource>\n                    <ui:ImageIconSource Source=\"{StaticResource BrandsPatreonSymbolWhite}\" />\n                </controls:SettingsAccountLinkExpander.IconSource>\n                <controls:SettingsAccountLinkExpander.IsLoading>\n                    <MultiBinding Converter=\"{x:Static BoolConverters.Or}\">\n                        <Binding Path=\"ConnectPatreonCommand.IsRunning\" />\n                        <Binding Path=\"!IsInitialUpdateFinished\" />\n                    </MultiBinding>\n                </controls:SettingsAccountLinkExpander.IsLoading>\n            </controls:SettingsAccountLinkExpander>\n\n            <sg:SpacedGrid\n                Margin=\"0,6,0,0\"\n                RowDefinitions=\"Auto,*,*,*\"\n                RowSpacing=\"4\">\n                <TextBlock\n                    Margin=\"0,0,0,4\"\n                    FontWeight=\"Medium\"\n                    Text=\"{x:Static lang:Resources.Label_Integrations}\" />\n\n                <controls:SettingsAccountLinkExpander\n                    Grid.Row=\"2\"\n                    ConnectCommand=\"{Binding ConnectCivitCommand}\"\n                    DisconnectCommand=\"{Binding DisconnectCivitCommand}\"\n                    Header=\"CivitAI\"\n                    HeaderTargetUri=\"{x:Static sm:Assets.CivitAIUrl}\"\n                    IsConnected=\"{Binding CivitStatus.IsConnected}\"\n                    OffDescription=\"Connect to Download Models that require login\"\n                    OnDescriptionExtra=\"{Binding CivitStatus.UsernameWithParentheses}\">\n                    <controls:SettingsAccountLinkExpander.IconSource>\n                        <ui:BitmapIconSource UriSource=\"avares://StabilityMatrix.Avalonia/Assets/brands-civitai.png\" />\n                    </controls:SettingsAccountLinkExpander.IconSource>\n                    <controls:SettingsAccountLinkExpander.IsLoading>\n                        <MultiBinding Converter=\"{x:Static BoolConverters.Or}\">\n                            <Binding Path=\"ConnectCivitCommand.IsRunning\" />\n                            <Binding Path=\"!IsInitialUpdateFinished\" />\n                        </MultiBinding>\n                    </controls:SettingsAccountLinkExpander.IsLoading>\n                </controls:SettingsAccountLinkExpander>\n\n                <controls:SettingsAccountLinkExpander\n                    Grid.Row=\"3\"\n                    ConnectCommand=\"{Binding ConnectHuggingFaceCommand}\"\n                    DisconnectCommand=\"{Binding DisconnectHuggingFaceCommand}\"\n                    Header=\"Hugging Face\"\n                    HeaderTargetUri=\"https://huggingface.co/settings/tokens\"\n                    IsConnected=\"{Binding IsHuggingFaceConnected}\"\n                    OffDescription=\"Connect to download models that require login from Hugging Face\"\n                    OnDescriptionExtra=\"{Binding HuggingFaceUsernameWithParentheses}\">\n                    <controls:SettingsAccountLinkExpander.IconSource>\n                        <ui:BitmapIconSource UriSource=\"avares://StabilityMatrix.Avalonia/Assets/brands-hf-logo.png\" />\n                    </controls:SettingsAccountLinkExpander.IconSource>\n                    <controls:SettingsAccountLinkExpander.IsLoading>\n                        <MultiBinding Converter=\"{x:Static BoolConverters.Or}\">\n                            <Binding Path=\"ConnectHuggingFaceCommand.IsRunning\" />\n                            <Binding Path=\"!IsInitialUpdateFinished\" />\n                        </MultiBinding>\n                    </controls:SettingsAccountLinkExpander.IsLoading>\n                </controls:SettingsAccountLinkExpander>\n\n            </sg:SpacedGrid>\n\n        </StackPanel>\n    </ScrollViewer>\n\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Settings;\n\n[RegisterSingleton<AccountSettingsPage>]\npublic partial class AccountSettingsPage : UserControlBase\n{\n    public AccountSettingsPage()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Settings/AnalyticsSettingsPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Settings.AnalyticsSettingsPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avaloniaEdit=\"https://github.com/avaloniaui/avaloniaedit\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:fluent=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:settings=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    d:DesignHeight=\"750\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"settings:AnalyticsSettingsViewModel\"\n    mc:Ignorable=\"d\">\n    <ScrollViewer VerticalScrollBarVisibility=\"Auto\">\n        <sg:SpacedGrid\n            Margin=\"8,8\"\n            RowDefinitions=\"Auto,Auto,Auto\"\n            RowSpacing=\"4\">\n            <ui:SettingsExpander\n                Grid.Row=\"0\"\n                Margin=\"8,0,8,4\"\n                Description=\"{x:Static lang:Resources.Text_AnalyticsDescriptionShort}\"\n                Header=\"Usage data\">\n                <ui:SettingsExpander.IconSource>\n                    <fluent:SymbolIconSource IconVariant=\"Filled\" Symbol=\"Box\" />\n                </ui:SettingsExpander.IconSource>\n                <ui:SettingsExpander.Footer>\n                    <ToggleSwitch IsChecked=\"{Binding IsPackageInstallAnalyticsEnabled, Mode=TwoWay}\" />\n                </ui:SettingsExpander.Footer>\n                <ui:SettingsExpanderItem Padding=\"8\">\n                    <StackPanel>\n                        <TextBlock Margin=\"8\" Text=\"Example Telemetry\" />\n                        <avaloniaEdit:TextEditor\n                            Margin=\"16,0,16,16\"\n                            Padding=\"8\"\n                            HorizontalAlignment=\"Stretch\"\n                            Background=\"#33000000\"\n                            BorderThickness=\"0\"\n                            CornerRadius=\"8\"\n                            FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n                            FontSize=\"14\"\n                            IsReadOnly=\"True\"\n                            SyntaxHighlighting=\"JavaScript\"\n                            Text=\"{x:Static lang:Resources.AnalyticsExample_InstallData}\" />\n                    </StackPanel>\n                </ui:SettingsExpanderItem>\n            </ui:SettingsExpander>\n\n            <ui:InfoBar\n                Title=\"{x:Static lang:Resources.Text_AnalyticsDataPrivacyInfo}\"\n                Grid.Row=\"1\"\n                Margin=\"8,0,8,4\"\n                IsClosable=\"False\"\n                IsOpen=\"True\"\n                Severity=\"Success\">\n                <ui:InfoBar.IconSource>\n                    <fluent:SymbolIconSource IconVariant=\"Filled\" Symbol=\"Shield\" />\n                </ui:InfoBar.IconSource>\n            </ui:InfoBar>\n\n            <controls:HyperlinkIconButton\n                Grid.Row=\"2\"\n                Margin=\"0,16\"\n                Content=\"{x:Static lang:Resources.Label_PrivacyPolicy}\"\n                NavigateUri=\"https://lykos.ai/privacy\" />\n        </sg:SpacedGrid>\n    </ScrollViewer>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Settings/AnalyticsSettingsPage.axaml.cs",
    "content": "﻿using Avalonia.Markup.Xaml;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Settings;\n\n[RegisterTransient<AnalyticsSettingsPage>]\npublic partial class AnalyticsSettingsPage : UserControlBase\n{\n    public AnalyticsSettingsPage()\n    {\n        InitializeComponent();\n    }\n\n    private void InitializeComponent()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Settings.InferenceSettingsPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:inference=\"clr-namespace:StabilityMatrix.Avalonia.Models.Inference\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models1=\"clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core\"\n    xmlns:native=\"clr-namespace:StabilityMatrix.Native;assembly=StabilityMatrix.Native\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vmSettings=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings\"\n    d:DataContext=\"{x:Static mocks:DesignData.InferenceSettingsViewModel}\"\n    d:DesignHeight=\"650\"\n    d:DesignWidth=\"900\"\n    x:DataType=\"vmSettings:InferenceSettingsViewModel\"\n    mc:Ignorable=\"d\">\n\n    <controls:UserControlBase.Styles>\n        <Style Selector=\"sg|SpacedGrid &gt; ui|SettingsExpander\">\n            <Setter Property=\"Margin\" Value=\"8,0\" />\n        </Style>\n    </controls:UserControlBase.Styles>\n\n    <controls:UserControlBase.Resources>\n        <models1:DimensionStringComparer x:Key=\"DimensionStringComparer\" />\n    </controls:UserControlBase.Resources>\n\n    <ScrollViewer VerticalScrollBarVisibility=\"Auto\">\n        <StackPanel Margin=\"8,16\" Spacing=\"8\">\n\n            <!--  Prompt  -->\n            <sg:SpacedGrid RowDefinitions=\"Auto,*\" RowSpacing=\"4\">\n                <TextBlock\n                    Margin=\"0,0,0,4\"\n                    FontWeight=\"Medium\"\n                    Text=\"{x:Static lang:Resources.Label_Prompt}\" />\n                <!--  Auto Completion  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    Margin=\"8,0\"\n                    Header=\"{x:Static lang:Resources.Label_AutoCompletion}\">\n\n                    <ui:SettingsExpander.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-solid fa-wand-magic-sparkles\" />\n                    </ui:SettingsExpander.IconSource>\n\n                    <ui:SettingsExpander.Footer>\n                        <ToggleSwitch IsChecked=\"{Binding IsPromptCompletionEnabled}\" />\n                    </ui:SettingsExpander.Footer>\n\n                    <!--  Tag csv selection  -->\n                    <ui:SettingsExpanderItem\n                        Content=\"{x:Static lang:Resources.Label_PromptTags}\"\n                        Description=\"{x:Static lang:Resources.Label_PromptTagsDescription}\"\n                        IconSource=\"Tag\"\n                        IsEnabled=\"{Binding IsPromptCompletionEnabled}\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <ui:FAComboBox ItemsSource=\"{Binding AvailableTagCompletionCsvs}\" SelectedItem=\"{Binding SelectedTagCompletionCsv}\" />\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                    <!--  Tag csv import  -->\n                    <ui:SettingsExpanderItem\n                        Content=\"{x:Static lang:Resources.Label_PromptTagsImport}\"\n                        IconSource=\"Add\"\n                        IsEnabled=\"{Binding IsPromptCompletionEnabled}\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <Button Command=\"{Binding ImportTagCsvCommand}\" Content=\"Import\" />\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                    <!--  Remove underscores  -->\n                    <ui:SettingsExpanderItem\n                        Content=\"{x:Static lang:Resources.Label_CompletionReplaceUnderscoresWithSpaces}\"\n                        IconSource=\"Underline\"\n                        IsEnabled=\"{Binding IsPromptCompletionEnabled}\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <CheckBox Margin=\"8\" IsChecked=\"{Binding IsCompletionRemoveUnderscoresEnabled}\" />\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n                </ui:SettingsExpander>\n            </sg:SpacedGrid>\n\n            <!--  General  -->\n            <sg:SpacedGrid RowDefinitions=\"Auto,*,*,*\" RowSpacing=\"4\">\n                <TextBlock\n                    Margin=\"0,0,0,4\"\n                    FontWeight=\"Medium\"\n                    Text=\"{x:Static lang:Resources.Label_General}\" />\n\n                <!--  Filter Extra Networks  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    Header=\"Filter Extra Networks by Base Model\"\n                    IconSource=\"Filter\">\n                    <ui:SettingsExpander.Footer>\n                        <ToggleSwitch Margin=\"0,-8,0,-6\" IsChecked=\"{Binding FilterExtraNetworksByBaseModel}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n\n                <!--  Image Viewer  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"2\"\n                    Header=\"{x:Static lang:Resources.Label_ImageViewer}\"\n                    IconSource=\"Image\"\n                    IsExpanded=\"True\">\n\n                    <!--  Pixel grid  -->\n                    <ui:SettingsExpanderItem Content=\"Show pixel grid at high zoom levels\" IconSource=\"ViewAll\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <CheckBox Margin=\"8\" IsChecked=\"{Binding IsImageViewerPixelGridEnabled}\" />\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                    <!--  Recycle Option  -->\n                    <ui:SettingsExpanderItem\n                        Content=\"Move files to the Recycle Bin when deleting\"\n                        Description=\"If disabled, files will be permanently deleted\"\n                        IsVisible=\"{x:Static native:NativeFileOperations.IsRecycleBinAvailable}\">\n                        <ui:SettingsExpanderItem.IconSource>\n                            <fluentIcons:SymbolIconSource Symbol=\"Delete\" />\n                        </ui:SettingsExpanderItem.IconSource>\n                        <ui:SettingsExpanderItem.Footer>\n                            <CheckBox Margin=\"8\" IsChecked=\"{Binding IsInferenceImageBrowserUseRecycleBinForDelete}\" />\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                </ui:SettingsExpander>\n\n                <!--  Output Image Files  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"3\"\n                    Header=\"{x:Static lang:Resources.Label_OutputImageFiles}\"\n                    IsExpanded=\"True\">\n                    <ui:SettingsExpander.IconSource>\n                        <fluentIcons:SymbolIconSource Symbol=\"TabDesktopImage\" />\n                    </ui:SettingsExpander.IconSource>\n                    <!--  File name pattern  -->\n                    <ui:SettingsExpanderItem\n                        Content=\"File name pattern\"\n                        Description=\"{Binding OutputImageFileNameFormatSample}\"\n                        IconSource=\"Rename\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <TextBox\n                                Name=\"OutputImageFileNameFormatTextBox\"\n                                MinWidth=\"150\"\n                                FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n                                FontSize=\"13\"\n                                Text=\"{Binding OutputImageFileNameFormat}\"\n                                Watermark=\"{x:Static inference:FileNameFormat.DefaultTemplate}\" />\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n                </ui:SettingsExpander>\n\n                <ui:TeachingTip\n                    Title=\"Format Variables\"\n                    Grid.Row=\"3\"\n                    IsOpen=\"{Binding #OutputImageFileNameFormatTextBox.IsFocused}\"\n                    PreferredPlacement=\"Top\"\n                    Target=\"{Binding #OutputImageFileNameFormatTextBox, Mode=OneWay}\">\n                    <DataGrid AutoGenerateColumns=\"True\" ItemsSource=\"{Binding OutputImageFileNameFormatVars}\" />\n                </ui:TeachingTip>\n\n            </sg:SpacedGrid>\n\n            <!--  Dimensions  -->\n            <sg:SpacedGrid RowDefinitions=\"Auto,*,*,*\" RowSpacing=\"4\">\n                <TextBlock\n                    Margin=\"0,0,0,4\"\n                    FontWeight=\"Medium\"\n                    Text=\"Dimensions\" />\n\n                <!--  Step Change  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    Description=\"Controls how much the dimensions increase or decrease per step when adjusting with arrows or scroll.\"\n                    Header=\"Step Size\">\n                    <ui:SettingsExpander.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-solid fa-stairs\" />\n                    </ui:SettingsExpander.IconSource>\n                    <ui:SettingsExpander.Footer>\n                        <ui:NumberBox\n                            LargeChange=\"16\"\n                            Maximum=\"1024\"\n                            Minimum=\"8\"\n                            SmallChange=\"8\"\n                            SpinButtonPlacementMode=\"Inline\"\n                            ValidationMode=\"InvalidInputOverwritten\"\n                            Value=\"{Binding InferenceDimensionStepChange}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n\n                <!--  Favorite Dimensions  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"2\"\n                    Header=\"Favorite Dimensions\"\n                    IsExpanded=\"True\">\n                    <ui:SettingsExpander.IconSource>\n                        <fluentIcons:SymbolIconSource Symbol=\"Grid\" />\n                    </ui:SettingsExpander.IconSource>\n\n                    <ui:SettingsExpanderItem>\n                        <Grid RowDefinitions=\"Auto, *\">\n                            <ui:CommandBar>\n                                <ui:CommandBar.PrimaryCommands>\n                                    <ui:CommandBarButton\n                                        Width=\"45\"\n                                        Height=\"50\"\n                                        Command=\"{Binding AddRowCommand}\"\n                                        IconSource=\"Add\"\n                                        Label=\"{x:Static lang:Resources.Action_Save}\" />\n                                    <ui:CommandBarButton\n                                        Width=\"45\"\n                                        Height=\"50\"\n                                        Command=\"{Binding RemoveSelectedRowCommand}\"\n                                        CommandParameter=\"{Binding #DimensionsGrid.SelectedItem}\"\n                                        IconSource=\"Remove\"\n                                        Label=\"{x:Static lang:Resources.Action_Remove}\">\n                                        <ui:CommandBarButton.IsEnabled>\n                                            <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                                <Binding\n                                                    Converter=\"{x:Static ObjectConverters.IsNotNull}\"\n                                                    ElementName=\"DimensionsGrid\"\n                                                    Path=\"SelectedItem\" />\n                                            </MultiBinding>\n                                        </ui:CommandBarButton.IsEnabled>\n                                    </ui:CommandBarButton>\n                                </ui:CommandBar.PrimaryCommands>\n                            </ui:CommandBar>\n                            <DataGrid\n                                x:Name=\"DimensionsGrid\"\n                                Grid.Row=\"1\"\n                                MaxHeight=\"400\"\n                                ItemsSource=\"{Binding FavoriteDimensions}\">\n                                <DataGrid.Columns>\n                                    <DataGridTextColumn\n                                        Width=\"5*\"\n                                        x:DataType=\"system:String\"\n                                        Binding=\"{Binding .}\"\n                                        CanUserSort=\"True\"\n                                        CustomSortComparer=\"{StaticResource DimensionStringComparer}\"\n                                        FontFamily=\"Cascadia Code,Consolas,Menlo,Monospace,DejaVu Sans Mono,monospace\"\n                                        Header=\"Dimension\"\n                                        SortMemberPath=\".\" />\n                                </DataGrid.Columns>\n                            </DataGrid>\n                        </Grid>\n                    </ui:SettingsExpanderItem>\n                </ui:SettingsExpander>\n            </sg:SpacedGrid>\n\n        </StackPanel>\n    </ScrollViewer>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Settings;\n\n[RegisterSingleton<InferenceSettingsPage>]\npublic partial class InferenceSettingsPage : UserControlBase\n{\n    public InferenceSettingsPage()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Settings.MainSettingsPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:avalonia=\"https://github.com/projektanker/icons.avalonia\"\n    xmlns:checkpointManager=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointManager\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:hardwareInfo=\"clr-namespace:StabilityMatrix.Core.Helper.HardwareInfo;assembly=StabilityMatrix.Core\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:scroll=\"clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vmSettings=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings\"\n    d:DataContext=\"{x:Static mocks:DesignData.MainSettingsViewModel}\"\n    d:DesignHeight=\"700\"\n    d:DesignWidth=\"800\"\n    x:CompileBindings=\"True\"\n    x:DataType=\"vmSettings:MainSettingsViewModel\"\n    Focusable=\"True\"\n    mc:Ignorable=\"d\">\n\n    <controls:UserControlBase.Resources>\n        <converters:CultureInfoDisplayConverter x:Key=\"CultureInfoDisplayConverter\" />\n        <converters:IndexPlusOneConverter x:Key=\"IndexPlusOneConverter\" />\n        <converters:EnumStringConverter x:Key=\"EnumStringConverter\" />\n        <converters:EnumToBooleanConverter x:Key=\"EnumBoolConverter\" />\n        <converters:NumberFormatModeSampleConverter x:Key=\"NumberFormatModeSampleConverter\" />\n    </controls:UserControlBase.Resources>\n\n    <controls:UserControlBase.Styles>\n        <Style Selector=\"sg|SpacedGrid &gt; ui|SettingsExpander\">\n            <Setter Property=\"Margin\" Value=\"8,0\" />\n        </Style>\n    </controls:UserControlBase.Styles>\n\n    <ScrollViewer VerticalScrollBarVisibility=\"Auto\">\n        <StackPanel Margin=\"8,16\" Spacing=\"8\">\n            <!--  General  -->\n            <sg:SpacedGrid RowDefinitions=\"Auto,*,*,*\" RowSpacing=\"4\">\n                <TextBlock\n                    Margin=\"0,0,0,4\"\n                    FontWeight=\"Medium\"\n                    Text=\"{x:Static lang:Resources.Label_General}\" />\n                <!--  Link to Inference Sub-Settings  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    Margin=\"8,0\"\n                    ActionIconSource=\"ChevronRight\"\n                    Command=\"{Binding NavigateToSubPageCommand}\"\n                    CommandParameter=\"{x:Type vmSettings:InferenceSettingsViewModel}\"\n                    Header=\"{x:Static lang:Resources.Label_Inference}\"\n                    IsClickEnabled=\"True\">\n                    <ui:SettingsExpander.IconSource>\n                        <fluentIcons:SymbolIconSource\n                            FontSize=\"10\"\n                            IconVariant=\"Filled\"\n                            Symbol=\"AppGeneric\" />\n                    </ui:SettingsExpander.IconSource>\n                </ui:SettingsExpander>\n                <!--  Link to Notifications Sub-Settings  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"2\"\n                    Margin=\"8,0\"\n                    ActionIconSource=\"ChevronRight\"\n                    Command=\"{Binding NavigateToSubPageCommand}\"\n                    CommandParameter=\"{x:Type vmSettings:NotificationSettingsViewModel}\"\n                    Header=\"{x:Static lang:Resources.Label_Notifications}\"\n                    IsClickEnabled=\"True\">\n                    <ui:SettingsExpander.IconSource>\n                        <fluentIcons:SymbolIconSource FontSize=\"10\" Symbol=\"Alert\" />\n                    </ui:SettingsExpander.IconSource>\n                </ui:SettingsExpander>\n            </sg:SpacedGrid>\n\n            <!--  Integrations  -->\n            <sg:SpacedGrid RowDefinitions=\"Auto,*,*\" RowSpacing=\"4\">\n                <TextBlock\n                    Margin=\"0,0,0,4\"\n                    FontWeight=\"Medium\"\n                    Text=\"{x:Static lang:Resources.Label_Integrations}\" />\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    Margin=\"8,0\"\n                    ActionIconSource=\"ChevronRight\"\n                    Command=\"{Binding NavigateToSubPageCommand}\"\n                    CommandParameter=\"{x:Type vmSettings:AccountSettingsViewModel}\"\n                    Header=\"{x:Static lang:Resources.Label_Accounts}\"\n                    IsClickEnabled=\"True\">\n                    <ui:SettingsExpander.IconSource>\n                        <fluentIcons:SymbolIconSource\n                            FontSize=\"10\"\n                            IconVariant=\"Filled\"\n                            Symbol=\"Person\" />\n                    </ui:SettingsExpander.IconSource>\n                </ui:SettingsExpander>\n                <ui:SettingsExpander\n                    Grid.Row=\"2\"\n                    Margin=\"8,0,8,4\"\n                    Header=\"{x:Static lang:Resources.Label_DiscordRichPresence}\">\n                    <ui:SettingsExpander.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-brands fa-discord\" />\n                    </ui:SettingsExpander.IconSource>\n                    <ui:SettingsExpander.Footer>\n                        <ToggleSwitch IsChecked=\"{Binding IsDiscordRichPresenceEnabled}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n            </sg:SpacedGrid>\n\n            <!--  Checkpoints Manager Options  -->\n            <Grid RowDefinitions=\"Auto,*,Auto,Auto\">\n                <TextBlock\n                    Margin=\"0,0,0,8\"\n                    FontWeight=\"Medium\"\n                    Text=\"{x:Static lang:Resources.Label_CheckpointManager}\" />\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    Margin=\"8,4\"\n                    Description=\"Choose whether to Copy or Move files when dragging and dropping files into the Checkpoint Manager\"\n                    Header=\"Import Behaviour\">\n                    <ui:SettingsExpander.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-solid fa-file-import\" />\n                    </ui:SettingsExpander.IconSource>\n                    <ui:SettingsExpander.Footer>\n                        <ToggleSwitch\n                            IsChecked=\"{Binding MoveFilesOnImport}\"\n                            OffContent=\"Copy\"\n                            OnContent=\"Move\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n                <ui:SettingsExpander\n                    Grid.Row=\"2\"\n                    Margin=\"8,0,8,4\"\n                    Description=\"{x:Static lang:Resources.Label_RemoveSymlinksOnShutdown_Details}\"\n                    Header=\"{x:Static lang:Resources.Label_RemoveSymlinksOnShutdown}\"\n                    IconSource=\"Folder\">\n                    <ui:SettingsExpander.Footer>\n                        <ToggleSwitch IsChecked=\"{Binding RemoveSymlinksOnShutdown}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n                <ui:SettingsExpander\n                    Grid.Row=\"3\"\n                    Margin=\"8,0\"\n                    Description=\"{x:Static lang:Resources.Label_ResetCheckpointsCache_Details}\"\n                    Header=\"{x:Static lang:Resources.Label_ResetCheckpointsCache}\"\n                    IconSource=\"Refresh\">\n                    <ui:SettingsExpander.Footer>\n                        <Button Command=\"{Binding ResetCheckpointCache}\" Content=\"{x:Static lang:Resources.Label_ResetCheckpointsCache}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n            </Grid>\n\n            <!--  Environment Options  -->\n            <Grid RowDefinitions=\"Auto,Auto,Auto,Auto,Auto\">\n                <TextBlock\n                    Margin=\"0,0,0,8\"\n                    FontWeight=\"Medium\"\n                    Text=\"{x:Static lang:Resources.Label_PackageEnvironment}\" />\n\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    Margin=\"8,0\"\n                    Header=\"{x:Static lang:Resources.Label_EnvironmentVariables}\"\n                    IconSource=\"OtherUser\">\n                    <ui:SettingsExpander.Footer>\n                        <Button Command=\"{Binding OpenEnvVarsDialogCommand}\" Content=\"{x:Static lang:Resources.Action_Edit}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n\n                <ui:SettingsExpander\n                    Grid.Row=\"2\"\n                    Margin=\"8,4,8,0\"\n                    Header=\"{x:Static lang:Resources.Label_EmbeddedPython}\">\n                    <ui:SettingsExpander.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-brands fa-python\" />\n                    </ui:SettingsExpander.IconSource>\n                    <ui:SettingsExpander.Footer>\n                        <StackPanel Orientation=\"Horizontal\" Spacing=\"16\">\n                            <controls:ProgressRing\n                                BorderThickness=\"3\"\n                                IsEnabled=\"{Binding CheckPythonVersionCommand.IsRunning}\"\n                                IsIndeterminate=\"True\"\n                                IsVisible=\"{Binding CheckPythonVersionCommand.IsRunning}\" />\n                            <DropDownButton Content=\"Run\">\n                                <DropDownButton.Flyout>\n                                    <MenuFlyout Placement=\"Bottom\">\n                                        <MenuItem Command=\"{Binding RunPythonProcessCommand}\" Header=\"Command\" />\n                                        <MenuItem Command=\"{Binding ClearPipCacheCommand}\" Header=\"Clear Pip Cache\" />\n                                        <MenuItem Command=\"{Binding ClearUvCacheCommand}\" Header=\"Clear uv Cache\" />\n                                    </MenuFlyout>\n                                </DropDownButton.Flyout>\n                            </DropDownButton>\n                            <Button Command=\"{Binding CheckPythonVersionCommand}\" Content=\"{x:Static lang:Resources.Action_CheckVersion}\" />\n                        </StackPanel>\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n\n                <ui:SettingsExpander\n                    Grid.Row=\"3\"\n                    Margin=\"8,4,8,0\"\n                    Header=\"Git\">\n                    <ui:SettingsExpander.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-brands fa-git-alt\" />\n                    </ui:SettingsExpander.IconSource>\n                    <ui:SettingsExpander.Footer>\n                        <StackPanel Orientation=\"Horizontal\" Spacing=\"16\">\n                            <DropDownButton Content=\"Run\">\n                                <DropDownButton.Flyout>\n                                    <MenuFlyout Placement=\"Bottom\">\n                                        <MenuItem Command=\"{Binding RunGitProcessCommand}\" Header=\"Command\" />\n                                        <MenuItem\n                                            Command=\"{Binding FixGitLongPathsCommand}\"\n                                            Header=\"Enable Long Paths\"\n                                            IsVisible=\"{OnPlatform Default=False,\n                                                                   Windows=True}\" />\n                                    </MenuFlyout>\n                                </DropDownButton.Flyout>\n                            </DropDownButton>\n                        </StackPanel>\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n\n                <ui:SettingsExpander\n                    Grid.Row=\"4\"\n                    Margin=\"8,4,8,4\"\n                    Description=\"{x:Static lang:Resources.Label_UnsupportedPythonVersionDetails}\"\n                    Header=\"{x:Static lang:Resources.Label_ShowUnsupportedPythonVersions}\">\n                    <ui:SettingsExpander.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-brands fa-python\" />\n                    </ui:SettingsExpander.IconSource>\n                    <ui:SettingsExpander.Footer>\n                        <ToggleSwitch IsChecked=\"{Binding ShowAllAvailablePythonVersions}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n            </Grid>\n\n            <!--  Model Browser Options  -->\n            <Grid RowDefinitions=\"Auto,*,Auto\">\n                <TextBlock\n                    Margin=\"0,0,0,8\"\n                    FontWeight=\"Medium\"\n                    Text=\"{x:Static lang:Resources.Label_ModelBrowser}\" />\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    Margin=\"8,0\"\n                    Description=\"{x:Static lang:Resources.Label_AutoSearchOnLoad_Description}\"\n                    Header=\"{x:Static lang:Resources.Label_AutoSearchOnLoad}\">\n                    <ui:SettingsExpander.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-solid fa-spinner\" />\n                    </ui:SettingsExpander.IconSource>\n                    <ui:SettingsExpander.Footer>\n                        <ToggleSwitch IsChecked=\"{Binding AutoLoadCivitModels}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n                <ui:SettingsExpander\n                    Grid.Row=\"2\"\n                    Margin=\"8,4\"\n                    Description=\"Show/hide certain base model types in the Model Browser\"\n                    Header=\"Base Model Filter\">\n                    <ui:SettingsExpander.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-solid fa-filter\" />\n                    </ui:SettingsExpander.IconSource>\n                    <scroll:BetterScrollViewer MaxHeight=\"400\">\n                        <ItemsRepeater ItemsSource=\"{Binding AllBaseModelTypes}\">\n                            <ItemsRepeater.Layout>\n                                <StackLayout Orientation=\"Vertical\" />\n                            </ItemsRepeater.Layout>\n                            <ItemsRepeater.ItemTemplate>\n                                <DataTemplate x:DataType=\"checkpointManager:BaseModelOptionViewModel\">\n                                    <Grid ColumnDefinitions=\"*,*\" RowDefinitions=\"Auto, Auto\">\n                                        <Separator\n                                            Grid.Row=\"0\"\n                                            Grid.Column=\"0\"\n                                            Grid.ColumnSpan=\"2\" />\n                                        <TextBlock\n                                            Grid.Row=\"1\"\n                                            Grid.Column=\"0\"\n                                            Margin=\"32,0,0,0\"\n                                            VerticalAlignment=\"Center\"\n                                            FontSize=\"16\"\n                                            Text=\"{Binding ModelType}\" />\n                                        <ToggleSwitch\n                                            Grid.Row=\"1\"\n                                            Grid.Column=\"1\"\n                                            Margin=\"0,0,32,0\"\n                                            HorizontalAlignment=\"Center\"\n                                            FontSize=\"16\"\n                                            IsChecked=\"{Binding IsSelected}\" />\n\n                                    </Grid>\n                                </DataTemplate>\n                            </ItemsRepeater.ItemTemplate>\n                        </ItemsRepeater>\n                    </scroll:BetterScrollViewer>\n                </ui:SettingsExpander>\n            </Grid>\n\n            <!--  Workflow Browser Options  -->\n            <sg:SpacedGrid RowDefinitions=\"Auto, Auto\">\n                <TextBlock\n                    Margin=\"0,0,0,8\"\n                    FontWeight=\"Medium\"\n                    Text=\"{x:Static lang:Resources.Label_WorkflowBrowser}\" />\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    Margin=\"8,0,8,4\"\n                    Header=\"{x:Static lang:Resources.Label_InfiniteScrolling}\">\n                    <ui:SettingsExpander.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-solid fa-list\" />\n                    </ui:SettingsExpander.IconSource>\n                    <ui:SettingsExpander.Footer>\n                        <ToggleSwitch IsChecked=\"{Binding InfinitelyScrollWorkflowBrowser}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n            </sg:SpacedGrid>\n\n            <!--  Console  -->\n            <sg:SpacedGrid RowDefinitions=\"Auto,*\" RowSpacing=\"4\">\n                <TextBlock\n                    Margin=\"0,0,0,4\"\n                    FontWeight=\"Medium\"\n                    Text=\"{x:Static lang:Resources.Label_Console}\" />\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    Margin=\"8,0,8,4\"\n                    Description=\"{x:Static lang:Resources.Label_HistorySize_Description}\"\n                    Header=\"{x:Static lang:Resources.Label_HistorySize}\">\n                    <ui:SettingsExpander.IconSource>\n                        <fluentIcons:SymbolIconSource FontSize=\"10\" Symbol=\"History\" />\n                    </ui:SettingsExpander.IconSource>\n                    <ui:SettingsExpander.Footer>\n                        <ui:NumberBox\n                            Maximum=\"10000000\"\n                            Minimum=\"-1\"\n                            PlaceholderText=\"0\"\n                            SimpleNumberFormat=\"F0\"\n                            SpinButtonPlacementMode=\"Compact\"\n                            ValidationMode=\"InvalidInputOverwritten\"\n                            Value=\"{Binding ConsoleLogHistorySize}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n            </sg:SpacedGrid>\n\n            <!--  Theme  -->\n            <Grid RowDefinitions=\"Auto,*,*,*,*,*\">\n                <TextBlock\n                    Margin=\"0,0,0,8\"\n                    FontWeight=\"Medium\"\n                    Text=\"{x:Static lang:Resources.Label_Appearance}\" />\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    Margin=\"8,0,8,4\"\n                    Header=\"{x:Static lang:Resources.Label_Theme}\"\n                    IconSource=\"WeatherMoon\">\n                    <ui:SettingsExpander.Footer>\n                        <ComboBox\n                            MinWidth=\"100\"\n                            ItemsSource=\"{Binding AvailableThemes}\"\n                            SelectedItem=\"{Binding SelectedTheme}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n                <ui:SettingsExpander\n                    Grid.Row=\"2\"\n                    Margin=\"8,0,8,4\"\n                    Header=\"{x:Static lang:Resources.Label_Language}\"\n                    IconSource=\"Character\">\n                    <ui:SettingsExpander.Footer>\n                        <ComboBox\n                            MinWidth=\"100\"\n                            DisplayMemberBinding=\"{Binding Converter={StaticResource CultureInfoDisplayConverter}}\"\n                            ItemsSource=\"{Binding AvailableLanguages}\"\n                            SelectedItem=\"{Binding SelectedLanguage}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n\n                <ui:SettingsExpander\n                    Grid.Row=\"3\"\n                    Margin=\"8,0,8,4\"\n                    Header=\"{x:Static lang:Resources.Label_NumberFormat}\">\n                    <ui:SettingsExpander.IconSource>\n                        <fluentIcons:SymbolIconSource\n                            FontSize=\"10\"\n                            IconVariant=\"Filled\"\n                            Symbol=\"DecimalArrowLeft\" />\n                    </ui:SettingsExpander.IconSource>\n                    <ui:SettingsExpander.Footer>\n                        <ComboBox\n                            MinWidth=\"100\"\n                            ItemsSource=\"{Binding NumberFormatModes}\"\n                            SelectedItem=\"{Binding SelectedNumberFormatMode}\">\n                            <ComboBox.ItemTemplate>\n                                <DataTemplate>\n                                    <TextBlock>\n                                        <Run Text=\"{Binding Converter={x:Static converters:EnumAttributeConverters.DisplayName}}\" />\n                                        <Run Text=\"-\" />\n                                        <Run Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\" Text=\"{Binding Converter={StaticResource NumberFormatModeSampleConverter}}\" />\n                                    </TextBlock>\n                                </DataTemplate>\n                            </ComboBox.ItemTemplate>\n                        </ComboBox>\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n\n                <ui:SettingsExpander\n                    Grid.Row=\"4\"\n                    Margin=\"8,0,8,4\"\n                    Header=\"{x:Static lang:Resources.Label_HolidayMode}\">\n                    <ui:SettingsExpander.IconSource>\n                        <fluentIcons:SymbolIconSource\n                            FontSize=\"10\"\n                            IconVariant=\"Filled\"\n                            Symbol=\"WeatherSnowflake\" />\n                    </ui:SettingsExpander.IconSource>\n                    <ui:SettingsExpander.Footer>\n                        <ui:FAComboBox ItemsSource=\"{Binding HolidayModes}\" SelectedItem=\"{Binding HolidayModeSetting}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n\n                <ui:SettingsExpander\n                    Grid.Row=\"5\"\n                    Margin=\"8,0,8,4\"\n                    ActionIconSource=\"ChevronRight\"\n                    Command=\"{Binding NavigateToSubPageCommand}\"\n                    CommandParameter=\"{x:Type vmSettings:InferenceSettingsViewModel}\"\n                    Header=\"Inference (Test)\"\n                    IconSource=\"Code\"\n                    IsClickEnabled=\"True\"\n                    IsVisible=\"{Binding SharedState.IsDebugMode}\" />\n            </Grid>\n\n            <!--  System Options  -->\n            <sg:SpacedGrid RowDefinitions=\"Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,*\" RowSpacing=\"4\">\n                <TextBlock\n                    Margin=\"0,0,0,4\"\n                    FontWeight=\"Medium\"\n                    Text=\"{x:Static lang:Resources.Label_System}\" />\n\n                <!--  Updates page  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    ActionIconSource=\"ChevronRight\"\n                    Command=\"{Binding NavigateToSubPageCommand}\"\n                    CommandParameter=\"{x:Type vmSettings:UpdateSettingsViewModel}\"\n                    Header=\"{x:Static lang:Resources.Label_Updates}\"\n                    IsClickEnabled=\"True\">\n                    <ui:SettingsExpander.IconSource>\n                        <fluentIcons:SymbolIconSource\n                            FontSize=\"10\"\n                            IconVariant=\"Filled\"\n                            Symbol=\"ArrowSync\" />\n                    </ui:SettingsExpander.IconSource>\n                </ui:SettingsExpander>\n\n                <!--  Analytics page  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"2\"\n                    ActionIconSource=\"ChevronRight\"\n                    Command=\"{Binding NavigateToSubPageCommand}\"\n                    CommandParameter=\"{x:Type vmSettings:AnalyticsSettingsViewModel}\"\n                    Header=\"{x:Static lang:Resources.Label_Analytics}\"\n                    IsClickEnabled=\"True\">\n                    <ui:SettingsExpander.IconSource>\n                        <controls:FASymbolIconSource Symbol=\"fa-solid fa-chart-simple\" />\n                    </ui:SettingsExpander.IconSource>\n                </ui:SettingsExpander>\n\n                <!--  Add to start menu  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"3\"\n                    Description=\"{x:Static lang:Resources.Label_AddToStartMenu_Details}\"\n                    Header=\"{x:Static lang:Resources.Label_AddToStartMenu}\"\n                    IconSource=\"StarAdd\"\n                    ToolTip.Tip=\"{OnPlatform Default={x:Static lang:Resources.Label_OnlyAvailableOnWindows},\n                                             Windows={x:Null}}\">\n                    <ui:SettingsExpander.Footer>\n                        <StackPanel Orientation=\"Horizontal\" Spacing=\"8\">\n                            <controls:ProgressRing\n                                BorderThickness=\"3\"\n                                IsEnabled=\"{Binding IsVisible, RelativeSource={RelativeSource Self}}\"\n                                IsIndeterminate=\"True\">\n                                <controls:ProgressRing.IsVisible>\n                                    <MultiBinding Converter=\"{x:Static BoolConverters.Or}\">\n                                        <Binding Path=\"AddToStartMenuCommand.IsRunning\" />\n                                        <Binding Path=\"AddToGlobalStartMenuCommand.IsRunning\" />\n                                    </MultiBinding>\n                                </controls:ProgressRing.IsVisible>\n                            </controls:ProgressRing>\n\n                            <SplitButton\n                                Command=\"{Binding AddToStartMenuCommand}\"\n                                Content=\"{x:Static lang:Resources.Action_AddForCurrentUser}\"\n                                IsEnabled=\"{OnPlatform Default=False,\n                                                       Windows=True}\">\n                                <SplitButton.Flyout>\n                                    <ui:FAMenuFlyout Placement=\"Bottom\">\n                                        <ui:MenuFlyoutItem\n                                            Command=\"{Binding AddToGlobalStartMenuCommand}\"\n                                            IconSource=\"Admin\"\n                                            Text=\"{x:Static lang:Resources.Action_AddForAllUsers}\" />\n                                    </ui:FAMenuFlyout>\n                                </SplitButton.Flyout>\n                            </SplitButton>\n                        </StackPanel>\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n\n                <ui:SettingsExpander\n                    Grid.Row=\"4\"\n                    Margin=\"8,0,8,0\"\n                    Description=\"Limits the number of simultaneous downloads. Does not apply to package installs. Set to 0 for unlimited.\"\n                    Header=\"Maximum Simultaneous Downloads\">\n                    <ui:SettingsExpander.IconSource>\n                        <fluentIcons:SymbolIconSource FontSize=\"10\" Symbol=\"ArrowDownload\" />\n                    </ui:SettingsExpander.IconSource>\n                    <ui:SettingsExpander.Footer>\n                        <ui:NumberBox\n                            Maximum=\"10000000\"\n                            Minimum=\"0\"\n                            PlaceholderText=\"0\"\n                            SimpleNumberFormat=\"F0\"\n                            SpinButtonPlacementMode=\"Compact\"\n                            ValidationMode=\"InvalidInputOverwritten\"\n                            Value=\"{Binding MaxConcurrentDownloads}\" />\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n\n                <!--  Select Data Directory  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"5\"\n                    Description=\"{x:Static lang:Resources.Label_SelectNewDataDirectory_Details}\"\n                    IconSource=\"MoveToFolder\">\n                    <ui:SettingsExpander.Header>\n                        <StackPanel Orientation=\"Vertical\">\n                            <TextBlock Text=\"{x:Static lang:Resources.Label_SelectNewDataDirectory}\" />\n                            <TextBlock FontSize=\"12\" Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\">\n                                <Run Text=\"{x:Static lang:Resources.Label_CurrentDirectory}\" />\n                                <Run Text=\"{Binding DataDirectory}\" />\n                            </TextBlock>\n                        </StackPanel>\n                    </ui:SettingsExpander.Header>\n                    <ui:SettingsExpander.Footer>\n                        <Button Command=\"{Binding PickNewDataDirectory}\">\n                            <Grid ColumnDefinitions=\"Auto, Auto\">\n                                <avalonia:Icon\n                                    Grid.Row=\"0\"\n                                    Margin=\"0,0,8,0\"\n                                    VerticalAlignment=\"Center\"\n                                    Value=\"fa-solid fa-folder-open\" />\n                                <TextBlock\n                                    Grid.Column=\"1\"\n                                    VerticalAlignment=\"Center\"\n                                    Text=\"{x:Static lang:Resources.Action_SelectDirectory}\" />\n                            </Grid>\n                        </Button>\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n\n                <ui:SettingsExpander\n                    Grid.Row=\"6\"\n                    Description=\"{x:Static lang:Resources.Label_SelectNewDataDirectory_Details}\"\n                    IconSource=\"MoveToFolder\">\n                    <ui:SettingsExpander.Header>\n                        <StackPanel Orientation=\"Vertical\">\n                            <TextBlock Text=\"Select new Models Folder\" />\n                            <TextBlock FontSize=\"12\" Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\">\n                                <Run Text=\"{x:Static lang:Resources.Label_CurrentDirectory}\" />\n                                <Run Text=\"{Binding ModelsDirectory}\" />\n                            </TextBlock>\n                        </StackPanel>\n                    </ui:SettingsExpander.Header>\n                    <ui:SettingsExpander.Footer>\n                        <Button Command=\"{Binding PickNewModelsFolder}\">\n                            <Grid ColumnDefinitions=\"Auto, Auto\">\n                                <avalonia:Icon\n                                    Grid.Row=\"0\"\n                                    Margin=\"0,0,8,0\"\n                                    VerticalAlignment=\"Center\"\n                                    Value=\"fa-solid fa-folder-open\" />\n                                <TextBlock\n                                    Grid.Column=\"1\"\n                                    VerticalAlignment=\"Center\"\n                                    Text=\"{x:Static lang:Resources.Action_SelectDirectory}\" />\n                            </Grid>\n                        </Button>\n                    </ui:SettingsExpander.Footer>\n                </ui:SettingsExpander>\n\n                <!--  App Folders  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"7\"\n                    Header=\"{x:Static lang:Resources.Label_AppFolders}\"\n                    IconSource=\"FolderLink\">\n\n                    <ui:SettingsExpanderItem>\n                        <ItemsRepeater ItemsSource=\"{Binding DirectoryShortcutCommands}\">\n                            <ItemsRepeater.Layout>\n                                <WrapLayout />\n                            </ItemsRepeater.Layout>\n                            <ItemsRepeater.ItemTemplate>\n                                <DataTemplate DataType=\"models:CommandItem\">\n                                    <controls:HyperlinkIconButton\n                                        Margin=\"4\"\n                                        Padding=\"12,6\"\n                                        Command=\"{Binding Command}\"\n                                        Content=\"{Binding DisplayName}\" />\n                                </DataTemplate>\n                            </ItemsRepeater.ItemTemplate>\n\n                        </ItemsRepeater>\n                    </ui:SettingsExpanderItem>\n\n                </ui:SettingsExpander>\n\n                <!--  System Settings  -->\n                <ui:SettingsExpander\n                    Grid.Row=\"8\"\n                    Header=\"{x:Static lang:Resources.Label_SystemSettings}\"\n                    IconSource=\"Settings\">\n\n                    <ui:SettingsExpanderItem Content=\"Default GPU\" Description=\"This influences which version of pytorch will be used during package installs\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <controls:BetterComboBox\n                                HorizontalAlignment=\"Stretch\"\n                                ItemsSource=\"{Binding GpuInfoCollection}\"\n                                SelectedItem=\"{Binding PreferredGpu}\">\n                                <controls:BetterComboBox.ItemTemplate>\n                                    <DataTemplate DataType=\"hardwareInfo:GpuInfo\">\n                                        <TextBlock Text=\"{Binding Name}\" />\n                                    </DataTemplate>\n                                </controls:BetterComboBox.ItemTemplate>\n                            </controls:BetterComboBox>\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                    <!--  Windows Long Paths  -->\n                    <ui:SettingsExpanderItem\n                        Content=\"{x:Static lang:Resources.Label_EnableLongPaths}\"\n                        Description=\"{x:Static lang:Resources.Label_EnableLongPathsDescription}\"\n                        IsEnabled=\"{OnPlatform Default=False,\n                                               Windows=True}\"\n                        ToolTip.Tip=\"{OnPlatform Default={x:Static lang:Resources.Label_OnlyAvailableOnWindows},\n                                                 Windows={x:Null}}\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <StackPanel Orientation=\"Horizontal\" Spacing=\"8\">\n                                <controls:ProgressRing\n                                    BorderThickness=\"3\"\n                                    IsEnabled=\"{Binding IsVisible, RelativeSource={RelativeSource Self}}\"\n                                    IsIndeterminate=\"True\"\n                                    IsVisible=\"{Binding WindowsLongPathsToggleClickCommand.IsRunning}\" />\n                                <ToggleSwitch\n                                    Command=\"{Binding WindowsLongPathsToggleClickCommand}\"\n                                    IsChecked=\"{Binding IsWindowsLongPathsEnabled}\"\n                                    IsEnabled=\"{Binding !WindowsLongPathsToggleClickCommand.IsRunning}\" />\n                            </StackPanel>\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n                </ui:SettingsExpander>\n\n                <!--  System Information  -->\n                <ui:SettingsExpander Grid.Row=\"9\" Header=\"{x:Static lang:Resources.Label_SystemInformation}\">\n                    <ui:SettingsExpander.IconSource>\n                        <fluentIcons:SymbolIconSource\n                            FontSize=\"10\"\n                            IconVariant=\"Filled\"\n                            Symbol=\"Info\" />\n                    </ui:SettingsExpander.IconSource>\n\n                    <!--  Cpu  -->\n                    <ui:SettingsExpanderItem>\n                        <ui:SettingsExpanderItem.IconSource>\n                            <controls:FASymbolIconSource\n                                FontSize=\"10\"\n                                Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                Symbol=\"fa-solid fa-microchip\" />\n                        </ui:SettingsExpanderItem.IconSource>\n\n                        <sg:SpacedGrid\n                            ColumnDefinitions=\"Auto,Auto\"\n                            ColumnSpacing=\"16\"\n                            DataContext=\"{Binding CpuInfoAsync^}\"\n                            RowDefinitions=\"Auto,Auto\">\n                            <TextBlock Grid.Column=\"0\" Text=\"CPU\" />\n                            <SelectableTextBlock\n                                Grid.Row=\"0\"\n                                Grid.Column=\"1\"\n                                Foreground=\"{DynamicResource TextControlPlaceholderForeground}\"\n                                Text=\"{Binding ProcessorCaption}\"\n                                TextWrapping=\"WrapWithOverflow\" />\n                        </sg:SpacedGrid>\n\n                    </ui:SettingsExpanderItem>\n\n                    <!--  Memory  -->\n                    <ui:SettingsExpanderItem>\n                        <ui:SettingsExpanderItem.IconSource>\n                            <controls:FASymbolIconSource\n                                FontSize=\"10\"\n                                Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                Symbol=\"fa-solid fa-memory\" />\n                        </ui:SettingsExpanderItem.IconSource>\n\n                        <sg:SpacedGrid\n                            ColumnDefinitions=\"Auto,Auto\"\n                            ColumnSpacing=\"16\"\n                            DataContext=\"{Binding MemoryInfo}\"\n                            RowDefinitions=\"Auto,Auto\">\n                            <TextBlock Grid.Column=\"0\" Text=\"Total Memory\" />\n                            <SelectableTextBlock\n                                Grid.Row=\"0\"\n                                Grid.Column=\"1\"\n                                Foreground=\"{DynamicResource TextControlPlaceholderForeground}\"\n                                TextWrapping=\"WrapWithOverflow\">\n                                <SelectableTextBlock.Text>\n                                    <MultiBinding StringFormat=\"{}{0} ({1} usable)\">\n                                        <Binding Converter=\"{x:Static converters:StringFormatConverters.MemoryBytes}\" Path=\"TotalInstalledBytes\" />\n                                        <Binding Converter=\"{x:Static converters:StringFormatConverters.MemoryBytes}\" Path=\"TotalPhysicalBytes\" />\n                                    </MultiBinding>\n                                </SelectableTextBlock.Text>\n                            </SelectableTextBlock>\n\n                            <TextBlock\n                                Grid.Row=\"1\"\n                                Grid.Column=\"0\"\n                                IsVisible=\"{x:Static hardwareInfo:HardwareHelper.IsLiveMemoryUsageInfoAvailable}\"\n                                Text=\"Available Memory\" />\n                            <SelectableTextBlock\n                                Grid.Row=\"1\"\n                                Grid.Column=\"1\"\n                                Foreground=\"{DynamicResource TextControlPlaceholderForeground}\"\n                                IsVisible=\"{x:Static hardwareInfo:HardwareHelper.IsLiveMemoryUsageInfoAvailable}\"\n                                Text=\"{Binding AvailablePhysicalBytes, Converter={x:Static converters:StringFormatConverters.MemoryBytes}}\"\n                                TextWrapping=\"WrapWithOverflow\" />\n                        </sg:SpacedGrid>\n\n                    </ui:SettingsExpanderItem>\n\n                    <!--  GPUs  -->\n                    <ui:SettingsExpanderItem>\n                        <ui:SettingsExpanderItem.IconSource>\n                            <controls:FASymbolIconSource\n                                FontSize=\"10\"\n                                Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                Symbol=\"fa-solid fa-tachograph-digital\" />\n                        </ui:SettingsExpanderItem.IconSource>\n                        <ItemsControl ItemsSource=\"{Binding GpuInfos}\">\n                            <ItemsControl.ItemTemplate>\n                                <DataTemplate DataType=\"hardwareInfo:GpuInfo\">\n                                    <sg:SpacedGrid ColumnDefinitions=\"Auto,Auto,Auto\" RowDefinitions=\"Auto,Auto\">\n                                        <TextBlock Text=\"{Binding Index, StringFormat={}{0}, Converter={StaticResource IndexPlusOneConverter}}\" Theme=\"{DynamicResource BodyStrongTextBlockStyle}\" />\n                                        <SelectableTextBlock\n                                            Grid.Row=\"0\"\n                                            Grid.Column=\"1\"\n                                            Text=\"{Binding Name}\" />\n                                        <SelectableTextBlock\n                                            Grid.Row=\"1\"\n                                            Grid.Column=\"1\"\n                                            Grid.ColumnSpan=\"2\"\n                                            Foreground=\"{DynamicResource TextControlPlaceholderForeground}\"\n                                            IsVisible=\"{Binding !!MemoryBytes}\"\n                                            Text=\"{Binding MemoryBytes, Converter={x:Static converters:StringFormatConverters.MemoryBytes}}\"\n                                            TextWrapping=\"WrapWithOverflow\" />\n                                    </sg:SpacedGrid>\n                                </DataTemplate>\n                            </ItemsControl.ItemTemplate>\n\n                            <ItemsControl.ItemsPanel>\n                                <ItemsPanelTemplate>\n                                    <StackPanel Spacing=\"8\" />\n                                </ItemsPanelTemplate>\n                            </ItemsControl.ItemsPanel>\n\n                        </ItemsControl>\n\n                    </ui:SettingsExpanderItem>\n                </ui:SettingsExpander>\n            </sg:SpacedGrid>\n\n            <!--  Debug Options  -->\n            <Grid IsVisible=\"{Binding SharedState.IsDebugMode}\" RowDefinitions=\"auto,*\">\n                <TextBlock\n                    Margin=\"0,0,0,8\"\n                    FontWeight=\"Medium\"\n                    Text=\"Debug Options\" />\n                <ui:SettingsExpander\n                    Grid.Row=\"1\"\n                    Margin=\"8,0,8,0\"\n                    Command=\"{Binding LoadDebugInfo}\"\n                    Header=\"Debug Options\"\n                    IconSource=\"Code\">\n\n                    <ui:SettingsExpanderItem\n                        Margin=\"4,0\"\n                        Description=\"Paths\"\n                        IconSource=\"Folder\">\n                        <SelectableTextBlock\n                            Foreground=\"{DynamicResource TextControlPlaceholderForeground}\"\n                            Text=\"{Binding DebugPaths}\"\n                            TextWrapping=\"WrapWithOverflow\" />\n                    </ui:SettingsExpanderItem>\n\n                    <ui:SettingsExpanderItem\n                        Margin=\"4,0\"\n                        Description=\"Compat Info\"\n                        IconSource=\"StarFilled\">\n                        <SelectableTextBlock\n                            Foreground=\"{DynamicResource TextControlPlaceholderForeground}\"\n                            Text=\"{Binding DebugCompatInfo}\"\n                            TextWrapping=\"WrapWithOverflow\" />\n                    </ui:SettingsExpanderItem>\n\n                    <ui:SettingsExpanderItem\n                        Margin=\"4,0\"\n                        Description=\"GPU Info\"\n                        IconSource=\"FullScreenMaximize\">\n                        <SelectableTextBlock\n                            Foreground=\"{DynamicResource TextControlPlaceholderForeground}\"\n                            Text=\"{Binding DebugGpuInfo}\"\n                            TextWrapping=\"WrapWithOverflow\" />\n                    </ui:SettingsExpanderItem>\n\n                    <ui:SettingsExpanderItem\n                        Margin=\"4,0\"\n                        Content=\"Animation Scale\"\n                        Description=\"Lower values = faster animations. 0x means animations are instant.\"\n                        IconSource=\"Clock\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <ComboBox ItemsSource=\"{Binding AnimationScaleOptions}\" SelectedItem=\"{Binding SelectedAnimationScale}\">\n                                <ComboBox.ItemTemplate>\n                                    <DataTemplate>\n                                        <TextBlock>\n                                            <Run Text=\"{Binding}\" /><Run Text=\"x\" />\n                                        </TextBlock>\n                                    </DataTemplate>\n                                </ComboBox.ItemTemplate>\n                            </ComboBox>\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                    <ui:SettingsExpanderItem\n                        Margin=\"4,0\"\n                        Content=\"Notification\"\n                        IconSource=\"CommentAdd\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <Button Command=\"{Binding DebugNotificationCommand}\" Content=\"New Notification\" />\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                    <ui:SettingsExpanderItem\n                        Margin=\"4,0\"\n                        Content=\"Content Dialog\"\n                        IconSource=\"NewWindow\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <Button Command=\"{Binding DebugContentDialogCommand}\" Content=\"Show Dialog\" />\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                    <ui:SettingsExpanderItem\n                        Margin=\"4,0\"\n                        Content=\"Exceptions\"\n                        IconSource=\"Flag\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <StackPanel Orientation=\"Horizontal\" Spacing=\"8\">\n                                <Button Command=\"{Binding DebugThrowExceptionCommand}\" Content=\"Unhandled Exception\" />\n                                <Button Command=\"{Binding DebugThrowDispatcherExceptionCommand}\" Content=\"Dispatcher Unhandled Exception\" />\n                            </StackPanel>\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                    <ui:SettingsExpanderItem\n                        Margin=\"4,0,4,4\"\n                        Content=\"Download Manager tests\"\n                        IconSource=\"Flag\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <Button\n                                Margin=\"0,8\"\n                                Command=\"{Binding DebugTrackedDownloadCommand}\"\n                                Content=\"Add Tracked Download\" />\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                    <ui:SettingsExpanderItem\n                        Margin=\"4,0,4,4\"\n                        Content=\"Refresh Models Index\"\n                        IconSource=\"SyncFolder\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <Button\n                                Margin=\"0,8\"\n                                Command=\"{Binding DebugRefreshModelsIndexCommand}\"\n                                Content=\"Refresh Index\" />\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                    <ui:SettingsExpanderItem\n                        Margin=\"4,0,4,4\"\n                        Content=\"Make image grid\"\n                        IconSource=\"Image\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <Button\n                                Margin=\"0,8\"\n                                Command=\"{Binding DebugMakeImageGridCommand}\"\n                                Content=\"Select images\" />\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                    <ui:SettingsExpanderItem\n                        Margin=\"4,0,4,4\"\n                        Content=\"Image metadata parser\"\n                        IconSource=\"Flag\">\n                        <ui:SettingsExpanderItem.Footer>\n                            <Button\n                                Margin=\"0,8\"\n                                Command=\"{Binding DebugImageMetadataCommand}\"\n                                Content=\"Choose image\" />\n                        </ui:SettingsExpanderItem.Footer>\n                    </ui:SettingsExpanderItem>\n\n                    <ItemsRepeater Margin=\"4,0,4,4\" ItemsSource=\"{Binding DebugCommands}\">\n                        <ItemsRepeater.Layout>\n                            <UniformGridLayout />\n                        </ItemsRepeater.Layout>\n                        <ItemsRepeater.ItemTemplate>\n                            <DataTemplate DataType=\"models:CommandItem\">\n                                <Button\n                                    Margin=\"4,8\"\n                                    Command=\"{Binding Command}\"\n                                    Content=\"{Binding DisplayName}\" />\n                            </DataTemplate>\n                        </ItemsRepeater.ItemTemplate>\n                    </ItemsRepeater>\n\n                </ui:SettingsExpander>\n            </Grid>\n\n            <!--  TODO: Directories card  -->\n\n            <Grid RowDefinitions=\"auto,*\">\n                <StackPanel\n                    Grid.Row=\"1\"\n                    HorizontalAlignment=\"Left\"\n                    Orientation=\"Vertical\">\n                    <TextBlock\n                        Margin=\"0,8\"\n                        FontSize=\"15\"\n                        FontWeight=\"Bold\"\n                        Text=\"{x:Static lang:Resources.Label_About}\" />\n                    <Image\n                        Width=\"112\"\n                        Height=\"112\"\n                        Margin=\"8\"\n                        HorizontalAlignment=\"Left\"\n                        Source=\"/Assets/Icon.png\" />\n                    <TextBlock\n                        Margin=\"8\"\n                        FontWeight=\"Medium\"\n                        Text=\"{x:Static lang:Resources.Label_StabilityMatrix}\" />\n                    <Panel>\n                        <Button\n                            Name=\"VersionButton\"\n                            Margin=\"8,0,8,8\"\n                            Padding=\"2,0,2,0\"\n                            BorderThickness=\"0\"\n                            Classes=\"transparent\"\n                            Command=\"{Binding OnVersionClick}\"\n                            Content=\"{Binding AppVersion}\" />\n                        <ui:TeachingTip\n                            Title=\"{Binding VersionFlyoutText}\"\n                            IsOpen=\"{Binding IsVersionTapTeachingTipOpen}\"\n                            PreferredPlacement=\"RightTop\"\n                            Target=\"{Binding #VersionButton}\" />\n                    </Panel>\n\n                    <StackPanel HorizontalAlignment=\"Left\" Orientation=\"Horizontal\">\n                        <Button\n                            Margin=\"8\"\n                            HorizontalAlignment=\"Left\"\n                            Command=\"{Binding ShowLicensesDialogCommand}\"\n                            Content=\"{x:Static lang:Resources.Label_LicenseAndOpenSourceNotices}\" />\n                    </StackPanel>\n                </StackPanel>\n            </Grid>\n\n            <!--  Extra space at the bottom  -->\n            <Panel Margin=\"0,0,0,16\" />\n        </StackPanel>\n    </ScrollViewer>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Settings;\n\n[RegisterSingleton<MainSettingsPage>]\npublic partial class MainSettingsPage : UserControlBase\n{\n    public MainSettingsPage()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Settings/NotificationSettingsPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Settings.NotificationSettingsPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Core.Models.Settings;assembly=StabilityMatrix.Core\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vmSettings=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings\"\n    d:DataContext=\"{x:Static mocks:DesignData.NotificationSettingsViewModel}\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"vmSettings:NotificationSettingsViewModel\"\n    mc:Ignorable=\"d\">\n\n    <controls:UserControlBase.Resources>\n        <DataTemplate x:Key=\"ItemTemplate\" DataType=\"vmSettings:NotificationSettingsItem\">\n            <controls:Card Padding=\"16\">\n                <Grid ColumnDefinitions=\"*,Auto\">\n                    <StackPanel VerticalAlignment=\"Center\">\n                        <TextBlock Text=\"{Binding Key.DisplayName}\" />\n                    </StackPanel>\n\n                    <controls:BetterComboBox\n                        Grid.Column=\"1\"\n                        MinWidth=\"100\"\n                        ItemsSource=\"{x:Static vmSettings:NotificationSettingsItem.AvailableOptions}\"\n                        SelectedItem=\"{Binding Option}\">\n                        <controls:BetterComboBox.ItemTemplate>\n                            <DataTemplate x:DataType=\"models:NotificationOption\">\n                                <TextBlock>\n                                    <TextBlock.Text>\n                                        <MultiBinding StringFormat=\"{}{0} - {1}\">\n                                            <Binding Converter=\"{x:Static converters:EnumAttributeConverters.DisplayName}\" />\n                                            <Binding Converter=\"{x:Static converters:EnumAttributeConverters.DisplayDescription}\" />\n                                        </MultiBinding>\n                                    </TextBlock.Text>\n                                </TextBlock>\n                            </DataTemplate>\n                        </controls:BetterComboBox.ItemTemplate>\n                        <controls:BetterComboBox.SelectionBoxItemTemplate>\n                            <DataTemplate x:DataType=\"models:NotificationOption\">\n                                <TextBlock Text=\"{Binding Converter={x:Static converters:EnumAttributeConverters.DisplayName}}\" />\n                            </DataTemplate>\n                        </controls:BetterComboBox.SelectionBoxItemTemplate>\n                    </controls:BetterComboBox>\n                </Grid>\n\n            </controls:Card>\n        </DataTemplate>\n    </controls:UserControlBase.Resources>\n\n    <ScrollViewer Padding=\"16\" VerticalScrollBarVisibility=\"Auto\">\n        <ItemsControl \n            ItemTemplate=\"{StaticResource ItemTemplate}\" \n            ItemsSource=\"{Binding Items}\">\n            <ItemsControl.ItemsPanel>\n                <ItemsPanelTemplate>\n                    <StackPanel Spacing=\"8\" />\n                </ItemsPanelTemplate>\n            </ItemsControl.ItemsPanel>\n        </ItemsControl>\n    </ScrollViewer>\n\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Settings/NotificationSettingsPage.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views.Settings;\n\n[RegisterSingleton<NotificationSettingsPage>]\npublic partial class NotificationSettingsPage : UserControlBase\n{\n    public NotificationSettingsPage()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Settings/UpdateSettingsPage.axaml",
    "content": "﻿<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.Settings.UpdateSettingsPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:fluentIcons=\"clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent\"\n    xmlns:lang=\"clr-namespace:StabilityMatrix.Avalonia.Languages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:models=\"clr-namespace:StabilityMatrix.Avalonia.Models\"\n    xmlns:sg=\"clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia\"\n    xmlns:sm=\"clr-namespace:StabilityMatrix.Avalonia\"\n    xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    xmlns:vmSettings=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings\"\n    xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Converters\"\n    d:DataContext=\"{x:Static mocks:DesignData.UpdateSettingsViewModel}\"\n    d:DesignHeight=\"550\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"vmSettings:UpdateSettingsViewModel\"\n    mc:Ignorable=\"d\">\n    \n    <controls:UserControlBase.Resources>\n        <converters:BooleanChoiceMultiConverter x:Key=\"BoolChoiceMultiConverter\" />\n    </controls:UserControlBase.Resources>\n\n    <ScrollViewer VerticalScrollBarVisibility=\"Auto\">\n        <StackPanel Margin=\"16,16,24,16\" Spacing=\"2\">\n\n            <sg:SpacedGrid Margin=\"0,4,0,16\" ColumnDefinitions=\"*,Auto\">\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"16\">\n                    <fluentIcons:SymbolIcon\n                        Width=\"64\"\n                        Height=\"64\"\n                        FontSize=\"64\"\n                        IconVariant=\"Filled\"\n                        Symbol=\"ArrowSync\" />\n\n                    <StackPanel VerticalAlignment=\"Center\">\n                        <TextBlock\n                            Margin=\"-1,0,0,0\"\n                            HorizontalAlignment=\"Left\"\n                            Text=\"{Binding HeaderText}\"\n                            Theme=\"{DynamicResource SubtitleTextBlockStyle}\" />\n                        <TextBlock \n                            HorizontalAlignment=\"Left\"\n                            Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                            Text=\"{Binding SubtitleText}\" />\n                    </StackPanel>\n                </StackPanel>\n                \n                <Button\n                    Grid.Column=\"1\"\n                    Classes=\"accent\"\n                    Content=\"{x:Static lang:Resources.Action_CheckForUpdates}\"\n                    Command=\"{Binding CheckForUpdatesCommand}\"\n                    HorizontalAlignment=\"Right\">\n                </Button>\n            </sg:SpacedGrid>\n\n            <!--  Auto updates toggle  -->\n            <ui:SettingsExpander \n                Description=\"Periodically checks for updates\" \n                Header=\"Notify when updates are ready to install\">\n                <ui:SettingsExpander.IconSource>\n                    <fluentIcons:SymbolIconSource Symbol=\"Megaphone\" />\n                </ui:SettingsExpander.IconSource>\n                <ui:SettingsExpander.Footer>\n                    <ToggleSwitch IsChecked=\"{Binding IsAutoCheckUpdatesEnabled}\" />\n                </ui:SettingsExpander.Footer>\n            </ui:SettingsExpander>\n\n            <!--  Channel radio buttons  -->\n            <ui:SettingsExpander Header=\"Preferred Update Channel\" IsExpanded=\"True\">\n                <ui:SettingsExpander.IconSource>\n                    <fluentIcons:SymbolIconSource Symbol=\"Branch\" />\n                </ui:SettingsExpander.IconSource>\n\n                <ui:SettingsExpanderItem>\n                    <ListBox\n                        SelectionChanged=\"ChannelListBox_OnSelectionChanged\"\n                        IsEnabled=\"{Binding IsAutoCheckUpdatesEnabled}\"\n                        ItemContainerTheme=\"{DynamicResource ListBoxItemBorderlessTheme}\"\n                        ItemsSource=\"{Binding AvailableUpdateChannelCards}\"\n                        SelectedItem=\"{Binding SelectedUpdateChannelCard}\">\n                        <ListBox.ItemTemplate>\n                            <DataTemplate DataType=\"{x:Type models:UpdateChannelCard}\">\n                                <RadioButton \n                                    GroupName=\"UpdateChannel\"\n                                    IsChecked=\"{Binding $parent[ListBoxItem].IsSelected}\" >\n                                    <StackPanel VerticalAlignment=\"Top\" HorizontalAlignment=\"Left\">\n                                        <StackPanel Orientation=\"Horizontal\" Spacing=\"5\">\n                                            <TextBlock \n                                                VerticalAlignment=\"Center\"\n                                                Text=\"{Binding DisplayName}\">\n                                                <TextBlock.Foreground>\n                                                    <MultiBinding Converter=\"{StaticResource BoolChoiceMultiConverter}\">\n                                                        <Binding Path=\"IsSelectable\" />\n                                                        <DynamicResource ResourceKey=\"TextFillColorPrimaryBrush\"/>\n                                                        <DynamicResource ResourceKey=\"TextFillColorTertiaryBrush\"/>\n                                                    </MultiBinding>\n                                                </TextBlock.Foreground>\n                                            </TextBlock>\n                                            <Ellipse\n                                                HorizontalAlignment=\"Left\"\n                                                VerticalAlignment=\"Center\"\n                                                IsVisible=\"{Binding LatestVersionString, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                                                Width=\"4\"\n                                                Height=\"4\"\n                                                Fill=\"{DynamicResource TextFillColorTertiaryBrush}\" />\n                                            <TextBlock\n                                                VerticalAlignment=\"Center\"\n                                                FontSize=\"13\"\n                                                Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                                IsVisible=\"{Binding LatestVersionString, Converter={x:Static StringConverters.IsNotNullOrEmpty}}\"\n                                                Text=\"{Binding LatestVersionString}\" />\n                                            \n                                            <!-- Update available icon -->\n                                            <fluentIcons:SymbolIcon\n                                                ToolTip.Tip=\"{x:Static lang:Resources.Label_UpdateAvailable}\"\n                                                IsVisible=\"{Binding IsLatestVersionUpdateable}\"\n                                                VerticalAlignment=\"Center\"\n                                                FontSize=\"16\"\n                                                Foreground=\"#3592c4\"\n                                                Symbol=\"ArrowCircleUpRight\" />\n                                        </StackPanel>\n\n                                        <TextBlock\n                                            FontSize=\"13\"\n                                            TextAlignment=\"Start\"\n                                            Margin=\"0,0,0,0\"\n                                            Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n                                            Text=\"{Binding Description}\"\n                                            Theme=\"{DynamicResource CaptionTextBlockStyle}\" />\n                                    </StackPanel>\n                                </RadioButton>\n                            </DataTemplate>\n                        </ListBox.ItemTemplate>\n                        <ListBox.ItemsPanel>\n                            <ItemsPanelTemplate>\n                                <StackPanel Spacing=\"8\" />\n                            </ItemsPanelTemplate>\n                        </ListBox.ItemsPanel>\n                    </ListBox>\n                </ui:SettingsExpanderItem>\n\n\n            </ui:SettingsExpander>\n\n        </StackPanel>\n    </ScrollViewer>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/Settings/UpdateSettingsPage.axaml.cs",
    "content": "﻿using System.Linq;\nusing Avalonia.Controls;\nusing Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels.Settings;\nusing StabilityMatrix.Core.Models.Update;\n\nnamespace StabilityMatrix.Avalonia.Views.Settings;\n\n[RegisterSingleton<UpdateSettingsPage>]\npublic partial class UpdateSettingsPage : UserControlBase\n{\n    public UpdateSettingsPage()\n    {\n        InitializeComponent();\n    }\n\n    private void ChannelListBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)\n    {\n        var listBox = (ListBox)sender!;\n\n        if (e.AddedItems.Count == 0 || e.AddedItems[0] is not UpdateChannelCard item)\n        {\n            return;\n        }\n\n        var vm = (UpdateSettingsViewModel)DataContext!;\n\n        if (!vm.VerifyChannelSelection(item))\n        {\n            listBox.Selection.Clear();\n\n            listBox.Selection.SelectedItem = vm.AvailableUpdateChannelCards.First(\n                c => c.UpdateChannel == UpdateChannel.Stable\n            );\n\n            vm.ShowLoginRequiredDialog();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/SettingsPage.axaml",
    "content": "<controls:UserControlBase\n    x:Class=\"StabilityMatrix.Avalonia.Views.SettingsPage\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n    xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n    xmlns:vmBase=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels.Base\"\n    xmlns:local=\"clr-namespace:StabilityMatrix.Avalonia\"\n    Focusable=\"True\"\n    d:DataContext=\"{x:Static mocks:DesignData.SettingsViewModel}\"\n    d:DesignHeight=\"700\"\n    d:DesignWidth=\"800\"\n    x:CompileBindings=\"True\"\n    x:DataType=\"vm:SettingsViewModel\"\n    mc:Ignorable=\"d\">\n    \n    <controls:UserControlBase.Resources>\n        <!-- Override styles for BreadcrumbBar -->\n        <!-- ReSharper disable Xaml.RedundantResource -->\n        <x:Double x:Key=\"BreadcrumbBarItemThemeFontSize\">24</x:Double>\n        <x:Double x:Key=\"BreadcrumbBarChevronFontSize\">17</x:Double>\n        <Thickness x:Key=\"BreadcrumbBarChevronPadding\">6,3</Thickness>\n        <FontWeight x:Key=\"BreadcrumbBarItemFontWeight\">Medium</FontWeight>\n        <!-- ReSharper restore Xaml.RedundantResource -->\n    </controls:UserControlBase.Resources>\n    \n    <Grid RowDefinitions=\"Auto,*\">\n        <ui:BreadcrumbBar\n            Grid.Row=\"0\"\n            Margin=\"16,8\"\n            x:Name=\"BreadcrumbBar\"\n            ItemsSource=\"{Binding CurrentPagePath}\">\n            <ui:BreadcrumbBar.ItemTemplate>\n                <DataTemplate x:DataType=\"vmBase:PageViewModelBase\">\n                    <ui:BreadcrumbBarItem Content=\"{Binding Title}\" />\n                </DataTemplate>\n            </ui:BreadcrumbBar.ItemTemplate>\n        </ui:BreadcrumbBar>\n        \n        <ui:Frame\n            Grid.Row=\"1\"\n            Name=\"FrameView\">\n            <ui:Frame.NavigationPageFactory>\n                <local:ViewLocator/>\n            </ui:Frame.NavigationPageFactory>\n        </ui:Frame>\n    </Grid>\n\n\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/SettingsPage.axaml.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Linq;\nusing Avalonia.Interactivity;\nusing Avalonia.Threading;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Media.Animation;\nusing FluentAvalonia.UI.Navigation;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.DependencyInjection;\nusing StabilityMatrix.Avalonia.Animations;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<SettingsPage>]\npublic partial class SettingsPage : UserControlBase, IHandleNavigation\n{\n    private readonly INavigationService<SettingsViewModel> settingsNavigationService;\n\n    private bool hasLoaded;\n\n    private SettingsViewModel ViewModel => (SettingsViewModel)DataContext!;\n\n    [DesignOnly(true)]\n    [Obsolete(\"For XAML use only\", true)]\n    public SettingsPage()\n        : this(App.Services.GetRequiredService<INavigationService<SettingsViewModel>>()) { }\n\n    public SettingsPage(INavigationService<SettingsViewModel> settingsNavigationService)\n    {\n        this.settingsNavigationService = settingsNavigationService;\n\n        InitializeComponent();\n\n        settingsNavigationService.SetFrame(FrameView);\n        settingsNavigationService.TypedNavigation += NavigationService_OnTypedNavigation;\n        FrameView.Navigated += FrameView_Navigated;\n        BreadcrumbBar.ItemClicked += BreadcrumbBar_ItemClicked;\n    }\n\n    /// <inheritdoc />\n    protected override void OnLoaded(RoutedEventArgs e)\n    {\n        base.OnLoaded(e);\n\n        if (!hasLoaded)\n        {\n            // Initial load, navigate to first page\n            Dispatcher.UIThread.Post(\n                () =>\n                    settingsNavigationService.NavigateTo(\n                        ViewModel.SubPages[0],\n                        new SuppressNavigationTransitionInfo()\n                    )\n            );\n\n            hasLoaded = true;\n        }\n    }\n\n    private void NavigationService_OnTypedNavigation(object? sender, TypedNavigationEventArgs e)\n    {\n        ViewModel.CurrentPage = ViewModel.SubPages.FirstOrDefault(x => x.GetType() == e.ViewModelType);\n    }\n\n    private async void FrameView_Navigated(object? sender, NavigationEventArgs args)\n    {\n        if (args.Content is not PageViewModelBase vm)\n        {\n            return;\n        }\n\n        ViewModel.CurrentPage = vm;\n    }\n\n    private async void BreadcrumbBar_ItemClicked(BreadcrumbBar sender, BreadcrumbBarItemClickedEventArgs args)\n    {\n        // Skip if already on same page\n        if (args.Item is not PageViewModelBase viewModel || viewModel == ViewModel.CurrentPage)\n        {\n            return;\n        }\n\n        settingsNavigationService.NavigateTo(viewModel, BetterSlideNavigationTransition.PageSlideFromLeft);\n    }\n\n    public bool GoBack()\n    {\n        return settingsNavigationService.GoBack();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml",
    "content": "﻿<controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n             xmlns:viewModels=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n             mc:Ignorable=\"d\" d:DesignWidth=\"800\" d:DesignHeight=\"450\"\n             x:DataType=\"viewModels:WorkflowsPageViewModel\"\n             x:Class=\"StabilityMatrix.Avalonia.Views.WorkflowsPage\">\n    <TabControl ItemsSource=\"{Binding Pages}\"\n                SelectedItem=\"{Binding SelectedPage}\"/>\n</controls:UserControlBase>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Avalonia.Controls;\n\nnamespace StabilityMatrix.Avalonia.Views;\n\n[RegisterSingleton<WorkflowsPage>]\npublic partial class WorkflowsPage : UserControlBase\n{\n    public WorkflowsPage()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia/app.manifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n  <!-- This manifest is used on Windows only.\n       Don't remove it as it might cause problems with window transparency and embeded controls.\n       For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->\n  <assemblyIdentity version=\"1.0.0.0\" name=\"StabilityMatrix.Avalonia.Desktop\"/>\n\n  <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n      <!-- A list of the Windows versions that this application has been tested on\n           and is designed to work with. Uncomment the appropriate elements\n           and Windows will automatically select the most compatible environment. -->\n\n      <!-- Windows 10 -->\n      <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" />\n    </application>\n  </compatibility>\n</assembly>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Controls/LogViewerControl.axaml",
    "content": "<UserControl x:Class=\"StabilityMatrix.Avalonia.Diagnostics.LogViewer.Controls.LogViewerControl\"\n             xmlns=\"https://github.com/avaloniaui\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:converters=\"clr-namespace:StabilityMatrix.Avalonia.Diagnostics.LogViewer.Converters\"\n             xmlns:logging=\"clr-namespace:StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging\"\n             x:CompileBindings=\"True\"\n             x:DataType=\"logging:ILogDataStoreImpl\"\n             mc:Ignorable=\"d\" d:DesignHeight=\"450\" d:DesignWidth=\"800\" >\n\n    <Grid RowDefinitions=\"*,Auto\">\n        <Grid.Resources>\n            <converters:ChangeColorTypeConverter x:Key=\"ColorConverter\" />\n            <converters:EventIdConverter x:Key=\"EventIdConverter\"/>\n            <SolidColorBrush x:Key=\"ColorBlack\">Black</SolidColorBrush>\n            <SolidColorBrush x:Key=\"ColorTransparent\">Transparent</SolidColorBrush>\n        </Grid.Resources>\n        <Grid.Styles>\n            <Style Selector=\"DataGridRow\">\n                <Setter Property=\"Padding\" Value=\"0\" />\n                <Setter Property=\"Foreground\" \n                        x:DataType=\"logging:LogModel\"\n                        Value=\"{Binding Color.Foreground, \n                        FallbackValue=White,\n                        Converter={StaticResource ColorConverter}, ConverterParameter={StaticResource ColorBlack}}\" />\n                <Setter Property=\"Background\"\n                        x:DataType=\"logging:LogModel\"\n                        Value=\"{Binding Color.Background,\n                        FallbackValue=Black,\n                        Converter={StaticResource ColorConverter}, ConverterParameter={StaticResource ColorTransparent}}\" />\n            </Style>    \n            <Style Selector=\"DataGridCell.size\">\n                <Setter Property=\"FontSize\" Value=\"13\" />\n                <Setter Property=\"Padding\" Value=\"0\" />\n            </Style>\n        </Grid.Styles>\n        <DataGrid x:Name=\"MyDataGrid\"\n                  ItemsSource=\"{Binding DataStore.Entries}\" AutoGenerateColumns=\"False\"\n                  CanUserResizeColumns=\"True\"\n                  CanUserReorderColumns=\"True\"\n                  CanUserSortColumns=\"False\"\n                  LayoutUpdated=\"OnLayoutUpdated\">\n            \n            <DataGrid.Styles>\n                <Style Selector=\"TextBlock\">\n                    <Setter Property=\"TextWrapping\" Value=\"WrapWithOverflow\" />\n                </Style>\n            </DataGrid.Styles>\n            \n            <DataGrid.Columns>\n                <DataGridTextColumn CellStyleClasses=\"size\" Header=\"Time\" Width=\"Auto\"  Binding=\"{Binding Timestamp}\" IsVisible=\"{Binding #IsTimestampVisible.IsChecked}\"/>\n                <DataGridTextColumn CellStyleClasses=\"size\" Header=\"Level\" Width=\"Auto\" Binding=\"{Binding LogLevel}\" />\n                <!--<DataGridTextColumn CellStyleClasses=\"size\" Header=\"Event Id\" Width=\"120\" Binding=\"{Binding EventId, Converter={StaticResource EventIdConverter}}\" />-->\n                <DataGridTextColumn CellStyleClasses=\"size\" Header=\"Callsite\" Width=\"Auto\" Binding=\"{Binding LoggerDisplayName}\" />\n                <DataGridTextColumn CellStyleClasses=\"size\" Header=\"State\" Width=\"*\" Binding=\"{Binding State}\" />\n                <DataGridTextColumn CellStyleClasses=\"size\" Header=\"Exception\" Width=\"Auto\" Binding=\"{Binding Exception}\" />\n            </DataGrid.Columns>\n        </DataGrid>\n\n        <StackPanel Grid.Row=\"1\" Margin=\"20 10\" Orientation=\"Horizontal\">\n            <CheckBox x:Name=\"CanAutoScroll\"\n                      FontSize=\"11\"\n                      Content=\"Auto Scroll log\"\n                      IsChecked=\"True\"/>\n            <CheckBox x:Name=\"IsTimestampVisible\"\n                      FontSize=\"11\"\n                      Content=\"Show Timestamp\"/>\n        </StackPanel>\n\n\n    </Grid>\n\n</UserControl>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Controls/LogViewerControl.axaml.cs",
    "content": "using System.Collections.Specialized;\nusing Avalonia.Controls;\nusing Avalonia.LogicalTree;\nusing Avalonia.Threading;\nusing StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Controls;\n\npublic partial class LogViewerControl : UserControl\n{\n    public LogViewerControl() => InitializeComponent();\n\n    private ILogDataStoreImpl? vm;\n    private LogModel? item;\n\n    protected override void OnDataContextChanged(EventArgs e)\n    {\n        base.OnDataContextChanged(e);\n\n        if (DataContext is null)\n            return;\n\n        vm = (ILogDataStoreImpl)DataContext;\n        vm.DataStore.Entries.CollectionChanged += OnCollectionChanged;\n    }\n\n    private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)\n    {\n        Dispatcher.UIThread.Post(() =>\n        {\n            item = MyDataGrid.ItemsSource.Cast<LogModel>().LastOrDefault();\n        });\n    }\n\n    protected void OnLayoutUpdated(object? sender, EventArgs e)\n    {\n        if (CanAutoScroll.IsChecked != true || item is null)\n            return;\n\n        MyDataGrid.ScrollIntoView(item, null);\n        item = null;\n    }\n\n    protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)\n    {\n        base.OnDetachedFromLogicalTree(e);\n\n        if (vm is null)\n            return;\n        vm.DataStore.Entries.CollectionChanged -= OnCollectionChanged;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/ChangeColorTypeConverter.cs",
    "content": "﻿using System.Globalization;\nusing Avalonia.Data.Converters;\nusing Avalonia.Media;\nusing SysDrawColor = System.Drawing.Color;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Converters;\n\npublic class ChangeColorTypeConverter : IValueConverter\n{\n    public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is null)\n            return new SolidColorBrush((Color)(parameter ?? Colors.Black));\n\n        var sysDrawColor = (SysDrawColor)value!;\n        return new SolidColorBrush(\n            Color.FromArgb(sysDrawColor.A, sysDrawColor.R, sysDrawColor.G, sysDrawColor.B)\n        );\n    }\n\n    public object ConvertBack(\n        object? value,\n        Type targetType,\n        object? parameter,\n        CultureInfo culture\n    ) => throw new NotImplementedException();\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Converters/EventIdConverter.cs",
    "content": "﻿using System.Globalization;\nusing Avalonia.Data.Converters;\nusing Microsoft.Extensions.Logging;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Converters;\n\npublic class EventIdConverter : IValueConverter\n{\n    public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is null)\n            return \"0\";\n\n        var eventId = (EventId)value;\n\n        return eventId.ToString();\n    }\n\n    // If not implemented, an error is thrown\n    public object ConvertBack(\n        object? value,\n        Type targetType,\n        object? parameter,\n        CultureInfo culture\n    ) => new EventId(0, value?.ToString() ?? string.Empty);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Extensions/LoggerExtensions.cs",
    "content": "﻿using Microsoft.Extensions.Logging;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Extensions;\n\npublic static class LoggerExtensions\n{\n    public static void Emit(\n        this ILogger logger,\n        EventId eventId,\n        LogLevel logLevel,\n        string message,\n        Exception? exception = null,\n        params object?[] args\n    )\n    {\n        if (logger is null)\n            return;\n\n        //if (!logger.IsEnabled(logLevel))\n        //    return;\n\n        switch (logLevel)\n        {\n            case LogLevel.Trace:\n                logger.LogTrace(eventId, message, args);\n                break;\n\n            case LogLevel.Debug:\n                logger.LogDebug(eventId, message, args);\n                break;\n\n            case LogLevel.Information:\n                logger.LogInformation(eventId, message, args);\n                break;\n\n            case LogLevel.Warning:\n                logger.LogWarning(eventId, exception, message, args);\n                break;\n\n            case LogLevel.Error:\n                logger.LogError(eventId, exception, message, args);\n                break;\n\n            case LogLevel.Critical:\n                logger.LogCritical(eventId, exception, message, args);\n                break;\n        }\n    }\n\n    public static void TestPattern(this ILogger logger, EventId eventId)\n    {\n        var exception = new Exception(\"Test Error Message\");\n\n        logger.Emit(eventId, LogLevel.Trace, \"Trace Test Pattern\");\n        logger.Emit(eventId, LogLevel.Debug, \"Debug Test Pattern\");\n        logger.Emit(eventId, LogLevel.Information, \"Information Test Pattern\");\n        logger.Emit(eventId, LogLevel.Warning, \"Warning Test Pattern\");\n        logger.Emit(eventId, LogLevel.Error, \"Error Test Pattern\", exception);\n        logger.Emit(eventId, LogLevel.Critical, \"Critical Test Pattern\", exception);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/DataStoreLoggerConfiguration.cs",
    "content": "﻿using System.Drawing;\nusing Microsoft.Extensions.Logging;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging;\n\npublic class DataStoreLoggerConfiguration\n{\n    #region Properties\n\n    public EventId EventId { get; set; }\n\n    public Dictionary<LogLevel, LogEntryColor> Colors { get; } =\n        new()\n        {\n            [LogLevel.Trace] = new LogEntryColor { Foreground = Color.DarkGray },\n            [LogLevel.Debug] = new LogEntryColor { Foreground = Color.Gray },\n            [LogLevel.Information] = new LogEntryColor { Foreground = Color.WhiteSmoke, },\n            [LogLevel.Warning] = new LogEntryColor { Foreground = Color.Orange },\n            [LogLevel.Error] = new LogEntryColor\n            {\n                Foreground = Color.White,\n                Background = Color.OrangeRed\n            },\n            [LogLevel.Critical] = new LogEntryColor\n            {\n                Foreground = Color.White,\n                Background = Color.Red\n            },\n            [LogLevel.None] = new LogEntryColor { Foreground = Color.Magenta }\n        };\n\n    #endregion\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/ILogDataStore.cs",
    "content": "﻿using System.Collections.ObjectModel;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging;\n\npublic interface ILogDataStore\n{\n    ObservableCollection<LogModel> Entries { get; }\n    void AddEntry(LogModel logModel);\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/ILogDataStoreImpl.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging;\n\npublic interface ILogDataStoreImpl\n{\n    public ILogDataStore DataStore { get; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogDataStore.cs",
    "content": "﻿using System.Collections.ObjectModel;\nusing Avalonia.Threading;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging;\n\npublic class LogDataStore : ILogDataStore\n{\n    public static LogDataStore Instance { get; } = new();\n\n    #region Fields\n\n    private static readonly SemaphoreSlim _semaphore = new(initialCount: 1);\n\n    #endregion\n\n    #region Properties\n\n    public ObservableCollection<LogModel> Entries { get; } = new();\n\n    #endregion\n\n    #region Methods\n\n    public virtual void AddEntry(LogModel logModel)\n    {\n        // ensure only one operation at time from multiple threads\n        _semaphore.Wait();\n\n        Dispatcher.UIThread.Post(() =>\n        {\n            Entries.Add(logModel);\n        });\n\n        _semaphore.Release();\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogEntryColor.cs",
    "content": "﻿using System.Drawing;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging;\n\npublic class LogEntryColor\n{\n    public LogEntryColor() { }\n\n    public LogEntryColor(Color foreground, Color background)\n    {\n        Foreground = foreground;\n        Background = background;\n    }\n\n    public Color Foreground { get; set; } = Color.Black;\n    public Color Background { get; set; } = Color.Transparent;\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/Logging/LogModel.cs",
    "content": "﻿using Microsoft.Extensions.Logging;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging;\n\npublic class LogModel\n{\n    #region Properties\n\n    public DateTime Timestamp { get; set; }\n\n    public LogLevel LogLevel { get; set; }\n\n    public EventId EventId { get; set; }\n\n    public object? State { get; set; }\n\n    public string? LoggerName { get; set; }\n\n    public string? CallerClassName { get; set; }\n\n    public string? CallerMemberName { get; set; }\n\n    public string? Exception { get; set; }\n\n    public LogEntryColor? Color { get; set; }\n\n    #endregion\n\n    public string LoggerDisplayName =>\n        LoggerName?.Split('.', StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? \"\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/LogViewerControlViewModel.cs",
    "content": "﻿using StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels;\n\npublic class LogViewerControlViewModel : ViewModel, ILogDataStoreImpl\n{\n    #region Constructor\n\n    public LogViewerControlViewModel(ILogDataStore dataStore)\n    {\n        DataStore = dataStore;\n    }\n\n    #endregion\n\n    #region Properties\n\n    public ILogDataStore DataStore { get; set; }\n\n    #endregion\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ObservableObject.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Runtime.CompilerServices;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels;\n\npublic class ObservableObject : INotifyPropertyChanged\n{\n    protected bool Set<TValue>(\n        ref TValue field,\n        TValue newValue,\n        [CallerMemberName] string? propertyName = null\n    )\n    {\n        if (EqualityComparer<TValue>.Default.Equals(field, newValue))\n            return false;\n        field = newValue;\n        OnPropertyChanged(propertyName);\n\n        return true;\n    }\n\n    public event PropertyChangedEventHandler? PropertyChanged;\n\n    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Core/ViewModels/ViewModel.cs",
    "content": "﻿namespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels;\n\npublic class ViewModel\n    : ObservableObject { /* skip */\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/DataStoreLoggerTarget.cs",
    "content": "﻿using System.Diagnostics;\nusing Microsoft.Extensions.Logging;\nusing NLog;\nusing NLog.Targets;\nusing StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging;\nusing MsLogLevel = Microsoft.Extensions.Logging.LogLevel;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer;\n\n[Target(\"DataStoreLogger\")]\npublic class DataStoreLoggerTarget : TargetWithLayout\n{\n    #region Fields\n\n    private ILogDataStore? _dataStore;\n    private DataStoreLoggerConfiguration? _config;\n\n    #endregion\n\n    #region methods\n\n    protected override void InitializeTarget()\n    {\n        // we need to inject dependencies\n        // var serviceProvider = ResolveService<IServiceProvider>();\n\n        // reference the shared instance\n        _dataStore = LogDataStore.Instance;\n        // _dataStore = serviceProvider.GetRequiredService<ILogDataStore>();\n\n        // load the config options\n        /*var options\n            = serviceProvider.GetService<IOptionsMonitor<DataStoreLoggerConfiguration>>();*/\n\n        // _config = options?.CurrentValue ?? new DataStoreLoggerConfiguration();\n        _config = new DataStoreLoggerConfiguration();\n\n        base.InitializeTarget();\n    }\n\n    protected override void Write(LogEventInfo logEvent)\n    {\n        // cast NLog Loglevel to Microsoft LogLevel type\n        var logLevel = (MsLogLevel)Enum.ToObject(typeof(MsLogLevel), logEvent.Level.Ordinal);\n\n        // format the message\n        var message = RenderLogEvent(Layout, logEvent);\n\n        // retrieve the EventId\n        logEvent.Properties.TryGetValue(\"EventId\", out var result);\n        if (result is not EventId eventId)\n        {\n            eventId = _config!.EventId;\n        }\n\n        // add log entry\n        _dataStore?.AddEntry(\n            new LogModel\n            {\n                Timestamp = DateTime.UtcNow,\n                LogLevel = logLevel,\n                // do we override the default EventId if it exists?\n                EventId =\n                    eventId.Id == 0 && (_config?.EventId.Id ?? 0) != 0 ? _config!.EventId : eventId,\n                State = message,\n                LoggerName = logEvent.LoggerName,\n                CallerClassName = logEvent.CallerClassName,\n                CallerMemberName = logEvent.CallerMemberName,\n                Exception =\n                    logEvent.Exception?.Message ?? (logLevel == MsLogLevel.Error ? message : \"\"),\n                Color = _config!.Colors[logLevel],\n            }\n        );\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Extensions/ServicesExtension.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\nusing NLog;\nusing StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.Logging;\nusing StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels;\nusing LogDataStore = StabilityMatrix.Avalonia.Diagnostics.LogViewer.Logging.LogDataStore;\nusing MsLogLevel = Microsoft.Extensions.Logging.LogLevel;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Extensions;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic static class ServicesExtension\n{\n    public static IServiceCollection AddLogViewer(this IServiceCollection services)\n    {\n        services.AddSingleton<ILogDataStore>(Core.Logging.LogDataStore.Instance);\n        services.AddSingleton<LogViewerControlViewModel>();\n\n        return services;\n    }\n\n    public static IServiceCollection AddLogViewer(\n        this IServiceCollection services,\n        Action<DataStoreLoggerConfiguration> configure\n    )\n    {\n        services.AddSingleton<ILogDataStore>(Core.Logging.LogDataStore.Instance);\n        services.AddSingleton<LogViewerControlViewModel>();\n        services.Configure(configure);\n\n        return services;\n    }\n\n    public static ILoggingBuilder AddNLogTargets(\n        this ILoggingBuilder builder,\n        IConfiguration config\n    )\n    {\n        LogManager\n            .Setup()\n            // Register custom Target\n            .SetupExtensions(\n                extensionBuilder =>\n                    extensionBuilder.RegisterTarget<DataStoreLoggerTarget>(\"DataStoreLogger\")\n            );\n\n        /*builder\n            .ClearProviders()\n            .SetMinimumLevel(MsLogLevel.Trace)\n            // Load NLog settings from appsettings*.json\n            .AddNLog(config,\n                // custom options for capturing the EventId information\n                new NLogProviderOptions\n                {\n                    // https://nlog-project.org/2021/08/25/nlog-5-0-preview1-ready.html#nlogextensionslogging-changes-capture-of-eventid\n                    IgnoreEmptyEventId = false,\n                    CaptureEventId = EventIdCaptureType.Legacy\n                });*/\n\n        return builder;\n    }\n\n    public static ILoggingBuilder AddNLogTargets(\n        this ILoggingBuilder builder,\n        IConfiguration config,\n        Action<DataStoreLoggerConfiguration> configure\n    )\n    {\n        builder.AddNLogTargets(config);\n        builder.Services.Configure(configure);\n        return builder;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Graeme Grant\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/Logging/LogDataStore.cs",
    "content": "﻿using Avalonia.Threading;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.LogViewer.Logging;\n\npublic class LogDataStore : Core.Logging.LogDataStore\n{\n    #region Methods\n\n    public override async void AddEntry(Core.Logging.LogModel logModel) =>\n        await Dispatcher.UIThread.InvokeAsync(() => base.AddEntry(logModel));\n\n    #endregion\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/LogViewer/README.md",
    "content": "## LogViewer\n\nSource code in the `StabilityMatrix.Avalonia.Diagnostics.LogViewer `namespace is included from [CodeProject](https://www.codeproject.com/Articles/5357417/LogViewer-Control-for-WinForms-WPF-and-Avalonia-in) under the [MIT License](LICENSE)."
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n    <Import Project=\"..\\ConditionalSymbols.props\" />\n    <Import Project=\"..\\Runtimes.Default.props\" />\n    \n    <PropertyGroup>\n        <BuiltInComInteropSupport>true</BuiltInComInteropSupport>\n        <EnableWindowsTargeting>true</EnableWindowsTargeting>\n    </PropertyGroup>\n    \n    <ItemGroup>\n        <InternalsVisibleTo Include=\"StabilityMatrix.Tests\" />\n    </ItemGroup>\n    \n    <ItemGroup>\n      <Folder Include=\"LogViewer\\Core\\\" />\n      <Folder Include=\"Views\\\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <PackageReference Include=\"Avalonia\" />\n      <PackageReference Include=\"Avalonia.Controls.DataGrid\" />\n      <PackageReference Include=\"DotNet.Bundle\" />\n      <PackageReference Include=\"Microsoft.Extensions.DependencyInjection\" />\n      <PackageReference Include=\"Microsoft.Extensions.Hosting\" />\n      <PackageReference Include=\"Microsoft.Extensions.Logging.Abstractions\" />\n      <PackageReference Include=\"NLog\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <Compile Update=\"Views\\LogWindow.axaml.cs\">\n        <DependentUpon>LogWindow.axaml</DependentUpon>\n      </Compile>\n    </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/ViewModels/LogWindowViewModel.cs",
    "content": "﻿using Microsoft.Extensions.DependencyInjection;\nusing StabilityMatrix.Avalonia.Diagnostics.LogViewer.Core.ViewModels;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.ViewModels;\n\npublic class LogWindowViewModel\n{\n    public LogViewerControlViewModel LogViewer { get; }\n\n    public LogWindowViewModel(LogViewerControlViewModel logViewer)\n    {\n        LogViewer = logViewer;\n    }\n\n    public static LogWindowViewModel FromServiceProvider(IServiceProvider services)\n    {\n        return new LogWindowViewModel(services.GetRequiredService<LogViewerControlViewModel>());\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/Views/LogWindow.axaml",
    "content": "﻿<Window\n    x:Class=\"StabilityMatrix.Avalonia.Diagnostics.Views.LogWindow\"\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Diagnostics.LogViewer.Controls\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.Diagnostics.ViewModels\"\n    Title=\"Log Viewer\"\n    Width=\"900\"\n    Height=\"750\"\n    Focusable=\"True\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    x:CompileBindings=\"True\"\n    x:DataType=\"vm:LogWindowViewModel\"\n    WindowStartupLocation=\"CenterScreen\"\n    mc:Ignorable=\"d\">\n\n    <controls:LogViewerControl DataContext=\"{Binding LogViewer}\" />\n\n</Window>\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.Diagnostics/Views/LogWindow.axaml.cs",
    "content": "using Avalonia.Controls;\nusing Avalonia.Input;\nusing Avalonia.Interactivity;\nusing StabilityMatrix.Avalonia.Diagnostics.ViewModels;\n\nnamespace StabilityMatrix.Avalonia.Diagnostics.Views;\n\npublic partial class LogWindow : Window\n{\n    public LogWindow()\n    {\n        InitializeComponent();\n    }\n\n    public static IDisposable Attach(TopLevel root, IServiceProvider serviceProvider)\n    {\n        return Attach(root, serviceProvider, new KeyGesture(Key.F11));\n    }\n\n    public static IDisposable Attach(\n        TopLevel root,\n        IServiceProvider serviceProvider,\n        KeyGesture gesture\n    )\n    {\n        return (root ?? throw new ArgumentNullException(nameof(root))).AddDisposableHandler(\n            KeyDownEvent,\n            PreviewKeyDown,\n            RoutingStrategies.Tunnel\n        );\n\n        void PreviewKeyDown(object? sender, KeyEventArgs e)\n        {\n            if (gesture.Matches(e))\n            {\n                var window = new LogWindow()\n                {\n                    DataContext = LogWindowViewModel.FromServiceProvider(serviceProvider)\n                };\n                window.Show();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Avalonia.pupnet.conf",
    "content": "################################################################################\n# PUPNET DEPLOY: 1.4.0\n################################################################################\n\n########################################\n# APP PREAMBLE\n########################################\n\n# Mandatory application base name. This MUST BE the base name of the main executable file. It should NOT\n# include any directory part or extension, i.e. do not append '.exe' or '.dll'. It should not contain\n# spaces or invalid filename characters.\nAppBaseName = StabilityMatrix.Avalonia\n\n# Mandatory application friendly name.\nAppFriendlyName = Stability Matrix\n\n# Mandatory application ID in reverse DNS form. This should stay constant for lifetime of the software.\nAppId = zone.lykos.stabilitymatrix\n\n# Mandatory application version and package release of form: 'VERSION[RELEASE]'. Use optional square\n# brackets to denote package release, i.e. '1.2.3[1]'. Release refers to a change to the deployment\n# package, rather the application. If release part is absent (i.e. '1.2.3'), the release value defaults\n# to '1'. Note that the version-release value given here may be overridden from the command line.\nAppVersionRelease = 2.0.0[1]\n\n# Mandatory single line application short summary description.\nAppShortSummary = Package and checkpoint manager for Stable Diffusion.\n\n# Optional multi-line (surround with triple \"\"\" quotes) application description which may provide\n# longer text than AppShortSummary. Text separated by an empty line will be treated as paragraphs\n# (complex formatting should be avoided). The content is used by package builders where supported,\n# including RPM and DEB, and may optionally be used to populate the '<description>' element in the\n# AppStream metadata through the use of a macro variable.\nAppDescription =\n\n# Mandatory application license ID. This should be one of the recognised SPDX license\n# identifiers, such as: 'MIT', 'GPL-3.0-or-later' or 'Apache-2.0'. For a proprietary or\n# custom license, use 'LicenseRef-Proprietary' or 'LicenseRef-LICENSE'.\nAppLicenseId = LicenseRef-Proprietary\n\n# Optional path to application copyright/license text file. If provided, it will be packaged with the\n# application and used with package builders where supported.\nAppLicenseFile = LICENSE\n\n# Optional path to application changelog file. IMPORTANT. If given, this file should contain version\n# information in a predefined format. Namely, it should contain one or more version headings of form:\n# '+ VERSION;DATE', under which are to be listed change items of form: '- Change description'. Formatted\n# information will be parsed and used to populate AppStream metadata. Additionally, it will be packaged\n# with the application and used with package builders where supported. NOTE. Superfluous text in the file\n# is ignored, so the file may also contain README information.\n# For information: https://github.com/kuiperzone/PupNet-Deploy.\nAppChangeFile =\n\n########################################\n# PUBLISHER\n########################################\n\n# Mandatory publisher, group or creator.\nPublisherName = Lykos\n\n# Optional copyright statement.\nPublisherCopyright = Copyright (C) Lykos 2023\n\n# Optional publisher or application web-link name. Note that Windows Setup packages\n# require both PublisherLinkName and PublisherLinkUrl in order to include the link as\n# an item in program menu entries. Do not modify name, as may leave old entries in updated installations.\nPublisherLinkName = Home Page\n\n# Optional publisher or application web-link URL.\nPublisherLinkUrl = https://lykos.ai\n\n# Publisher or maintainer email contact. Although optional, some package builders (i.e. DEB) require it\n# and may warn or fail unless provided.\nPublisherEmail = stability-matrix@lykos.ai\n\n########################################\n# DESKTOP INTEGRATION\n########################################\n\n# Boolean (true or false) which indicates whether the application is hidden on the desktop. It is used to\n# populate the 'NoDisplay' field of the .desktop file. The default is false. Setting to true will also\n# cause the main application start menu entry to be omitted for Windows Setup.\nDesktopNoDisplay = false\n\n# Boolean (true or false) which indicates whether the application runs in the terminal, rather than\n# providing a GUI. It is used to populate the 'Terminal' field of the .desktop file.\nDesktopTerminal = false\n\n# Optional path to a Linux desktop file. If empty (default), one will be generated automatically from\n# the information in this file. Supplying a custom file, however, allows for mime-types and\n# internationalisation. If supplied, the file MUST contain the line: 'Exec=${INSTALL_EXEC}'\n# in order to use the correct install location. Other macros may be used to help automate the content.\n# Note. PupNet Deploy can generate you a desktop file. Use --help and 'pupnet --help macro' for reference.\n# See: https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html\nDesktopFile =\n\n# Optional command name to start the application from the terminal. If, for example, AppBaseName is\n# 'Zone.Kuiper.HelloWorld', the value here may be set to a simpler and/or lower-case variant such as\n# 'helloworld'. It must not contain spaces or invalid filename characters. Do not add any extension such\n# as '.exe'. If empty, the application will not be in the path and cannot be started from the command line.\n# For Windows Setup packages, see also SetupCommandPrompt. StartCommand is not\n# supported for all packages kinds (i.e. Flatpak). Default is empty (none).\nStartCommand = stabilitymatrix\n\n# Optional category for the application. The value should be one of the recognised Freedesktop top-level\n# categories, such as: Audio, Development, Game, Office, Utility etc. Only a single value should be\n# provided here which will be used, where supported, to populate metadata. The default is empty.\n# See: https://specifications.freedesktop.org/menu-spec/latest/apa.html\nPrimeCategory = Utility\n\n# Path to AppStream metadata file. It is optional, but recommended as it is used by software centers.\n# Note. The contents of the files may use macro variables. Use 'pupnet --help macro' for reference.\n# See: https://docs.appimage.org/packaging-guide/optional/appstream.html\nMetaFile =\n\n# Optional icon file paths. The value may include multiple filenames separated with semicolon or given\n# in multi-line form. Valid types are SVG, PNG and ICO (ICO ignored on Linux). Note that the inclusion\n# of a scalable SVG is preferable on Linux, whereas PNGs must be one of the standard sizes and MUST\n# include the size in the filename in the form: name.32x32.png' or 'name.32.png'.\nIconFiles = \"\"\"\n    StabilityMatrix.Avalonia/Assets/Icon.512x512.png\n    StabilityMatrix.Avalonia/Assets/Icon.ico\n\"\"\"\n\n########################################\n# DOTNET PUBLISH\n########################################\n\n# Optional path relative to this file in which to find the dotnet project (.csproj) or solution (.sln)\n# file, or the directory containing it. If empty (default), a single project or solution file is\n# expected under the same directory as this file. IMPORTANT. If set to 'NONE', dotnet publish\n# is disabled (not called). Instead, only DotnetPostPublish is called.\nDotnetProjectPath = StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj\n\n# Optional arguments supplied to 'dotnet publish'. Do NOT include '-r' (runtime), or '-c' (configuration)\n# here as they will be added according to command line arguments. Typically you want as a minimum:\n# '-p:Version=${APP_VERSION} --self-contained true'. Additional useful arguments include:\n# '-p:DebugType=None -p:DebugSymbols=false -p:PublishSingleFile=true -p:PublishReadyToRun=true\n# -p:PublishTrimmed=true -p:TrimMode=link'. Note. This value may use macro variables. Use 'pupnet --help macro'\n# for reference. See: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish\nDotnetPublishArgs = -p:Version=${APP_VERSION} -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:DebugType=None -p:DebugSymbols=false --self-contained\n\n# Post-publish (or standalone build) command on Linux (ignored on Windows). It is called after dotnet\n# publish, but before the final output is built. This could, for example, be a script which copies\n# additional files into the build directory given by ${BUILD_APP_BIN}. The working directory will be\n# the location of this file. This value is optional, but becomes mandatory if DotnetProjectPath equals\n# 'NONE'. Note. This value may use macro variables. Additionally, scripts may use these as environment\n# variables. Use 'pupnet --help macro' for reference.\nDotnetPostPublish =\n\n# Post-publish (or standalone build) command on Windows (ignored on Linux). This should perform\n# the equivalent operation, as required, as DotnetPostPublish, but using DOS commands and batch\n# scripts. Multiple commands may be specified, separated by semicolon or given in multi-line form.\n# Note. This value may use macro variables. Additionally, scripts may use these as environment\n# variables. Use 'pupnet --help macro' for reference.\nDotnetPostPublishOnWindows =\n\n########################################\n# PACKAGE OUTPUT\n########################################\n\n# Optional package name (excludes version etc.). If empty, defaults to AppBaseName. However, it is\n# used not only to specify the base output filename, but to identify the application in DEB and RPM\n# packages. You may wish, therefore, to ensure that the value represents a unique name. Naming\n# requirements are strict and must contain only alpha-numeric and '-', '+' and '.' characters.\nPackageName = StabilityMatrix\n\n# Output directory, or subdirectory relative to this file. It will be created if it does not exist and\n# will contain the final deploy output files. If empty, it defaults to the location of this file.\nOutputDirectory = Release/linux-x64\n\n########################################\n# APPIMAGE OPTIONS\n########################################\n\n# Additional arguments for use with appimagetool. Useful for signing. Default is empty.\nAppImageArgs =\n\n# Boolean (true or false) which sets whether to include the application version in the AppImage filename,\n# i.e. 'HelloWorld-1.2.3-x86_64.AppImage'. Default is false. It is ignored if the output filename is\n# specified at command line.\nAppImageVersionOutput = false\n\n########################################\n# FLATPAK OPTIONS\n########################################\n\n# The runtime platform. Invariably for .NET (inc. Avalonia), this should be 'org.freedesktop.Platform'.\n# Refer: https://docs.flatpak.org/en/latest/available-runtimes.html\nFlatpakPlatformRuntime = org.freedesktop.Platform\n\n# The platform SDK. Invariably for .NET (inc. Avalonia applications) this should be 'org.freedesktop.Sdk'.\n# The SDK must be installed on the build system.\nFlatpakPlatformSdk = org.freedesktop.Sdk\n\n# The platform runtime version. The latest available version may change periodically.\n# Refer to Flatpak documentation.\nFlatpakPlatformVersion = 22.08\n\n# Flatpak manifest 'finish-args' sandbox permissions. Optional, but if empty, the application will have\n# extremely limited access to the host environment. This option may be used to grant required\n# application permissions. Values here should be prefixed with '--' and separated by semicolon or given\n# in multi-line form. Refer: https://docs.flatpak.org/en/latest/sandbox-permissions.html\nFlatpakFinishArgs = \"\"\"\n    --socket=wayland\n    --socket=x11\n    --filesystem=host\n    --share=network\n\"\"\"\n\n# Additional arguments for use with flatpak-builder. Useful for signing. Default is empty.\n# See flatpak-builder --help.\nFlatpakBuilderArgs =\n\n########################################\n# RPM OPTIONS\n########################################\n\n# Boolean (true or false) which specifies whether to build the RPM package with 'AutoReq' equal to yes or no.\n# For dotnet application, the value should typically be false, but see RpmRequires below.\n# Refer: https://rpm-software-management.github.io/rpm/manual/spec.html\nRpmAutoReq = false\n\n# Boolean (true or false) which specifies whether to build the RPM package with 'AutoProv' equal to yes or no.\n# Refer: https://rpm-software-management.github.io/rpm/manual/spec.html\nRpmAutoProv = true\n\n# Optional list of RPM dependencies. The list may include multiple values separated with semicolon or given\n# in multi-line form. If empty, a self-contained dotnet package will successfully run on many (but not all)\n# Linux distros. In some cases, it will be necessary to explicitly specify additional dependencies.\n# Default values are recommended for use with dotnet and RPM packages at the time of writing.\n# For updated information, see: https://learn.microsoft.com/en-us/dotnet/core/install/linux-rhel#dependencies\nRpmRequires = \"\"\"\n    krb5-libs\n    libicu\n    openssl-libs\n    zlib\n\"\"\"\n\n########################################\n# DEBIAN OPTIONS\n########################################\n\n# Optional list of Debian dependencies. The list may include multiple values separated with semicolon or given\n# in multi-line form. If empty, a self-contained dotnet package will successfully run on many (but not all)\n# Linux distros. In some cases, it will be necessary to explicitly specify additional dependencies.\n# Default values are recommended for use with dotnet and Debian packages at the time of writing.\n# For updated information, see: https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu#dependencies\nDebianRecommends = \"\"\"\n    libc6\n    libgcc1\n    libgcc-s1\n    libgssapi-krb5-2\n    libicu\n    libssl\n    libstdc++6\n    libunwind\n    zlib1g\n\"\"\"\n\n########################################\n# WINDOWS SETUP OPTIONS\n########################################\n\n# Boolean (true or false) which specifies whether the application is to be installed in administrative\n# mode, or per-user. Default is false. See: https://jrsoftware.org/ishelp/topic_admininstallmode.htm\nSetupAdminInstall = false\n\n# Optional command prompt title. The Windows installer will NOT add your application to the path. However,\n# if your package contains a command-line utility, setting this value will ensure that a 'Command Prompt'\n# program menu entry is added (with this title) which, when launched, will open a dedicated command\n# window with your application directory in its path. Default is empty. See also StartCommand.\nSetupCommandPrompt = Command Prompt\n\n# Mandatory value which specifies minimum version of Windows that your software runs on. Windows 8 = 6.2,\n# Windows 10/11 = 10. Default: 10. See: https://jrsoftware.org/ishelp/topic_setup_minversion.htm\nSetupMinWindowsVersion = 10\n\n# Optional name and parameters of the Sign Tool to be used to digitally sign: the installer,\n# uninstaller, and contained exe and dll files. If empty, files will not be signed.\n# See: https://jrsoftware.org/ishelp/topic_setup_signtool.htm\nSetupSignTool =\n\n# Optional suffix for the installer output filename. The default is empty, but you may wish set it to:\n# 'Setup' or similar. This, for example, will output a file of name: HelloWorldSetup-x86_64.exe\n# Ignored if the output filename is specified at command line.\nSetupSuffixOutput =\n\n# Boolean (true or false) which sets whether to include the application version in the setup filename,\n# i.e. 'HelloWorld-1.2.3-x86_64.exe'. Default is false. Ignored if the output filename is specified\n# at command line.\nSetupVersionOutput = false\n"
  },
  {
    "path": "StabilityMatrix.Core/Animation/GifConverter.cs",
    "content": "﻿using KGySoft.Drawing.Imaging;\nusing KGySoft.Drawing.SkiaSharp;\nusing SkiaSharp;\n\nnamespace StabilityMatrix.Core.Animation;\n\npublic class GifConverter\n{\n    public static IEnumerable<IReadableBitmapData> EnumerateAnimatedWebp(Stream webpSource)\n    {\n        using var webp = new SKManagedStream(webpSource);\n        using var codec = SKCodec.Create(webp);\n\n        var info = new SKImageInfo(codec.Info.Width, codec.Info.Height);\n\n        for (var i = 0; i < codec.FrameCount; i++)\n        {\n            using var tempSurface = new SKBitmap(info);\n\n            codec.GetFrameInfo(i, out var frameInfo);\n\n            var decodeInfo = info.WithAlphaType(frameInfo.AlphaType);\n\n            tempSurface.TryAllocPixels(decodeInfo);\n\n            var result = codec.GetPixels(decodeInfo, tempSurface.GetPixels(), new SKCodecOptions(i));\n\n            if (result != SKCodecResult.Success)\n                throw new InvalidDataException($\"Could not decode frame {i} of {codec.FrameCount}.\");\n\n            using var peekPixels = tempSurface.PeekPixels();\n\n            yield return peekPixels.GetReadableBitmapData(WorkingColorSpace.Default);\n        }\n    }\n\n    public static Task ConvertAnimatedWebpToGifAsync(Stream webpSource, Stream gifOutput)\n    {\n        var gifBitmaps = EnumerateAnimatedWebp(webpSource);\n\n        return GifEncoder.EncodeAnimationAsync(\n            new AnimatedGifConfiguration(gifBitmaps, TimeSpan.FromMilliseconds(150))\n            {\n                Quantizer = OptimizedPaletteQuantizer.Wu(alphaThreshold: 0),\n                AllowDeltaFrames = true\n            },\n            gifOutput\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/A3WebApiManager.cs",
    "content": "﻿using Refit;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Api;\n\npublic class A3WebApiManager : IA3WebApiManager\n{\n    private IA3WebApi? client;\n    public IA3WebApi Client\n    {\n        get\n        {\n            // Return the existing client if it exists\n            if (client != null)\n            {\n                return client;\n            }\n            // Create a new client and store it otherwise\n            client = CreateClient();\n            return client;\n        }\n    }\n    \n    private readonly ISettingsManager settingsManager;\n    private readonly IHttpClientFactory httpClientFactory;\n    public RefitSettings? RefitSettings { get; init; }\n    public string? BaseUrl { get; set; }\n    \n    public A3WebApiManager(ISettingsManager settingsManager, IHttpClientFactory httpClientFactory)\n    {\n        this.settingsManager = settingsManager;\n        this.httpClientFactory = httpClientFactory;\n    }\n\n    public void ResetClient()\n    {\n        client = null;\n    }\n\n    private IA3WebApi CreateClient()\n    {\n        var settings = settingsManager.Settings;\n        \n        // First check override\n        if (settings.WebApiHost != null)\n        {\n            BaseUrl = settings.WebApiHost;\n\n            if (settings.WebApiPort != null)\n            {\n                BaseUrl += $\":{settings.WebApiPort}\";\n            }\n        }\n        else\n        {\n            // Otherwise use default\n            BaseUrl = \"http://localhost:7860\";\n        }\n\n        var httpClient = httpClientFactory.CreateClient(\"A3Client\");\n        httpClient.BaseAddress = new Uri(BaseUrl);\n        var api = RestService.For<IA3WebApi>(httpClient, RefitSettings);\n        return api;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/ApiFactory.cs",
    "content": "﻿using Refit;\n\nnamespace StabilityMatrix.Core.Api;\n\npublic class ApiFactory : IApiFactory\n{\n    private readonly IHttpClientFactory httpClientFactory;\n    public RefitSettings? RefitSettings { get; init; }\n\n    public ApiFactory(IHttpClientFactory httpClientFactory)\n    {\n        this.httpClientFactory = httpClientFactory;\n    }\n\n    public T CreateRefitClient<T>(Uri baseAddress)\n    {\n        var httpClient = httpClientFactory.CreateClient(nameof(T));\n        httpClient.BaseAddress = baseAddress;\n        return RestService.For<T>(httpClient, RefitSettings);\n    }\n\n    public T CreateRefitClient<T>(Uri baseAddress, RefitSettings refitSettings)\n    {\n        var httpClient = httpClientFactory.CreateClient(nameof(T));\n        httpClient.BaseAddress = baseAddress;\n\n        return RestService.For<T>(httpClient, refitSettings);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/CivitCompatApiManager.cs",
    "content": "﻿using Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Api;\n\n/// <summary>\n/// Provides a compatibility layer for interacting with Civita APIs and Discovery APIs.\n/// This class decides dynamically whether to use the Civita API or an alternative Discovery API\n/// based on internal conditions.\n/// </summary>\n[RegisterSingleton<CivitCompatApiManager>]\npublic class CivitCompatApiManager(\n    ILogger<CivitCompatApiManager> logger,\n    ICivitApi civitApi,\n    ILykosModelDiscoveryApi discoveryApi,\n    ISettingsManager settingsManager\n) : ICivitApi\n{\n    private bool ShouldUseDiscoveryApi => settingsManager.Settings.CivitUseDiscoveryApi;\n\n    public Task<CivitModelsResponse> GetModels(CivitModelsRequest request)\n    {\n        if (ShouldUseDiscoveryApi)\n        {\n            logger.LogDebug($\"Using Discovery API for {nameof(GetModels)}\");\n            return discoveryApi.GetModels(request, transcodeAnimToImage: true, transcodeVideoToImage: true);\n        }\n\n        return civitApi.GetModels(request);\n    }\n\n    public Task<CivitModel> GetModelById(int id)\n    {\n        /*if (ShouldUseDiscoveryApi)\n        {\n            logger.LogDebug($\"Using Discovery API for {nameof(GetModelById)}\");\n            return discoveryApi.GetModelById(id);\n        }*/\n        return civitApi.GetModelById(id);\n    }\n\n    public Task<CivitModelVersionResponse> GetModelVersionByHash(string hash)\n    {\n        return civitApi.GetModelVersionByHash(hash);\n    }\n\n    public Task<CivitModelVersion> GetModelVersionById(int id)\n    {\n        return civitApi.GetModelVersionById(id);\n    }\n\n    public Task<HttpResponseMessage> GetBaseModelList()\n    {\n        return civitApi.GetBaseModelList();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/IA3WebApi.cs",
    "content": "﻿using Refit;\nusing StabilityMatrix.Core.Models.Api;\n\nnamespace StabilityMatrix.Core.Api;\n\n[Headers(\"User-Agent: StabilityMatrix\")]\npublic interface IA3WebApi\n{\n    [Get(\"/internal/ping\")]\n    Task<string> GetPing(CancellationToken cancellationToken = default);\n    \n    [Post(\"/sdapi/v1/txt2img\")]\n    Task<ImageResponse> TextToImage([Body] TextToImageRequest request, CancellationToken cancellationToken = default);\n    \n    [Get(\"/sdapi/v1/progress\")]\n    Task<ProgressResponse> GetProgress([Body] ProgressRequest request, CancellationToken cancellationToken = default);\n    \n    [Get(\"/sdapi/v1/options\")]\n    Task<A3Options> GetOptions(CancellationToken cancellationToken = default);\n    \n    [Post(\"/sdapi/v1/options\")]\n    Task SetOptions([Body] A3Options request, CancellationToken cancellationToken = default);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/IA3WebApiManager.cs",
    "content": "﻿using Refit;\n\nnamespace StabilityMatrix.Core.Api;\n\npublic interface IA3WebApiManager\n{\n    IA3WebApi Client { get; }\n    RefitSettings? RefitSettings { get; init; }\n    string? BaseUrl { get; set; }\n    void ResetClient();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/IApiFactory.cs",
    "content": "﻿using Refit;\n\nnamespace StabilityMatrix.Core.Api;\n\npublic interface IApiFactory\n{\n    public T CreateRefitClient<T>(Uri baseAddress);\n\n    public T CreateRefitClient<T>(Uri baseAddress, RefitSettings refitSettings);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/ICivitApi.cs",
    "content": "﻿using System.Text.Json.Nodes;\nusing Refit;\nusing StabilityMatrix.Core.Models.Api;\n\nnamespace StabilityMatrix.Core.Api;\n\n[Headers(\"User-Agent: StabilityMatrix/1.0\")]\npublic interface ICivitApi\n{\n    [Get(\"/api/v1/models\")]\n    Task<CivitModelsResponse> GetModels(CivitModelsRequest request);\n\n    [Get(\"/api/v1/models/{id}\")]\n    Task<CivitModel> GetModelById([AliasAs(\"id\")] int id);\n\n    [Get(\"/api/v1/model-versions/by-hash/{hash}\")]\n    Task<CivitModelVersionResponse> GetModelVersionByHash([Query] string hash);\n\n    [Get(\"/api/v1/model-versions/{id}\")]\n    Task<CivitModelVersion> GetModelVersionById(int id);\n\n    [Get(\"/api/v1/models?baseModels=gimmethelist\")]\n    Task<HttpResponseMessage> GetBaseModelList();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/ICivitTRPCApi.cs",
    "content": "﻿using Refit;\nusing StabilityMatrix.Core.Models.Api.CivitTRPC;\n\nnamespace StabilityMatrix.Core.Api;\n\n[Headers(\n    \"Content-Type: application/x-www-form-urlencoded\",\n    \"Referer: https://civitai.com\",\n    \"Origin: https://civitai.com\"\n)]\npublic interface ICivitTRPCApi\n{\n    [QueryUriFormat(UriFormat.UriEscaped)]\n    [Get(\"/api/trpc/userProfile.get\")]\n    Task<CivitUserProfileResponse> GetUserProfile(\n        [Query] CivitUserProfileRequest input,\n        [Authorize] string bearerToken,\n        CancellationToken cancellationToken = default\n    );\n\n    [QueryUriFormat(UriFormat.UriEscaped)]\n    [Get(\"/api/trpc/buzz.getUserAccount\")]\n    Task<CivitTrpcArrayResponse<CivitUserAccountResponse>> GetUserAccount(\n        [Query] string input,\n        [Authorize] string bearerToken,\n        CancellationToken cancellationToken = default\n    );\n\n    [QueryUriFormat(UriFormat.UriEscaped)]\n    [Get(\"/api/trpc/buzz.getUserAccount\")]\n    Task<CivitTrpcArrayResponse<CivitUserAccountResponse>> GetUserAccount(\n        [Authorize] string bearerToken,\n        CancellationToken cancellationToken = default\n    );\n\n    /// <summary>\n    /// Calls <see cref=\"GetUserAccount(string, string, CancellationToken)\"/> with default JSON input.\n    /// Not required and returns 401 since Oct 2025 since civit changes.\n    /// Mainly just use <see cref=\"GetUserAccount(string, CancellationToken)\"/> instead.\n    /// </summary>\n    Task<CivitTrpcArrayResponse<CivitUserAccountResponse>> GetUserAccountDefault(\n        string bearerToken,\n        CancellationToken cancellationToken = default\n    )\n    {\n        return GetUserAccount(\n            \"{\\\"json\\\":null,\\\"meta\\\":{\\\"values\\\":[\\\"undefined\\\"]}}\",\n            bearerToken,\n            cancellationToken\n        );\n    }\n\n    [QueryUriFormat(UriFormat.UriEscaped)]\n    [Get(\"/api/trpc/user.getById\")]\n    Task<CivitTrpcResponse<CivitGetUserByIdResponse>> GetUserById(\n        [Query] CivitGetUserByIdRequest input,\n        [Authorize] string bearerToken,\n        CancellationToken cancellationToken = default\n    );\n\n    [Post(\"/api/trpc/user.toggleFavoriteModel\")]\n    Task<HttpResponseMessage> ToggleFavoriteModel(\n        [Body] CivitUserToggleFavoriteModelRequest request,\n        [Authorize] string bearerToken,\n        CancellationToken cancellationToken = default\n    );\n\n    [QueryUriFormat(UriFormat.UriEscaped)]\n    [Get(\"/api/trpc/image.getGenerationData\")]\n    Task<CivitTrpcResponse<CivitImageGenerationDataResponse>> GetImageGenerationData(\n        [Query] string input,\n        CancellationToken cancellationToken = default\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/IComfyApi.cs",
    "content": "﻿using Refit;\nusing StabilityMatrix.Core.Models.Api.Comfy;\n\nnamespace StabilityMatrix.Core.Api;\n\n[Headers(\"User-Agent: StabilityMatrix\")]\npublic interface IComfyApi\n{\n    [Post(\"/prompt\")]\n    Task<ComfyPromptResponse> PostPrompt(\n        [Body] ComfyPromptRequest prompt,\n        CancellationToken cancellationToken = default\n    );\n\n    [Post(\"/interrupt\")]\n    Task PostInterrupt(CancellationToken cancellationToken = default);\n\n    /// <summary>\n    /// Upload an image to Comfy\n    /// </summary>\n    /// <param name=\"image\">Image as StreamPart</param>\n    /// <param name=\"overwrite\">Whether to overwrite at destination</param>\n    /// <param name=\"type\">One of \"input\", \"temp\", \"output\"</param>\n    /// <param name=\"subfolder\">Subfolder</param>\n    /// <param name=\"cancellationToken\">Cancellation Token</param>\n    [Multipart]\n    [Post(\"/upload/image\")]\n    Task<ComfyUploadImageResponse> PostUploadImage(\n        StreamPart image,\n        string? overwrite = null,\n        string? type = null,\n        string? subfolder = null,\n        CancellationToken cancellationToken = default\n    );\n\n    [Get(\"/history/{promptId}\")]\n    Task<Dictionary<string, ComfyHistoryResponse>> GetHistory(\n        string promptId,\n        CancellationToken cancellationToken = default\n    );\n\n    [Get(\"/object_info/{nodeType}\")]\n    Task<Dictionary<string, ComfyObjectInfo>> GetObjectInfo(\n        string nodeType,\n        CancellationToken cancellationToken = default\n    );\n\n    [Get(\"/view\")]\n    Task<Stream> GetImage(\n        string filename,\n        string subfolder,\n        string type,\n        CancellationToken cancellationToken = default\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/IHuggingFaceApi.cs",
    "content": "using System.Threading.Tasks;\nusing Refit;\nusing StabilityMatrix.Core.Models.Api.HuggingFace;\n\nnamespace StabilityMatrix.Core.Api;\n\npublic interface IHuggingFaceApi\n{\n    [Get(\"/api/whoami-v2\")]\n    Task<IApiResponse<HuggingFaceUser>> GetCurrentUserAsync([Header(\"Authorization\")] string authorization);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/IInvokeAiApi.cs",
    "content": "﻿using Refit;\nusing StabilityMatrix.Core.Models.Api.Invoke;\n\nnamespace StabilityMatrix.Core.Api;\n\n[Headers(\"User-Agent: StabilityMatrix\")]\npublic interface IInvokeAiApi\n{\n    [Get(\"/api/v2/models/scan_folder\")]\n    Task<List<ScanFolderResult>> ScanFolder(\n        [Query, AliasAs(\"scan_path\")] string scanPath,\n        CancellationToken cancellationToken = default\n    );\n\n    [Post(\"/api/v2/models/install\")]\n    Task InstallModel(\n        [Body] InstallModelRequest request,\n        [Query] string source,\n        [Query] bool inplace = true,\n        CancellationToken cancellationToken = default\n    );\n\n    [Get(\"/api/v2/models/install\")]\n    Task<List<ModelInstallResult>> GetModelInstallStatus(CancellationToken cancellationToken = default);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/ILykosAnalyticsApi.cs",
    "content": "﻿using System.ComponentModel;\nusing Refit;\nusing StabilityMatrix.Core.Models.Api.Lykos;\nusing StabilityMatrix.Core.Models.Api.Lykos.Analytics;\n\nnamespace StabilityMatrix.Core.Api;\n\n[Localizable(false)]\n[Headers(\"User-Agent: StabilityMatrix\")]\npublic interface ILykosAnalyticsApi\n{\n    [Post(\"/api/analytics\")]\n    Task PostInstallData([Body] AnalyticsRequest data, CancellationToken cancellationToken = default);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/ILykosAuthApiV1.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Net;\nusing Refit;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Api.Lykos;\n\nnamespace StabilityMatrix.Core.Api;\n\n[Localizable(false)]\n[Headers(\"User-Agent: StabilityMatrix\")]\n[Obsolete(\"Use ILykosAuthApiV2\")]\npublic interface ILykosAuthApiV1\n{\n    [Headers(\"Authorization: Bearer\")]\n    [Get(\"/api/Users/{email}\")]\n    Task<GetUserResponse> GetUser(string email, CancellationToken cancellationToken = default);\n\n    [Headers(\"Authorization: Bearer\")]\n    [Get(\"/api/Users/me\")]\n    Task<GetUserResponse> GetUserSelf(CancellationToken cancellationToken = default);\n\n    [Post(\"/api/Accounts\")]\n    Task<LykosAccountV1Tokens> PostAccount(\n        [Body] PostAccountRequest request,\n        CancellationToken cancellationToken = default\n    );\n\n    [Post(\"/api/Login\")]\n    Task<LykosAccountV1Tokens> PostLogin(\n        [Body] PostLoginRequest request,\n        CancellationToken cancellationToken = default\n    );\n\n    [Headers(\"Authorization: Bearer\")]\n    [Post(\"/api/Login/Refresh\")]\n    Task<LykosAccountV1Tokens> PostLoginRefresh(\n        [Body] PostLoginRefreshRequest request,\n        CancellationToken cancellationToken = default\n    );\n\n    [Get(\"/api/oauth/google/callback\")]\n    Task<LykosAccountV1Tokens> GetOAuthGoogleCallback(\n        [Query] string code,\n        [Query] string state,\n        [Query] string codeVerifier,\n        CancellationToken cancellationToken = default\n    );\n\n    [Get(\"/api/oauth/google/links/login-or-signup\")]\n    Task<Uri> GetOAuthGoogleLoginOrSignupLink(\n        string redirectUri,\n        string codeChallenge,\n        string codeChallengeMethod,\n        CancellationToken cancellationToken = default\n    );\n\n    [Headers(\"Authorization: Bearer\")]\n    [Get(\"/api/oauth/patreon/redirect\")]\n    Task<HttpResponseMessage> GetPatreonOAuthRedirect(\n        string redirectUrl,\n        CancellationToken cancellationToken = default\n    );\n\n    public async Task<string> GetPatreonOAuthUrl(\n        string redirectUrl,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var result = await GetPatreonOAuthRedirect(redirectUrl, cancellationToken).ConfigureAwait(false);\n\n        if (result.StatusCode != HttpStatusCode.Redirect)\n        {\n            result.EnsureSuccessStatusCode();\n            throw new InvalidOperationException($\"Expected a redirect 302 response, got {result.StatusCode}\");\n        }\n\n        return result.Headers.Location?.ToString()\n            ?? throw new InvalidOperationException(\"Expected a redirect URL, but got none\");\n    }\n\n    [Headers(\"Authorization: Bearer\")]\n    [Delete(\"/api/oauth/patreon\")]\n    Task DeletePatreonOAuth(CancellationToken cancellationToken = default);\n\n    [Headers(\"Authorization: Bearer\")]\n    [Get(\"/api/files/download\")]\n    Task<GetFilesDownloadResponse> GetFilesDownload(\n        string path,\n        CancellationToken cancellationToken = default\n    );\n\n    [Get(\"/api/Models/recommended\")]\n    Task<CivitModelsResponse> GetRecommendedModels();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/ILykosModelDiscoveryApi.cs",
    "content": "﻿using Refit;\nusing StabilityMatrix.Core.Models.Api;\n\nnamespace StabilityMatrix.Core.Api;\n\npublic interface ILykosModelDiscoveryApi\n{\n    [Get(\"/civit/models\")]\n    Task<CivitModelsResponse> GetModels(\n        [Query] CivitModelsRequest request,\n        [Header(\"X-Transcode-Video-To-Image\")] bool? transcodeVideoToImage = null,\n        [Header(\"X-Transcode-Anim-To-Image\")] bool? transcodeAnimToImage = null\n    );\n\n    [Get(\"/civit/models/{id}\")]\n    Task<CivitModel> GetModelById([AliasAs(\"id\")] int id);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/IOpenArtApi.cs",
    "content": "﻿using Refit;\nusing StabilityMatrix.Core.Models.Api.OpenArt;\n\nnamespace StabilityMatrix.Core.Api;\n\n[Headers(\"User-Agent: StabilityMatrix\")]\npublic interface IOpenArtApi\n{\n    [Get(\"/feed\")]\n    Task<OpenArtSearchResponse> GetFeedAsync([Query] OpenArtFeedRequest request);\n\n    [Get(\"/list\")]\n    Task<OpenArtSearchResponse> SearchAsync([Query] OpenArtSearchRequest request);\n\n    [Post(\"/download\")]\n    Task<OpenArtDownloadResponse> DownloadWorkflowAsync([Body] OpenArtDownloadRequest request);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/IOpenModelDbApi.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing Apizr.Caching;\nusing Apizr.Caching.Attributes;\nusing Apizr.Configuring;\nusing Refit;\nusing StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\nnamespace StabilityMatrix.Core.Api;\n\n[BaseAddress(\"https://openmodeldb.info\")]\npublic interface IOpenModelDbApi\n{\n    [Get(\"/api/v1/models.json\"), Cache(CacheMode.GetOrFetch, \"0.00:02:00\")]\n    Task<OpenModelDbModelsResponse> GetModels();\n\n    [Get(\"/api/v1/tags.json\"), Cache(CacheMode.GetOrFetch, \"0.00:10:00\")]\n    Task<OpenModelDbTagsResponse> GetTags();\n\n    [Get(\"/api/v1/architectures.json\"), Cache(CacheMode.GetOrFetch, \"0.00:10:00\")]\n    Task<OpenModelDbArchitecturesResponse> GetArchitectures();\n}\n\n[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]\n[JsonSerializable(typeof(OpenModelDbModelsResponse))]\n[JsonSerializable(typeof(OpenModelDbTagsResponse))]\n[JsonSerializable(typeof(OpenModelDbArchitecturesResponse))]\npublic partial class OpenModelDbApiJsonContext : JsonSerializerContext;\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/IPyPiApi.cs",
    "content": "﻿using Refit;\nusing StabilityMatrix.Core.Models.Api.Pypi;\n\nnamespace StabilityMatrix.Core.Api;\n\n[Headers(\"User-Agent: StabilityMatrix/2.x\")]\npublic interface IPyPiApi\n{\n    [Get(\"/pypi/{packageName}/json\")]\n    Task<PyPiResponse> GetPackageInfo(string packageName);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/ITokenProvider.cs",
    "content": "﻿namespace StabilityMatrix.Core.Api;\n\npublic interface ITokenProvider\n{\n    Task<string> GetAccessTokenAsync();\n    Task<(string AccessToken, string RefreshToken)> RefreshTokensAsync();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/LykosAuthApi/.refitter",
    "content": "{\n  \"openApiPath\": \"https://auth.lykos.ai/swagger/v2/swagger.json\",\n  \"outputFolder\": \"./StabilityMatrix.Core/Api/LykosAuthApi/Generated\",\n  \"outputFilename\": \"Refitter.g.cs\",\n  \"namespace\": \"StabilityMatrix.Core.Api.LykosAuthApi\",\n  \"naming\": {\n    \"useOpenApiTitle\": false,\n    \"interfaceName\": \"LykosAuthApiV2\"\n  },\n  \"includePathMatches\": [\n    \"^/api/v2/Accounts/me$\",\n    \"^/api/v2/oauth/patreon\",\n    \"^/api/v2/files/download\"\n  ],\n  \"trimUnusedSchema\": true,\n  \"operationNameGenerator\": \"SingleClientFromPathSegments\"\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/LykosAuthApi/Generated/Refitter.g.cs",
    "content": "// <auto-generated>\n//     This code was generated by Refitter.\n// </auto-generated>\n\n\nusing Refit;\nusing System.Collections.Generic;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\n\n#nullable enable annotations\n\nnamespace StabilityMatrix.Core.Api.LykosAuthApi\n{\n    [System.CodeDom.Compiler.GeneratedCode(\"Refitter\", \"1.4.1.0\")]\n    public partial interface ILykosAuthApiV2\n    {\n        /// <returns>OK</returns>\n        /// <exception cref=\"ApiException\">\n        /// Thrown when the request returns a non-success status code:\n        /// <list type=\"table\">\n        /// <listheader>\n        /// <term>Status</term>\n        /// <description>Description</description>\n        /// </listheader>\n        /// <item>\n        /// <term>401</term>\n        /// <description>Unauthorized</description>\n        /// </item>\n        /// <item>\n        /// <term>404</term>\n        /// <description>Not Found</description>\n        /// </item>\n        /// </list>\n        /// </exception>\n        [Headers(\"Accept: text/plain, application/json, text/json\")]\n        [Get(\"/api/v2/Accounts/me\")]\n        Task<AccountResponse> ApiV2AccountsMe();\n\n        /// <returns>OK</returns>\n        /// <exception cref=\"ApiException\">\n        /// Thrown when the request returns a non-success status code:\n        /// <list type=\"table\">\n        /// <listheader>\n        /// <term>Status</term>\n        /// <description>Description</description>\n        /// </listheader>\n        /// <item>\n        /// <term>400</term>\n        /// <description>Bad Request</description>\n        /// </item>\n        /// <item>\n        /// <term>401</term>\n        /// <description>Unauthorized</description>\n        /// </item>\n        /// <item>\n        /// <term>403</term>\n        /// <description>Forbidden</description>\n        /// </item>\n        /// </list>\n        /// </exception>\n        [Headers(\"Accept: text/plain, application/json, text/json\")]\n        [Get(\"/api/v2/files/download\")]\n        Task<FilesDownloadResponse> ApiV2FilesDownload([Query] string path);\n\n        /// <returns>A <see cref=\"Task\"/> that completes when the request is finished.</returns>\n        /// <exception cref=\"ApiException\">\n        /// Thrown when the request returns a non-success status code:\n        /// <list type=\"table\">\n        /// <listheader>\n        /// <term>Status</term>\n        /// <description>Description</description>\n        /// </listheader>\n        /// <item>\n        /// <term>302</term>\n        /// <description>Found</description>\n        /// </item>\n        /// <item>\n        /// <term>401</term>\n        /// <description>Unauthorized</description>\n        /// </item>\n        /// </list>\n        /// </exception>\n        [Headers(\"Accept: text/plain, application/json, text/json\")]\n        [Get(\"/api/v2/oauth/patreon/redirect\")]\n        Task ApiV2OauthPatreonRedirect([Query] System.Uri redirectUrl);\n\n        /// <returns>OK</returns>\n        /// <exception cref=\"ApiException\">\n        /// Thrown when the request returns a non-success status code:\n        /// <list type=\"table\">\n        /// <listheader>\n        /// <term>Status</term>\n        /// <description>Description</description>\n        /// </listheader>\n        /// <item>\n        /// <term>401</term>\n        /// <description>Unauthorized</description>\n        /// </item>\n        /// </list>\n        /// </exception>\n        [Headers(\"Accept: text/plain, application/json, text/json\")]\n        [Get(\"/api/v2/oauth/patreon/link\")]\n        Task<System.Uri> ApiV2OauthPatreonLink([Query] System.Uri redirectUrl);\n\n        /// <returns>A <see cref=\"Task\"/> that completes when the request is finished.</returns>\n        /// <exception cref=\"ApiException\">\n        /// Thrown when the request returns a non-success status code:\n        /// <list type=\"table\">\n        /// <listheader>\n        /// <term>Status</term>\n        /// <description>Description</description>\n        /// </listheader>\n        /// <item>\n        /// <term>401</term>\n        /// <description>Unauthorized</description>\n        /// </item>\n        /// </list>\n        /// </exception>\n        [Headers(\"Accept: text/plain, application/json, text/json\")]\n        [Delete(\"/api/v2/oauth/patreon\")]\n        Task ApiV2OauthPatreon();\n\n        /// <returns>A <see cref=\"Task\"/> that completes when the request is finished.</returns>\n        /// <exception cref=\"ApiException\">\n        /// Thrown when the request returns a non-success status code:\n        /// <list type=\"table\">\n        /// <listheader>\n        /// <term>Status</term>\n        /// <description>Description</description>\n        /// </listheader>\n        /// <item>\n        /// <term>302</term>\n        /// <description>Found</description>\n        /// </item>\n        /// <item>\n        /// <term>401</term>\n        /// <description>Unauthorized</description>\n        /// </item>\n        /// <item>\n        /// <term>404</term>\n        /// <description>Not Found</description>\n        /// </item>\n        /// <item>\n        /// <term>400</term>\n        /// <description>Bad Request</description>\n        /// </item>\n        /// </list>\n        /// </exception>\n        [Headers(\"Accept: text/plain, application/json, text/json\")]\n        [Get(\"/api/v2/oauth/patreon/callback\")]\n        Task ApiV2OauthPatreonCallback([Query] string code, [Query] string state);\n\n\n    }\n\n}\n\n//----------------------\n// <auto-generated>\n//     Generated using the NSwag toolchain v14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org)\n// </auto-generated>\n//----------------------\n\n#pragma warning disable 108 // Disable \"CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended.\"\n#pragma warning disable 114 // Disable \"CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.\"\n#pragma warning disable 472 // Disable \"CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'\n#pragma warning disable 612 // Disable \"CS0612 '...' is obsolete\"\n#pragma warning disable 649 // Disable \"CS0649 Field is never assigned to, and will always have its default value null\"\n#pragma warning disable 1573 // Disable \"CS1573 Parameter '...' has no matching param tag in the XML comment for ...\n#pragma warning disable 1591 // Disable \"CS1591 Missing XML comment for publicly visible type or member ...\"\n#pragma warning disable 8073 // Disable \"CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'\"\n#pragma warning disable 3016 // Disable \"CS3016 Arrays as attribute arguments is not CLS-compliant\"\n#pragma warning disable 8603 // Disable \"CS8603 Possible null reference return\"\n#pragma warning disable 8604 // Disable \"CS8604 Possible null reference argument for parameter\"\n#pragma warning disable 8625 // Disable \"CS8625 Cannot convert null literal to non-nullable reference type\"\n#pragma warning disable 8765 // Disable \"CS8765 Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes).\"\n\nnamespace StabilityMatrix.Core.Api.LykosAuthApi\n{\n    using System = global::System;\n\n    \n\n    [System.CodeDom.Compiler.GeneratedCode(\"NJsonSchema\", \"14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))\")]\n    public partial class AccountResponse\n    {\n\n        [JsonPropertyName(\"id\")]\n        public string Id { get; set; }\n\n        [JsonPropertyName(\"roles\")]\n        public ICollection<string> Roles { get; set; }\n\n        [JsonPropertyName(\"permissions\")]\n        public ICollection<string> Permissions { get; set; }\n\n        [JsonPropertyName(\"patreonId\")]\n        public string PatreonId { get; set; }\n\n    }\n\n    [System.CodeDom.Compiler.GeneratedCode(\"NJsonSchema\", \"14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))\")]\n    public partial class FilesDownloadResponse\n    {\n\n        [JsonPropertyName(\"downloadUrl\")]\n        public System.Uri DownloadUrl { get; set; }\n\n        [JsonPropertyName(\"expiresAt\")]\n        public System.DateTimeOffset? ExpiresAt { get; set; }\n\n    }\n\n    [System.CodeDom.Compiler.GeneratedCode(\"NJsonSchema\", \"14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))\")]\n    public partial class ProblemDetails\n    {\n\n        [JsonPropertyName(\"type\")]\n        public string Type { get; set; }\n\n        [JsonPropertyName(\"title\")]\n        public string Title { get; set; }\n\n        [JsonPropertyName(\"status\")]\n        public int? Status { get; set; }\n\n        [JsonPropertyName(\"detail\")]\n        public string Detail { get; set; }\n\n        [JsonPropertyName(\"instance\")]\n        public string Instance { get; set; }\n\n        private IDictionary<string, object> _additionalProperties;\n\n        [JsonExtensionData]\n        public IDictionary<string, object> AdditionalProperties\n        {\n            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }\n            set { _additionalProperties = value; }\n        }\n\n    }\n\n\n}\n\n#pragma warning restore  108\n#pragma warning restore  114\n#pragma warning restore  472\n#pragma warning restore  612\n#pragma warning restore 1573\n#pragma warning restore 1591\n#pragma warning restore 8073\n#pragma warning restore 3016\n#pragma warning restore 8603\n#pragma warning restore 8604\n#pragma warning restore 8625"
  },
  {
    "path": "StabilityMatrix.Core/Api/LykosAuthApi/IRecommendedModelsApi.cs",
    "content": "﻿using Refit;\nusing StabilityMatrix.Core.Models.Api.Lykos;\n\nnamespace StabilityMatrix.Core.Api.LykosAuthApi;\n\npublic interface IRecommendedModelsApi\n{\n    [Get(\"/api/v2/Models/recommended\")]\n    Task<RecommendedModelsV2Response> GetRecommendedModels();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/LykosAuthTokenProvider.cs",
    "content": "using Injectio.Attributes;\nusing OpenIddict.Client;\nusing StabilityMatrix.Core.Api.LykosAuthApi;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Lykos;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Api;\n\n[RegisterSingleton<LykosAuthTokenProvider>]\npublic class LykosAuthTokenProvider(\n    Lazy<ILykosAuthApiV2> lazyLykosAuthApi,\n    ISecretsManager secretsManager,\n    OpenIddictClientService openIdClient\n) : ITokenProvider\n{\n    private readonly Lazy<ILykosAuthApiV2> lazyLykosAuthApi = lazyLykosAuthApi;\n\n    // Lazy as instantiating requires the current class to be instantiated.\n\n    /// <inheritdoc />\n    public async Task<string> GetAccessTokenAsync()\n    {\n        var secrets = await secretsManager.SafeLoadAsync().ConfigureAwait(false);\n\n        return secrets.LykosAccountV2?.AccessToken ?? \"\";\n    }\n\n    /// <inheritdoc />\n    public async Task<(string AccessToken, string RefreshToken)> RefreshTokensAsync()\n    {\n        var secrets = await secretsManager.SafeLoadAsync().ConfigureAwait(false);\n\n        if (string.IsNullOrWhiteSpace(secrets.LykosAccountV2?.RefreshToken))\n        {\n            throw new InvalidOperationException(\"No refresh token found\");\n        }\n\n        var result = await openIdClient\n            .AuthenticateWithRefreshTokenAsync(\n                new OpenIddictClientModels.RefreshTokenAuthenticationRequest\n                {\n                    ProviderName = OpenIdClientConstants.LykosAccount.ProviderName,\n                    RefreshToken = secrets.LykosAccountV2.RefreshToken\n                }\n            )\n            .ConfigureAwait(false);\n\n        if (string.IsNullOrEmpty(result.RefreshToken))\n        {\n            throw new InvalidOperationException(\"No refresh token returned\");\n        }\n\n        secrets = secrets with\n        {\n            LykosAccountV2 = new LykosAccountV2Tokens(\n                result.AccessToken,\n                result.RefreshToken,\n                result.IdentityToken\n            )\n        };\n\n        await secretsManager.SaveAsync(secrets).ConfigureAwait(false);\n\n        return (result.AccessToken, result.RefreshToken);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/OpenIdClientConstants.cs",
    "content": "﻿namespace StabilityMatrix.Core.Api;\n\n/// <summary>\n/// Contains constant values related to OpenID Clients\n/// </summary>\npublic static class OpenIdClientConstants\n{\n    public static class LykosAccount\n    {\n        public const string ProviderName = \"Lykos Account\";\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/PromptGen/.refitter",
    "content": "{\n  \"openApiPath\": \"http://localhost:5174/api/swagger.json\",\n  \"outputFolder\": \"./StabilityMatrix.Core/Api/PromptGen/Generated\",\n  \"outputFilename\": \"Refitter.g.cs\",\n  \"namespace\": \"StabilityMatrix.Core.Api.PromptGenApi\",\n  \"naming\": {\n    \"useOpenApiTitle\": false,\n    \"interfaceName\": \"PromptGenApi\"\n  },\n  \"includePathMatches\": [\n    \"^/account/me/tokens$\",\n    \"^/expand-prompt$\"\n  ],\n  \"trimUnusedSchema\": true,\n  \"operationNameGenerator\": \"SingleClientFromPathSegments\"\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Api/PromptGen/Generated/Refitter.g.cs",
    "content": "// <auto-generated>\n//     This code was generated by Refitter.\n// </auto-generated>\n\n\nusing Refit;\nusing System.Collections.Generic;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\n\n#nullable enable annotations\n\nnamespace StabilityMatrix.Core.Api.PromptGenApi\n{\n    [System.CodeDom.Compiler.GeneratedCode(\"Refitter\", \"1.4.1.0\")]\n    public partial interface IPromptGenApi\n    {\n        /// <summary>Get current user's token balance</summary>\n        /// <remarks>Retrieves the token balance for the currently authenticated user.</remarks>\n        /// <returns>Payload of TokenBalance</returns>\n        /// <exception cref=\"ApiException\">Thrown when the request returns a non-success status code.</exception>\n        [Headers(\"Accept: application/json\")]\n        [Get(\"/account/me/tokens\")]\n        Task<TokenBalance> AccountMeTokens();\n\n        /// <summary>Expand a prompt</summary>\n        /// <remarks>Expand a prompt using the OpenAI Chat API.</remarks>\n        /// <param name=\"body\">The prompt to expand.</param>\n        /// <returns>Payload of PromptExpansionResponse</returns>\n        /// <exception cref=\"ApiException\">Thrown when the request returns a non-success status code.</exception>\n        [Headers(\"Accept: application/json\")]\n        [Post(\"/expand-prompt\")]\n        Task<PromptExpansionResponse> ExpandPrompt([Body] PromptExpansionRequest body);\n\n\n    }\n\n}\n\n//----------------------\n// <auto-generated>\n//     Generated using the NSwag toolchain v14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org)\n// </auto-generated>\n//----------------------\n\n#pragma warning disable 108 // Disable \"CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended.\"\n#pragma warning disable 114 // Disable \"CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.\"\n#pragma warning disable 472 // Disable \"CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'\n#pragma warning disable 612 // Disable \"CS0612 '...' is obsolete\"\n#pragma warning disable 649 // Disable \"CS0649 Field is never assigned to, and will always have its default value null\"\n#pragma warning disable 1573 // Disable \"CS1573 Parameter '...' has no matching param tag in the XML comment for ...\n#pragma warning disable 1591 // Disable \"CS1591 Missing XML comment for publicly visible type or member ...\"\n#pragma warning disable 8073 // Disable \"CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'\"\n#pragma warning disable 3016 // Disable \"CS3016 Arrays as attribute arguments is not CLS-compliant\"\n#pragma warning disable 8603 // Disable \"CS8603 Possible null reference return\"\n#pragma warning disable 8604 // Disable \"CS8604 Possible null reference argument for parameter\"\n#pragma warning disable 8625 // Disable \"CS8625 Cannot convert null literal to non-nullable reference type\"\n#pragma warning disable 8765 // Disable \"CS8765 Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes).\"\n\nnamespace StabilityMatrix.Core.Api.PromptGenApi\n{\n    using System = global::System;\n\n    \n\n    [System.CodeDom.Compiler.GeneratedCode(\"NJsonSchema\", \"14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))\")]\n    public partial class PromptEnhanceResponse\n    {\n\n        [JsonPropertyName(\"positivePrompt\")]\n        public string PositivePrompt { get; set; }\n\n        [JsonPropertyName(\"negativePrompt\")]\n        public string NegativePrompt { get; set; }\n\n        private IDictionary<string, object> _additionalProperties;\n\n        [JsonExtensionData]\n        public IDictionary<string, object> AdditionalProperties\n        {\n            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }\n            set { _additionalProperties = value; }\n        }\n\n    }\n\n    [System.CodeDom.Compiler.GeneratedCode(\"NJsonSchema\", \"14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))\")]\n    public partial class PromptExpansionRequest\n    {\n\n        [JsonPropertyName(\"prompt\")]\n        public PromptToEnhance Prompt { get; set; }\n\n        [JsonPropertyName(\"model\")]\n        public string Model { get; set; }\n\n        [JsonPropertyName(\"seed\")]\n        public long? Seed { get; set; }\n\n        [JsonPropertyName(\"mode\")]\n        [JsonConverter(typeof(JsonStringEnumConverter))]\n        public PromptExpansionRequestMode Mode { get; set; } = StabilityMatrix.Core.Api.PromptGenApi.PromptExpansionRequestMode.Focused;\n\n        [JsonPropertyName(\"clientOverride\")]\n        public string ClientOverride { get; set; }\n\n        [JsonPropertyName(\"modelTags\")]\n\n        // TODO(system.text.json): Add string enum item converter\n        public ICollection<ModelTags> ModelTags { get; set; }\n\n        private IDictionary<string, object> _additionalProperties;\n\n        [JsonExtensionData]\n        public IDictionary<string, object> AdditionalProperties\n        {\n            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }\n            set { _additionalProperties = value; }\n        }\n\n    }\n\n    [System.CodeDom.Compiler.GeneratedCode(\"NJsonSchema\", \"14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))\")]\n    public partial class PromptExpansionResponse\n    {\n\n        [JsonPropertyName(\"availableTokens\")]\n        public int AvailableTokens { get; set; }\n\n        [JsonPropertyName(\"operationId\")]\n        public string OperationId { get; set; }\n\n        [JsonPropertyName(\"response\")]\n        public PromptEnhanceResponse Response { get; set; }\n\n        private IDictionary<string, object> _additionalProperties;\n\n        [JsonExtensionData]\n        public IDictionary<string, object> AdditionalProperties\n        {\n            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }\n            set { _additionalProperties = value; }\n        }\n\n    }\n\n    [System.CodeDom.Compiler.GeneratedCode(\"NJsonSchema\", \"14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))\")]\n    public partial class PromptToEnhance\n    {\n\n        [JsonPropertyName(\"positivePrompt\")]\n        public string PositivePrompt { get; set; }\n\n        [JsonPropertyName(\"negativePrompt\")]\n        public string NegativePrompt { get; set; }\n\n        [JsonPropertyName(\"model\")]\n        public string Model { get; set; }\n\n        private IDictionary<string, object> _additionalProperties;\n\n        [JsonExtensionData]\n        public IDictionary<string, object> AdditionalProperties\n        {\n            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }\n            set { _additionalProperties = value; }\n        }\n\n    }\n\n    [System.CodeDom.Compiler.GeneratedCode(\"NJsonSchema\", \"14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))\")]\n    public partial class TokenBalance\n    {\n\n        [JsonPropertyName(\"available\")]\n        public int Available { get; set; }\n\n        [JsonPropertyName(\"used\")]\n        public int Used { get; set; }\n\n        [JsonPropertyName(\"total\")]\n        public int Total { get; set; }\n\n        private IDictionary<string, object> _additionalProperties;\n\n        [JsonExtensionData]\n        public IDictionary<string, object> AdditionalProperties\n        {\n            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }\n            set { _additionalProperties = value; }\n        }\n\n    }\n\n    [System.CodeDom.Compiler.GeneratedCode(\"NJsonSchema\", \"14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))\")]\n    public enum PromptExpansionRequestMode\n    {\n\n        [System.Runtime.Serialization.EnumMember(Value = @\"Focused\")]\n        Focused = 0,\n\n        [System.Runtime.Serialization.EnumMember(Value = @\"Balanced\")]\n        Balanced = 1,\n\n        [System.Runtime.Serialization.EnumMember(Value = @\"Imaginative\")]\n        Imaginative = 2,\n\n    }\n\n    [System.CodeDom.Compiler.GeneratedCode(\"NJsonSchema\", \"14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))\")]\n    public enum ModelTags\n    {\n\n        [System.Runtime.Serialization.EnumMember(Value = @\"Flux\")]\n        Flux = 0,\n\n        [System.Runtime.Serialization.EnumMember(Value = @\"Pony\")]\n        Pony = 1,\n\n        [System.Runtime.Serialization.EnumMember(Value = @\"Sdxl\")]\n        Sdxl = 2,\n\n        [System.Runtime.Serialization.EnumMember(Value = @\"Illustrious\")]\n        Illustrious = 3,\n\n    }\n\n\n}\n\n#pragma warning restore  108\n#pragma warning restore  114\n#pragma warning restore  472\n#pragma warning restore  612\n#pragma warning restore 1573\n#pragma warning restore 1591\n#pragma warning restore 8073\n#pragma warning restore 3016\n#pragma warning restore 8603\n#pragma warning restore 8604\n#pragma warning restore 8625"
  },
  {
    "path": "StabilityMatrix.Core/Api/TokenAuthHeaderHandler.cs",
    "content": "﻿using System.Net;\nusing System.Net.Http.Headers;\nusing NLog;\nusing Polly;\nusing Polly.Retry;\nusing StabilityMatrix.Core.Helper;\n\nnamespace StabilityMatrix.Core.Api;\n\npublic class TokenAuthHeaderHandler : DelegatingHandler\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private readonly AsyncRetryPolicy<HttpResponseMessage> policy;\n    private readonly ITokenProvider tokenProvider;\n\n    public Func<HttpRequestMessage, bool> RequestFilter { get; set; } =\n        request => request.Headers.Authorization is { Scheme: \"Bearer\" };\n\n    public Func<HttpResponseMessage, bool> ResponseFilter { get; set; } =\n        response =>\n            response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden\n            && response.RequestMessage?.Headers.Authorization is { Scheme: \"Bearer\", Parameter: { } param }\n            && !string.IsNullOrWhiteSpace(param);\n\n    public TokenAuthHeaderHandler(ITokenProvider tokenProvider)\n    {\n        this.tokenProvider = tokenProvider;\n\n        policy = Policy\n            .HandleResult(ResponseFilter)\n            .RetryAsync(\n                async (result, _) =>\n                {\n                    var oldToken = ObjectHash.GetStringSignature(\n                        await tokenProvider.GetAccessTokenAsync().ConfigureAwait(false)\n                    );\n                    Logger.Info(\n                        \"Refreshing access token for status ({StatusCode})\",\n                        result.Result.StatusCode\n                    );\n                    var (newToken, _) = await tokenProvider.RefreshTokensAsync().ConfigureAwait(false);\n\n                    Logger.Info(\n                        \"Access token refreshed: {OldToken} -> {NewToken}\",\n                        ObjectHash.GetStringSignature(oldToken),\n                        ObjectHash.GetStringSignature(newToken)\n                    );\n                }\n            );\n    }\n\n    protected override Task<HttpResponseMessage> SendAsync(\n        HttpRequestMessage request,\n        CancellationToken cancellationToken\n    )\n    {\n        return policy.ExecuteAsync(async () =>\n        {\n            // Only add if Authorization is already set to Bearer and access token is not empty\n            // this allows some routes to not use the access token\n            if (RequestFilter(request))\n            {\n                var accessToken = await tokenProvider.GetAccessTokenAsync().ConfigureAwait(false);\n\n                if (!string.IsNullOrWhiteSpace(accessToken))\n                {\n                    request.Headers.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n                }\n            }\n\n            return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Attributes/BoolStringMemberAttribute.cs",
    "content": "﻿namespace StabilityMatrix.Core.Attributes;\n\n[AttributeUsage(AttributeTargets.Property)]\npublic class BoolStringMemberAttribute(string trueString, string falseString) : Attribute\n{\n    public string TrueString { get; } = trueString;\n    public string FalseString { get; } = falseString;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Attributes/ManagedServiceAttribute.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing JetBrains.Annotations;\n\nnamespace StabilityMatrix.Core.Attributes;\n\n[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]\n[MeansImplicitUse(ImplicitUseKindFlags.Access, ImplicitUseTargetFlags.Itself)]\n[AttributeUsage(AttributeTargets.Class)]\npublic class ManagedServiceAttribute : Attribute;\n"
  },
  {
    "path": "StabilityMatrix.Core/Attributes/PreloadAttribute.cs",
    "content": "﻿namespace StabilityMatrix.Core.Attributes;\n\n/// <summary>\n/// Marks that a ViewModel should have its OnLoaded and OnLoadedAsync methods called in the background\n/// during MainWindow initialization, after LibraryDirectory is set.\n/// </summary>\n[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]\npublic class PreloadAttribute : Attribute { }\n"
  },
  {
    "path": "StabilityMatrix.Core/Attributes/SingletonAttribute.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing JetBrains.Annotations;\n\nnamespace StabilityMatrix.Core.Attributes;\n\n[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]\n[MeansImplicitUse(ImplicitUseKindFlags.Access, ImplicitUseTargetFlags.Itself)]\n[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]\npublic class SingletonAttribute : Attribute\n{\n    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]\n    public Type? InterfaceType { get; init; }\n\n    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]\n    public Type? ImplType { get; init; }\n\n    public SingletonAttribute() { }\n\n    public SingletonAttribute(Type interfaceType)\n    {\n        InterfaceType = interfaceType;\n    }\n\n    public SingletonAttribute(Type interfaceType, Type implType)\n    {\n        InterfaceType = implType;\n        ImplType = implType;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Attributes/TransientAttribute.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing JetBrains.Annotations;\n\nnamespace StabilityMatrix.Core.Attributes;\n\n[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]\n[MeansImplicitUse(ImplicitUseKindFlags.Access, ImplicitUseTargetFlags.Itself)]\n[AttributeUsage(AttributeTargets.Class)]\npublic class TransientAttribute : Attribute\n{\n    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]\n    public Type? InterfaceType { get; }\n\n    public TransientAttribute() { }\n\n    public TransientAttribute(Type interfaceType)\n    {\n        InterfaceType = interfaceType;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Attributes/TypedNodeOptionsAttribute.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\n\nnamespace StabilityMatrix.Core.Attributes;\n\n/// <summary>\n/// Options for <see cref=\"ComfyTypedNodeBase{TOutput}\"/>\n/// </summary>\n[AttributeUsage(AttributeTargets.Class)]\npublic class TypedNodeOptionsAttribute : Attribute\n{\n    public string? Name { get; init; }\n\n    public string[]? RequiredExtensions { get; init; }\n\n    public IEnumerable<ExtensionSpecifier> GetRequiredExtensionSpecifiers()\n    {\n        return RequiredExtensions?.Select(ExtensionSpecifier.Parse) ?? Enumerable.Empty<ExtensionSpecifier>();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Attributes/ViewAttribute.cs",
    "content": "﻿namespace StabilityMatrix.Core.Attributes;\n\n[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]\npublic class ViewAttribute : Attribute\n{\n    public Type ViewType { get; init; }\n    public bool IsPersistent { get; init; }\n    \n    public ViewAttribute(Type type)\n    {\n        ViewType = type;\n    }\n    \n    public ViewAttribute(Type type, bool persistent)\n    {\n        ViewType = type;\n        IsPersistent = persistent;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Converters/Json/AnalyticsRequestConverter.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Models.Api.Lykos.Analytics;\n\nnamespace StabilityMatrix.Core.Converters.Json;\n\npublic class AnalyticsRequestConverter : JsonConverter<AnalyticsRequest>\n{\n    public override AnalyticsRequest? Read(\n        ref Utf8JsonReader reader,\n        Type typeToConvert,\n        JsonSerializerOptions options\n    )\n    {\n        using var jsonDocument = JsonDocument.ParseValue(ref reader);\n        var root = jsonDocument.RootElement;\n\n        if (!root.TryGetProperty(\"Type\", out var typeProperty))\n            throw new JsonException(\"Missing Type property\");\n\n        var type = typeProperty.GetString();\n        return type switch\n        {\n            \"package-install\"\n                => JsonSerializer.Deserialize<PackageInstallAnalyticsRequest>(root.GetRawText(), options),\n            \"first-time-install\"\n                => JsonSerializer.Deserialize<FirstTimeInstallAnalytics>(root.GetRawText(), options),\n            \"launch\" => JsonSerializer.Deserialize<LaunchAnalyticsRequest>(root.GetRawText(), options),\n            _ => throw new JsonException($\"Unknown Type: {type}\")\n        };\n    }\n\n    public override void Write(Utf8JsonWriter writer, AnalyticsRequest value, JsonSerializerOptions options)\n    {\n        JsonSerializer.Serialize(writer, value, value.GetType(), options);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Converters/Json/DefaultUnknownEnumConverter.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Reflection;\nusing System.Runtime.Serialization;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Converters.Json;\n\npublic class DefaultUnknownEnumConverter<\n    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T\n> : JsonConverter<T>\n    where T : Enum\n{\n    /// <summary>\n    /// Lazy initialization for <see cref=\"EnumMemberValues\"/>.\n    /// </summary>\n    private readonly Lazy<Dictionary<string, T>> _enumMemberValuesLazy =\n        new(\n            () =>\n                typeof(T)\n                    .GetFields()\n                    .Where(field => field.IsStatic)\n                    .Select(\n                        field =>\n                            new\n                            {\n                                FieldName = field.Name,\n                                FieldValue = (T)field.GetValue(null)!,\n                                EnumMemberValue = field\n                                    .GetCustomAttributes<EnumMemberAttribute>(false)\n                                    .FirstOrDefault()\n                                    ?.Value?.ToString()\n                            }\n                    )\n                    .ToDictionary(x => x.EnumMemberValue ?? x.FieldName, x => x.FieldValue)\n        );\n\n    /// <summary>\n    /// Gets a dictionary of enum member values, keyed by the EnumMember attribute value, or the field name if no EnumMember attribute is present.\n    /// </summary>\n    private Dictionary<string, T> EnumMemberValues => _enumMemberValuesLazy.Value;\n\n    /// <summary>\n    /// Lazy initialization for <see cref=\"EnumMemberNames\"/>.\n    /// </summary>\n    private readonly Lazy<Dictionary<T, string>> _enumMemberNamesLazy;\n\n    /// <summary>\n    /// Gets a dictionary of enum member names, keyed by the enum member value.\n    /// </summary>\n    private Dictionary<T, string> EnumMemberNames => _enumMemberNamesLazy.Value;\n\n    /// <summary>\n    /// Gets the value of the \"Unknown\" enum member, or the 0 value if no \"Unknown\" member is present.\n    /// </summary>\n    private T UnknownValue =>\n        EnumMemberValues.TryGetValue(\"Unknown\", out var res) ? res : (T)Enum.ToObject(typeof(T), 0);\n\n    /// <inheritdoc />\n    public override bool HandleNull => true;\n\n    public DefaultUnknownEnumConverter()\n    {\n        _enumMemberNamesLazy = new Lazy<Dictionary<T, string>>(\n            () => EnumMemberValues.ToDictionary(x => x.Value, x => x.Key)\n        );\n    }\n\n    /// <inheritdoc />\n    public override T Read(\n        ref Utf8JsonReader reader,\n        Type typeToConvert,\n        JsonSerializerOptions options\n    )\n    {\n        if (reader.TokenType is not (JsonTokenType.String or JsonTokenType.PropertyName))\n        {\n            throw new JsonException(\"Expected String or PropertyName token\");\n        }\n\n        if (reader.GetString() is { } readerString)\n        {\n            // First try get exact match\n            if (EnumMemberValues.TryGetValue(readerString, out var enumMemberValue))\n            {\n                return enumMemberValue;\n            }\n\n            // Otherwise try get case-insensitive match\n            if (\n                EnumMemberValues.Keys.FirstOrDefault(\n                    key => key.Equals(readerString, StringComparison.OrdinalIgnoreCase)\n                ) is\n                { } enumMemberName\n            )\n            {\n                return EnumMemberValues[enumMemberName];\n            }\n\n            Debug.WriteLine($\"Unknown enum member value for {typeToConvert}: {readerString}\");\n        }\n\n        return UnknownValue;\n    }\n\n    /// <inheritdoc />\n    public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)\n    {\n        if (value == null)\n        {\n            writer.WriteNullValue();\n            return;\n        }\n\n        writer.WriteStringValue(EnumMemberNames[value]);\n    }\n\n    /// <inheritdoc />\n    public override T ReadAsPropertyName(\n        ref Utf8JsonReader reader,\n        Type typeToConvert,\n        JsonSerializerOptions options\n    ) => Read(ref reader, typeToConvert, options);\n\n    /// <inheritdoc />\n    public override void WriteAsPropertyName(\n        Utf8JsonWriter writer,\n        T? value,\n        JsonSerializerOptions options\n    ) => Write(writer, value, options);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Converters/Json/LaunchOptionValueJsonConverter.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Converters.Json;\n\npublic class LaunchOptionValueJsonConverter : JsonConverter<object?>\n{\n    public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n    {\n        try\n        {\n            var boolValue = reader.GetBoolean();\n            return boolValue;\n        }\n        catch (InvalidOperationException)\n        {\n            // ignored\n        }\n        \n        try\n        {\n            var intValue = reader.GetInt32();\n            return intValue;\n        }\n        catch (InvalidOperationException)\n        {\n            // ignored\n        }\n        \n        try\n        {\n            var strValue = reader.GetString();\n            return strValue;\n        }\n        catch (InvalidOperationException)\n        {\n            return null;\n        }\n    }\n\n    public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerOptions options)\n    {\n        switch (value)\n        {\n            case bool boolValue:\n                writer.WriteBooleanValue(boolValue);\n                break;\n            case int intValue:\n                writer.WriteNumberValue(intValue);\n                break;\n            case string strValue:\n                writer.WriteStringValue(strValue);\n                break;\n            default:\n                throw new ArgumentOutOfRangeException(nameof(value));\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Converters/Json/NodeConnectionBaseJsonConverter.cs",
    "content": "﻿using System.Reflection;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\nnamespace StabilityMatrix.Core.Converters.Json;\n\npublic class NodeConnectionBaseJsonConverter : JsonConverter<NodeConnectionBase>\n{\n    /// <inheritdoc />\n    public override NodeConnectionBase Read(\n        ref Utf8JsonReader reader,\n        Type typeToConvert,\n        JsonSerializerOptions options\n    )\n    {\n        // Read as Data array\n        reader.Read();\n        var data = new object[2];\n        reader.Read();\n        data[0] = reader.GetString() ?? throw new JsonException(\"Expected string for node name\");\n        reader.Read();\n        data[1] = reader.GetInt32();\n        reader.Read();\n\n        if (Activator.CreateInstance(typeToConvert) is not NodeConnectionBase instance)\n        {\n            throw new JsonException($\"Failed to create instance of {typeToConvert}\");\n        }\n\n        var propertyInfo =\n            typeToConvert.GetProperty(\"Data\", BindingFlags.Public | BindingFlags.Instance)\n            ?? throw new JsonException($\"Failed to get Data property of {typeToConvert}\");\n\n        propertyInfo.SetValue(instance, data);\n\n        return instance;\n    }\n\n    /// <inheritdoc />\n    public override void Write(Utf8JsonWriter writer, NodeConnectionBase value, JsonSerializerOptions options)\n    {\n        // Write as Data array\n        writer.WriteStartArray();\n        writer.WriteStringValue(value.Data?[0] as string);\n        writer.WriteNumberValue((int)value.Data?[1]!);\n        writer.WriteEndArray();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Converters/Json/OneOfJsonConverter.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Serialization;\nusing OneOf;\n\nnamespace StabilityMatrix.Core.Converters.Json;\n\npublic class OneOfJsonConverter<T1, T2> : JsonConverter<OneOf<T1, T2>>\n{\n    /// <inheritdoc />\n    public override OneOf<T1, T2> Read(\n        ref Utf8JsonReader reader,\n        Type typeToConvert,\n        JsonSerializerOptions options\n    )\n    {\n        // Not sure how else to do this without polymorphic type markers but that would not serialize into T1/T2\n        // So just try to deserialize T1, if it fails, try T2\n        Exception? t1Exception = null;\n        Exception? t2Exception = null;\n\n        try\n        {\n            if (JsonSerializer.Deserialize<T1>(ref reader, options) is { } t1)\n            {\n                return t1;\n            }\n        }\n        catch (JsonException e)\n        {\n            t1Exception = e;\n        }\n\n        try\n        {\n            if (JsonSerializer.Deserialize<T2>(ref reader, options) is { } t2)\n            {\n                return t2;\n            }\n        }\n        catch (JsonException e)\n        {\n            t2Exception = e;\n        }\n\n        throw new JsonException(\n            $\"Failed to deserialize OneOf<{typeof(T1)}, {typeof(T2)}> as either {typeof(T1)} or {typeof(T2)}\",\n            new AggregateException([t1Exception, t2Exception])\n        );\n    }\n\n    /// <inheritdoc />\n    public override void Write(Utf8JsonWriter writer, OneOf<T1, T2> value, JsonSerializerOptions options)\n    {\n        if (value.IsT0)\n        {\n            JsonSerializer.Serialize(writer, value.AsT0, options);\n        }\n        else\n        {\n            JsonSerializer.Serialize(writer, value.AsT1, options);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Converters/Json/ParsableStringValueJsonConverter.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Globalization;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Core.Converters.Json;\n\npublic class ParsableStringValueJsonConverter<\n    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T\n> : JsonConverter<T>\n    where T : StringValue, IParsable<T>\n{\n    /// <inheritdoc />\n    public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n    {\n        if (reader.TokenType != JsonTokenType.String)\n        {\n            throw new JsonException();\n        }\n\n        var value = reader.GetString();\n        if (value is null)\n        {\n            return default;\n        }\n\n        // Use TryParse result if available\n        if (T.TryParse(value, CultureInfo.InvariantCulture, out var result))\n        {\n            return result;\n        }\n\n        // Otherwise use Activator\n        return (T?)Activator.CreateInstance(typeToConvert, value);\n    }\n\n    /// <inheritdoc />\n    public override T ReadAsPropertyName(\n        ref Utf8JsonReader reader,\n        Type typeToConvert,\n        JsonSerializerOptions options\n    )\n    {\n        if (reader.TokenType != JsonTokenType.PropertyName)\n        {\n            throw new JsonException(\"Unexpected token type\");\n        }\n\n        var value = reader.GetString();\n        if (value is null)\n        {\n            throw new JsonException(\"Property name cannot be null\");\n        }\n\n        // Use TryParse result if available\n        if (T.TryParse(value, CultureInfo.InvariantCulture, out var result))\n        {\n            return result;\n        }\n\n        // Otherwise use Activator\n        return (T?)Activator.CreateInstance(typeToConvert, value)\n            ?? throw new JsonException(\"Property name cannot be null\");\n    }\n\n    /// <inheritdoc />\n    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)\n    {\n        if (value is IFormattable formattable)\n        {\n            writer.WriteStringValue(formattable.ToString(null, CultureInfo.InvariantCulture));\n        }\n        else\n        {\n            writer.WriteStringValue(value.ToString());\n        }\n    }\n\n    /// <inheritdoc />\n    public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options)\n    {\n        if (value is null)\n        {\n            throw new JsonException(\"Property name cannot be null\");\n        }\n\n        if (value is IFormattable formattable)\n        {\n            writer.WritePropertyName(formattable.ToString(null, CultureInfo.InvariantCulture));\n        }\n        else\n        {\n            writer.WritePropertyName(value.ToString());\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Converters/Json/SKColorJsonConverter.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Serialization;\nusing SkiaSharp;\n\nnamespace StabilityMatrix.Core.Converters.Json;\n\npublic class SKColorJsonConverter : JsonConverter<SKColor>\n{\n    public override SKColor Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n    {\n        if (!reader.TryGetUInt32(out var value))\n        {\n            return SKColor.Empty;\n        }\n\n        return new SKColor(value);\n    }\n\n    public override void Write(Utf8JsonWriter writer, SKColor value, JsonSerializerOptions options)\n    {\n        // Convert to uint in the format ARGB\n        var argbColor = (uint)((value.Alpha << 24) | (value.Red << 16) | (value.Green << 8) | value.Blue);\n\n        writer.WriteNumberValue(argbColor);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Converters/Json/SemVersionJsonConverter.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Serialization;\nusing Semver;\n\nnamespace StabilityMatrix.Core.Converters.Json;\n\npublic class SemVersionJsonConverter : JsonConverter<SemVersion>\n{\n    public override SemVersion Read(\n        ref Utf8JsonReader reader,\n        Type typeToConvert,\n        JsonSerializerOptions options) =>\n        SemVersion.Parse(reader.GetString()!, SemVersionStyles.Strict);\n\n    public override void Write(\n        Utf8JsonWriter writer,\n        SemVersion value,\n        JsonSerializerOptions options) =>\n        writer.WriteStringValue(value.ToString());\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Converters/Json/StringJsonConverter.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Globalization;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing JetBrains.Annotations;\n\nnamespace StabilityMatrix.Core.Converters.Json;\n\n/// <summary>\n/// Json converter for types that serialize to string by `ToString()` and\n/// can be created by `Activator.CreateInstance(Type, string)`\n/// Types implementing <see cref=\"IFormattable\"/> will be formatted with <see cref=\"CultureInfo.InvariantCulture\"/>\n/// </summary>\n[PublicAPI]\npublic class StringJsonConverter<\n    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T\n> : JsonConverter<T>\n{\n    /// <inheritdoc />\n    public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n    {\n        if (reader.TokenType != JsonTokenType.String)\n        {\n            throw new JsonException();\n        }\n\n        var value = reader.GetString();\n        if (value is null)\n        {\n            return default;\n        }\n\n        return (T?)Activator.CreateInstance(typeToConvert, value);\n    }\n\n    /// <inheritdoc />\n    public override T ReadAsPropertyName(\n        ref Utf8JsonReader reader,\n        Type typeToConvert,\n        JsonSerializerOptions options\n    )\n    {\n        if (reader.TokenType != JsonTokenType.String)\n        {\n            throw new JsonException();\n        }\n\n        var value = reader.GetString();\n        if (value is null)\n        {\n            throw new JsonException(\"Property name cannot be null\");\n        }\n\n        return (T?)Activator.CreateInstance(typeToConvert, value)\n            ?? throw new JsonException(\"Property name cannot be null\");\n    }\n\n    /// <inheritdoc />\n    public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)\n    {\n        if (value is null)\n        {\n            writer.WriteNullValue();\n            return;\n        }\n\n        if (value is IFormattable formattable)\n        {\n            writer.WriteStringValue(formattable.ToString(null, CultureInfo.InvariantCulture));\n        }\n        else\n        {\n            writer.WriteStringValue(value.ToString());\n        }\n    }\n\n    /// <inheritdoc />\n    public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options)\n    {\n        if (value is null)\n        {\n            throw new JsonException(\"Property name cannot be null\");\n        }\n\n        if (value is IFormattable formattable)\n        {\n            writer.WriteStringValue(formattable.ToString(null, CultureInfo.InvariantCulture));\n        }\n        else\n        {\n            writer.WriteStringValue(value.ToString());\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Database/CivitModelQueryCacheEntry.cs",
    "content": "﻿using LiteDB;\nusing StabilityMatrix.Core.Models.Api;\n\nnamespace StabilityMatrix.Core.Database;\n\n/// <summary>\n/// Cache entry for the result of a Civit model query response\n/// </summary>\npublic class CivitModelQueryCacheEntry\n{\n    // This is set as the hash of the request object (ObjectHash.GetMd5Guid)\n    public Guid Id { get; set; }\n    \n    public DateTimeOffset? InsertedAt { get; set; }\n    \n    public CivitModelsRequest? Request { get; set; }\n    \n    [BsonRef(\"CivitModels\")]\n    public List<CivitModel>? Items { get; set; }\n    \n    public CivitMetadata? Metadata { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Database/ILiteDbContext.cs",
    "content": "﻿using LiteDB.Async;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\n\nnamespace StabilityMatrix.Core.Database;\n\npublic interface ILiteDbContext : IDisposable\n{\n    LiteDatabaseAsync Database { get; }\n\n    ILiteCollectionAsync<CivitModel> CivitModels { get; }\n    ILiteCollectionAsync<CivitModelVersion> CivitModelVersions { get; }\n    ILiteCollectionAsync<CivitModelQueryCacheEntry> CivitModelQueryCache { get; }\n    ILiteCollectionAsync<LocalModelFile> LocalModelFiles { get; }\n    ILiteCollectionAsync<InferenceProjectEntry> InferenceProjects { get; }\n    ILiteCollectionAsync<LocalImageFile> LocalImageFiles { get; }\n    ILiteCollectionAsync<CivitBaseModelTypeCacheEntry> CivitBaseModelTypeCache { get; }\n\n    Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync(string hashBlake3);\n    Task<bool> UpsertCivitModelAsync(CivitModel civitModel);\n    Task<bool> UpsertCivitModelAsync(IEnumerable<CivitModel> civitModels);\n    Task<bool> UpsertCivitModelQueryCacheEntryAsync(CivitModelQueryCacheEntry entry);\n    Task<GithubCacheEntry?> GetGithubCacheEntry(string cacheKey);\n    Task<bool> UpsertGithubCacheEntry(GithubCacheEntry cacheEntry);\n\n    /// <summary>\n    /// Clear all Collections that store re-fetchable cache type data.\n    /// </summary>\n    Task ClearAllCacheCollectionsAsync();\n\n    /// <summary>\n    /// Executes a query with exception logging and collection clearing.\n    /// This will handle unique exceptions once keyed by string representation for each collection,\n    /// and throws if repeated.\n    /// </summary>\n    /// <typeparam name=\"T\">The type of collection to query.</typeparam>\n    /// <typeparam name=\"TResult\">The type of result to return.</typeparam>\n    /// <param name=\"collection\">The collection to query.</param>\n    /// <param name=\"task\">The task representing the query to execute.</param>\n    /// <returns>The result of the query, or default value on handled exception.</returns>\n    Task<TResult?> TryQueryWithClearOnExceptionAsync<T, TResult>(\n        ILiteCollectionAsync<T> collection,\n        Task<TResult> task\n    );\n\n    Task<PyPiCacheEntry?> GetPyPiCacheEntry(string? cacheKey);\n    Task<bool> UpsertPyPiCacheEntry(PyPiCacheEntry cacheEntry);\n\n    Task<CivitBaseModelTypeCacheEntry?> GetCivitBaseModelTypeCacheEntry(string id);\n    Task<bool> UpsertCivitBaseModelTypeCacheEntry(CivitBaseModelTypeCacheEntry entry);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Database/LiteDbContext.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Globalization;\nusing AsyncAwaitBestPractices;\nusing LiteDB;\nusing LiteDB.Async;\nusing LiteDB.Engine;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Options;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Configs;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Database;\n\npublic class LiteDbContext : ILiteDbContext\n{\n    private readonly ILogger<LiteDbContext> logger;\n    private readonly ISettingsManager settingsManager;\n    private readonly DebugOptions debugOptions;\n\n    // Tracks handled exceptions\n    private readonly HashSet<HandledExceptionInfo> handledExceptions = [];\n\n    private readonly Lazy<LiteDatabaseAsync> lazyDatabase;\n    public LiteDatabaseAsync Database => lazyDatabase.Value;\n\n    // Notification events\n    public event EventHandler? CivitModelsChanged;\n\n    // Collections (Tables)\n    public ILiteCollectionAsync<CivitModel> CivitModels => Database.GetCollection<CivitModel>(\"CivitModels\");\n    public ILiteCollectionAsync<CivitModelVersion> CivitModelVersions =>\n        Database.GetCollection<CivitModelVersion>(\"CivitModelVersions\");\n    public ILiteCollectionAsync<CivitModelQueryCacheEntry> CivitModelQueryCache =>\n        Database.GetCollection<CivitModelQueryCacheEntry>(\"CivitModelQueryCache\");\n    public ILiteCollectionAsync<GithubCacheEntry> GithubCache =>\n        Database.GetCollection<GithubCacheEntry>(\"GithubCache\");\n    public ILiteCollectionAsync<LocalModelFile> LocalModelFiles =>\n        Database.GetCollection<LocalModelFile>(\"LocalModelFiles\");\n    public ILiteCollectionAsync<InferenceProjectEntry> InferenceProjects =>\n        Database.GetCollection<InferenceProjectEntry>(\"InferenceProjects\");\n    public ILiteCollectionAsync<LocalImageFile> LocalImageFiles =>\n        Database.GetCollection<LocalImageFile>(\"LocalImageFiles\");\n    public ILiteCollectionAsync<PyPiCacheEntry> PyPiCache =>\n        Database.GetCollection<PyPiCacheEntry>(\"PyPiCache\");\n    public ILiteCollectionAsync<CivitBaseModelTypeCacheEntry> CivitBaseModelTypeCache =>\n        Database.GetCollection<CivitBaseModelTypeCacheEntry>(\"CivitBaseModelTypeCache\");\n\n    public LiteDbContext(\n        ILogger<LiteDbContext> logger,\n        ISettingsManager settingsManager,\n        IOptions<DebugOptions> debugOptions\n    )\n    {\n        this.logger = logger;\n        this.settingsManager = settingsManager;\n        this.debugOptions = debugOptions.Value;\n\n        lazyDatabase = new Lazy<LiteDatabaseAsync>(CreateDatabase);\n    }\n\n    private LiteDatabaseAsync CreateDatabase()\n    {\n        // Try at most twice:\n        //  - attempt 0: open/repair if needed\n        //  - on \"Detected loop in FindAll\": dispose, delete file, try once more\n        const int maxAttempts = 2;\n        var dbPath = Path.Combine(settingsManager.LibraryDir, \"StabilityMatrix.db\");\n\n        for (var attempt = 0; attempt < maxAttempts; attempt++)\n        {\n            LiteDatabaseAsync? db = null;\n\n            try\n            {\n                if (debugOptions.TempDatabase)\n                {\n                    db = new LiteDatabaseAsync(\":temp:\");\n                    RegisterRefs();\n                    return db;\n                }\n\n                db = new LiteDatabaseAsync(\n                    new ConnectionString { Filename = dbPath, Connection = ConnectionType.Shared }\n                );\n\n                var sortOption = db.Collation.SortOptions;\n                if (sortOption is not CompareOptions.Ordinal)\n                {\n                    logger.LogDebug(\n                        \"Database collation is not Ordinal ({SortOption}), rebuilding...\",\n                        sortOption\n                    );\n                    var options = new RebuildOptions\n                    {\n                        Collation = new Collation(CultureInfo.InvariantCulture.LCID, CompareOptions.Ordinal),\n                    };\n                    db.RebuildAsync(options).GetAwaiter().GetResult();\n                }\n\n                RegisterRefs();\n                return db;\n            }\n            catch (LiteAsyncException ex)\n                when (ex.InnerException is LiteException e\n                    && e.Message.Contains(\"Detected loop in FindAll\", StringComparison.OrdinalIgnoreCase)\n                )\n            {\n                logger.LogWarning(\"Database corruption detected ({Message}), rebuilding...\", e.Message);\n\n                try\n                {\n                    db?.Dispose();\n                }\n                catch\n                {\n                    // ignored\n                }\n\n                try\n                {\n                    // Backup then delete, in case we want to inspect later.\n                    var corruptPath = dbPath + \".old-\" + DateTime.UtcNow.ToString(\"yyyyMMddHHmmss\");\n                    if (File.Exists(dbPath))\n                    {\n                        File.Copy(dbPath, corruptPath, overwrite: false);\n                        File.Delete(dbPath);\n                    }\n                }\n                catch (Exception delEx)\n                {\n                    logger.LogWarning(\"Failed to delete corrupt DB: {Message}\", delEx.Message);\n                    // If we can't delete, no point retrying; break to fallback.\n                    break;\n                }\n            }\n            catch (IOException ioEx)\n            {\n                logger.LogWarning(\n                    \"Database in use or not accessible ({Message}), using temporary database\",\n                    ioEx.Message\n                );\n                break; // fall through to temp\n            }\n        }\n\n        // Fallback to temporary database\n        var tempDb = new LiteDatabaseAsync(\":temp:\");\n        RegisterRefs();\n        return tempDb;\n\n        void RegisterRefs()\n        {\n            LiteDBExtensions.Register<CivitModel, CivitModelVersion>(\n                m => m.ModelVersions,\n                \"CivitModelVersions\"\n            );\n            LiteDBExtensions.Register<CivitModelQueryCacheEntry, CivitModel>(e => e.Items, \"CivitModels\");\n            LiteDBExtensions.Register<LocalModelFile, CivitModel>(e => e.LatestModelInfo, \"CivitModels\");\n        }\n    }\n\n    public async Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync(string hashBlake3)\n    {\n        var version = await CivitModelVersions\n            .Query()\n            .Where(mv =>\n                mv.Files!.Select(f => f.Hashes)\n                    .Select(hashes => hashes.BLAKE3)\n                    .Any(hash => hash == hashBlake3)\n            )\n            .FirstOrDefaultAsync()\n            .ConfigureAwait(false);\n\n        if (version is null)\n            return (null, null);\n\n        var model = await CivitModels\n            .Query()\n            .Include(m => m.ModelVersions)\n            .Where(m => m.ModelVersions!.Select(v => v.Id).Any(id => id == version.Id))\n            .FirstOrDefaultAsync()\n            .ConfigureAwait(false);\n\n        return (model, version);\n    }\n\n    public async Task<bool> UpsertCivitModelAsync(CivitModel civitModel)\n    {\n        // Insert model versions first then model\n        var versionsUpdated = await CivitModelVersions\n            .UpsertAsync(civitModel.ModelVersions)\n            .ConfigureAwait(false);\n        var updated = await CivitModels.UpsertAsync(civitModel).ConfigureAwait(false);\n        // Notify listeners on any change\n        var anyUpdated = versionsUpdated > 0 || updated;\n        if (anyUpdated)\n        {\n            CivitModelsChanged?.Invoke(this, EventArgs.Empty);\n        }\n        return anyUpdated;\n    }\n\n    public async Task<bool> UpsertCivitModelAsync(IEnumerable<CivitModel> civitModels)\n    {\n        var civitModelsArray = civitModels.ToArray();\n        // Get all model versions then insert models\n        var versions = civitModelsArray.SelectMany(model => model.ModelVersions ?? new());\n        var versionsUpdated = await CivitModelVersions.UpsertAsync(versions).ConfigureAwait(false);\n        var updated = await CivitModels.UpsertAsync(civitModelsArray).ConfigureAwait(false);\n        // Notify listeners on any change\n        var anyUpdated = versionsUpdated > 0 || updated > 0;\n        if (updated > 0 || versionsUpdated > 0)\n        {\n            CivitModelsChanged?.Invoke(this, EventArgs.Empty);\n        }\n        return anyUpdated;\n    }\n\n    // Add to cache\n    public async Task<bool> UpsertCivitModelQueryCacheEntryAsync(CivitModelQueryCacheEntry entry)\n    {\n        var changed = await CivitModelQueryCache.UpsertAsync(entry).ConfigureAwait(false);\n        if (changed)\n        {\n            CivitModelsChanged?.Invoke(this, EventArgs.Empty);\n        }\n\n        return changed;\n    }\n\n    public async Task<GithubCacheEntry?> GetGithubCacheEntry(string? cacheKey)\n    {\n        if (string.IsNullOrEmpty(cacheKey))\n            return null;\n\n        return await TryQueryWithClearOnExceptionAsync(GithubCache, GithubCache.FindByIdAsync(cacheKey))\n            .ConfigureAwait(false);\n    }\n\n    public Task<bool> UpsertGithubCacheEntry(GithubCacheEntry cacheEntry) =>\n        GithubCache.UpsertAsync(cacheEntry);\n\n    public async Task<PyPiCacheEntry?> GetPyPiCacheEntry(string? cacheKey)\n    {\n        if (string.IsNullOrEmpty(cacheKey))\n            return null;\n\n        return await TryQueryWithClearOnExceptionAsync(PyPiCache, PyPiCache.FindByIdAsync(cacheKey))\n            .ConfigureAwait(false);\n    }\n\n    public Task<bool> UpsertPyPiCacheEntry(PyPiCacheEntry cacheEntry) => PyPiCache.UpsertAsync(cacheEntry);\n\n    /// <summary>\n    /// Clear all Collections that store re-fetchable cache type data.\n    /// </summary>\n    public async Task ClearAllCacheCollectionsAsync()\n    {\n        var collectionNames = new List<string>\n        {\n            nameof(CivitModels),\n            nameof(CivitModelVersions),\n            nameof(CivitModelQueryCache),\n            nameof(GithubCache),\n            nameof(LocalModelFiles),\n            nameof(LocalImageFiles),\n        };\n\n        logger.LogInformation(\"Clearing all cache collections: [{@Names}]\", collectionNames);\n\n        foreach (var name in collectionNames)\n        {\n            var collection = Database.GetCollection(name);\n            await collection.DeleteAllAsync().ConfigureAwait(false);\n        }\n    }\n\n    /// <summary>\n    /// Executes a query with exception logging and collection clearing.\n    /// This will handle unique exceptions once keyed by string representation for each collection,\n    /// and throws if repeated.\n    /// </summary>\n    /// <typeparam name=\"T\">The type of collection to query.</typeparam>\n    /// <typeparam name=\"TResult\">The type of result to return.</typeparam>\n    /// <param name=\"collection\">The collection to query.</param>\n    /// <param name=\"task\">The task representing the query to execute.</param>\n    /// <returns>The result of the query, or default value on handled exception.</returns>\n    public async Task<TResult?> TryQueryWithClearOnExceptionAsync<T, TResult>(\n        ILiteCollectionAsync<T> collection,\n        Task<TResult> task\n    )\n    {\n        try\n        {\n            return await task.ConfigureAwait(false);\n        }\n        catch (Exception ex)\n        {\n            var exceptionInfo = new HandledExceptionInfo(\n                collection.Name,\n                ex.ToString(),\n                ex.InnerException?.ToString()\n            );\n\n            lock (handledExceptions)\n            {\n                var exceptionString = ex.InnerException is null\n                    ? $\"{ex.GetType()}\"\n                    : $\"{ex.GetType()} ({ex.InnerException.GetType()})\";\n\n                // Throw if exception was already handled previously this session\n                // then it's probably not a migration issue\n                if (handledExceptions.Contains(exceptionInfo))\n                {\n                    throw new AggregateException(\n                        $\"Repeated LiteDb error '{exceptionString}' while fetching from '{exceptionInfo.CollectionName}', previously handled\",\n                        ex\n                    );\n                }\n\n                // Log warning for known exception types, otherwise log error\n                if (\n                    ex is LiteException or LiteAsyncException\n                    && ex.InnerException\n                        is InvalidCastException // GitHub cache int type changes\n                            or ArgumentException // Unknown enum values\n                )\n                {\n                    logger.LogWarning(\n                        ex,\n                        \"LiteDb error while fetching from {Name}, collection will be cleared: {Exception}\",\n                        collection.Name,\n                        exceptionString\n                    );\n                }\n                else\n                {\n#if DEBUG\n                    throw;\n#else\n                    logger.LogError(\n                        ex,\n                        \"LiteDb unknown error while fetching from {Name}, collection will be cleared: {Exception}\",\n                        collection.Name,\n                        exceptionString\n                    );\n#endif\n                }\n\n                // Add to handled exceptions\n                handledExceptions.Add(exceptionInfo);\n            }\n\n            // Clear collection\n            await collection.DeleteAllAsync().ConfigureAwait(false);\n\n            // Get referenced collections\n            var referencedCollections = FindReferencedCollections(collection).ToArray();\n            if (referencedCollections.Length > 0)\n            {\n                logger.LogWarning(\n                    \"Clearing referenced collections: [{@Names}]\",\n                    referencedCollections.Select(c => c.Name)\n                );\n\n                foreach (var referencedCollection in referencedCollections)\n                {\n                    await referencedCollection.DeleteAllAsync().ConfigureAwait(false);\n                }\n            }\n        }\n\n        return default;\n    }\n\n    public void Dispose()\n    {\n        if (lazyDatabase.IsValueCreated)\n        {\n            try\n            {\n                Database.Dispose();\n            }\n            catch (ObjectDisposedException) { }\n            catch (ApplicationException)\n            {\n                // Ignores a mutex error from library\n                // https://stability-matrix.sentry.io/share/issue/5c62f37462444e7eab18cea314af231f/\n            }\n        }\n\n        GC.SuppressFinalize(this);\n    }\n\n    /// <summary>\n    /// Recursively find all referenced collections in the entity mapper of a collection.\n    /// </summary>\n    private IEnumerable<ILiteCollectionAsync<BsonDocument>> FindReferencedCollections<T>(\n        ILiteCollectionAsync<T> collection\n    )\n    {\n        var collectionNames = Database.UnderlyingDatabase.GetCollectionNames().ToArray();\n\n        foreach (\n            var referencedCollectionName in FindReferencedCollectionNamesRecursive(\n                collection.EntityMapper,\n                [collection.Name]\n            )\n        )\n        {\n            yield return Database.GetCollection(referencedCollectionName);\n        }\n\n        yield break;\n\n        IEnumerable<string> FindReferencedCollectionNamesRecursive(\n            EntityMapper entityMapper,\n            ImmutableHashSet<string> seenCollectionNames\n        )\n        {\n            foreach (var member in entityMapper.Members)\n            {\n                // Only look for members that are DBRef\n                if (!member.IsDbRef || member.UnderlyingType is not { } dbRefType)\n                    continue;\n\n                // Skip if not a collection or already seen\n                if (!collectionNames.Contains(dbRefType.Name) || seenCollectionNames.Contains(dbRefType.Name))\n                    continue;\n\n                var memberCollection = Database.GetCollection(dbRefType.Name);\n\n                seenCollectionNames = seenCollectionNames.Add(memberCollection.Name);\n                yield return memberCollection.Name;\n\n                // Also recursively find references in the referenced collection\n                foreach (\n                    var subCollectionName in FindReferencedCollectionNamesRecursive(\n                        memberCollection.EntityMapper,\n                        seenCollectionNames\n                    )\n                )\n                {\n                    seenCollectionNames = seenCollectionNames.Add(subCollectionName);\n                    yield return subCollectionName;\n                }\n            }\n        }\n    }\n\n    public async Task<CivitBaseModelTypeCacheEntry?> GetCivitBaseModelTypeCacheEntry(string id)\n    {\n        if (string.IsNullOrEmpty(id))\n            return null;\n\n        return await CivitBaseModelTypeCache.FindByIdAsync(id).ConfigureAwait(false);\n    }\n\n    public Task<bool> UpsertCivitBaseModelTypeCacheEntry(CivitBaseModelTypeCacheEntry entry) =>\n        CivitBaseModelTypeCache.UpsertAsync(entry);\n\n    private readonly record struct HandledExceptionInfo(\n        string CollectionName,\n        string Exception,\n        string? InnerException\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/AppException.cs",
    "content": "namespace StabilityMatrix.Core.Exceptions;\n\n/// <summary>\n/// Generic runtime exception with custom handling by notification service\n/// </summary>\npublic class AppException : ApplicationException\n{\n    public override string Message { get; }\n    public string? Details { get; init; }\n\n    public AppException(string message)\n    {\n        Message = message;\n    }\n\n    public AppException(string message, string details)\n    {\n        Message = message;\n        Details = details;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/CivitDownloadDisabledException.cs",
    "content": "﻿namespace StabilityMatrix.Core.Exceptions;\n\npublic class CivitDownloadDisabledException : UnauthorizedAccessException;\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/CivitLoginRequiredException.cs",
    "content": "﻿namespace StabilityMatrix.Core.Exceptions;\n\npublic class CivitLoginRequiredException : UnauthorizedAccessException;\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/ComfyNodeException.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Api.Comfy.WebSocketData;\n\nnamespace StabilityMatrix.Core.Exceptions;\n\npublic class ComfyNodeException : Exception\n{\n    public required ComfyWebSocketExecutionErrorData ErrorData { get; init; }\n    public required string JsonData { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/EarlyAccessException.cs",
    "content": "﻿namespace StabilityMatrix.Core.Exceptions;\n\npublic class EarlyAccessException : UnauthorizedAccessException;\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/FileExistsException.cs",
    "content": "﻿namespace StabilityMatrix.Core.Exceptions;\n\npublic class FileTransferExistsException : IOException\n{\n    public string SourceFile { get; }\n    public string DestinationFile { get; }\n\n    public FileTransferExistsException(string source, string destination)\n    {\n        SourceFile = source;\n        DestinationFile = destination;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/HuggingFaceLoginRequiredException.cs",
    "content": "﻿namespace StabilityMatrix.Core.Exceptions;\n\npublic class HuggingFaceLoginRequiredException : UnauthorizedAccessException;\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/MissingPrerequisiteException.cs",
    "content": "﻿namespace StabilityMatrix.Core.Exceptions;\n\npublic class MissingPrerequisiteException(\n    string missingPrereqName,\n    string message,\n    string? downloadLink = null\n) : Exception($\"{message}{Environment.NewLine}{downloadLink}{Environment.NewLine}\")\n{\n    public string MissingPrereqName { get; set; } = missingPrereqName;\n    public string? DownloadLink { get; set; } = downloadLink;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/ProcessException.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Text;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Exceptions;\n\n/// <summary>\n/// Exception that is thrown when a process fails.\n/// </summary>\npublic class ProcessException : Exception\n{\n    public ProcessResult? ProcessResult { get; }\n\n    public ProcessException(string message)\n        : base(message) { }\n\n    public ProcessException(ProcessResult processResult)\n        : base(\n            $\"Process {processResult.ProcessName} exited with code {processResult.ExitCode}. \"\n                + $\"{{StdOut = {processResult.StandardOutput}, StdErr = {processResult.StandardError}}}\"\n        )\n    {\n        ProcessResult = processResult;\n    }\n\n    public static void ThrowIfNonZeroExitCode(ProcessResult processResult)\n    {\n        if (processResult.IsSuccessExitCode)\n            return;\n\n        throw new ProcessException(processResult);\n    }\n\n    public static void ThrowIfNonZeroExitCode(Process process, string output)\n    {\n        if (!process.HasExited || process.ExitCode == 0)\n            return;\n\n        throw new ProcessException(\n            new ProcessResult\n            {\n                ProcessName = process.StartInfo.FileName,\n                ExitCode = process.ExitCode,\n                StandardOutput = output\n            }\n        );\n    }\n\n    public static void ThrowIfNonZeroExitCode(Process process, StringBuilder outputBuilder)\n    {\n        if (!process.HasExited || process.ExitCode == 0)\n            return;\n\n        throw new ProcessException(\n            new ProcessResult\n            {\n                ProcessName = process.StartInfo.FileName,\n                ExitCode = process.ExitCode,\n                StandardOutput = outputBuilder.ToString()\n            }\n        );\n    }\n\n    public static void ThrowIfNonZeroExitCode(Process process, string stdOut, string stdErr)\n    {\n        if (!process.HasExited || process.ExitCode == 0)\n            return;\n\n        throw new ProcessException(\n            new ProcessResult\n            {\n                ProcessName = process.StartInfo.FileName,\n                ExitCode = process.ExitCode,\n                StandardOutput = stdOut,\n                StandardError = stdErr\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/PromptError.cs",
    "content": "﻿namespace StabilityMatrix.Core.Exceptions;\n\npublic abstract class PromptError : ApplicationException\n{\n    public int TextOffset { get; }\n    public int TextEndOffset { get; }\n\n    protected PromptError(string message, int textOffset, int textEndOffset) : base(message)\n    {\n        TextOffset = textOffset;\n        TextEndOffset = textEndOffset;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/PromptSyntaxError.cs",
    "content": "﻿namespace StabilityMatrix.Core.Exceptions;\n\npublic class PromptSyntaxError : PromptError\n{\n    public static PromptSyntaxError Network_ExpectedSeparator(int textOffset, int textEndOffset) =>\n        new(\"Expected separator\", textOffset, textEndOffset);\n    \n    public static PromptSyntaxError Network_ExpectedType(int textOffset, int textEndOffset) =>\n        new(\"Expected network type\", textOffset, textEndOffset);\n    \n    public static PromptSyntaxError Network_ExpectedName(int textOffset, int textEndOffset) =>\n        new(\"Expected network name\", textOffset, textEndOffset);\n    \n    public static PromptSyntaxError Network_ExpectedWeight(int textOffset, int textEndOffset) =>\n        new(\"Expected network weight\", textOffset, textEndOffset);\n    \n    public static PromptSyntaxError UnexpectedEndOfText(int textOffset, int textEndOffset) =>\n        new(\"Unexpected end of text\", textOffset, textEndOffset);\n\n    /// <inheritdoc />\n    public PromptSyntaxError(string message, int textOffset, int textEndOffset) : base(message, textOffset, textEndOffset)\n    {\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/PromptUnknownModelError.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Tokens;\n\nnamespace StabilityMatrix.Core.Exceptions;\n\npublic class PromptUnknownModelError : PromptValidationError\n{\n    public string ModelName { get; }\n\n    public PromptExtraNetworkType ModelType { get; }\n\n    /// <inheritdoc />\n    public PromptUnknownModelError(\n        string message,\n        int textOffset,\n        int textEndOffset,\n        string modelName,\n        PromptExtraNetworkType modelType\n    )\n        : base(message, textOffset, textEndOffset)\n    {\n        ModelName = modelName;\n        ModelType = modelType;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Exceptions/PromptValidationError.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Tokens;\n\nnamespace StabilityMatrix.Core.Exceptions;\n\npublic class PromptValidationError : PromptError\n{\n    /// <inheritdoc />\n    public PromptValidationError(string message, int textOffset, int textEndOffset)\n        : base(message, textOffset, textEndOffset) { }\n\n    public static PromptValidationError Network_UnknownType(int textOffset, int textEndOffset) =>\n        new(\"Unknown network type\", textOffset, textEndOffset);\n\n    public static PromptUnknownModelError Network_UnknownModel(\n        string modelName,\n        PromptExtraNetworkType modelType,\n        int textOffset,\n        int textEndOffset\n    ) =>\n        new(\n            $\"Model '{modelName}' was not found locally\",\n            textOffset,\n            textEndOffset,\n            modelName,\n            modelType\n        );\n\n    public static PromptSyntaxError Network_InvalidWeight(int textOffset, int textEndOffset) =>\n        new(\"Invalid network weight, could not be parsed as double\", textOffset, textEndOffset);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/DictionaryExtensions.cs",
    "content": "﻿using System.Text;\n\nnamespace StabilityMatrix.Core.Extensions;\n\npublic static class DictionaryExtensions\n{\n    /// <summary>\n    /// Adds all items from another dictionary to this dictionary.\n    /// </summary>\n    public static void Update<TKey, TValue>(\n        this IDictionary<TKey, TValue> source,\n        IReadOnlyDictionary<TKey, TValue> collection\n    )\n        where TKey : notnull\n    {\n        foreach (var item in collection)\n        {\n            source[item.Key] = item.Value;\n        }\n    }\n    \n    /// <summary>\n    /// Formats a dictionary as a string for debug/logging purposes.\n    /// </summary>\n    public static string ToRepr<TKey, TValue>(\n        this IDictionary<TKey, TValue> source\n    )\n        where TKey : notnull\n    {\n        var sb = new StringBuilder();\n        sb.Append('{');\n        foreach (var (key, value) in source)\n        {\n            // for string types, use ToRepr\n            if (key is string keyString)\n            {\n                sb.Append($\"{keyString.ToRepr()}=\");\n            }\n            else\n            {\n                sb.Append($\"{key}=\");\n            }\n            \n            if (value is string valueString)\n            {\n                sb.Append($\"{valueString.ToRepr()}, \");\n            }\n            else\n            {\n                sb.Append($\"{value}, \");\n            }\n        }\n        sb.Append('}');\n        return sb.ToString();\n    }\n    \n    /// <summary>\n    /// Get or add a value to a dictionary.\n    /// </summary>\n    public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) \n        where TValue : new()\n    {\n        if (!dict.TryGetValue(key, out var val))\n        {\n            val = new TValue();\n            dict.Add(key, val);\n        }\n\n        return val;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/DirectoryPathExtensions.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing Microsoft.Extensions.Logging;\nusing Polly;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Core.Extensions;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic static class DirectoryPathExtensions\n{\n    /// <summary>\n    /// Deletes a directory and all of its contents recursively.\n    /// Uses Polly to retry the deletion if it fails, up to 5 times with an exponential backoff.\n    /// </summary>\n    public static Task DeleteVerboseAsync(\n        this DirectoryPath directory,\n        ILogger? logger = default,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var policy = Policy\n            .Handle<IOException>()\n            .WaitAndRetryAsync(\n                3,\n                attempt => TimeSpan.FromMilliseconds(50 * Math.Pow(2, attempt)),\n                onRetry: (exception, calculatedWaitDuration) =>\n                {\n                    logger?.LogWarning(\n                        exception,\n                        \"Deletion of {TargetDirectory} failed. Retrying in {CalculatedWaitDuration}\",\n                        directory,\n                        calculatedWaitDuration\n                    );\n                }\n            );\n\n        return policy.ExecuteAsync(async () =>\n        {\n            await Task.Run(() => DeleteVerbose(directory, logger, cancellationToken), cancellationToken)\n                .ConfigureAwait(false);\n        });\n    }\n\n    /// <summary>\n    /// Deletes a directory and all of its contents recursively.\n    /// Removes link targets without deleting the source.\n    /// </summary>\n    public static void DeleteVerbose(\n        this DirectoryPath directory,\n        ILogger? logger = default,\n        CancellationToken cancellationToken = default\n    )\n    {\n        cancellationToken.ThrowIfCancellationRequested();\n\n        // Skip if directory does not exist\n        if (!directory.Exists)\n        {\n            return;\n        }\n        // For junction points, delete with recursive false\n        if (directory.IsSymbolicLink)\n        {\n            logger?.LogInformation(\"Removing junction point {TargetDirectory}\", directory.FullPath);\n            try\n            {\n                directory.Delete(false);\n                return;\n            }\n            catch (IOException ex)\n            {\n                throw new IOException($\"Failed to delete junction point {directory.FullPath}\", ex);\n            }\n        }\n        // Recursively delete all subdirectories\n        foreach (var subDir in directory.EnumerateDirectories())\n        {\n            DeleteVerbose(subDir, logger, cancellationToken);\n        }\n\n        // Delete all files in the directory\n        foreach (var filePath in directory.EnumerateFiles())\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            try\n            {\n                filePath.Info.Attributes = FileAttributes.Normal;\n                filePath.Delete();\n            }\n            catch (IOException ex)\n            {\n                throw new IOException($\"Failed to delete file {filePath.FullPath}\", ex);\n            }\n        }\n\n        // Delete this directory\n        try\n        {\n            directory.Delete(false);\n        }\n        catch (IOException ex)\n        {\n            throw new IOException($\"Failed to delete directory {directory}\", ex);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/DynamicDataExtensions.cs",
    "content": "﻿using DynamicData;\n\nnamespace StabilityMatrix.Core.Extensions;\n\npublic static class DynamicDataExtensions\n{\n    /// <summary>\n    /// Loads the cache with the specified items in an optimised manner i.e. calculates the differences between the old and new items\n    ///  in the list and amends only the differences.\n    /// </summary>\n    /// <typeparam name=\"TObject\">The type of the object.</typeparam>\n    /// <typeparam name=\"TKey\">The type of the key.</typeparam>\n    /// <param name=\"source\">The source.</param>\n    /// <param name=\"allItems\">The items to add, update or delete.</param>\n    /// <exception cref=\"System.ArgumentNullException\">source.</exception>\n    public static void EditDiff<TObject, TKey>(\n        this ISourceCache<TObject, TKey> source,\n        IEnumerable<TObject> allItems\n    )\n        where TObject : IEquatable<TObject>\n        where TKey : notnull\n    {\n        if (source is null)\n        {\n            throw new ArgumentNullException(nameof(source));\n        }\n\n        if (allItems is null)\n        {\n            throw new ArgumentNullException(nameof(allItems));\n        }\n\n        source.EditDiff(allItems, (x, y) => x.Equals(y));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/EnumAttributes.cs",
    "content": "﻿namespace StabilityMatrix.Core.Extensions;\n\npublic static class EnumAttributeExtensions\n{\n    private static T? GetAttributeValue<T>(Enum value)\n    {\n        var type = value.GetType();\n        var fieldInfo = type.GetField(value.ToString());\n        // Get the string value attributes\n        var attribs = fieldInfo?.GetCustomAttributes(typeof(T), false) as T[];\n        // Return the first if there was a match.\n        return attribs?.Length > 0 ? attribs[0] : default;\n    }\n    /// <summary>\n    /// Gets the StringValue field attribute on a given enum value.\n    /// If not found, returns the enum value itself as a string.\n    /// </summary>\n    /// <param name=\"value\"></param>\n    /// <returns></returns>\n    public static string GetStringValue(this Enum value)\n    {\n        var attr = GetAttributeValue<StringValueAttribute>(value)?.StringValue;\n        return attr ?? Enum.GetName(value.GetType(), value)!;\n    }\n    /// <summary>\n    /// Gets the Description field attribute on a given enum value.\n    /// </summary>\n    /// <param name=\"value\"></param>\n    /// <returns></returns>\n    public static string? GetDescription(this Enum value)\n    {\n        return GetAttributeValue<DescriptionAttribute>(value)?.Description;\n    }\n}\n\n[AttributeUsage(AttributeTargets.Field)]\npublic sealed class StringValueAttribute : Attribute\n{\n    public string StringValue { get; }\n    public StringValueAttribute(string value) {\n        StringValue = value;\n    }\n}\n\n[AttributeUsage(AttributeTargets.Field)]\npublic sealed class DescriptionAttribute : Attribute\n{\n    public string Description { get; }\n    public DescriptionAttribute(string value) {\n        Description = value;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/EnumConversion.cs",
    "content": "﻿namespace StabilityMatrix.Core.Extensions;\n\npublic static class EnumConversionExtensions\n{\n    public static T? ConvertTo<T>(this Enum value) where T : Enum\n    {\n        var type = value.GetType();\n        var fieldInfo = type.GetField(value.ToString());\n        // Get the string value attributes\n        var attribs = fieldInfo?.GetCustomAttributes(typeof(ConvertToAttribute<T>), false) as ConvertToAttribute<T>[];\n        // Return the first if there was a match.\n        return attribs?.Length > 0 ? attribs[0].ConvertToEnum : default;\n    }\n}\n\n[AttributeUsage(AttributeTargets.Field)]\npublic sealed class ConvertToAttribute<T> : Attribute where T : Enum\n{\n    public T ConvertToEnum { get; }\n    public ConvertToAttribute(T toEnum)\n    {\n        ConvertToEnum = toEnum;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/EnumerableExtensions.cs",
    "content": "﻿namespace StabilityMatrix.Core.Extensions;\n\npublic static class EnumerableExtensions\n{\n    public static IEnumerable<(int, T)> Enumerate<T>(this IEnumerable<T> items, int start)\n    {\n        return items.Select((item, index) => (index + start, item));\n    }\n\n    public static IEnumerable<(int, T)> Enumerate<T>(this IEnumerable<T> items)\n    {\n        return items.Select((item, index) => (index, item));\n    }\n\n    /// <summary>\n    /// Nested for loop helper\n    /// </summary>\n    public static IEnumerable<(T, T)> Product<T>(this IEnumerable<T> items, IEnumerable<T> other)\n    {\n        return from item1 in items from item2 in other select (item1, item2);\n    }\n\n    public static async Task<IEnumerable<TResult>> SelectAsync<TSource, TResult>(\n        this IEnumerable<TSource> source,\n        Func<TSource, Task<TResult>> method,\n        int concurrency = int.MaxValue\n    )\n    {\n        using var semaphore = new SemaphoreSlim(concurrency);\n        return await Task.WhenAll(\n                source.Select(async s =>\n                {\n                    try\n                    {\n                        // ReSharper disable once AccessToDisposedClosure\n                        await semaphore.WaitAsync().ConfigureAwait(false);\n                        return await method(s).ConfigureAwait(false);\n                    }\n                    finally\n                    {\n                        // ReSharper disable once AccessToDisposedClosure\n                        semaphore.Release();\n                    }\n                })\n            )\n            .ConfigureAwait(false);\n    }\n\n    /// <summary>\n    /// Executes a specified action on each element in a collection.\n    /// </summary>\n    /// <typeparam name=\"T\">The type of elements in the collection.</typeparam>\n    /// <param name=\"items\">The collection to iterate over.</param>\n    /// <param name=\"action\">The action to perform on each element in the collection.</param>\n    public static void ForEach<T>(this IEnumerable<T> items, Action<T> action)\n    {\n        foreach (var item in items)\n        {\n            action(item);\n        }\n    }\n\n    // Concat an element if not null\n    public static IEnumerable<T> AppendIfNotNull<T>(this IEnumerable<T> source, T? element)\n        where T : class\n    {\n        if (element != null)\n        {\n            return source.Append(element);\n        }\n        return source;\n    }\n\n    // Concat an enumerable if not null\n    public static IEnumerable<T> ConcatIfNotNull<T>(this IEnumerable<T> source, IEnumerable<T>? elements)\n        where T : class\n    {\n        if (elements != null)\n        {\n            return source.Concat(elements);\n        }\n        return source;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/HashExtensions.cs",
    "content": "﻿using Blake3;\n\nnamespace StabilityMatrix.Core.Extensions;\n\npublic static class HashExtensions\n{\n    public static Guid ToGuid(this Hash hash)\n    {\n        return new Guid(hash.AsSpan()[..16]);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/JsonObjectExtensions.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Nodes;\nusing JetBrains.Annotations;\n\nnamespace StabilityMatrix.Core.Extensions;\n\n[PublicAPI]\npublic static class JsonObjectExtensions\n{\n    /// <summary>\n    /// Returns the value of a property with the specified name, or the specified default value if not found.\n    /// </summary>\n    public static T? GetPropertyValueOrDefault<T>(\n        this JsonObject jsonObject,\n        string propertyName,\n        T? defaultValue = default\n    )\n    {\n        if (!jsonObject.TryGetPropertyValue(propertyName, out var node))\n        {\n            return defaultValue;\n        }\n\n        return node.Deserialize<T>();\n    }\n\n    /// <summary>\n    /// Get a keyed value from a JsonObject if it is not null,\n    /// otherwise add and return a new instance of a JsonObject.\n    /// </summary>\n    public static JsonObject GetOrAddNonNullJsonObject(this JsonObject jsonObject, string key)\n    {\n        if (jsonObject.TryGetPropertyValue(key, out var value) && value is JsonObject jsonObjectValue)\n        {\n            return jsonObjectValue;\n        }\n\n        var newJsonObject = new JsonObject();\n        jsonObject[key] = newJsonObject;\n        return newJsonObject;\n    }\n\n    /// <summary>\n    /// Get a keyed value path from a JsonObject if it is not null,\n    /// otherwise add and return a new instance of a JsonObject.\n    /// </summary>\n    public static JsonObject GetOrAddNonNullJsonObject(\n        this JsonObject jsonObject,\n        IEnumerable<string> keyPath\n    )\n    {\n        return keyPath.Aggregate(jsonObject, (current, key) => current.GetOrAddNonNullJsonObject(key));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/LiteDBExtensions.cs",
    "content": "﻿using System.Collections.Concurrent;\nusing System.Linq.Expressions;\nusing System.Reflection;\nusing LiteDB;\nusing LiteDB.Async;\n\nnamespace StabilityMatrix.Core.Extensions;\n\n// ReSharper disable once InconsistentNaming\npublic static class LiteDBExtensions\n{\n    private static readonly ConcurrentDictionary<Type, (Type PropertyType, string MemberName, bool IsList)> Mapper =\n        new();\n\n    public static void Register<T, TU>(Expression<Func<T, List<TU>?>> exp, string? collection = null)\n    {\n        var member = (exp.Body is MemberExpression body ? body.Member : null) as PropertyInfo;\n        if (member == null)\n            throw new ArgumentException(\"Expecting Member Expression\");\n        BsonMapper.Global.Entity<T>().DbRef(exp, collection);\n        Mapper.TryAdd(typeof(T), (typeof(TU), member.Name, true));\n    }\n\n    public static void Register<T, TU>(Expression<Func<T, TU?>> exp, string? collection = null)\n    {\n        var member = (exp.Body is MemberExpression body ? body.Member : null) as PropertyInfo;\n        if (member == null)\n            throw new ArgumentException(\"Expecting Member Expression\");\n        BsonMapper.Global.Entity<T>().DbRef(exp, collection);\n        Mapper.TryAdd(typeof(T), (typeof(TU), member.Name, false));\n    }\n\n    public static ILiteCollection<T>? IncludeAll<T>(this ILiteCollection<T> col)\n    {\n        if (!Mapper.ContainsKey(typeof(T)))\n            return null;\n\n        var stringList = new List<string>();\n        var key = typeof(T);\n        var values = new List<string>();\n        var flag = true;\n        while (Mapper.TryGetValue(key, out var tuple))\n        {\n            var str = tuple.MemberName + (tuple.IsList ? \"[*]\" : \"\");\n            values.Add(flag ? \"$.\" + str : str);\n            stringList.Add(string.Join(\".\", values));\n            key = tuple.PropertyType;\n            flag = false;\n        }\n\n        return stringList.Aggregate(col, (current, keySelector) => current.Include((BsonExpression)keySelector));\n    }\n\n    public static ILiteCollectionAsync<T> IncludeAll<T>(this ILiteCollectionAsync<T> col)\n    {\n        if (!Mapper.ContainsKey(typeof(T)))\n            return col;\n\n        var stringList = new List<string>();\n        var key = typeof(T);\n        var values = new List<string>();\n        var flag = true;\n        while (Mapper.TryGetValue(key, out var tuple))\n        {\n            var str = tuple.MemberName + (tuple.IsList ? \"[*]\" : \"\");\n            values.Add(flag ? \"$.\" + str : str);\n            stringList.Add(string.Join(\".\", values));\n            key = tuple.PropertyType;\n            flag = false;\n        }\n\n        return stringList.Aggregate(col, (current, keySelector) => current.Include((BsonExpression)keySelector));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/NullableExtensions.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Diagnostics;\nusing System.Runtime.CompilerServices;\nusing JetBrains.Annotations;\n\nnamespace StabilityMatrix.Core.Extensions;\n\npublic static class NullableExtensions\n{\n    /// <summary>\n    /// Unwraps a nullable object, throwing an exception if it is null.\n    /// </summary>\n    /// <exception cref=\"InvalidOperationException\">\n    /// Thrown if (<typeparamref name=\"T\"/>) <paramref name=\"obj\"/> is null.\n    /// </exception>\n    [DebuggerStepThrough]\n    [EditorBrowsable(EditorBrowsableState.Never)]\n    [MethodImpl(MethodImplOptions.AggressiveInlining)]\n    [ContractAnnotation(\"obj:null => halt; obj:notnull => notnull\")]\n    [return: System.Diagnostics.CodeAnalysis.NotNull]\n    public static T Unwrap<T>(\n        [System.Diagnostics.CodeAnalysis.NotNull] this T? obj,\n        [CallerArgumentExpression(\"obj\")] string? paramName = null\n    )\n        where T : class\n    {\n        if (obj is null)\n        {\n            throw new ArgumentNullException(paramName, $\"Unwrap of a null value ({typeof(T)}) {paramName}.\");\n        }\n        return obj;\n    }\n\n    /// <summary>\n    /// Unwraps a nullable struct object, throwing an exception if it is null.\n    /// </summary>\n    /// <exception cref=\"InvalidOperationException\">\n    /// Thrown if (<typeparamref name=\"T\"/>) <paramref name=\"obj\"/> is null.\n    /// </exception>\n    [DebuggerStepThrough]\n    [EditorBrowsable(EditorBrowsableState.Never)]\n    [MethodImpl(MethodImplOptions.AggressiveInlining)]\n    [ContractAnnotation(\"obj:null => halt\")]\n    public static T Unwrap<T>(\n        [System.Diagnostics.CodeAnalysis.NotNull] this T? obj,\n        [CallerArgumentExpression(\"obj\")] string? paramName = null\n    )\n        where T : struct\n    {\n        if (obj is null)\n        {\n            throw new ArgumentNullException(paramName, $\"Unwrap of a null value ({typeof(T)}) {paramName}.\");\n        }\n        return obj.Value;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/ObjectExtensions.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Reflection;\nusing JetBrains.Annotations;\nusing RockLib.Reflection.Optimized;\n\nnamespace StabilityMatrix.Core.Extensions;\n\n[PublicAPI]\npublic static class ObjectExtensions\n{\n    /// <summary>\n    /// Cache of Types to named field getters\n    /// </summary>\n    private static readonly Dictionary<Type, Dictionary<string, Func<object, object>>> FieldGetterTypeCache = new();\n\n    /// <summary>\n    /// Cache of Types to named field setters\n    /// </summary>\n    private static readonly Dictionary<Type, Dictionary<string, Action<object, object>>> FieldSetterTypeCache = new();\n\n    /// <summary>\n    /// Cache of Types to named property getters\n    /// </summary>\n    private static readonly Dictionary<Type, Dictionary<string, Func<object, object>>> PropertyGetterTypeCache = new();\n\n    /// <summary>\n    /// Get the value of a named private field from an object\n    /// </summary>\n    /// <remarks>\n    /// The field must be defined by the runtime type of <see cref=\"obj\"/> or its first base type.\n    /// For higher inheritance levels, use <see cref=\"GetPrivateField{TObject,T}\"/> to specify the exact defining type.\n    /// </remarks>\n    public static T? GetPrivateField<T>(this object obj, string fieldName)\n    {\n        // Check cache\n        var fieldGetterCache = FieldGetterTypeCache.GetOrAdd(obj.GetType());\n\n        if (!fieldGetterCache.TryGetValue(fieldName, out var fieldGetter))\n        {\n            // Get the field\n            var field = obj.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);\n            // Try get from parent\n            field ??= obj.GetType().BaseType?.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);\n\n            if (field is null)\n            {\n                throw new ArgumentException($\"Field {fieldName} not found on type {obj.GetType().Name}\");\n            }\n\n            // Create a getter for the field\n            fieldGetter = field.CreateGetter();\n\n            // Add to cache\n            fieldGetterCache.Add(fieldName, fieldGetter);\n        }\n\n        return (T?)fieldGetter(obj);\n    }\n\n    /// <summary>\n    /// Get the value of a protected property from an object\n    /// </summary>\n    /// <remarks>\n    /// The property must be defined by the runtime type of <see cref=\"obj\"/> or its first base type.\n    /// </remarks>\n    public static object? GetProtectedProperty(this object obj, [LocalizationRequired(false)] string propertyName)\n    {\n        // Check cache\n        var fieldGetterCache = PropertyGetterTypeCache.GetOrAdd(obj.GetType());\n\n        if (!fieldGetterCache.TryGetValue(propertyName, out var propertyGetter))\n        {\n            // Get the field\n            var propertyInfo = obj.GetType()\n                .GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);\n            // Try get from parent\n            propertyInfo ??= obj.GetType()\n                .BaseType\n                ?.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);\n\n            if (propertyInfo is null)\n            {\n                throw new ArgumentException($\"Property {propertyName} not found on type {obj.GetType().Name}\");\n            }\n\n            // Create a getter for the field\n            propertyGetter = o => propertyInfo.GetValue(o)!;\n\n            // Add to cache\n            fieldGetterCache.Add(propertyName, propertyGetter);\n        }\n\n        return (object?)propertyGetter(obj);\n    }\n\n    /// <summary>\n    /// Get the value of a protected property from an object\n    /// </summary>\n    /// <remarks>\n    /// The property must be defined by the runtime type of <see cref=\"obj\"/> or its first base type.\n    /// </remarks>\n    public static T? GetProtectedProperty<T>(this object obj, [LocalizationRequired(false)] string propertyName)\n        where T : class\n    {\n        return (T?)GetProtectedProperty(obj, propertyName);\n    }\n\n    /// <summary>\n    /// Get the value of a named private field from an object\n    /// </summary>\n    /// <typeparam name=\"TObject\">Type of the object that defines the field, must be a base class of <see cref=\"obj\"/></typeparam>\n    /// <typeparam name=\"T\">Type of the field</typeparam>\n    public static T? GetPrivateField<TObject, T>(this TObject obj, string fieldName)\n        where TObject : class\n    {\n        // Check cache\n        var fieldGetterCache = FieldGetterTypeCache.GetOrAdd(typeof(TObject));\n\n        if (!fieldGetterCache.TryGetValue(fieldName, out var fieldGetter))\n        {\n            // Get the field\n            var field = typeof(TObject).GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);\n\n            if (field is null)\n            {\n                throw new ArgumentException(\n                    $\"Field {typeof(TObject).Name}.{fieldName} not found on type {obj.GetType().Name}\"\n                );\n            }\n\n            // Create a getter for the field\n            fieldGetter = field.CreateGetter();\n\n            // Add to cache\n            fieldGetterCache.Add(fieldName, fieldGetter);\n        }\n\n        return (T?)fieldGetter(obj);\n    }\n\n    /// <summary>\n    /// Set the value of a named private field on an object\n    /// </summary>\n    public static void SetPrivateField(this object obj, string fieldName, object value)\n    {\n        // Check cache\n        var fieldSetterCache = FieldSetterTypeCache.GetOrAdd(obj.GetType());\n\n        if (!fieldSetterCache.TryGetValue(fieldName, out var fieldSetter))\n        {\n            // Get the field\n            var field = obj.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);\n            // Try get from parent\n            field ??= obj.GetType().BaseType?.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);\n\n            if (field is null)\n            {\n                throw new ArgumentException($\"Field {fieldName} not found on type {obj.GetType().Name}\");\n            }\n\n            // Create a setter for the field\n            fieldSetter = field.CreateSetter();\n\n            // Add to cache\n            fieldSetterCache.Add(fieldName, fieldSetter);\n        }\n\n        fieldSetter(obj, value);\n    }\n\n    /// <summary>\n    /// Set the value of a named private field on an object\n    /// </summary>\n    public static void SetPrivateField<T>(this object obj, string fieldName, T? value)\n    {\n        // Check cache\n        var fieldSetterCache = FieldSetterTypeCache.GetOrAdd(obj.GetType());\n\n        if (!fieldSetterCache.TryGetValue(fieldName, out var fieldSetter))\n        {\n            // Get the field\n            var field = obj.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);\n            // Try get from parent\n            field ??= obj.GetType().BaseType?.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);\n\n            if (field is null)\n            {\n                throw new ArgumentException($\"Field {fieldName} not found on type {obj.GetType().Name}\");\n            }\n\n            // Create a setter for the field\n            fieldSetter = field.CreateSetter();\n\n            // Add to cache\n            fieldSetterCache.Add(fieldName, fieldSetter);\n        }\n\n        fieldSetter(obj, value!);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/ProgressExtensions.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Extensions;\n\npublic static class ProgressExtensions\n{\n    [return: NotNullIfNotNull(nameof(progress))]\n    public static Action<ProcessOutput>? AsProcessOutputHandler(\n        this IProgress<ProgressReport>? progress,\n        bool setMessageAsOutput = true\n    )\n    {\n        if (progress is null)\n        {\n            return null;\n        }\n\n        return output =>\n        {\n            progress.Report(\n                new ProgressReport\n                {\n                    Progress = -1f,\n                    IsIndeterminate = true,\n                    Message = setMessageAsOutput ? output.Text : null,\n                    ProcessOutput = output,\n                    PrintToConsole = true\n                }\n            );\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/SemVersionExtensions.cs",
    "content": "﻿using Semver;\n\nnamespace StabilityMatrix.Core.Extensions;\n\npublic static class SemVersionExtensions\n{\n    public static string ToDisplayString(this SemVersion version)\n    {\n        var versionString = $\"{version.Major}.{version.Minor}.{version.Patch}\";\n\n        // Add the build metadata if we have pre-release information\n        if (version.PrereleaseIdentifiers.Count > 0)\n        {\n            versionString += $\"-{version.Prerelease}\";\n\n            if (!string.IsNullOrWhiteSpace(version.Metadata))\n            {\n                // First 7 characters of the commit hash\n                versionString += $\"+{version.Metadata[..7]}\";\n            }\n        }\n        return versionString;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/ServiceProviderExtensions.cs",
    "content": "﻿using System.Collections.Immutable;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\n\nnamespace StabilityMatrix.Core.Extensions;\n\npublic static class ServiceProviderExtensions\n{\n    /// <summary>\n    /// Gets all managed <see cref=\"IDisposable\"/> services from the <see cref=\"ServiceProvider\"/>.\n    /// Accesses the private field `Root[ServiceProviderEngineScope]._disposables[List&lt;object&gt;?]`.\n    /// </summary>\n    /// <param name=\"serviceProvider\"></param>\n    /// <returns></returns>\n    public static List<object> GetDisposables(this ServiceProvider serviceProvider)\n    {\n        // ServiceProvider: internal ServiceProviderEngineScope Root { get; }\n        var root =\n            serviceProvider.GetProtectedProperty(\"Root\")\n            ?? throw new InvalidOperationException(\"Could not get ServiceProviderEngineScope Root.\");\n\n        // ServiceProviderEngineScope: private List<object>? _disposables\n        var disposables = root.GetPrivateField<List<object>?>(\"_disposables\");\n\n        return disposables ?? [];\n    }\n\n    private static void SafeDispose(\n        this ServiceProvider serviceProvider,\n        TimeSpan timeoutTotal,\n        TimeSpan timeoutPerDispose,\n        ILogger logger\n    )\n    {\n        var timeoutTotalCts = new CancellationTokenSource(timeoutTotal);\n\n        // Dispose services\n        var toDispose = serviceProvider.GetDisposables().OfType<IDisposable>().ToImmutableList();\n\n        logger.LogDebug(\"OnExit: Preparing to Dispose {Count} Services\", toDispose.Count);\n\n        // Dispose IDisposable services\n        foreach (var disposable in toDispose)\n        {\n            logger.LogTrace(\"OnExit: Disposing {Name}\", disposable.GetType().Name);\n\n            using var instanceCts = CancellationTokenSource.CreateLinkedTokenSource(\n                timeoutTotalCts.Token,\n                new CancellationTokenSource(timeoutPerDispose).Token\n            );\n\n            try\n            {\n                Task.Run(() => disposable.Dispose(), instanceCts.Token).Wait(instanceCts.Token);\n            }\n            catch (OperationCanceledException)\n            {\n                logger.LogWarning(\"OnExit: Timeout disposing {Name}\", disposable.GetType().Name);\n            }\n            catch (Exception e)\n            {\n                logger.LogError(e, \"OnExit: Failed to dispose {Name}\", disposable.GetType().Name);\n            }\n        }\n    }\n\n    private static async ValueTask SafeDisposeAsync(\n        this ServiceProvider serviceProvider,\n        TimeSpan timeoutTotal,\n        TimeSpan timeoutPerDispose,\n        ILogger logger\n    )\n    {\n        var timeoutTotalCts = new CancellationTokenSource(timeoutTotal);\n\n        // Dispose services\n        var toDispose = serviceProvider.GetDisposables().OfType<IDisposable>().ToImmutableList();\n\n        // Dispose IDisposable services\n        foreach (var disposable in toDispose)\n        {\n            logger.LogTrace(\"Disposing {Name}\", disposable.GetType().Name);\n\n            using var instanceCts = CancellationTokenSource.CreateLinkedTokenSource(\n                timeoutTotalCts.Token,\n                new CancellationTokenSource(timeoutPerDispose).Token\n            );\n\n            try\n            {\n                if (disposable is IAsyncDisposable asyncDisposable)\n                {\n                    await asyncDisposable\n                        .DisposeAsync()\n                        .AsTask()\n                        .WaitAsync(instanceCts.Token)\n                        .ConfigureAwait(false);\n                }\n                else\n                {\n                    await Task.Run(() => disposable.Dispose(), instanceCts.Token)\n                        .WaitAsync(instanceCts.Token)\n                        .ConfigureAwait(false);\n                }\n            }\n            catch (OperationCanceledException)\n            {\n                logger.LogWarning(\"Timeout disposing {Name}\", disposable.GetType().Name);\n            }\n            catch (Exception e)\n            {\n                logger.LogError(e, \"Failed to dispose {Name}\", disposable.GetType().Name);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/SizeExtensions.cs",
    "content": "﻿using System.Drawing;\n\nnamespace StabilityMatrix.Core.Extensions;\n\npublic static class SizeExtensions\n{\n    public static Size WithScale(this Size size, double scale)\n    {\n        return new Size((int)Math.Floor(size.Width * scale), (int)Math.Floor(size.Height * scale));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/StringExtensions.cs",
    "content": "﻿using System.Diagnostics.Contracts;\nusing System.Text;\nusing System.Text.RegularExpressions;\n\nnamespace StabilityMatrix.Core.Extensions;\n\npublic static class StringExtensions\n{\n    private static string EncodeNonAsciiCharacters(string value)\n    {\n        var sb = new StringBuilder();\n        foreach (var c in value)\n        {\n            // If not ascii or not printable\n            if (c > 127 || c < 32)\n            {\n                // This character is too big for ASCII\n                var encodedValue = \"\\\\u\" + ((int)c).ToString(\"x4\");\n                sb.Append(encodedValue);\n            }\n            else\n            {\n                sb.Append(c);\n            }\n        }\n        return sb.ToString();\n    }\n\n    /// <summary>\n    /// Converts string to repr\n    /// </summary>\n    public static string ToRepr(this string? str)\n    {\n        if (str is null)\n        {\n            return \"<null>\";\n        }\n        using var writer = new StringWriter();\n        writer.Write(\"'\");\n        foreach (var ch in str)\n        {\n            writer.Write(\n                ch switch\n                {\n                    '\\0' => \"\\\\0\",\n                    '\\n' => \"\\\\n\",\n                    '\\r' => \"\\\\r\",\n                    '\\t' => \"\\\\t\",\n                    // Non ascii\n                    _ when ch > 127 || ch < 32 => $\"\\\\u{(int)ch:x4}\",\n                    _ => ch.ToString()\n                }\n            );\n        }\n        writer.Write(\"'\");\n\n        return writer.ToString();\n    }\n\n    /// <summary>\n    /// Counts continuous sequence of a character\n    /// from the start of the string\n    /// </summary>\n    public static int CountStart(this string str, char c)\n    {\n        var count = 0;\n        foreach (var ch in str)\n        {\n            if (ch == c)\n            {\n                count++;\n            }\n            else\n            {\n                break;\n            }\n        }\n        return count;\n    }\n\n    /// <summary>\n    /// Strips the substring from the start of the string\n    /// </summary>\n    [Pure]\n    public static string StripStart(this string str, string subString)\n    {\n        var index = str.IndexOf(subString, StringComparison.Ordinal);\n        return index < 0 ? str : str.Remove(index, subString.Length);\n    }\n\n    /// <summary>\n    /// Strips the substring from the end of the string\n    /// </summary>\n    [Pure]\n    public static string StripEnd(this string str, string subString)\n    {\n        var index = str.LastIndexOf(subString, StringComparison.Ordinal);\n        return index < 0 ? str : str.Remove(index, subString.Length);\n    }\n\n    /// <summary>\n    /// Splits lines by \\n and \\r\\n\n    /// </summary>\n    [Pure]\n    // ReSharper disable once ReturnTypeCanBeEnumerable.Global\n    public static string[] SplitLines(this string str, StringSplitOptions options = StringSplitOptions.None)\n    {\n        return str.Split(new[] { \"\\r\\n\", \"\\n\" }, options);\n    }\n\n    /// <summary>\n    /// Normalizes directory separator characters in a given path\n    /// </summary>\n    [Pure]\n    public static string NormalizePathSeparators(this string path)\n    {\n        return path.Replace('\\\\', '/');\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/TypeExtensions.cs",
    "content": "﻿using System.Reflection;\n\nnamespace StabilityMatrix.Core.Extensions;\n\npublic static class TypeExtensions\n{\n    /// <summary>\n    /// Get all properties marked with an attribute of type <see cref=\"TAttribute\"/>\n    /// </summary>\n    public static IEnumerable<PropertyInfo> GetPropertiesWithAttribute<TAttribute>(this Type type)\n        where TAttribute : Attribute\n    {\n        return type.GetProperties().Where(p => Attribute.IsDefined(p, typeof(TAttribute)));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Extensions/UriExtensions.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Web;\n\nnamespace StabilityMatrix.Core.Extensions;\n\n[Localizable(false)]\npublic static class UriExtensions\n{\n    public static Uri WithQuery(this Uri uri, string key, string value)\n    {\n        var builder = new UriBuilder(uri);\n        var query = HttpUtility.ParseQueryString(builder.Query);\n        query[key] = value;\n        builder.Query = query.ToString() ?? string.Empty;\n        return builder.Uri;\n    }\n\n    public static Uri Append(this Uri uri, params string[] paths)\n    {\n        return new Uri(\n            paths.Aggregate(\n                uri.AbsoluteUri,\n                (current, path) => $\"{current.TrimEnd('/')}/{path.TrimStart('/')}\"\n            )\n        );\n    }\n\n    /// <summary>\n    /// Returns a new Uri with the query values redacted.\n    /// Non-empty query values are replaced with a single asterisk.\n    /// </summary>\n    public static Uri RedactQueryValues(this Uri uri)\n    {\n        var builder = new UriBuilder(uri);\n\n        var queryCollection = HttpUtility.ParseQueryString(builder.Query);\n\n        foreach (var key in queryCollection.AllKeys)\n        {\n            if (!string.IsNullOrEmpty(queryCollection[key]))\n            {\n                queryCollection[key] = \"*\";\n            }\n        }\n\n        builder.Query = queryCollection.ToString() ?? string.Empty;\n        return builder.Uri;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Git/CachedCommandGitVersionProvider.cs",
    "content": "﻿using Microsoft.Extensions.Caching.Memory;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Core.Git;\n\npublic class CachedCommandGitVersionProvider(string repositoryUri, IPrerequisiteHelper prerequisiteHelper)\n    : IGitVersionProvider\n{\n    private readonly CommandGitVersionProvider commandGitVersionProvider =\n        new(repositoryUri, prerequisiteHelper);\n    private readonly IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions());\n\n    public async Task<IReadOnlyList<GitVersion>> FetchTagsAsync(\n        int limit = 0,\n        CancellationToken cancellationToken = default\n    )\n    {\n        return (\n            await memoryCache\n                .GetOrCreateAsync(\n                    \"tags\",\n                    async entry =>\n                    {\n                        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);\n                        return await commandGitVersionProvider\n                            .FetchTagsAsync(limit, cancellationToken)\n                            .ConfigureAwait(false);\n                    }\n                )\n                .ConfigureAwait(false)\n        )!;\n    }\n\n    public async Task<IReadOnlyList<GitVersion>> FetchBranchesAsync(\n        int limit = 0,\n        CancellationToken cancellationToken = default\n    )\n    {\n        return (\n            await memoryCache\n                .GetOrCreateAsync(\n                    \"branches\",\n                    async entry =>\n                    {\n                        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);\n                        return await commandGitVersionProvider\n                            .FetchBranchesAsync(limit, cancellationToken)\n                            .ConfigureAwait(false);\n                    }\n                )\n                .ConfigureAwait(false)\n        )!;\n    }\n\n    public async Task<IReadOnlyList<GitVersion>> FetchCommitsAsync(\n        string? branch,\n        int limit = 0,\n        CancellationToken cancellationToken = default\n    )\n    {\n        return (\n            await memoryCache\n                .GetOrCreateAsync(\n                    $\"commits-{branch}\",\n                    async entry =>\n                    {\n                        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);\n                        return await commandGitVersionProvider\n                            .FetchCommitsAsync(branch, limit, cancellationToken)\n                            .ConfigureAwait(false);\n                    }\n                )\n                .ConfigureAwait(false)\n        )!;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Git/CommandGitVersionProvider.cs",
    "content": "﻿using System.ComponentModel;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Git;\n\n/// <summary>\n/// Fetch git versions via commands to a git process runner\n/// </summary>\n[Localizable(false)]\npublic class CommandGitVersionProvider(string repositoryUri, IPrerequisiteHelper prerequisiteHelper)\n    : IGitVersionProvider\n{\n    public async Task<IReadOnlyList<GitVersion>> FetchTagsAsync(\n        int limit = 0,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var tags = new List<GitVersion>();\n        ProcessArgs args =\n        [\n            \"-c\",\n            \"versionsort.suffix=-\",\n            \"ls-remote\",\n            \"--tags\",\n            \"--sort=-v:refname\",\n            repositoryUri,\n        ];\n\n        var result = await prerequisiteHelper\n            .GetGitOutput(args)\n            .EnsureSuccessExitCode()\n            .ConfigureAwait(false);\n\n        if (result is { IsSuccessExitCode: true, StandardOutput: not null })\n        {\n            var tagNames = result\n                .StandardOutput.Split('\\n', StringSplitOptions.RemoveEmptyEntries)\n                .Select(line => line.Split('\\t').LastOrDefault()?.Replace(\"refs/tags/\", \"\").Trim())\n                .Where(s => !string.IsNullOrWhiteSpace(s))\n                .Select(s =>\n                {\n                    const string peel = \"^{}\";\n                    return s!.EndsWith(peel, StringComparison.Ordinal) ? s[..^peel.Length] : s;\n                })\n                .Distinct()\n                .Take(limit > 0 ? limit : int.MaxValue);\n\n            tags.AddRange(tagNames.Select(tag => new GitVersion { Tag = tag }));\n        }\n\n        return tags;\n    }\n\n    public async Task<IReadOnlyList<GitVersion>> FetchBranchesAsync(\n        int limit = 0,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var branches = new List<GitVersion>();\n        ProcessArgs args = [\"ls-remote\", \"--heads\", repositoryUri];\n\n        var result = await prerequisiteHelper\n            .GetGitOutput(args)\n            .EnsureSuccessExitCode()\n            .ConfigureAwait(false);\n\n        if (result is { IsSuccessExitCode: true, StandardOutput: not null })\n        {\n            var branchLines = result.StandardOutput.Split('\\n', StringSplitOptions.RemoveEmptyEntries);\n            var branchNames = branchLines\n                .Select(line => line.Split('\\t').LastOrDefault()?.Replace(\"refs/heads/\", \"\").Trim())\n                .Where(line => !string.IsNullOrWhiteSpace(line))\n                .Take(limit > 0 ? limit : int.MaxValue);\n\n            branches.AddRange(branchNames.Select(branch => new GitVersion { Branch = branch }));\n        }\n\n        return branches;\n    }\n\n    public async Task<IReadOnlyList<GitVersion>> FetchCommitsAsync(\n        string? branch = null,\n        int limit = 0,\n        CancellationToken cancellationToken = default\n    )\n    {\n        // Cannot use ls-remote, so clone to temp directory and fetch\n        var commits = new List<GitVersion>();\n\n        using var tempDirectory = new TempDirectoryPath();\n\n        ProcessArgs args = [\"clone\", \"--bare\", \"--filter=tree:0\", \"--single-branch\"];\n\n        if (!string.IsNullOrEmpty(branch))\n        {\n            args = args.Concat([\"--branch\", branch]);\n        }\n\n        args = args.Concat([repositoryUri, tempDirectory.FullPath]);\n\n        _ = await prerequisiteHelper.GetGitOutput(args).EnsureSuccessExitCode().ConfigureAwait(false);\n\n        _ = await prerequisiteHelper\n            .GetGitOutput([\"fetch\", \"--all\"], tempDirectory.FullPath)\n            .EnsureSuccessExitCode()\n            .ConfigureAwait(false);\n\n        // If not branch not specified, get it now\n        if (string.IsNullOrEmpty(branch))\n        {\n            var branchResult = await prerequisiteHelper\n                .GetGitOutput([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], tempDirectory.FullPath)\n                .EnsureSuccessExitCode()\n                .ConfigureAwait(false);\n\n            if (string.IsNullOrEmpty(branchResult.StandardOutput?.Trim()))\n            {\n                // Could not get branch\n                return [];\n            }\n\n            branch = branchResult.StandardOutput.Trim();\n        }\n\n        ProcessArgs logArgs = [\"log\", \"--pretty=format:%H\", \"--no-decorate\"];\n\n        if (limit > 0)\n        {\n            logArgs = logArgs.Concat([$\"--max-count={limit}\"]);\n        }\n\n        var logResult = await prerequisiteHelper\n            .GetGitOutput(logArgs.Concat([branch]), tempDirectory.FullPath)\n            .EnsureSuccessExitCode()\n            .ConfigureAwait(false);\n\n        if (logResult is { StandardOutput: not null })\n        {\n            var commitLines = logResult.StandardOutput.Split('\\n', StringSplitOptions.RemoveEmptyEntries);\n\n            // Only accept lines of 40 characters and valid hexadecimal hash\n            var commitHashes = commitLines\n                .Select(line => line.Trim())\n                .Where(line => !string.IsNullOrWhiteSpace(line))\n                .Where(line => line.Length == 40 && line.All(char.IsLetterOrDigit));\n\n            var commitObjs = commitHashes\n                .Take(limit > 0 ? limit : int.MaxValue)\n                .Select(commitHash => new GitVersion { Branch = branch, CommitSha = commitHash });\n\n            commits.AddRange(commitObjs);\n        }\n\n        return commits;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Git/IGitVersionProvider.cs",
    "content": "﻿using StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Core.Git;\n\npublic interface IGitVersionProvider\n{\n    /// <summary>\n    /// Fetches all tags from the remote repository.\n    /// </summary>\n    Task<IReadOnlyList<GitVersion>> FetchTagsAsync(\n        int limit = 0,\n        CancellationToken cancellationToken = default\n    );\n\n    /// <summary>\n    /// Fetches all branches from the remote repository.\n    /// </summary>\n    Task<IReadOnlyList<GitVersion>> FetchBranchesAsync(\n        int limit = 0,\n        CancellationToken cancellationToken = default\n    );\n\n    /// <summary>\n    /// Fetch the latest commits for a branch.\n    /// If null, the default branch is used.\n    /// </summary>\n    Task<IReadOnlyList<GitVersion>> FetchCommitsAsync(\n        string? branch = null,\n        int limit = 0,\n        CancellationToken cancellationToken = default\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Analytics/AnalyticsHelper.cs",
    "content": "﻿using Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Models.Api.Lykos.Analytics;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Helper.Analytics;\n\n[RegisterSingleton<IAnalyticsHelper, AnalyticsHelper>]\npublic class AnalyticsHelper(\n    ILogger<AnalyticsHelper> logger,\n    ILykosAnalyticsApi analyticsApi,\n    ISettingsManager settingsManager\n) : IAnalyticsHelper\n{\n    public AnalyticsSettings Settings => settingsManager.Settings.Analytics;\n\n    public async Task TrackPackageInstallAsync(string packageName, string packageVersion, bool isSuccess)\n    {\n        if (!Settings.IsUsageDataEnabled)\n        {\n            return;\n        }\n\n        var data = new PackageInstallAnalyticsRequest\n        {\n            PackageName = packageName,\n            PackageVersion = packageVersion,\n            IsSuccess = isSuccess,\n            Timestamp = DateTimeOffset.UtcNow\n        };\n\n        try\n        {\n            await analyticsApi.PostInstallData(data).ConfigureAwait(false);\n        }\n        catch (Exception ex)\n        {\n            logger.LogError(ex, \"Error sending install data\");\n        }\n    }\n\n    public async Task TrackFirstTimeInstallAsync(\n        string? selectedPackageName,\n        IEnumerable<string>? selectedRecommendedModels,\n        bool firstTimeSetupSkipped\n    )\n    {\n        if (!Settings.IsUsageDataEnabled)\n        {\n            return;\n        }\n\n        var data = new FirstTimeInstallAnalytics\n        {\n            SelectedPackageName = selectedPackageName,\n            SelectedRecommendedModels = selectedRecommendedModels,\n            FirstTimeSetupSkipped = firstTimeSetupSkipped,\n            Timestamp = DateTimeOffset.UtcNow\n        };\n\n        try\n        {\n            await analyticsApi.PostInstallData(data).ConfigureAwait(false);\n        }\n        catch (Exception ex)\n        {\n            logger.LogError(ex, \"Error sending first time install data\");\n        }\n    }\n\n    public async Task TrackAsync(AnalyticsRequest data)\n    {\n        if (!Settings.IsUsageDataEnabled)\n        {\n            return;\n        }\n\n        try\n        {\n            await analyticsApi.PostInstallData(data).ConfigureAwait(false);\n        }\n        catch (Exception ex)\n        {\n            logger.LogError(ex, \"Error sending analytics data\");\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Analytics/IAnalyticsHelper.cs",
    "content": "using StabilityMatrix.Core.Models.Api.Lykos.Analytics;\n\nnamespace StabilityMatrix.Core.Helper.Analytics;\n\npublic interface IAnalyticsHelper\n{\n    Task TrackPackageInstallAsync(string packageName, string packageVersion, bool isSuccess);\n\n    Task TrackFirstTimeInstallAsync(\n        string? selectedPackageName,\n        IEnumerable<string>? selectedRecommendedModels,\n        bool firstTimeSetupSkipped\n    );\n\n    Task TrackAsync(AnalyticsRequest data);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/ArchiveHelper.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Runtime.Versioning;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing NLog;\nusing SharpCompress.Common;\nusing SharpCompress.Readers;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing Timer = System.Timers.Timer;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic record struct ArchiveInfo(ulong Size, ulong CompressedSize);\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic static partial class ArchiveHelper\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    /// <summary>\n    /// Platform-specific 7z executable name.\n    /// </summary>\n    public static string SevenZipFileName\n    {\n        get\n        {\n            if (Compat.IsWindows)\n            {\n                return \"7za.exe\";\n            }\n            if (Compat.IsLinux)\n            {\n                return \"7zzs\";\n            }\n            if (Compat.IsMacOS)\n            {\n                return \"7zz\";\n            }\n            throw new PlatformNotSupportedException(\"7z is not supported on this platform.\");\n        }\n    }\n\n    // HomeDir is set by ISettingsManager.TryFindLibrary()\n    public static string HomeDir { get; set; } = string.Empty;\n\n    public static string SevenZipPath => Path.Combine(HomeDir, \"Assets\", SevenZipFileName);\n\n    [GeneratedRegex(@\"(?<=Size:\\s*)\\d+|(?<=Compressed:\\s*)\\d+\")]\n    private static partial Regex Regex7ZOutput();\n\n    [GeneratedRegex(@\"(?<=\\s*)\\d+(?=%)\")]\n    private static partial Regex Regex7ZProgressDigits();\n\n    [GeneratedRegex(@\"(\\d+)%.*- (.*)\")]\n    private static partial Regex Regex7ZProgressFull();\n\n    public static async Task<ArchiveInfo> TestArchive(string archivePath)\n    {\n        var process = ProcessRunner.StartAnsiProcess(SevenZipPath, new[] { \"t\", archivePath });\n        await process.WaitForExitAsync().ConfigureAwait(false);\n        var output = await process.StandardOutput.ReadToEndAsync().ConfigureAwait(false);\n        var matches = Regex7ZOutput().Matches(output);\n        var size = ulong.Parse(matches[0].Value);\n        var compressed = ulong.Parse(matches[1].Value);\n        return new ArchiveInfo(size, compressed);\n    }\n\n    public static async Task AddToArchive7Z(string archivePath, string sourceDirectory)\n    {\n        // Start 7z in the parent directory of the source directory\n        var sourceParent = Directory.GetParent(sourceDirectory)?.FullName ?? \"\";\n        // We must pass in as `directory\\` for archive path to be correct\n        var sourceDirName = new DirectoryInfo(sourceDirectory).Name;\n\n        var result = await ProcessRunner\n            .GetProcessResultAsync(\n                SevenZipPath,\n                new[] { \"a\", archivePath, sourceDirName + @\"\\\", \"-y\" },\n                workingDirectory: sourceParent\n            )\n            .ConfigureAwait(false);\n        result.EnsureSuccessExitCode();\n    }\n\n    public static async Task<ArchiveInfo> Extract7Z(string archivePath, string extractDirectory)\n    {\n        var args = $\"x {ProcessRunner.Quote(archivePath)} -o{ProcessRunner.Quote(extractDirectory)} -y\";\n\n        var result = await ProcessRunner\n            .GetProcessResultAsync(SevenZipPath, args)\n            .EnsureSuccessExitCode()\n            .ConfigureAwait(false);\n\n        var output = result.StandardOutput ?? \"\";\n\n        try\n        {\n            var matches = Regex7ZOutput().Matches(output);\n            var size = ulong.Parse(matches[0].Value);\n            var compressed = ulong.Parse(matches[1].Value);\n            return new ArchiveInfo(size, compressed);\n        }\n        catch (Exception e)\n        {\n            throw new Exception($\"Could not parse 7z output [{e.Message}]: {output.ToRepr()}\");\n        }\n    }\n\n    public static async Task<ArchiveInfo> Extract7Z(\n        string archivePath,\n        string extractDirectory,\n        IProgress<ProgressReport>? progress\n    )\n    {\n        var outputStore = new StringBuilder();\n        var onOutput = new Action<string?>(s =>\n        {\n            if (s == null)\n                return;\n\n            // Parse progress\n            Logger.Trace($\"7z: {s}\");\n            outputStore.AppendLine(s);\n            var match = Regex7ZProgressFull().Match(s);\n            if (match.Success)\n            {\n                var percent = int.Parse(match.Groups[1].Value);\n                var currentFile = match.Groups[2].Value;\n                progress?.Report(\n                    new ProgressReport(\n                        percent / (float)100,\n                        \"Extracting\",\n                        currentFile,\n                        type: ProgressType.Extract\n                    )\n                );\n            }\n        });\n        progress?.Report(new ProgressReport(-1, isIndeterminate: true, type: ProgressType.Extract));\n\n        // Need -bsp1 for progress reports\n        var args = $\"x {ProcessRunner.Quote(archivePath)} -o{ProcessRunner.Quote(extractDirectory)} -y -bsp1\";\n        Logger.Debug($\"Starting process '{SevenZipPath}' with arguments '{args}'\");\n\n        using var process = ProcessRunner.StartProcess(SevenZipPath, args, outputDataReceived: onOutput);\n\n        await process.WaitForExitAsync().ConfigureAwait(false);\n\n        ProcessException.ThrowIfNonZeroExitCode(process, outputStore);\n\n        progress?.Report(new ProgressReport(1f, \"Finished extracting\", type: ProgressType.Extract));\n\n        var output = outputStore.ToString();\n\n        try\n        {\n            var matches = Regex7ZOutput().Matches(output);\n            var size = ulong.Parse(matches[0].Value);\n            var compressed = ulong.Parse(matches[1].Value);\n            return new ArchiveInfo(size, compressed);\n        }\n        catch (Exception e)\n        {\n            throw new Exception($\"Could not parse 7z output [{e.Message}]: {output.ToRepr()}\");\n        }\n    }\n\n    /// <summary>\n    /// Extracts a zipped tar (i.e. '.tar.gz') archive.\n    /// First extracts the zipped tar, then extracts the tar and removes the tar.\n    /// </summary>\n    /// <param name=\"archivePath\"></param>\n    /// <param name=\"extractDirectory\"></param>\n    /// <returns></returns>\n    public static async Task<ArchiveInfo> Extract7ZTar(string archivePath, string extractDirectory)\n    {\n        if (!archivePath.EndsWith(\".tar.gz\"))\n        {\n            throw new ArgumentException(\"Archive must be a zipped tar.\");\n        }\n        // Extract the tar.gz to tar\n        await Extract7Z(archivePath, extractDirectory).ConfigureAwait(false);\n\n        // Extract the tar\n        var tarPath = Path.Combine(extractDirectory, Path.GetFileNameWithoutExtension(archivePath));\n        if (!File.Exists(tarPath))\n        {\n            throw new FileNotFoundException(\"Tar file not found.\", tarPath);\n        }\n\n        try\n        {\n            return await Extract7Z(tarPath, extractDirectory).ConfigureAwait(false);\n        }\n        finally\n        {\n            // Remove the tar\n            if (File.Exists(tarPath))\n            {\n                File.Delete(tarPath);\n            }\n        }\n    }\n\n    /// <summary>\n    /// Extracts with auto handling of tar.gz files.\n    /// </summary>\n    public static async Task<ArchiveInfo> Extract7ZAuto(string archivePath, string extractDirectory)\n    {\n        if (archivePath.EndsWith(\".tar.gz\"))\n        {\n            return await Extract7ZTar(archivePath, extractDirectory).ConfigureAwait(false);\n        }\n        else\n        {\n            return await Extract7Z(archivePath, extractDirectory).ConfigureAwait(false);\n        }\n    }\n\n    /// <summary>\n    /// Extract an archive to the output directory.\n    /// </summary>\n    /// <param name=\"progress\"></param>\n    /// <param name=\"archivePath\"></param>\n    /// <param name=\"outputDirectory\">Output directory, created if does not exist.</param>\n    public static async Task Extract(\n        string archivePath,\n        string outputDirectory,\n        IProgress<ProgressReport>? progress = default\n    )\n    {\n        Directory.CreateDirectory(outputDirectory);\n        progress?.Report(new ProgressReport(-1, isIndeterminate: true));\n\n        var count = 0ul;\n\n        // Get true size\n        var (total, _) = await TestArchive(archivePath).ConfigureAwait(false);\n\n        // If not available, use the size of the archive file\n        if (total == 0)\n        {\n            total = (ulong)new FileInfo(archivePath).Length;\n        }\n\n        // Create an DispatchTimer that monitors the progress of the extraction\n        var progressMonitor = progress switch\n        {\n            null => null,\n            _ => new Timer(TimeSpan.FromMilliseconds(36))\n        };\n\n        if (progressMonitor != null)\n        {\n            progressMonitor.Elapsed += (_, _) =>\n            {\n                if (count == 0)\n                    return;\n                progress!.Report(new ProgressReport(count, total, message: \"Extracting\"));\n            };\n        }\n\n        await Task.Factory.StartNew(\n            () =>\n            {\n                var extractOptions = new ExtractionOptions { Overwrite = true, ExtractFullPath = true, };\n                using var stream = File.OpenRead(archivePath);\n                using var archive = ReaderFactory.Open(stream);\n\n                // Start the progress reporting timer\n                progressMonitor?.Start();\n\n                while (archive.MoveToNextEntry())\n                {\n                    var entry = archive.Entry;\n                    if (!entry.IsDirectory)\n                    {\n                        count += (ulong)entry.CompressedSize;\n                    }\n                    archive.WriteEntryToDirectory(outputDirectory, extractOptions);\n                }\n            },\n            TaskCreationOptions.LongRunning\n        )\n            .ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(progress: 1, message: \"Done extracting\"));\n        progressMonitor?.Stop();\n        Logger.Info(\"Finished extracting archive {}\", archivePath);\n    }\n\n    /// <summary>\n    /// Extract an archive to the output directory, using SharpCompress managed code.\n    /// does not require 7z to be installed, but no progress reporting.\n    /// </summary>\n    public static async Task ExtractManaged(string archivePath, string outputDirectory)\n    {\n        await using var stream = File.OpenRead(archivePath);\n        await ExtractManaged(stream, outputDirectory).ConfigureAwait(false);\n    }\n\n    /// <summary>\n    /// Extract an archive to the output directory, using SharpCompress managed code.\n    /// does not require 7z to be installed, but no progress reporting.\n    /// </summary>\n    public static async Task ExtractManaged(Stream stream, string outputDirectory)\n    {\n        var fullOutputDir = Path.GetFullPath(outputDirectory);\n        using var reader = ReaderFactory.Open(stream);\n        while (reader.MoveToNextEntry())\n        {\n            var entry = reader.Entry;\n            var outputPath = Path.Combine(outputDirectory, entry.Key);\n\n            if (entry.IsDirectory)\n            {\n                if (!Directory.Exists(outputPath))\n                {\n                    Directory.CreateDirectory(outputPath);\n                }\n            }\n            else\n            {\n                var folder = Path.GetDirectoryName(entry.Key)!;\n                var destDir = Path.GetFullPath(Path.Combine(fullOutputDir, folder));\n\n                if (!Directory.Exists(destDir))\n                {\n                    if (!destDir.StartsWith(fullOutputDir, StringComparison.Ordinal))\n                    {\n                        throw new ExtractionException(\n                            \"Entry is trying to create a directory outside of the destination directory.\"\n                        );\n                    }\n\n                    Directory.CreateDirectory(destDir);\n                }\n\n                // Check if symbolic link\n                if (entry.LinkTarget != null)\n                {\n                    // Not sure why but symlink entries have a key that ends with a space\n                    // and some broken path suffix, so we'll remove everything after the last space\n                    Logger.Debug(\n                        $\"Checking if output path {outputPath} contains space char: {outputPath.Contains(' ')}\"\n                    );\n                    if (outputPath.Contains(' '))\n                    {\n                        outputPath = outputPath[..outputPath.LastIndexOf(' ')];\n                    }\n\n                    Logger.Debug(\n                        $\"Extracting symbolic link [{entry.Key.ToRepr()}] \"\n                            + $\"({outputPath.ToRepr()} to {entry.LinkTarget.ToRepr()})\"\n                    );\n                    // Try to write link, if fail, continue copy file\n                    try\n                    {\n                        // Delete path if exists\n                        File.Delete(outputPath);\n                        File.CreateSymbolicLink(outputPath, entry.LinkTarget);\n                        continue;\n                    }\n                    catch (IOException e)\n                    {\n                        Logger.Warn($\"Could not extract symbolic link, copying file instead: {e.Message}\");\n                    }\n                }\n\n                // Write file\n                await using var entryStream = reader.OpenEntryStream();\n                await using var fileStream = File.Create(outputPath);\n                await entryStream.CopyToAsync(fileStream).ConfigureAwait(false);\n            }\n        }\n    }\n\n    [SupportedOSPlatform(\"macos\")]\n    public static async Task ExtractDmg(string archivePath, DirectoryPath extractDir)\n    {\n        using var mountPoint = new TempDirectoryPath();\n\n        // Mount the dmg\n        await ProcessRunner\n            .GetProcessResultAsync(\"hdiutil\", [\"attach\", archivePath, \"-mountpoint\", mountPoint.FullPath])\n            .EnsureSuccessExitCode()\n            .ConfigureAwait(false);\n\n        try\n        {\n            // Copy apps\n            foreach (var sourceDir in mountPoint.EnumerateDirectories(\"*.app\"))\n            {\n                var destDir = extractDir.JoinDir(sourceDir.RelativeTo(mountPoint));\n\n                await ProcessRunner\n                    .GetProcessResultAsync(\"cp\", [\"-R\", sourceDir.FullPath, destDir.FullPath])\n                    .EnsureSuccessExitCode()\n                    .ConfigureAwait(false);\n            }\n        }\n        finally\n        {\n            // Unmount the dmg\n            await ProcessRunner\n                .GetProcessResultAsync(\"hdiutil\", [\"detach\", mountPoint.FullPath])\n                .ConfigureAwait(false);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Cache/GithubApiCache.cs",
    "content": "﻿using Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing Octokit;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Models.Database;\n\nnamespace StabilityMatrix.Core.Helper.Cache;\n\n[RegisterSingleton<IGithubApiCache, GithubApiCache>]\npublic class GithubApiCache(\n    ILiteDbContext dbContext,\n    IGitHubClient githubApi,\n    ILogger<IGithubApiCache> logger\n) : IGithubApiCache\n{\n    private readonly TimeSpan cacheDuration = TimeSpan.FromMinutes(15);\n\n    public async Task<IEnumerable<Release>> GetAllReleases(string username, string repository)\n    {\n        var cacheKey = $\"Releases-{username}-{repository}\";\n        var cacheEntry = await dbContext.GetGithubCacheEntry(cacheKey).ConfigureAwait(false);\n        if (cacheEntry != null && !IsCacheExpired(cacheEntry.LastUpdated))\n        {\n            return cacheEntry.AllReleases.OrderByDescending(x => x.CreatedAt);\n        }\n\n        try\n        {\n            var allReleases = await githubApi\n                .Repository.Release.GetAll(username, repository)\n                .ConfigureAwait(false);\n            if (allReleases == null)\n            {\n                return new List<Release>();\n            }\n\n            var newCacheEntry = new GithubCacheEntry\n            {\n                CacheKey = cacheKey,\n                AllReleases = allReleases.OrderByDescending(x => x.CreatedAt)\n            };\n            await dbContext.UpsertGithubCacheEntry(newCacheEntry).ConfigureAwait(false);\n\n            return newCacheEntry.AllReleases;\n        }\n        catch (Exception ex)\n        {\n            logger.LogWarning(ex, \"Failed to get releases from Github API.\");\n            return cacheEntry?.AllReleases.OrderByDescending(x => x.CreatedAt) ?? Enumerable.Empty<Release>();\n        }\n    }\n\n    public async Task<IEnumerable<Branch>> GetAllBranches(string username, string repository)\n    {\n        var cacheKey = $\"Branches-{username}-{repository}\";\n        var cacheEntry = await dbContext.GetGithubCacheEntry(cacheKey).ConfigureAwait(false);\n        if (cacheEntry != null && !IsCacheExpired(cacheEntry.LastUpdated))\n        {\n            return cacheEntry.Branches;\n        }\n\n        try\n        {\n            var branches = await githubApi\n                .Repository.Branch.GetAll(username, repository)\n                .ConfigureAwait(false);\n            if (branches == null)\n            {\n                return new List<Branch>();\n            }\n\n            var newCacheEntry = new GithubCacheEntry { CacheKey = cacheKey, Branches = branches };\n            await dbContext.UpsertGithubCacheEntry(newCacheEntry).ConfigureAwait(false);\n\n            return newCacheEntry.Branches;\n        }\n        catch (Exception ex)\n        {\n            logger.LogWarning(ex, \"Failed to get branches from Github API.\");\n            return cacheEntry?.Branches ?? [];\n        }\n    }\n\n    public async Task<IEnumerable<GitCommit>?> GetAllCommits(\n        string username,\n        string repository,\n        string branch,\n        int page = 1,\n        int perPage = 10\n    )\n    {\n        var cacheKey = $\"Commits-{username}-{repository}-{branch}-{page}-{perPage}\";\n        var cacheEntry = await dbContext.GetGithubCacheEntry(cacheKey).ConfigureAwait(false);\n        if (cacheEntry != null && !IsCacheExpired(cacheEntry.LastUpdated))\n        {\n            return cacheEntry.Commits;\n        }\n\n        try\n        {\n            var commits = await githubApi\n                .Repository.Commit.GetAll(\n                    username,\n                    repository,\n                    new CommitRequest { Sha = branch },\n                    new ApiOptions\n                    {\n                        PageCount = page,\n                        PageSize = perPage,\n                        StartPage = page\n                    }\n                )\n                .ConfigureAwait(false);\n\n            if (commits == null)\n            {\n                return new List<GitCommit>();\n            }\n\n            var newCacheEntry = new GithubCacheEntry\n            {\n                CacheKey = cacheKey,\n                Commits = commits.Select(x => new GitCommit { Sha = x.Sha })\n            };\n            await dbContext.UpsertGithubCacheEntry(newCacheEntry).ConfigureAwait(false);\n\n            return newCacheEntry.Commits;\n        }\n        catch (Exception ex)\n        {\n            logger.LogWarning(ex, \"Failed to get commits from Github API.\");\n            return cacheEntry?.Commits ?? [];\n        }\n    }\n\n    private bool IsCacheExpired(DateTimeOffset expiration) =>\n        expiration.Add(cacheDuration) < DateTimeOffset.UtcNow;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Cache/IGithubApiCache.cs",
    "content": "﻿using Octokit;\nusing Refit;\nusing StabilityMatrix.Core.Models.Database;\n\nnamespace StabilityMatrix.Core.Helper.Cache;\n\npublic interface IGithubApiCache\n{\n    Task<IEnumerable<Release>> GetAllReleases(string username, string repository);\n\n    Task<IEnumerable<Branch>> GetAllBranches(string username, string repository);\n\n    Task<IEnumerable<GitCommit>?> GetAllCommits(\n        string username,\n        string repository,\n        string branch,\n        int page = 1,\n        [AliasAs(\"per_page\")] int perPage = 10\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Cache/IPyPiCache.cs",
    "content": "﻿using StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Core.Helper.Cache;\n\npublic interface IPyPiCache\n{\n    Task<IEnumerable<CustomVersion>> GetPackageVersions(string packageName);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Cache/LRUCache.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Runtime.CompilerServices;\n\nnamespace StabilityMatrix.Core.Helper.Cache;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic class LRUCache<TK,TV> where TK : notnull\n{\n    private readonly int capacity;\n    private readonly Dictionary<TK, LinkedListNode<LRUCacheItem<TK, TV>>> cacheMap = new();\n    private readonly LinkedList<LRUCacheItem<TK, TV>> lruList = new();\n\n    public LRUCache(int capacity)\n    {\n        this.capacity = capacity;\n    }\n\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public TV? Get(TK key)\n    {\n        if (cacheMap.TryGetValue(key, out var node))\n        {\n            var value = node.Value.Value;\n            lruList.Remove(node);\n            lruList.AddLast(node);\n            return value;\n        }\n        return default;\n    }\n    \n    public bool Get(TK key, out TV? value)\n    {\n        value = Get(key);\n        return value != null;\n    }\n\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public void Add(TK key, TV val)\n    {\n        if (cacheMap.TryGetValue(key, out var existingNode))\n        {\n            lruList.Remove(existingNode);\n        }\n        else if (cacheMap.Count >= capacity)\n        {\n            RemoveFirst();\n        }\n\n        var cacheItem = new LRUCacheItem<TK, TV>(key, val);\n        var node = new LinkedListNode<LRUCacheItem<TK, TV>>(cacheItem);\n        lruList.AddLast(node);\n        cacheMap[key] = node;\n    }\n\n    [MethodImpl(MethodImplOptions.Synchronized)]\n    public void Remove(TK key)\n    {\n        if (!cacheMap.TryGetValue(key, out var node)) return;\n        \n        lruList.Remove(node);\n        cacheMap.Remove(key);\n    }\n\n    private void RemoveFirst()\n    {\n        // Remove from LRUPriority\n        var node = lruList.First;\n        lruList.RemoveFirst();\n        \n        if (node == null) return;\n        \n        // Remove from cache\n        cacheMap.Remove(node.Value.Key);\n    }\n}\n\n// ReSharper disable once InconsistentNaming\ninternal class LRUCacheItem<TK,TV>\n{\n    public LRUCacheItem(TK k, TV v)\n    {\n        Key = k;\n        Value = v;\n    }\n    public TK Key;\n    public TV Value;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Cache/PyPiCache.cs",
    "content": "﻿using Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing NLog;\nusing Octokit;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Pypi;\nusing StabilityMatrix.Core.Models.Database;\n\nnamespace StabilityMatrix.Core.Helper.Cache;\n\n[RegisterSingleton<IPyPiCache, PyPiCache>]\npublic class PyPiCache(ILiteDbContext dbContext, IPyPiApi pyPiApi, ILogger<PyPiCache> logger) : IPyPiCache\n{\n    private readonly TimeSpan cacheDuration = TimeSpan.FromMinutes(15);\n\n    public async Task<IEnumerable<CustomVersion>> GetPackageVersions(string packageName)\n    {\n        var cacheKey = $\"Pypi-{packageName}\";\n        var cacheEntry = await dbContext.GetPyPiCacheEntry(cacheKey).ConfigureAwait(false);\n        if (cacheEntry != null && !IsCacheExpired(cacheEntry.LastUpdated))\n        {\n            return cacheEntry.Versions.OrderByDescending(x => x);\n        }\n\n        try\n        {\n            var packageInfo = await pyPiApi.GetPackageInfo(packageName).ConfigureAwait(false);\n            if (packageInfo?.Releases == null)\n            {\n                return new List<CustomVersion>();\n            }\n\n            var newCacheEntry = new PyPiCacheEntry\n            {\n                CacheKey = cacheKey,\n                Versions = packageInfo.Releases.Select(x => new CustomVersion(x.Key)).ToList()\n            };\n\n            await dbContext.UpsertPyPiCacheEntry(newCacheEntry).ConfigureAwait(false);\n\n            return newCacheEntry.Versions.OrderByDescending(x => x);\n        }\n        catch (ApiException ex)\n        {\n            logger.LogWarning(ex, \"Failed to get package info from PyPi API.\");\n            return cacheEntry?.Versions.OrderByDescending(x => x) ?? Enumerable.Empty<CustomVersion>();\n        }\n    }\n\n    private bool IsCacheExpired(DateTimeOffset expiration) =>\n        expiration.Add(cacheDuration) < DateTimeOffset.UtcNow;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/CodeTimer.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Diagnostics;\nusing System.Reactive.Disposables;\nusing System.Runtime.CompilerServices;\nusing System.Text;\n\nnamespace StabilityMatrix.Core.Helper;\n\n[Localizable(false)]\npublic class CodeTimer(string postFix = \"\", [CallerMemberName] string callerName = \"\") : IDisposable\n{\n    private static readonly Stack<CodeTimer> RunningTimers = new();\n\n    private readonly string name = $\"{callerName}\" + (string.IsNullOrEmpty(postFix) ? \"\" : $\" ({postFix})\");\n    private readonly Stopwatch stopwatch = new();\n    private bool isDisposed;\n\n    private CodeTimer? ParentTimer { get; set; }\n    private List<CodeTimer> SubTimers { get; } = new();\n\n    public void Start()\n    {\n        ObjectDisposedException.ThrowIf(isDisposed, this);\n\n        if (stopwatch.IsRunning)\n        {\n            return;\n        }\n\n        stopwatch.Start();\n\n        // Set parent as the top of the stack\n        if (RunningTimers.TryPeek(out var timer))\n        {\n            ParentTimer = timer;\n            timer.SubTimers.Add(this);\n        }\n\n        // Add ourselves to the stack\n        RunningTimers.Push(this);\n    }\n\n    /// <summary>\n    /// Start a new timer and return it.\n    /// </summary>\n    /// <param name=\"postFix\"></param>\n    /// <param name=\"callerName\"></param>\n    /// <returns></returns>\n    public static CodeTimer StartNew(string postFix = \"\", [CallerMemberName] string callerName = \"\")\n    {\n        var timer = new CodeTimer(postFix, callerName);\n        timer.Start();\n        return timer;\n    }\n\n    /// <summary>\n    /// Starts a new timer and returns it if DEBUG is defined, otherwise returns an empty IDisposable\n    /// </summary>\n    /// <param name=\"postFix\"></param>\n    /// <param name=\"callerName\"></param>\n    /// <returns></returns>\n    public static IDisposable StartDebug(string postFix = \"\", [CallerMemberName] string callerName = \"\")\n    {\n#if DEBUG\n        return StartNew(postFix, callerName);\n#else\n        return Disposable.Empty;\n#endif\n    }\n\n    /// <summary>\n    /// Formats a TimeSpan into a string. Chooses the most appropriate unit of time.\n    /// </summary>\n    public static string FormatTime(TimeSpan duration)\n    {\n        if (duration.TotalSeconds < 1)\n        {\n            return $\"{duration.TotalMilliseconds:0.00}ms\";\n        }\n\n        if (duration.TotalMinutes < 1)\n        {\n            return $\"{duration.TotalSeconds:0.00}s\";\n        }\n\n        if (duration.TotalHours < 1)\n        {\n            return $\"{duration.TotalMinutes:0.00}m\";\n        }\n\n        return $\"{duration.TotalHours:0.00}h\";\n    }\n\n    private static void OutputDebug(string message)\n    {\n        Debug.Write(message);\n    }\n\n    /// <summary>\n    /// Get results for this timer and all sub timers recursively\n    /// </summary>\n    private string GetResult()\n    {\n        var builder = new StringBuilder();\n\n        builder.AppendLine($\"{name}:\\ttook {FormatTime(stopwatch.Elapsed)}\");\n\n        foreach (var timer in SubTimers)\n        {\n            // For each sub timer layer, add a `|-` prefix\n            builder.AppendLine($\"|- {timer.GetResult()}\");\n        }\n\n        return builder.ToString();\n    }\n\n    public void Stop()\n    {\n        // Output if we're a root timer\n        Stop(printOutput: ParentTimer is null);\n    }\n\n    public void Stop(bool printOutput)\n    {\n        if (isDisposed || !stopwatch.IsRunning)\n        {\n            return;\n        }\n\n        stopwatch.Stop();\n\n        // Remove ourselves from the stack\n        if (RunningTimers.TryPop(out var timer))\n        {\n            if (timer != this)\n            {\n                throw new InvalidOperationException(\"Timer stack is corrupted\");\n            }\n        }\n        else\n        {\n            throw new InvalidOperationException(\"Timer stack is empty\");\n        }\n\n        // If we're a root timer, output all results\n        if (printOutput)\n        {\n#if DEBUG\n            OutputDebug(GetResult());\n#else\n            Console.WriteLine(GetResult());\n#endif\n            SubTimers.Clear();\n        }\n    }\n\n    public void Dispose()\n    {\n        if (isDisposed)\n            return;\n\n        Stop();\n\n        isDisposed = true;\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Compat.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Reflection;\nusing System.Runtime.InteropServices;\nusing System.Runtime.Versioning;\nusing Semver;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Core.Helper;\n\n/// <summary>\n/// Compatibility layer for checks and file paths on different platforms.\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic static class Compat\n{\n    private const string AppName = \"StabilityMatrix\";\n\n    public static SemVersion AppVersion { get; set; }\n\n    // OS Platform\n    public static PlatformKind Platform { get; }\n\n    [SupportedOSPlatformGuard(\"windows\")]\n    public static bool IsWindows => Platform.HasFlag(PlatformKind.Windows);\n\n    [SupportedOSPlatformGuard(\"linux\")]\n    public static bool IsLinux => Platform.HasFlag(PlatformKind.Linux);\n\n    [SupportedOSPlatformGuard(\"macos\")]\n    public static bool IsMacOS => Platform.HasFlag(PlatformKind.MacOS);\n\n    [UnsupportedOSPlatformGuard(\"windows\")]\n    public static bool IsUnix => Platform.HasFlag(PlatformKind.Unix);\n\n    public static bool IsArm => Platform.HasFlag(PlatformKind.Arm);\n    public static bool IsX64 => Platform.HasFlag(PlatformKind.X64);\n\n    // Paths\n\n    /// <summary>\n    /// AppData directory path. On Windows this is %AppData%, on Linux and MacOS this is ~/.config\n    /// </summary>\n    public static DirectoryPath AppData { get; }\n\n    /// <summary>\n    /// AppData + AppName (e.g. %AppData%\\StabilityMatrix)\n    /// </summary>\n    public static DirectoryPath AppDataHome { get; private set; }\n\n    /// <summary>\n    /// Set AppDataHome to a custom path. Used for testing.\n    /// </summary>\n    public static void SetAppDataHome(string path)\n    {\n        AppDataHome = path;\n    }\n\n    /// <summary>\n    /// Current directory the app is in.\n    /// </summary>\n    public static DirectoryPath AppCurrentDir { get; }\n\n    /// <summary>\n    /// Current path to the app binary.\n    /// </summary>\n    public static FilePath AppCurrentPath => AppCurrentDir.JoinFile(GetExecutableName());\n\n    /// <summary>\n    /// Path to the .app bundle on macOS.\n    /// </summary>\n    [SupportedOSPlatform(\"macos\")]\n    public static DirectoryPath? AppBundleCurrentPath { get; }\n\n    /// <summary>\n    /// Either the <see cref=\"AppCurrentPath\"/> File or <see cref=\"AppBundleCurrentPath\"/> directory on macOS.\n    /// </summary>\n    public static FileSystemPath AppOrBundleCurrentPath => IsMacOS ? AppBundleCurrentPath! : AppCurrentPath;\n\n    // File extensions\n    /// <summary>\n    /// Platform-specific executable extension.\n    /// \".exe\" on Windows, Empty string on Linux and MacOS.\n    /// </summary>\n    public static string ExeExtension { get; }\n\n    /// <summary>\n    /// Platform-specific dynamic library extension.\n    /// \".dll\" on Windows, \".dylib\" on MacOS, \".so\" on Linux.\n    /// </summary>\n    public static string DllExtension { get; }\n\n    /// <summary>\n    /// Delimiter for $PATH environment variable.\n    /// </summary>\n    public static char PathDelimiter => IsWindows ? ';' : ':';\n\n    static Compat()\n    {\n        var infoVersion = Assembly\n            .GetCallingAssembly()\n            .GetCustomAttribute<AssemblyInformationalVersionAttribute>()\n            ?.InformationalVersion;\n\n        AppVersion = SemVersion.Parse(infoVersion ?? \"0.0.0\", SemVersionStyles.Strict);\n\n        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))\n        {\n            Platform = PlatformKind.Windows;\n            AppCurrentDir = AppContext.BaseDirectory;\n            ExeExtension = \".exe\";\n            DllExtension = \".dll\";\n        }\n        else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))\n        {\n            Platform = PlatformKind.MacOS | PlatformKind.Unix;\n\n            // This is ./<AppName>.app/Contents/MacOS\n            var macDir = new DirectoryPath(AppContext.BaseDirectory);\n            // We need to go up two directories to get the .app directory\n            AppBundleCurrentPath = macDir.Parent?.Parent;\n            // Then CurrentDir is the next parent\n            AppCurrentDir = AppBundleCurrentPath!.Parent!;\n\n            ExeExtension = \"\";\n            DllExtension = \".dylib\";\n        }\n        else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))\n        {\n            Platform = PlatformKind.Linux | PlatformKind.Unix;\n\n            // For AppImage builds, the path is in `$APPIMAGE`\n            var appPath = Environment.GetEnvironmentVariable(\"APPIMAGE\") ?? AppContext.BaseDirectory;\n            AppCurrentDir =\n                Path.GetDirectoryName(appPath) ?? throw new Exception(\"Could not find application directory\");\n            ExeExtension = \"\";\n            DllExtension = \".so\";\n        }\n        else\n        {\n            throw new PlatformNotSupportedException();\n        }\n\n        if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)\n        {\n            Platform |= PlatformKind.Arm;\n        }\n        else\n        {\n            Platform |= PlatformKind.X64;\n        }\n\n        AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);\n\n        AppDataHome = AppData + AppName;\n    }\n\n    /// <summary>\n    /// Generic function to return different objects based on platform flags.\n    /// Parameters are checked in sequence with Compat.Platform.HasFlag,\n    /// the first match is returned.\n    /// </summary>\n    /// <exception cref=\"PlatformNotSupportedException\">Thrown when no targets match</exception>\n    public static T Switch<T>(params (PlatformKind platform, T target)[] targets)\n    {\n        foreach (var (platform, target) in targets)\n        {\n            if (Platform.HasFlag(platform))\n            {\n                return target;\n            }\n        }\n\n        throw new PlatformNotSupportedException(\n            $\"Platform {Platform.ToString()} is not in supported targets: \"\n                + $\"{string.Join(\", \", targets.Select(t => t.platform.ToString()))}\"\n        );\n    }\n\n    /// <summary>\n    /// Get the current application executable name.\n    /// </summary>\n    public static string GetExecutableName()\n    {\n        if (IsLinux)\n        {\n            // Use name component of APPIMAGE\n            var appImage = Environment.GetEnvironmentVariable(\"APPIMAGE\");\n            if (string.IsNullOrEmpty(appImage))\n            {\n#if DEBUG\n                return \"DEBUG_NOT_RUNNING_IN_APPIMAGE\";\n#else\n                throw new Exception(\"Could not find APPIMAGE environment variable\");\n#endif\n            }\n            return Path.GetFileName(appImage);\n        }\n        using var process = Process.GetCurrentProcess();\n        var fullPath = process.MainModule?.ModuleName;\n\n        if (string.IsNullOrEmpty(fullPath))\n        {\n            throw new Exception(\"Could not find executable name\");\n        }\n        return Path.GetFileName(fullPath);\n    }\n\n    /// <summary>\n    /// Get the current application executable or bundle name.\n    /// </summary>\n    public static string GetAppName()\n    {\n        // For other platforms, this is the same as the executable name\n        if (!IsMacOS)\n        {\n            return GetExecutableName();\n        }\n\n        // On macOS, get name of current bundle\n        return Path.GetFileName(AppBundleCurrentPath.Unwrap());\n    }\n\n    public static string GetEnvPathWithExtensions(params string[] paths)\n    {\n        var currentPath = Environment.GetEnvironmentVariable(\"PATH\", EnvironmentVariableTarget.Process);\n        var newPath = string.Join(PathDelimiter, paths);\n\n        if (string.IsNullOrEmpty(currentPath))\n        {\n            return string.Join(PathDelimiter, paths);\n        }\n        else\n        {\n            return newPath + PathDelimiter + currentPath;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/EnumerationOptionConstants.cs",
    "content": "﻿namespace StabilityMatrix.Core.Helper;\n\npublic static class EnumerationOptionConstants\n{\n    public static readonly EnumerationOptions TopLevelOnly =\n        new() { RecurseSubdirectories = false, IgnoreInaccessible = true };\n\n    public static readonly EnumerationOptions AllDirectories =\n        new() { RecurseSubdirectories = true, IgnoreInaccessible = true };\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/EnvPathBuilder.cs",
    "content": "namespace StabilityMatrix.Core.Helper;\n\npublic class EnvPathBuilder(params string[] initialPaths)\n{\n    private readonly List<string> paths = [..initialPaths];\n\n    public EnvPathBuilder AddPath(string path)\n    {\n        paths.Add(path);\n        return this;\n    }\n\n    public EnvPathBuilder RemovePath(string path)\n    {\n        paths.Remove(path);\n        return this;\n    }\n\n    public override string ToString()\n    {\n        return string.Join(Compat.PathDelimiter, paths);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/EventManager.cs",
    "content": "﻿using System.Globalization;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Inference;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Models.Update;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic record struct RunningPackageStatusChangedEventArgs(PackagePair? CurrentPackagePair);\n\npublic class EventManager\n{\n    public static EventManager Instance { get; } = new();\n\n    private EventManager() { }\n\n    public event EventHandler<int>? GlobalProgressChanged;\n    public event EventHandler? InstalledPackagesChanged;\n    public event EventHandler<bool>? OneClickInstallFinished;\n    public event EventHandler? TeachingTooltipNeeded;\n    public event EventHandler<bool>? DevModeSettingChanged;\n    public event EventHandler<UpdateInfo>? UpdateAvailable;\n    public event EventHandler<Guid>? PackageLaunchRequested;\n\n    public delegate Task PackageRelaunchRequestedEventHandler(\n        object? sender,\n        InstalledPackage package,\n        RunPackageOptions runPackageOptions\n    );\n    public event PackageRelaunchRequestedEventHandler? PackageRelaunchRequested;\n    public event EventHandler? ScrollToBottomRequested;\n    public event EventHandler<ProgressItem>? ProgressChanged;\n    public event EventHandler<RunningPackageStatusChangedEventArgs>? RunningPackageStatusChanged;\n    public event EventHandler<IPackageModificationRunner>? PackageInstallProgressAdded;\n    public delegate Task AddPackageInstallEventHandler(\n        object? sender,\n        IPackageModificationRunner runner,\n        IReadOnlyList<IPackageStep> steps,\n        Action onCompleted\n    );\n    public event EventHandler? ToggleProgressFlyout;\n    public event EventHandler<CultureInfo>? CultureChanged;\n    public event EventHandler? ModelIndexChanged;\n    public event EventHandler<FilePath>? ImageFileAdded;\n\n    public delegate Task InferenceProjectRequestedEventHandler(\n        object? sender,\n        LocalImageFile imageFile,\n        InferenceProjectType type\n    );\n    public event InferenceProjectRequestedEventHandler? InferenceProjectRequested;\n    public event EventHandler<InferenceQueueCustomPromptEventArgs>? InferenceQueueCustomPrompt;\n    public event EventHandler<int>? NavigateAndFindCivitModelRequested;\n    public event EventHandler<string?>? NavigateAndFindCivitAuthorRequested;\n    public event EventHandler? DownloadsTeachingTipRequested;\n    public event EventHandler? RecommendedModelsDialogClosed;\n    public event EventHandler? WorkflowInstalled;\n    public event EventHandler<string>? DeleteModelRequested;\n\n    public void OnGlobalProgressChanged(int progress) => GlobalProgressChanged?.Invoke(this, progress);\n\n    public void OnInstalledPackagesChanged() => InstalledPackagesChanged?.Invoke(this, EventArgs.Empty);\n\n    public void OnOneClickInstallFinished(bool skipped) => OneClickInstallFinished?.Invoke(this, skipped);\n\n    public void OnTeachingTooltipNeeded() => TeachingTooltipNeeded?.Invoke(this, EventArgs.Empty);\n\n    public void OnDevModeSettingChanged(bool value) => DevModeSettingChanged?.Invoke(this, value);\n\n    public void OnUpdateAvailable(UpdateInfo args) => UpdateAvailable?.Invoke(this, args);\n\n    public void OnPackageLaunchRequested(Guid packageId) => PackageLaunchRequested?.Invoke(this, packageId);\n\n    public void OnScrollToBottomRequested() => ScrollToBottomRequested?.Invoke(this, EventArgs.Empty);\n\n    public void OnProgressChanged(ProgressItem progress) => ProgressChanged?.Invoke(this, progress);\n\n    public void OnRunningPackageStatusChanged(PackagePair? currentPackagePair) =>\n        RunningPackageStatusChanged?.Invoke(\n            this,\n            new RunningPackageStatusChangedEventArgs(currentPackagePair)\n        );\n\n    public void OnPackageInstallProgressAdded(IPackageModificationRunner runner) =>\n        PackageInstallProgressAdded?.Invoke(this, runner);\n\n    public void OnToggleProgressFlyout() => ToggleProgressFlyout?.Invoke(this, EventArgs.Empty);\n\n    public void OnCultureChanged(CultureInfo culture) => CultureChanged?.Invoke(this, culture);\n\n    public void OnModelIndexChanged() => ModelIndexChanged?.Invoke(this, EventArgs.Empty);\n\n    public void OnImageFileAdded(FilePath filePath) => ImageFileAdded?.Invoke(this, filePath);\n\n    public void OnInferenceQueueCustomPrompt(InferenceQueueCustomPromptEventArgs e) =>\n        InferenceQueueCustomPrompt?.Invoke(this, e);\n\n    public void OnNavigateAndFindCivitModelRequested(int modelId) =>\n        NavigateAndFindCivitModelRequested?.Invoke(this, modelId);\n\n    public void OnDownloadsTeachingTipRequested() =>\n        DownloadsTeachingTipRequested?.Invoke(this, EventArgs.Empty);\n\n    public void OnRecommendedModelsDialogClosed() =>\n        RecommendedModelsDialogClosed?.Invoke(this, EventArgs.Empty);\n\n    public void OnPackageRelaunchRequested(InstalledPackage package, RunPackageOptions runPackageOptions) =>\n        PackageRelaunchRequested?.Invoke(this, package, runPackageOptions);\n\n    public void OnWorkflowInstalled() => WorkflowInstalled?.Invoke(this, EventArgs.Empty);\n\n    public void OnDeleteModelRequested(object? sender, string relativePath) =>\n        DeleteModelRequested?.Invoke(sender, relativePath);\n\n    public void OnInferenceProjectRequested(LocalImageFile imageFile, InferenceProjectType type) =>\n        InferenceProjectRequested?.Invoke(this, imageFile, type);\n\n    public void OnNavigateAndFindCivitAuthorRequested(string? author) =>\n        NavigateAndFindCivitAuthorRequested?.Invoke(this, author);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Factory/IPackageFactory.cs",
    "content": "﻿using StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Packages;\n\nnamespace StabilityMatrix.Core.Helper.Factory;\n\npublic interface IPackageFactory\n{\n    IEnumerable<BasePackage> GetAllAvailablePackages();\n    BasePackage? FindPackageByName(string? packageName);\n    BasePackage? this[string packageName] { get; }\n    PackagePair? GetPackagePair(InstalledPackage? installedPackage);\n    IEnumerable<BasePackage> GetPackagesByType(PackageType packageType);\n    BasePackage GetNewBasePackage(InstalledPackage installedPackage);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Factory/PackageFactory.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Helper.Factory;\n\n[RegisterSingleton<IPackageFactory, PackageFactory>]\npublic class PackageFactory : IPackageFactory\n{\n    private readonly IGithubApiCache githubApiCache;\n    private readonly ISettingsManager settingsManager;\n    private readonly IDownloadService downloadService;\n    private readonly IPrerequisiteHelper prerequisiteHelper;\n    private readonly IPyRunner pyRunner;\n    private readonly IUvManager uvManager;\n    private readonly IPyInstallationManager pyInstallationManager;\n    private readonly IPipWheelService pipWheelService;\n\n    /// <summary>\n    /// Mapping of package.Name to package\n    /// </summary>\n    private readonly Dictionary<string, BasePackage> basePackages;\n\n    public PackageFactory(\n        IEnumerable<BasePackage> basePackages,\n        IGithubApiCache githubApiCache,\n        ISettingsManager settingsManager,\n        IDownloadService downloadService,\n        IPrerequisiteHelper prerequisiteHelper,\n        IPyInstallationManager pyInstallationManager,\n        IPyRunner pyRunner,\n        IPipWheelService pipWheelService\n    )\n    {\n        this.githubApiCache = githubApiCache;\n        this.settingsManager = settingsManager;\n        this.downloadService = downloadService;\n        this.prerequisiteHelper = prerequisiteHelper;\n        this.pyRunner = pyRunner;\n        this.pyInstallationManager = pyInstallationManager;\n        this.pipWheelService = pipWheelService;\n        this.basePackages = basePackages.ToDictionary(x => x.Name);\n    }\n\n    public BasePackage GetNewBasePackage(InstalledPackage installedPackage)\n    {\n        return installedPackage.PackageName switch\n        {\n            \"ComfyUI\" => new ComfyUI(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"Fooocus\" => new Fooocus(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"stable-diffusion-webui\" => new A3WebUI(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"Fooocus-ControlNet-SDXL\" => new FocusControlNet(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"Fooocus-MRE\" => new FooocusMre(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"InvokeAI\" => new InvokeAI(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"kohya_ss\" => new KohyaSs(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyRunner,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"OneTrainer\" => new OneTrainer(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"RuinedFooocus\" => new RuinedFooocus(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"stable-diffusion-webui-forge\" => new SDWebForge(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"stable-diffusion-webui-directml\" => new StableDiffusionDirectMl(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"stable-diffusion-webui-ux\" => new StableDiffusionUx(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"StableSwarmUI\" => new StableSwarm(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"automatic\" => new VladAutomatic(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"voltaML-fast-stable-diffusion\" => new VoltaML(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"sdfx\" => new Sdfx(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"mashb1t-fooocus\" => new Mashb1tFooocus(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"reforge\" => new Reforge(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"FluxGym\" => new FluxGym(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"SimpleSDXL\" => new SimpleSDXL(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"Cogstudio\" => new Cogstudio(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"ComfyUI-Zluda\" => new ComfyZluda(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"stable-diffusion-webui-amdgpu-forge\" => new ForgeAmdGpu(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"forge-classic\" => new ForgeClassic(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"forge-neo\" => new ForgeNeo(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"framepack\" => new FramePack(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"framepack-studio\" => new FramePackStudio(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"ai-toolkit\" => new AiToolkit(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            \"Wan2GP\" => new Wan2GP(\n                githubApiCache,\n                settingsManager,\n                downloadService,\n                prerequisiteHelper,\n                pyInstallationManager,\n                pipWheelService\n            ),\n            _ => throw new ArgumentOutOfRangeException(nameof(installedPackage)),\n        };\n    }\n\n    public IEnumerable<BasePackage> GetAllAvailablePackages()\n    {\n        return basePackages\n            .Values.Where(p => !p.HasVulnerabilities)\n            .OrderBy(p => p.InstallerSortOrder)\n            .ThenBy(p => p.DisplayName);\n    }\n\n    public BasePackage? FindPackageByName(string? packageName)\n    {\n        return packageName == null ? null : basePackages.GetValueOrDefault(packageName);\n    }\n\n    public BasePackage? this[string packageName] => basePackages[packageName];\n\n    /// <inheritdoc />\n    public PackagePair? GetPackagePair(InstalledPackage? installedPackage)\n    {\n        if (installedPackage?.PackageName is not { } packageName)\n            return null;\n\n        return !basePackages.TryGetValue(packageName, out var basePackage)\n            ? null\n            : new PackagePair(installedPackage, basePackage);\n    }\n\n    public IEnumerable<BasePackage> GetPackagesByType(PackageType packageType) =>\n        basePackages.Values.Where(p => p.PackageType == packageType);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/FileHash.cs",
    "content": "﻿using System.Buffers;\nusing System.Diagnostics;\nusing System.IO.MemoryMappedFiles;\nusing System.Runtime.InteropServices;\nusing System.Security.Cryptography;\nusing Blake3;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic static class FileHash\n{\n    public static async Task<string> GetHashAsync(\n        HashAlgorithm hashAlgorithm,\n        Stream stream,\n        byte[] buffer,\n        Action<ulong>? progress = default\n    )\n    {\n        ulong totalBytesRead = 0;\n\n        using (hashAlgorithm)\n        {\n            int bytesRead;\n            while ((bytesRead = await stream.ReadAsync(buffer).ConfigureAwait(false)) != 0)\n            {\n                totalBytesRead += (ulong)bytesRead;\n                hashAlgorithm.TransformBlock(buffer, 0, bytesRead, null, 0);\n                progress?.Invoke(totalBytesRead);\n            }\n            hashAlgorithm.TransformFinalBlock(buffer, 0, 0);\n            var hash = hashAlgorithm.Hash;\n            if (hash == null || hash.Length == 0)\n            {\n                throw new InvalidOperationException(\"Hash algorithm did not produce a hash.\");\n            }\n            return BitConverter.ToString(hash).Replace(\"-\", string.Empty).ToLowerInvariant();\n        }\n    }\n\n    public static async Task<string> GetSha256Async(string filePath, IProgress<ProgressReport>? progress = default)\n    {\n        if (!File.Exists(filePath))\n        {\n            throw new FileNotFoundException($\"Could not find file: {filePath}\");\n        }\n\n        var totalBytes = Convert.ToUInt64(new FileInfo(filePath).Length);\n        var shared = ArrayPool<byte>.Shared;\n        var buffer = shared.Rent((int)FileTransfers.GetBufferSize(totalBytes));\n        try\n        {\n            await using var stream = File.OpenRead(filePath);\n\n            var hash = await GetHashAsync(\n                    SHA256.Create(),\n                    stream,\n                    buffer,\n                    totalBytesRead =>\n                    {\n                        progress?.Report(new ProgressReport(totalBytesRead, totalBytes, type: ProgressType.Hashing));\n                    }\n                )\n                .ConfigureAwait(false);\n            return hash;\n        }\n        finally\n        {\n            shared.Return(buffer);\n        }\n    }\n\n    public static async Task<string> GetBlake3Async(string filePath, IProgress<ProgressReport>? progress = default)\n    {\n        if (!File.Exists(filePath))\n        {\n            throw new FileNotFoundException($\"Could not find file: {filePath}\");\n        }\n\n        var totalBytes = Convert.ToUInt64(new FileInfo(filePath).Length);\n        var readBytes = 0ul;\n        var shared = ArrayPool<byte>.Shared;\n        var buffer = shared.Rent(GetBufferSize(totalBytes));\n        try\n        {\n            await using var stream = File.OpenRead(filePath);\n            using var hasher = Hasher.New();\n            while (true)\n            {\n                var bytesRead = await stream.ReadAsync(buffer).ConfigureAwait(false);\n                if (bytesRead == 0)\n                {\n                    break;\n                }\n                readBytes += (ulong)bytesRead;\n                hasher.Update(buffer.AsSpan(0, bytesRead));\n                progress?.Report(new ProgressReport(readBytes, totalBytes));\n            }\n            return hasher.Finalize().ToString();\n        }\n        finally\n        {\n            shared.Return(buffer);\n        }\n    }\n\n    public static async Task<Hash> GetBlake3Async(Stream stream, IProgress<ProgressReport>? progress = default)\n    {\n        var totalBytes = Convert.ToUInt64(stream.Length);\n        var readBytes = 0ul;\n        var shared = ArrayPool<byte>.Shared;\n        var buffer = shared.Rent((int)FileTransfers.GetBufferSize(totalBytes));\n        try\n        {\n            using var hasher = Hasher.New();\n            while (true)\n            {\n                var bytesRead = await stream.ReadAsync(buffer).ConfigureAwait(false);\n                if (bytesRead == 0)\n                {\n                    break;\n                }\n                readBytes += (ulong)bytesRead;\n                hasher.Update(buffer.AsSpan(0, bytesRead));\n                progress?.Report(new ProgressReport(readBytes, totalBytes));\n            }\n            return hasher.Finalize();\n        }\n        finally\n        {\n            shared.Return(buffer);\n        }\n    }\n\n    /// <summary>\n    /// Get the Blake3 hash of a span of data with multi-threading.\n    /// </summary>\n    public static Hash GetBlake3Parallel(ReadOnlySpan<byte> data)\n    {\n        using var hasher = Hasher.New();\n        hasher.UpdateWithJoin(data);\n        return hasher.Finalize();\n    }\n\n    /// <summary>\n    /// Task.Run wrapped <see cref=\"GetBlake3Parallel\"/>\n    /// </summary>\n    public static Task<Hash> GetBlake3ParallelAsync(ReadOnlyMemory<byte> data)\n    {\n        return Task.Run(() => GetBlake3Parallel(data.Span));\n    }\n\n    /// <summary>\n    /// Get the Blake3 hash of a file as memory-mapped with multi-threading.\n    /// </summary>\n    public static Hash GetBlake3MemoryMappedParallel(string filePath)\n    {\n        if (!File.Exists(filePath))\n        {\n            throw new FileNotFoundException(filePath);\n        }\n\n        var totalBytes = Convert.ToInt64(new FileInfo(filePath).Length);\n\n        using var hasher = Hasher.New();\n\n        // Memory map\n        using var fileStream = File.OpenRead(filePath);\n        using var memoryMappedFile = MemoryMappedFile.CreateFromFile(\n            fileStream,\n            null,\n            totalBytes,\n            MemoryMappedFileAccess.Read,\n            HandleInheritability.None,\n            false\n        );\n\n        using var accessor = memoryMappedFile.CreateViewAccessor(0, totalBytes, MemoryMappedFileAccess.Read);\n\n        Debug.Assert(accessor.Capacity == fileStream.Length);\n\n        var buffer = new byte[accessor.Capacity];\n        accessor.ReadArray(0, buffer, 0, buffer.Length);\n\n        hasher.UpdateWithJoin(buffer);\n        return hasher.Finalize();\n    }\n\n    /// <summary>\n    /// Task.Run wrapped <see cref=\"GetBlake3MemoryMappedParallel\"/>\n    /// </summary>\n    public static Task<Hash> GetBlake3MemoryMappedParallelAsync(string filePath)\n    {\n        return Task.Run(() => GetBlake3MemoryMappedParallel(filePath));\n    }\n\n    /// <summary>\n    /// Determines suitable buffer size for hashing based on stream length.\n    /// </summary>\n    private static int GetBufferSize(ulong totalBytes) =>\n        totalBytes switch\n        {\n            < Size.MiB => 8 * (int)Size.KiB,\n            < 500 * Size.MiB => 16 * (int)Size.KiB,\n            < Size.GiB => 32 * (int)Size.KiB,\n            _ => 64 * (int)Size.KiB\n        };\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/FileTransfers.cs",
    "content": "﻿using System.Buffers;\nusing System.Diagnostics.CodeAnalysis;\nusing NLog;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Helper;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic static class FileTransfers\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    /// <summary>\n    /// Determines suitable buffer size based on stream length.\n    /// </summary>\n    /// <param name=\"totalBytes\"></param>\n    /// <returns></returns>\n    public static ulong GetBufferSize(ulong totalBytes) =>\n        totalBytes switch\n        {\n            < Size.MiB => 8 * Size.KiB,\n            < 100 * Size.MiB => 16 * Size.KiB,\n            < 500 * Size.MiB => Size.MiB,\n            < Size.GiB => 16 * Size.MiB,\n            _ => 32 * Size.MiB,\n        };\n\n    /// <summary>\n    /// Copy all files and subfolders using a dictionary of source and destination file paths.\n    /// Non-existing directories within the paths will be created.\n    /// </summary>\n    /// <param name=\"files\">Dictionary of source and destination file paths</param>\n    /// <param name=\"fileProgress\">\n    /// Optional (per file) progress\n    /// <list type=\"bullet\">\n    /// <item>Current - Bytes read for file.</item>\n    /// <item>Total - Size of file in bytes.</item>\n    /// <item>Title - </item>\n    /// </list>\n    /// </param>\n    /// <param name=\"totalProgress\">\n    /// Optional (total) progress.\n    /// </param>\n    public static async Task CopyFiles(\n        Dictionary<string, string> files,\n        IProgress<ProgressReport>? fileProgress = default,\n        IProgress<ProgressReport>? totalProgress = default\n    )\n    {\n        var totalFiles = files.Count;\n        var completedFiles = 0;\n        var totalSize = Convert.ToUInt64(files.Keys.Select(x => new FileInfo(x).Length).Sum());\n        var totalRead = 0ul;\n\n        foreach (var (sourcePath, destPath) in files)\n        {\n            var totalReadForFile = 0ul;\n\n            await using var outStream = new FileStream(\n                destPath,\n                FileMode.Create,\n                FileAccess.Write,\n                FileShare.Read\n            );\n            await using var inStream = new FileStream(\n                sourcePath,\n                FileMode.Open,\n                FileAccess.Read,\n                FileShare.Read\n            );\n\n            var fileSize = (ulong)inStream.Length;\n            var fileName = Path.GetFileName(sourcePath);\n            completedFiles++;\n            var currentCompletedFiles = completedFiles;\n\n            await CopyStream(\n                    inStream,\n                    outStream,\n                    fileReadBytes =>\n                    {\n                        var lastRead = totalReadForFile;\n                        totalReadForFile = Convert.ToUInt64(fileReadBytes);\n                        totalRead += totalReadForFile - lastRead;\n                        fileProgress?.Report(\n                            new ProgressReport(\n                                totalReadForFile,\n                                fileSize,\n                                fileName,\n                                $\"{currentCompletedFiles}/{totalFiles}\"\n                            )\n                        );\n                        totalProgress?.Report(\n                            new ProgressReport(\n                                totalRead,\n                                totalSize,\n                                fileName,\n                                $\"{currentCompletedFiles}/{totalFiles}\"\n                            )\n                        );\n                    }\n                )\n                .ConfigureAwait(false);\n        }\n    }\n\n    private static async Task CopyStream(Stream from, Stream to, Action<long> progress)\n    {\n        var shared = ArrayPool<byte>.Shared;\n        var bufferSize = (int)GetBufferSize((ulong)from.Length);\n        var buffer = shared.Rent(bufferSize);\n        var totalRead = 0L;\n\n        try\n        {\n            while (totalRead < from.Length)\n            {\n                var read = await from.ReadAsync(buffer.AsMemory(0, bufferSize)).ConfigureAwait(false);\n                await to.WriteAsync(buffer.AsMemory(0, read)).ConfigureAwait(false);\n                totalRead += read;\n                progress(totalRead);\n            }\n        }\n        finally\n        {\n            shared.Return(buffer);\n        }\n    }\n\n    /// <summary>\n    /// Move all files and sub-directories within the source directory to the destination directory.\n    /// If the destination contains a file with the same name, we'll check if the hashes match.\n    /// On matching hashes we skip the file, otherwise we throw an exception.\n    /// </summary>\n    /// <exception cref=\"FileTransferExistsException\">\n    /// If moving files results in name collision with different hashes.\n    /// </exception>\n    public static async Task MoveAllFilesAndDirectories(\n        DirectoryPath sourceDir,\n        DirectoryPath destinationDir,\n        bool overwrite = false,\n        bool overwriteIfHashMatches = false,\n        bool deleteSymlinks = false\n    )\n    {\n        // Create the destination directory if it doesn't exist\n        if (!destinationDir.Exists)\n        {\n            destinationDir.Create();\n        }\n\n        // First move files\n        await MoveAllFiles(sourceDir, destinationDir, overwrite, overwriteIfHashMatches)\n            .ConfigureAwait(false);\n\n        // Then move directories\n        foreach (var subDir in sourceDir.Info.EnumerateDirectories())\n        {\n            if (deleteSymlinks && new DirectoryPath(subDir).IsSymbolicLink)\n            {\n                subDir.Delete(false);\n                continue;\n            }\n\n            var destinationSubDir = destinationDir.JoinDir(subDir.Name);\n            // Recursively move sub directories\n            await MoveAllFilesAndDirectories(subDir, destinationSubDir, overwrite, overwriteIfHashMatches)\n                .ConfigureAwait(false);\n\n            if (!subDir.EnumerateFileSystemInfos().Any())\n            {\n                subDir.Delete();\n            }\n\n            if (destinationSubDir.Exists)\n                continue;\n\n            destinationSubDir.Create();\n        }\n    }\n\n    /// <summary>\n    /// Move all files within the source directory to the destination directory.\n    /// If the destination contains a file with the same name, we'll check if the hashes match.\n    /// On matching hashes we skip the file, otherwise we throw an exception.\n    /// </summary>\n    /// <exception cref=\"FileTransferExistsException\">\n    /// If moving files results in name collision with different hashes.\n    /// </exception>\n    public static async Task MoveAllFiles(\n        DirectoryPath sourceDir,\n        DirectoryPath destinationDir,\n        bool overwrite = false,\n        bool overwriteIfHashMatches = false\n    )\n    {\n        foreach (var file in sourceDir.Info.EnumerateFiles())\n        {\n            var sourceFile = sourceDir.JoinFile(file.Name);\n            var destinationFile = destinationDir.JoinFile(file.Name);\n\n            await MoveFileAsync(sourceFile, destinationFile, overwrite, overwriteIfHashMatches)\n                .ConfigureAwait(false);\n        }\n    }\n\n    public static async Task MoveFileAsync(\n        FilePath sourceFile,\n        FilePath destinationFile,\n        bool overwrite = false,\n        bool overwriteIfHashMatches = false\n    )\n    {\n        if (destinationFile.Exists)\n        {\n            if (overwriteIfHashMatches)\n            {\n                // Check if files hashes are the same\n                var sourceHash = await FileHash.GetBlake3Async(sourceFile).ConfigureAwait(false);\n                var destinationHash = await FileHash.GetBlake3Async(destinationFile).ConfigureAwait(false);\n                // For same hash, just delete original file\n                if (sourceHash == destinationHash)\n                {\n                    Logger.Info(\n                        $\"Deleted source file {sourceFile.Name} as it already exists in {Path.GetDirectoryName(destinationFile)}.\"\n                            + $\" Matching Blake3 hash: {sourceHash}\"\n                    );\n                    sourceFile.Delete();\n                    return;\n                }\n\n                // append a number to the file name until it doesn't exist\n                for (var i = 0; i < 100; i++)\n                {\n                    var destDir = destinationFile.Directory;\n                    var baseName = destinationFile.NameWithoutExtension;\n                    var ext = destinationFile.Extension;\n                    var candidate = destDir?.JoinFile($\"{baseName} ({i}){ext}\");\n                    if (candidate?.Exists is false)\n                    {\n                        destinationFile = candidate;\n                        break;\n                    }\n                }\n            }\n            else if (!overwrite)\n            {\n                throw new FileTransferExistsException(sourceFile, destinationFile);\n            }\n        }\n\n        // Move the file\n        await sourceFile.MoveToAsync(destinationFile).ConfigureAwait(false);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/GenerationParametersConverter.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Diagnostics.CodeAnalysis;\nusing StabilityMatrix.Core.Models.Api.Comfy;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic static class GenerationParametersConverter\n{\n    private static readonly ImmutableDictionary<string, ComfySamplerScheduler> ParamsToSamplerSchedulers =\n        new Dictionary<string, ComfySamplerScheduler>\n        {\n            [\"DPM++ 2M Karras\"] = (ComfySampler.Dpmpp2M, ComfyScheduler.Karras),\n            [\"DPM++ SDE Karras\"] = (ComfySampler.DpmppSde, ComfyScheduler.Karras),\n            [\"DPM++ 2M SDE Exponential\"] = (ComfySampler.Dpmpp2MSde, ComfyScheduler.Exponential),\n            [\"DPM++ 2M SDE Karras\"] = (ComfySampler.Dpmpp2MSde, ComfyScheduler.Karras),\n            [\"Euler a\"] = (ComfySampler.EulerAncestral, ComfyScheduler.Normal),\n            [\"Euler\"] = (ComfySampler.Euler, ComfyScheduler.Normal),\n            [\"Euler Simple\"] = (ComfySampler.Euler, ComfyScheduler.Simple),\n            [\"LMS\"] = (ComfySampler.LMS, ComfyScheduler.Normal),\n            [\"Heun\"] = (ComfySampler.Heun, ComfyScheduler.Normal),\n            [\"Heun Beta\"] = (ComfySampler.Heun, ComfyScheduler.Beta),\n            [\"DPM2\"] = (ComfySampler.Dpm2, ComfyScheduler.Normal),\n            [\"DPM2 Karras\"] = (ComfySampler.Dpm2, ComfyScheduler.Karras),\n            [\"DPM2 a\"] = (ComfySampler.Dpm2Ancestral, ComfyScheduler.Normal),\n            [\"DPM2 a Karras\"] = (ComfySampler.Dpm2Ancestral, ComfyScheduler.Karras),\n            [\"DPM++ 2S a\"] = (ComfySampler.Dpmpp2SAncestral, ComfyScheduler.Normal),\n            [\"DPM++ 2S a Karras\"] = (ComfySampler.Dpmpp2SAncestral, ComfyScheduler.Karras),\n            [\"DPM++ 2M\"] = (ComfySampler.Dpmpp2M, ComfyScheduler.Normal),\n            [\"DPM++ SDE\"] = (ComfySampler.DpmppSde, ComfyScheduler.Normal),\n            [\"DPM++ 2M SDE\"] = (ComfySampler.Dpmpp2MSde, ComfyScheduler.Normal),\n            [\"DPM++ 3M SDE\"] = (ComfySampler.Dpmpp3MSde, ComfyScheduler.Normal),\n            [\"DPM++ 3M SDE Karras\"] = (ComfySampler.Dpmpp3MSde, ComfyScheduler.Karras),\n            [\"DPM++ 3M SDE Exponential\"] = (ComfySampler.Dpmpp3MSde, ComfyScheduler.Exponential),\n            [\"DPM fast\"] = (ComfySampler.DpmFast, ComfyScheduler.Normal),\n            [\"DPM adaptive\"] = (ComfySampler.DpmAdaptive, ComfyScheduler.Normal),\n            [\"LMS Karras\"] = (ComfySampler.LMS, ComfyScheduler.Karras),\n            [\"DDIM\"] = (ComfySampler.DDIM, ComfyScheduler.Normal),\n            [\"DDIM Beta\"] = (ComfySampler.DDIM, ComfyScheduler.Beta),\n            [\"UniPC\"] = (ComfySampler.UniPC, ComfyScheduler.Normal),\n        }.ToImmutableDictionary();\n\n    private static readonly ImmutableDictionary<ComfySamplerScheduler, string> SamplerSchedulersToParams =\n        ParamsToSamplerSchedulers.ToImmutableDictionary(x => x.Value, x => x.Key);\n\n    /// <summary>\n    /// Converts a parameters-type string to a <see cref=\"ComfySamplerScheduler\"/>.\n    /// </summary>\n    public static bool TryGetSamplerScheduler(string parameters, out ComfySamplerScheduler samplerScheduler)\n    {\n        return ParamsToSamplerSchedulers.TryGetValue(parameters, out samplerScheduler);\n    }\n\n    /// <summary>\n    /// Converts a <see cref=\"ComfySamplerScheduler\"/> to a parameters-type string.\n    /// </summary>\n    public static bool TryGetParameters(\n        ComfySamplerScheduler samplerScheduler,\n        [NotNullWhen(true)] out string? parameters\n    )\n    {\n        return SamplerSchedulersToParams.TryGetValue(samplerScheduler, out parameters);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/HardwareInfo/CpuInfo.cs",
    "content": "﻿namespace StabilityMatrix.Core.Helper.HardwareInfo;\n\npublic readonly record struct CpuInfo\n{\n    public string ProcessorCaption { get; init; }\n    public string ProcessorName { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs",
    "content": "﻿namespace StabilityMatrix.Core.Helper.HardwareInfo;\n\npublic record GpuInfo\n{\n    public int Index { get; set; }\n    public string? Name { get; init; } = string.Empty;\n    public ulong MemoryBytes { get; init; }\n    public string? ComputeCapability { get; init; }\n\n    /// <summary>\n    /// Gets the compute capability as a comparable decimal value (e.g. \"8.6\" becomes 8.6m)\n    /// Returns null if compute capability is not available\n    /// </summary>\n    public decimal? ComputeCapabilityValue =>\n        ComputeCapability != null && decimal.TryParse(ComputeCapability, out var value) ? value : null;\n\n    public MemoryLevel? MemoryLevel =>\n        MemoryBytes switch\n        {\n            <= 0 => HardwareInfo.MemoryLevel.Unknown,\n            < 4 * Size.GiB => HardwareInfo.MemoryLevel.Low,\n            < 8 * Size.GiB => HardwareInfo.MemoryLevel.Medium,\n            _ => HardwareInfo.MemoryLevel.High,\n        };\n\n    public bool IsNvidia\n    {\n        get\n        {\n            var name = Name?.ToLowerInvariant();\n\n            if (string.IsNullOrEmpty(name))\n                return false;\n\n            return name.Contains(\"nvidia\") || name.Contains(\"tesla\");\n        }\n    }\n\n    public bool IsBlackwellGpu()\n    {\n        if (ComputeCapability is null)\n            return false;\n\n        return ComputeCapabilityValue >= 12.0m;\n    }\n\n    public bool IsAmpereOrNewerGpu()\n    {\n        if (ComputeCapability is null)\n            return false;\n\n        return ComputeCapabilityValue >= 8.6m;\n    }\n\n    public bool IsLegacyNvidiaGpu()\n    {\n        if (ComputeCapability is null)\n            return false;\n\n        return ComputeCapabilityValue < 7.5m;\n    }\n\n    public bool IsWindowsRocmSupportedGpu()\n    {\n        var gfx = GetAmdGfxArch();\n        if (gfx is null)\n            return false;\n\n        return gfx.StartsWith(\"gfx110\") || gfx.StartsWith(\"gfx120\") || gfx.Equals(\"gfx1151\");\n    }\n\n    public bool IsAmd => Name?.Contains(\"amd\", StringComparison.OrdinalIgnoreCase) ?? false;\n    public bool IsIntel => Name?.Contains(\"arc\", StringComparison.OrdinalIgnoreCase) ?? false;\n\n    public string? GetAmdGfxArch()\n    {\n        if (!IsAmd || string.IsNullOrWhiteSpace(Name))\n            return null;\n\n        // Normalize for safer substring checks (handles RX7800 vs RX 7800, etc.)\n        var name = Name;\n        var nameNoSpaces = name.Replace(\" \", \"\", StringComparison.Ordinal);\n\n        return name switch\n        {\n            // RDNA4\n            _ when Has(\"R9700\") || Has(\"9070\") => \"gfx1201\",\n            _ when Has(\"9060\") => \"gfx1200\",\n\n            // RDNA3.5 APUs\n            _ when Has(\"860M\") => \"gfx1152\",\n            _ when Has(\"890M\") => \"gfx1150\",\n            _ when Has(\"8040S\") || Has(\"8050S\") || Has(\"8060S\") || Has(\"880M\") || Has(\"Z2 Extreme\") =>\n                \"gfx1151\",\n\n            // RDNA3 APUs (Phoenix)\n            _ when Has(\"740M\") || Has(\"760M\") || Has(\"780M\") || Has(\"Z1\") || Has(\"Z2\") => \"gfx1103\",\n\n            // RDNA3 dGPU Navi33\n            _ when Has(\"7400\") || Has(\"7500\") || Has(\"7600\") || Has(\"7650\") || Has(\"7700S\") => \"gfx1102\",\n\n            // RDNA3 dGPU Navi32\n            _ when Has(\"7700\") || Has(\"RX 7800\") || HasNoSpace(\"RX7800\") => \"gfx1101\",\n\n            // RDNA3 dGPU Navi31 (incl. Pro)\n            _ when Has(\"W7800\") || Has(\"7900\") || Has(\"7950\") || Has(\"7990\") => \"gfx1100\",\n\n            // RDNA2 APUs (Rembrandt)\n            _ when Has(\"660M\") || Has(\"680M\") => \"gfx1035\",\n\n            // RDNA2 Navi24 low-end (incl. some mobiles)\n            _ when Has(\"6300\") || Has(\"6400\") || Has(\"6450\") || Has(\"6500\") || Has(\"6550\") || Has(\"6500M\") =>\n                \"gfx1034\",\n\n            // RDNA2 Navi23\n            _ when Has(\"6600\") || Has(\"6650\") || Has(\"6700S\") || Has(\"6800S\") || Has(\"6600M\") => \"gfx1032\",\n\n            // RDNA2 Navi22 (note: desktop 6800 is NOT here; that’s Navi21/gfx1030)\n            _ when Has(\"6700\") || Has(\"6750\") || Has(\"6800M\") || Has(\"6850M\") => \"gfx1031\",\n\n            // RDNA2 Navi21 (big die)\n            _ when Has(\"6800\") || Has(\"6900\") || Has(\"6950\") => \"gfx1030\",\n\n            _ => null,\n        };\n\n        bool HasNoSpace(string s) =>\n            nameNoSpaces.Contains(\n                s.Replace(\" \", \"\", StringComparison.Ordinal),\n                StringComparison.OrdinalIgnoreCase\n            );\n        bool Has(string s) => name.Contains(s, StringComparison.OrdinalIgnoreCase);\n    }\n\n    public virtual bool Equals(GpuInfo? other)\n    {\n        if (other is null)\n            return false;\n        if (ReferenceEquals(this, other))\n            return true;\n        return Name == other.Name && MemoryBytes == other.MemoryBytes;\n    }\n\n    public override int GetHashCode()\n    {\n        return HashCode.Combine(Index, Name, MemoryBytes);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/HardwareInfo/HardwareHelper.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Diagnostics;\nusing System.Runtime.InteropServices;\nusing System.Runtime.Versioning;\nusing System.Text.RegularExpressions;\nusing Hardware.Info;\nusing Microsoft.Win32;\nusing NLog;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Core.Helper.HardwareInfo;\n\npublic static partial class HardwareHelper\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private static IReadOnlyList<GpuInfo>? cachedGpuInfos;\n    private static readonly object cachedGpuInfosLock = new();\n\n    private static readonly Lazy<IHardwareInfo> HardwareInfoLazy = new(() => new Hardware.Info.HardwareInfo()\n    );\n\n    public static IHardwareInfo HardwareInfo => HardwareInfoLazy.Value;\n\n    private static string RunBashCommand(string command)\n    {\n        var processInfo = new ProcessStartInfo(\"bash\", \"-c \\\"\" + command + \"\\\"\")\n        {\n            UseShellExecute = false,\n            RedirectStandardOutput = true,\n        };\n\n        var process = Process.Start(processInfo);\n\n        process.WaitForExit();\n\n        var output = process.StandardOutput.ReadToEnd();\n\n        return output;\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    private static IEnumerable<GpuInfo> IterGpuInfoWindows()\n    {\n        const string gpuRegistryKeyPath =\n            @\"SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\";\n\n        using var baseKey = Registry.LocalMachine.OpenSubKey(gpuRegistryKeyPath);\n\n        if (baseKey == null)\n            yield break;\n\n        var gpuIndex = 0;\n\n        foreach (var subKeyName in baseKey.GetSubKeyNames().Where(k => k.StartsWith(\"0\")))\n        {\n            using var subKey = baseKey.OpenSubKey(subKeyName);\n            if (subKey != null)\n            {\n                yield return new GpuInfo\n                {\n                    Index = gpuIndex++,\n                    Name = subKey.GetValue(\"DriverDesc\")?.ToString(),\n                    MemoryBytes = Convert.ToUInt64(subKey.GetValue(\"HardwareInformation.qwMemorySize\")),\n                };\n            }\n        }\n    }\n\n    [SupportedOSPlatform(\"linux\")]\n    private static IEnumerable<GpuInfo> IterGpuInfoLinux()\n    {\n        var output = RunBashCommand(\"lspci | grep -E '(VGA|3D)'\");\n        var gpuLines = output.Split(\"\\n\");\n\n        var gpuIndex = 0;\n\n        foreach (var line in gpuLines)\n        {\n            if (string.IsNullOrWhiteSpace(line))\n                continue;\n\n            var gpuId = line.Split(' ')[0]; // The GPU ID is the first part of the line\n            var gpuOutput = RunBashCommand($\"lspci -v -s {gpuId}\");\n\n            ulong memoryBytes = 0;\n            string? name = null;\n\n            // Parse output with regex\n            var match = Regex.Match(gpuOutput, @\"(VGA compatible controller|3D controller): ([^\\n]*)\");\n            if (match.Success)\n            {\n                name = match.Groups[2].Value.Trim();\n            }\n\n            match = Regex.Match(gpuOutput, @\"prefetchable\\) \\[size=(\\\\d+)M\\]\");\n            if (match.Success)\n            {\n                memoryBytes = ulong.Parse(match.Groups[1].Value) * 1024 * 1024;\n            }\n\n            yield return new GpuInfo\n            {\n                Index = gpuIndex++,\n                Name = name,\n                MemoryBytes = memoryBytes,\n            };\n        }\n    }\n\n    [SupportedOSPlatform(\"macos\")]\n    private static IEnumerable<GpuInfo> IterGpuInfoMacos()\n    {\n        HardwareInfo.RefreshVideoControllerList();\n\n        foreach (var (i, videoController) in HardwareInfo.VideoControllerList.Enumerate())\n        {\n            var gpuMemoryBytes = 0ul;\n\n            // For arm macs, use the shared system memory\n            if (Compat.IsArm)\n            {\n                gpuMemoryBytes = GetMemoryInfoImplGeneric().TotalPhysicalBytes;\n            }\n\n            yield return new GpuInfo\n            {\n                Index = i,\n                Name = videoController.Name,\n                MemoryBytes = gpuMemoryBytes,\n            };\n        }\n    }\n\n    /// <summary>\n    /// Yields GpuInfo for each GPU in the system.\n    /// </summary>\n    /// <param name=\"forceRefresh\">If true, refreshes cached GPU info.</param>\n    public static IEnumerable<GpuInfo> IterGpuInfo(bool forceRefresh = false)\n    {\n        // Use cached if available\n        if (!forceRefresh && cachedGpuInfos is not null)\n        {\n            return cachedGpuInfos;\n        }\n\n        using var _ = CodeTimer.StartDebug();\n\n        lock (cachedGpuInfosLock)\n        {\n            if (!forceRefresh && cachedGpuInfos is not null)\n            {\n                return cachedGpuInfos;\n            }\n\n            if (Compat.IsMacOS)\n            {\n                return cachedGpuInfos = IterGpuInfoMacos().ToList();\n            }\n\n            if (Compat.IsLinux || Compat.IsWindows)\n            {\n                try\n                {\n                    var smi = IterGpuInfoNvidiaSmi()?.ToList();\n                    var fallback = Compat.IsLinux\n                        ? IterGpuInfoLinux().ToList()\n                        : IterGpuInfoWindows().ToList();\n\n                    if (smi is null)\n                    {\n                        return cachedGpuInfos = fallback;\n                    }\n\n                    var newList = smi.Concat(fallback.Where(gpu => !gpu.IsNvidia))\n                        .Select(\n                            (gpu, index) =>\n                                new GpuInfo\n                                {\n                                    Name = gpu.Name,\n                                    Index = index,\n                                    MemoryBytes = gpu.MemoryBytes,\n                                }\n                        );\n\n                    return cachedGpuInfos = newList.ToList();\n                }\n                catch (Exception e)\n                {\n                    Logger.Error(e, \"Failed to get GPU info using nvidia-smi, falling back to registry\");\n\n                    var fallback = Compat.IsLinux\n                        ? IterGpuInfoLinux().ToList()\n                        : IterGpuInfoWindows().ToList();\n                    return cachedGpuInfos = fallback;\n                }\n            }\n\n            Logger.Error(\"Unknown OS, returning empty GPU info list\");\n            return cachedGpuInfos = [];\n        }\n    }\n\n    public static IEnumerable<GpuInfo>? IterGpuInfoNvidiaSmi()\n    {\n        using var _ = CodeTimer.StartDebug();\n\n        var psi = new ProcessStartInfo\n        {\n            FileName = \"nvidia-smi\",\n            UseShellExecute = false,\n            Arguments = \"--query-gpu name,memory.total,compute_cap --format=csv\",\n            RedirectStandardOutput = true,\n            CreateNoWindow = true,\n        };\n\n        var process = Process.Start(psi);\n        process?.WaitForExit();\n        var stdout = process?.StandardOutput.ReadToEnd();\n        var split = stdout?.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);\n        var results = split?[1..];\n\n        if (results is null)\n            return null;\n\n        var gpuInfos = new List<GpuInfo>();\n        for (var index = 0; index < results?.Length; index++)\n        {\n            var gpu = results[index];\n            var datas = gpu.Split(',', StringSplitOptions.RemoveEmptyEntries);\n            if (datas is not { Length: 3 })\n                continue;\n\n            var memory = Regex.Replace(datas[1], @\"([A-Z])\\w+\", \"\").Trim();\n\n            gpuInfos.Add(\n                new GpuInfo\n                {\n                    Name = datas[0],\n                    Index = index,\n                    MemoryBytes = Convert.ToUInt64(memory) * Size.MiB,\n                    ComputeCapability = datas[2].Trim(),\n                }\n            );\n        }\n\n        return gpuInfos;\n    }\n\n    /// <summary>\n    /// Gets the NVIDIA driver version using nvidia-smi.\n    /// Returns null if nvidia-smi is not available or fails.\n    /// </summary>\n    public static Version? GetNvidiaDriverVersion()\n    {\n        try\n        {\n            var psi = new ProcessStartInfo\n            {\n                FileName = \"nvidia-smi\",\n                UseShellExecute = false,\n                Arguments = \"--query-gpu=driver_version --format=csv,noheader\",\n                RedirectStandardOutput = true,\n                CreateNoWindow = true,\n            };\n\n            var process = Process.Start(psi);\n            process?.WaitForExit();\n            var stdout = process?.StandardOutput.ReadToEnd()?.Trim();\n\n            if (string.IsNullOrEmpty(stdout))\n                return null;\n\n            // Driver version is typically in the format \"xxx.xx\" (e.g., \"591.59\")\n            // We'll parse it as a Version object\n            return Version.TryParse(stdout, out var version) ? version : null;\n        }\n        catch (Exception e)\n        {\n            Logger.Warn(e, \"Failed to get NVIDIA driver version from nvidia-smi\");\n            return null;\n        }\n    }\n\n    /// <summary>\n    /// Return true if the system has at least one Nvidia GPU.\n    /// </summary>\n    public static bool HasNvidiaGpu()\n    {\n        return IterGpuInfo().Any(gpu => gpu.IsNvidia);\n    }\n\n    public static bool HasBlackwellGpu()\n    {\n        return IterGpuInfo()\n            .Any(gpu => gpu is { IsNvidia: true, Name: not null, ComputeCapabilityValue: >= 12.0m });\n    }\n\n    public static bool HasLegacyNvidiaGpu()\n    {\n        return IterGpuInfo()\n            .Any(gpu => gpu is { IsNvidia: true, Name: not null, ComputeCapabilityValue: < 7.5m });\n    }\n\n    public static bool HasAmpereOrNewerGpu()\n    {\n        return IterGpuInfo()\n            .Any(gpu => gpu is { IsNvidia: true, Name: not null, ComputeCapabilityValue: >= 8.6m });\n    }\n\n    /// <summary>\n    /// Return true if the system has at least one AMD GPU.\n    /// </summary>\n    public static bool HasAmdGpu()\n    {\n        return IterGpuInfo().Any(gpu => gpu.IsAmd);\n    }\n\n    public static bool HasWindowsRocmSupportedGpu() =>\n        IterGpuInfo().Any(gpu => gpu is { IsAmd: true, Name: not null } && gpu.IsWindowsRocmSupportedGpu());\n\n    public static GpuInfo? GetWindowsRocmSupportedGpu()\n    {\n        return IterGpuInfo().FirstOrDefault(gpu => gpu.IsWindowsRocmSupportedGpu());\n    }\n\n    public static bool HasIntelGpu() => IterGpuInfo().Any(gpu => gpu.IsIntel);\n\n    // Set ROCm for default if AMD and Linux\n    public static bool PreferRocm() => !HasNvidiaGpu() && HasAmdGpu() && Compat.IsLinux;\n\n    // Set DirectML for default if AMD and Windows\n    public static bool PreferDirectMLOrZluda() =>\n        !HasNvidiaGpu() && HasAmdGpu() && Compat.IsWindows && !HasWindowsRocmSupportedGpu();\n\n    private static readonly Lazy<bool> IsMemoryInfoAvailableLazy = new(() => TryGetMemoryInfo(out _));\n    public static bool IsMemoryInfoAvailable => IsMemoryInfoAvailableLazy.Value;\n    public static bool IsLiveMemoryUsageInfoAvailable => Compat.IsWindows && IsMemoryInfoAvailable;\n\n    public static bool TryGetMemoryInfo(out MemoryInfo memoryInfo)\n    {\n        try\n        {\n            memoryInfo = GetMemoryInfo();\n            return true;\n        }\n        catch (Exception ex)\n        {\n            Logger.Warn(ex, \"Failed to get memory info\");\n\n            memoryInfo = default;\n            return false;\n        }\n    }\n\n    /// <summary>\n    /// Gets the total and available physical memory in bytes.\n    /// </summary>\n    public static MemoryInfo GetMemoryInfo() =>\n        Compat.IsWindows ? GetMemoryInfoImplWindows() : GetMemoryInfoImplGeneric();\n\n    [SupportedOSPlatform(\"windows\")]\n    private static MemoryInfo GetMemoryInfoImplWindows()\n    {\n        var memoryStatus = new Win32MemoryStatusEx();\n\n        if (!GlobalMemoryStatusEx(ref memoryStatus))\n        {\n            throw new Win32Exception(Marshal.GetLastWin32Error());\n        }\n\n        if (!GetPhysicallyInstalledSystemMemory(out var installedMemoryKb))\n        {\n            throw new Win32Exception(Marshal.GetLastWin32Error());\n        }\n\n        return new MemoryInfo\n        {\n            TotalInstalledBytes = (ulong)installedMemoryKb * 1024,\n            TotalPhysicalBytes = memoryStatus.UllTotalPhys,\n            AvailablePhysicalBytes = memoryStatus.UllAvailPhys,\n        };\n    }\n\n    private static MemoryInfo GetMemoryInfoImplGeneric()\n    {\n        HardwareInfo.RefreshMemoryStatus();\n\n        // On macos only TotalPhysical is reported\n        if (Compat.IsMacOS)\n        {\n            return new MemoryInfo\n            {\n                TotalPhysicalBytes = HardwareInfo.MemoryStatus.TotalPhysical,\n                TotalInstalledBytes = HardwareInfo.MemoryStatus.TotalPhysical,\n            };\n        }\n\n        return new MemoryInfo\n        {\n            TotalPhysicalBytes = HardwareInfo.MemoryStatus.TotalPhysical,\n            TotalInstalledBytes = HardwareInfo.MemoryStatus.TotalPhysical,\n            AvailablePhysicalBytes = HardwareInfo.MemoryStatus.AvailablePhysical,\n        };\n    }\n\n    /// <summary>\n    /// Gets cpu info\n    /// </summary>\n    public static Task<CpuInfo> GetCpuInfoAsync() =>\n        Compat.IsWindows ? Task.FromResult(GetCpuInfoImplWindows()) : GetCpuInfoImplGenericAsync();\n\n    [SupportedOSPlatform(\"windows\")]\n    private static CpuInfo GetCpuInfoImplWindows()\n    {\n        var info = new CpuInfo();\n\n        using var processorKey = Registry.LocalMachine.OpenSubKey(\n            @\"Hardware\\Description\\System\\CentralProcessor\\0\",\n            RegistryKeyPermissionCheck.ReadSubTree\n        );\n\n        if (processorKey?.GetValue(\"ProcessorNameString\") is string processorName)\n        {\n            info = info with { ProcessorCaption = processorName.Trim() };\n        }\n\n        return info;\n    }\n\n    private static Task<CpuInfo> GetCpuInfoImplGenericAsync()\n    {\n        return Task.Run(() =>\n        {\n            HardwareInfo.RefreshCPUList();\n\n            if (HardwareInfo.CpuList.FirstOrDefault() is not { } cpu)\n            {\n                return default;\n            }\n\n            var processorCaption = cpu.Caption.Trim();\n\n            // Try name if caption is empty (like on macos)\n            if (string.IsNullOrWhiteSpace(processorCaption))\n            {\n                processorCaption = cpu.Name.Trim();\n            }\n\n            return new CpuInfo { ProcessorCaption = processorCaption };\n        });\n    }\n\n    [SupportedOSPlatform(\"windows\")]\n    [LibraryImport(\"kernel32.dll\", SetLastError = true)]\n    [return: MarshalAs(UnmanagedType.Bool)]\n    private static partial bool GetPhysicallyInstalledSystemMemory(out long totalMemoryInKilobytes);\n\n    [SupportedOSPlatform(\"windows\")]\n    [LibraryImport(\"kernel32.dll\", SetLastError = true)]\n    [return: MarshalAs(UnmanagedType.Bool)]\n    private static partial bool GlobalMemoryStatusEx(ref Win32MemoryStatusEx lpBuffer);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/HardwareInfo/MemoryInfo.cs",
    "content": "﻿namespace StabilityMatrix.Core.Helper.HardwareInfo;\n\npublic readonly record struct MemoryInfo\n{\n    public ulong TotalInstalledBytes { get; init; }\n\n    public ulong TotalPhysicalBytes { get; init; }\n\n    public ulong AvailablePhysicalBytes { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/HardwareInfo/MemoryLevel.cs",
    "content": "﻿namespace StabilityMatrix.Core.Helper.HardwareInfo;\n\npublic enum MemoryLevel\n{\n    Unknown,\n    Low,\n    Medium,\n    High\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/HardwareInfo/Win32MemoryStatusEx.cs",
    "content": "﻿using System.Runtime.InteropServices;\n\nnamespace StabilityMatrix.Core.Helper.HardwareInfo;\n\n[StructLayout(LayoutKind.Sequential)]\npublic struct Win32MemoryStatusEx\n{\n    public uint DwLength = (uint)Marshal.SizeOf(typeof(Win32MemoryStatusEx));\n    public uint DwMemoryLoad = 0;\n    public ulong UllTotalPhys = 0;\n    public ulong UllAvailPhys = 0;\n    public ulong UllTotalPageFile = 0;\n    public ulong UllAvailPageFile = 0;\n    public ulong UllTotalVirtual = 0;\n    public ulong UllAvailVirtual = 0;\n    public ulong UllAvailExtendedVirtual = 0;\n\n    public Win32MemoryStatusEx() { }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Runtime.Versioning;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic interface IPrerequisiteHelper\n{\n    string GitBinPath { get; }\n\n    bool IsPythonInstalled { get; }\n    bool IsVcBuildToolsInstalled { get; }\n    bool IsHipSdkInstalled { get; }\n\n    Task InstallAllIfNecessary(IProgress<ProgressReport>? progress = null);\n    Task InstallUvIfNecessary(IProgress<ProgressReport>? progress = null);\n    string UvExePath { get; }\n    bool IsUvInstalled { get; }\n    DirectoryPath DotnetDir { get; }\n    Task UnpackResourcesIfNecessary(IProgress<ProgressReport>? progress = null);\n    Task InstallGitIfNecessary(IProgress<ProgressReport>? progress = null);\n    Task InstallPythonIfNecessary(IProgress<ProgressReport>? progress = null);\n\n    [SupportedOSPlatform(\"Windows\")]\n    Task InstallVcRedistIfNecessary(IProgress<ProgressReport>? progress = null);\n\n    /// <summary>\n    /// Run embedded git with the given arguments.\n    /// </summary>\n    Task RunGit(\n        ProcessArgs args,\n        Action<ProcessOutput>? onProcessOutput = null,\n        string? workingDirectory = null\n    );\n\n    Task<ProcessResult> GetGitOutput(ProcessArgs args, string? workingDirectory = null);\n\n    async Task<bool> CheckIsGitRepository(string repositoryPath)\n    {\n        var result = await GetGitOutput([\"rev-parse\", \"--is-inside-work-tree\"], repositoryPath)\n            .ConfigureAwait(false);\n\n        return result.ExitCode == 0 && result.StandardOutput?.Trim().ToLowerInvariant() == \"true\";\n    }\n\n    async Task<GitVersion> GetGitRepositoryVersion(string repositoryPath)\n    {\n        var version = new GitVersion();\n\n        // Get tag\n        if (\n            await GetGitOutput([\"describe\", \"--tags\", \"--abbrev=0\"], repositoryPath).ConfigureAwait(false) is\n            { IsSuccessExitCode: true } tagResult\n        )\n        {\n            version = version with { Tag = tagResult.StandardOutput?.Trim() };\n        }\n\n        // Get branch\n        if (\n            await GetGitOutput([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], repositoryPath).ConfigureAwait(false) is\n            { IsSuccessExitCode: true } branchResult\n        )\n        {\n            version = version with { Branch = branchResult.StandardOutput?.Trim() };\n        }\n\n        // Get commit sha\n        if (\n            await GetGitOutput([\"rev-parse\", \"HEAD\"], repositoryPath).ConfigureAwait(false) is\n            { IsSuccessExitCode: true } shaResult\n        )\n        {\n            version = version with { CommitSha = shaResult.StandardOutput?.Trim() };\n        }\n\n        return version;\n    }\n\n    async Task CloneGitRepository(\n        string rootDir,\n        string repositoryUrl,\n        GitVersion? version = null,\n        Action<ProcessOutput>? onProcessOutput = null\n    )\n    {\n        // Decide shallow clone only when not pinning to arbitrary commit post-clone\n        var isShallowOk = version is null || version.Tag is not null;\n\n        var cloneArgs = new ProcessArgsBuilder(\"clone\");\n        if (isShallowOk)\n        {\n            cloneArgs = cloneArgs.AddArgs(\"--depth\", \"1\", \"--single-branch\");\n        }\n\n        if (!string.IsNullOrWhiteSpace(version?.Tag))\n        {\n            cloneArgs = cloneArgs.AddArgs(\"--branch\", version.Tag!);\n        }\n        else if (!string.IsNullOrWhiteSpace(version?.Branch))\n        {\n            cloneArgs = cloneArgs.AddArgs(\"--branch\", version.Branch!);\n        }\n\n        cloneArgs = cloneArgs.AddArgs(repositoryUrl);\n\n        await RunGit(cloneArgs.ToProcessArgs(), onProcessOutput, rootDir).ConfigureAwait(false);\n\n        // If pinning to a specific commit, we need a destination directory to continue\n        if (!string.IsNullOrWhiteSpace(version?.CommitSha))\n        {\n            await RunGit([\"fetch\", \"--depth\", \"1\", \"origin\", version.CommitSha!], onProcessOutput, rootDir)\n                .ConfigureAwait(false);\n            await RunGit([\"checkout\", \"--force\", version.CommitSha!], onProcessOutput, rootDir)\n                .ConfigureAwait(false);\n            await RunGit(\n                    [\"submodule\", \"update\", \"--init\", \"--recursive\", \"--depth\", \"1\"],\n                    onProcessOutput,\n                    rootDir\n                )\n                .ConfigureAwait(false);\n        }\n    }\n\n    async Task UpdateGitRepository(\n        string repositoryDir,\n        string repositoryUrl,\n        GitVersion version,\n        Action<ProcessOutput>? onProcessOutput = null,\n        bool usePrune = false,\n        bool allowRebaseFallback = true,\n        bool allowResetHardFallback = false\n    )\n    {\n        if (!Directory.Exists(Path.Combine(repositoryDir, \".git\")))\n        {\n            await RunGit([\"init\"], onProcessOutput, repositoryDir).ConfigureAwait(false);\n            await RunGit([\"remote\", \"add\", \"origin\", repositoryUrl], onProcessOutput, repositoryDir)\n                .ConfigureAwait(false);\n        }\n\n        // Ensure origin url matches the expected one\n        await RunGit([\"remote\", \"set-url\", \"origin\", repositoryUrl], onProcessOutput, repositoryDir)\n            .ConfigureAwait(false);\n\n        // Specify Tag\n        if (version.Tag is not null)\n        {\n            await RunGit([\"fetch\", \"--tags\", \"--force\"], onProcessOutput, repositoryDir)\n                .ConfigureAwait(false);\n            await RunGit([\"checkout\", version.Tag, \"--force\"], onProcessOutput, repositoryDir)\n                .ConfigureAwait(false);\n            // Update submodules\n            await RunGit([\"submodule\", \"update\", \"--init\", \"--recursive\"], onProcessOutput, repositoryDir)\n                .ConfigureAwait(false);\n        }\n        // Specify Branch + CommitSha\n        else if (version.Branch is not null && version.CommitSha is not null)\n        {\n            await RunGit([\"fetch\", \"--force\", \"origin\", version.CommitSha], onProcessOutput, repositoryDir)\n                .ConfigureAwait(false);\n\n            await RunGit([\"checkout\", \"--force\", version.CommitSha], onProcessOutput, repositoryDir)\n                .ConfigureAwait(false);\n            // Update submodules\n            await RunGit(\n                    [\"submodule\", \"update\", \"--init\", \"--recursive\", \"--depth\", \"1\"],\n                    onProcessOutput,\n                    repositoryDir\n                )\n                .ConfigureAwait(false);\n        }\n        // Specify Branch (Use latest commit)\n        else if (version.Branch is not null)\n        {\n            // Fetch (optional prune)\n            var fetchArgs = new ProcessArgsBuilder(\"fetch\", \"--force\");\n            if (usePrune)\n                fetchArgs = fetchArgs.AddArg(\"--prune\");\n            fetchArgs = fetchArgs.AddArg(\"origin\");\n            await RunGit(fetchArgs.ToProcessArgs(), onProcessOutput, repositoryDir).ConfigureAwait(false);\n\n            // Checkout\n            await RunGit([\"checkout\", \"--force\", version.Branch], onProcessOutput, repositoryDir)\n                .ConfigureAwait(false);\n\n            // Try ff-only first\n            var ffOnlyResult = await GetGitOutput(\n                    [\"pull\", \"--ff-only\", \"--autostash\", \"origin\", version.Branch],\n                    repositoryDir\n                )\n                .ConfigureAwait(false);\n\n            if (ffOnlyResult.ExitCode != 0)\n            {\n                if (allowRebaseFallback)\n                {\n                    var rebaseResult = await GetGitOutput(\n                            [\"pull\", \"--rebase\", \"--autostash\", \"origin\", version.Branch],\n                            repositoryDir\n                        )\n                        .ConfigureAwait(false);\n\n                    rebaseResult.EnsureSuccessExitCode();\n                }\n                else if (allowResetHardFallback)\n                {\n                    await RunGit(\n                            [\"fetch\", \"--force\", \"origin\", version.Branch],\n                            onProcessOutput,\n                            repositoryDir\n                        )\n                        .ConfigureAwait(false);\n                    await RunGit(\n                            [\"reset\", \"--hard\", $\"origin/{version.Branch}\"],\n                            onProcessOutput,\n                            repositoryDir\n                        )\n                        .ConfigureAwait(false);\n                }\n                else\n                {\n                    ffOnlyResult.EnsureSuccessExitCode();\n                }\n            }\n\n            // Update submodules\n            await RunGit(\n                    [\"submodule\", \"update\", \"--init\", \"--recursive\", \"--depth\", \"1\"],\n                    onProcessOutput,\n                    repositoryDir\n                )\n                .ConfigureAwait(false);\n        }\n        // Not specified\n        else\n        {\n            throw new ArgumentException(\n                \"Version must have a tag, branch + commit sha, or branch only.\",\n                nameof(version)\n            );\n        }\n    }\n\n    Task<ProcessResult> GetGitRepositoryRemoteOriginUrl(string repositoryPath)\n    {\n        return GetGitOutput([\"config\", \"--get\", \"remote.origin.url\"], repositoryPath);\n    }\n\n    Task InstallTkinterIfNecessary(IProgress<ProgressReport>? progress = null);\n    Task RunNpm(\n        ProcessArgs args,\n        string? workingDirectory = null,\n        Action<ProcessOutput>? onProcessOutput = null,\n        IReadOnlyDictionary<string, string>? envVars = null\n    );\n\n    AnsiProcess RunNpmDetached(\n        ProcessArgs args,\n        string? workingDirectory = null,\n        Action<ProcessOutput>? onProcessOutput = null,\n        IReadOnlyDictionary<string, string>? envVars = null\n    );\n    Task InstallNodeIfNecessary(IProgress<ProgressReport>? progress = null);\n    Task InstallPackageRequirements(\n        BasePackage package,\n        PyVersion? pyVersion = null,\n        IProgress<ProgressReport>? progress = null\n    );\n    Task InstallPackageRequirements(\n        List<PackagePrerequisite> prerequisites,\n        PyVersion? pyVersion = null,\n        IProgress<ProgressReport>? progress = null\n    );\n\n    Task InstallDotnetIfNecessary(IProgress<ProgressReport>? progress = null);\n\n    Task<Process> RunDotnet(\n        ProcessArgs args,\n        string? workingDirectory = null,\n        Action<ProcessOutput>? onProcessOutput = null,\n        IReadOnlyDictionary<string, string>? envVars = null,\n        bool waitForExit = true\n    );\n\n    Task<bool> FixGitLongPaths();\n    Task AddMissingLibsToVenv(\n        DirectoryPath installedPackagePath,\n        PyBaseInstall baseInstall,\n        IProgress<ProgressReport>? progress = null\n    );\n    Task InstallPythonIfNecessary(PyVersion version, IProgress<ProgressReport>? progress = null);\n    Task InstallVirtualenvIfNecessary(PyVersion version, IProgress<ProgressReport>? progress = null);\n    Task InstallTkinterIfNecessary(PyVersion version, IProgress<ProgressReport>? progress = null);\n    string? GetGfxArchFromAmdGpuName(GpuInfo? gpu = null);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/ISharedFolders.cs",
    "content": "﻿using StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic interface ISharedFolders\n{\n    void SetupLinksForPackage(BasePackage basePackage, DirectoryPath installDirectory);\n    void RemoveLinksForAllPackages();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/ImageMetadata.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Text;\nusing System.Text.Json;\nusing ExifLibrary;\nusing KGySoft.CoreLibraries;\nusing MetadataExtractor;\nusing MetadataExtractor.Formats.Exif;\nusing MetadataExtractor.Formats.Png;\nusing MetadataExtractor.Formats.WebP;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing Directory = MetadataExtractor.Directory;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic class ImageMetadata\n{\n    private IReadOnlyList<Directory>? Directories { get; set; }\n\n    private static readonly byte[] PngHeader = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];\n    private static readonly byte[] Idat = \"IDAT\"u8.ToArray();\n    private static readonly byte[] Text = \"tEXt\"u8.ToArray();\n\n    private static readonly byte[] Riff = \"RIFF\"u8.ToArray();\n    private static readonly byte[] Webp = \"WEBP\"u8.ToArray();\n\n    public static ImageMetadata ParseFile(FilePath path)\n    {\n        return new ImageMetadata { Directories = ImageMetadataReader.ReadMetadata(path) };\n    }\n\n    public static ImageMetadata ParseFile(Stream stream)\n    {\n        return new ImageMetadata { Directories = ImageMetadataReader.ReadMetadata(stream) };\n    }\n\n    public System.Drawing.Size? GetImageSize()\n    {\n        if (Directories?.OfType<PngDirectory>().FirstOrDefault() is { } header)\n        {\n            header.TryGetInt32(PngDirectory.TagImageWidth, out var width);\n            header.TryGetInt32(PngDirectory.TagImageHeight, out var height);\n\n            return new System.Drawing.Size(width, height);\n        }\n\n        return null;\n    }\n\n    public static System.Drawing.Size GetImageSize(byte[] inputImage)\n    {\n        var imageWidthBytes = inputImage[0x10..0x14];\n        var imageHeightBytes = inputImage[0x14..0x18];\n        var imageWidth = BitConverter.ToInt32(imageWidthBytes.AsEnumerable().Reverse().ToArray());\n        var imageHeight = BitConverter.ToInt32(imageHeightBytes.AsEnumerable().Reverse().ToArray());\n\n        return new System.Drawing.Size(imageWidth, imageHeight);\n    }\n\n    public static System.Drawing.Size GetImageSize(BinaryReader reader)\n    {\n        var oldPosition = reader.BaseStream.Position;\n\n        reader.BaseStream.Position = 0x10;\n        var imageWidthBytes = reader.ReadBytes(4);\n        var imageHeightBytes = reader.ReadBytes(4);\n\n        var imageWidth = BitConverter.ToInt32(imageWidthBytes.AsEnumerable().Reverse().ToArray());\n        var imageHeight = BitConverter.ToInt32(imageHeightBytes.AsEnumerable().Reverse().ToArray());\n\n        reader.BaseStream.Position = oldPosition;\n\n        return new System.Drawing.Size(imageWidth, imageHeight);\n    }\n\n    public static (\n        string? Parameters,\n        string? ParametersJson,\n        string? SMProject,\n        string? ComfyNodes,\n        string? CivitParameters\n    ) GetAllFileMetadata(FilePath filePath)\n    {\n        if (filePath.Extension.Equals(\".webp\", StringComparison.OrdinalIgnoreCase))\n        {\n            var paramsJson = ReadTextChunkFromWebp(filePath, ExifDirectoryBase.TagImageDescription);\n            var smProj = ReadTextChunkFromWebp(filePath, ExifDirectoryBase.TagSoftware);\n\n            return (null, paramsJson, smProj, null, null);\n        }\n\n        if (\n            filePath.Extension.Equals(\".jpeg\", StringComparison.OrdinalIgnoreCase)\n            || filePath.Extension.Equals(\".jpg\", StringComparison.OrdinalIgnoreCase)\n        )\n        {\n            var file = ImageFile.FromFile(filePath.Info.FullName);\n            var userComment = file.Properties.Get(ExifTag.UserComment);\n            var bytes = userComment.Interoperability.Data.Skip(8).ToArray();\n            var userCommentString = Encoding.BigEndianUnicode.GetString(bytes);\n\n            return (null, null, null, null, userCommentString);\n        }\n\n        using var stream = filePath.Info.OpenRead();\n        using var reader = new BinaryReader(stream);\n\n        var parameters = ReadTextChunk(reader, \"parameters\");\n        var parametersJson = ReadTextChunk(reader, \"parameters-json\");\n        var smProject = ReadTextChunk(reader, \"smproj\");\n        var comfyNodes = ReadTextChunk(reader, \"prompt\");\n        var civitParameters = ReadTextChunk(reader, \"user_comment\");\n\n        return (\n            string.IsNullOrEmpty(parameters) ? null : parameters,\n            string.IsNullOrEmpty(parametersJson) ? null : parametersJson,\n            string.IsNullOrEmpty(smProject) ? null : smProject,\n            string.IsNullOrEmpty(comfyNodes) ? null : comfyNodes,\n            string.IsNullOrEmpty(civitParameters) ? null : civitParameters\n        );\n    }\n\n    public IEnumerable<Tag>? GetTextualData()\n    {\n        // Get the PNG-tEXt directory\n        return Directories\n            ?.Where(d => d.Name == \"PNG-tEXt\")\n            .SelectMany(d => d.Tags)\n            .Where(t => t.Name == \"Textual Data\");\n    }\n\n    public GenerationParameters? GetGenerationParameters()\n    {\n        var textualData = GetTextualData()?.ToArray();\n        if (textualData is null)\n        {\n            return null;\n        }\n\n        // Use \"parameters-json\" tag if exists\n        if (\n            textualData.FirstOrDefault(tag =>\n                tag.Description is { } desc && desc.StartsWith(\"parameters-json: \")\n            ) is\n            { Description: { } description }\n        )\n        {\n            description = description.StripStart(\"parameters-json: \");\n\n            return JsonSerializer.Deserialize<GenerationParameters>(description);\n        }\n\n        // Otherwise parse \"parameters\" tag\n        if (\n            textualData.FirstOrDefault(tag =>\n                tag.Description is { } desc && desc.StartsWith(\"parameters: \")\n            ) is\n            { Description: { } parameters }\n        )\n        {\n            parameters = parameters.StripStart(\"parameters: \");\n\n            if (GenerationParameters.TryParse(parameters, out var generationParameters))\n            {\n                return generationParameters;\n            }\n        }\n\n        return null;\n    }\n\n    public static string ReadTextChunk(BinaryReader byteStream, string key)\n    {\n        byteStream.BaseStream.Position = 0;\n\n        // Read first 8 bytes and make sure they match the png header\n        if (!byteStream.ReadBytes(8).SequenceEqual(PngHeader))\n        {\n            return string.Empty;\n        }\n\n        while (byteStream.BaseStream.Position < byteStream.BaseStream.Length - 4)\n        {\n            var chunkSize = BitConverter.ToInt32(byteStream.ReadBytes(4).AsEnumerable().Reverse().ToArray());\n            var chunkType = Encoding.UTF8.GetString(byteStream.ReadBytes(4));\n\n            if (chunkType == Encoding.UTF8.GetString(Idat))\n            {\n                return string.Empty;\n            }\n\n            if (chunkType == Encoding.UTF8.GetString(Text))\n            {\n                var textBytes = byteStream.ReadBytes(chunkSize);\n                var text = Encoding.UTF8.GetString(textBytes);\n                if (text.StartsWith($\"{key}\\0\"))\n                {\n                    return text[(key.Length + 1)..];\n                }\n            }\n            else\n            {\n                // skip chunk data\n                byteStream.BaseStream.Position += chunkSize;\n            }\n\n            // skip crc\n            byteStream.BaseStream.Position += 4;\n        }\n\n        return string.Empty;\n    }\n\n    public static MemoryStream? BuildImageWithoutMetadata(FilePath imagePath)\n    {\n        using var byteStream = new BinaryReader(File.OpenRead(imagePath));\n        byteStream.BaseStream.Position = 0;\n\n        if (!byteStream.ReadBytes(8).SequenceEqual(PngHeader))\n        {\n            return null;\n        }\n\n        var memoryStream = new MemoryStream();\n        memoryStream.Write(PngHeader);\n\n        // add the IHDR chunk\n        var ihdrStuff = byteStream.ReadBytes(25);\n        memoryStream.Write(ihdrStuff);\n\n        // find IDATs\n        while (byteStream.BaseStream.Position < byteStream.BaseStream.Length - 4)\n        {\n            var chunkSizeBytes = byteStream.ReadBytes(4);\n            var chunkSize = BitConverter.ToInt32(chunkSizeBytes.AsEnumerable().Reverse().ToArray());\n            var chunkTypeBytes = byteStream.ReadBytes(4);\n            var chunkType = Encoding.UTF8.GetString(chunkTypeBytes);\n\n            if (chunkType != Encoding.UTF8.GetString(Idat))\n            {\n                // skip chunk data\n                byteStream.BaseStream.Position += chunkSize;\n                // skip crc\n                byteStream.BaseStream.Position += 4;\n                continue;\n            }\n\n            memoryStream.Write(chunkSizeBytes);\n            memoryStream.Write(chunkTypeBytes);\n            var idatBytes = byteStream.ReadBytes(chunkSize);\n            memoryStream.Write(idatBytes);\n            var crcBytes = byteStream.ReadBytes(4);\n            memoryStream.Write(crcBytes);\n        }\n\n        // Add IEND chunk\n        memoryStream.Write([0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82]);\n        memoryStream.Position = 0;\n        return memoryStream;\n    }\n\n    /// <summary>\n    /// Reads an EXIF tag from a webp file and returns the value as string\n    /// </summary>\n    /// <param name=\"filePath\">The webp file to read EXIF data from</param>\n    /// <param name=\"exifTag\">Use <see cref=\"ExifDirectoryBase\"/> constants for the tag you'd like to search for</param>\n    /// <returns></returns>\n    public static string ReadTextChunkFromWebp(FilePath filePath, int exifTag)\n    {\n        var exifDirs = WebPMetadataReader.ReadMetadata(filePath).OfType<ExifIfd0Directory>().FirstOrDefault();\n        return exifDirs is null ? string.Empty : exifDirs.GetString(exifTag) ?? string.Empty;\n    }\n\n    public static IEnumerable<byte> AddMetadataToWebp(\n        byte[] inputImage,\n        Dictionary<ExifTag, string> exifTagData\n    )\n    {\n        using var byteStream = new BinaryReader(new MemoryStream(inputImage));\n        byteStream.BaseStream.Position = 0;\n\n        // Read first 8 bytes and make sure they match the RIFF header\n        if (!byteStream.ReadBytes(4).SequenceEqual(Riff))\n        {\n            return Array.Empty<byte>();\n        }\n\n        // skip 4 bytes then read next 4 for webp header\n        byteStream.BaseStream.Position += 4;\n        if (!byteStream.ReadBytes(4).SequenceEqual(Webp))\n        {\n            return Array.Empty<byte>();\n        }\n\n        while (byteStream.BaseStream.Position < byteStream.BaseStream.Length - 4)\n        {\n            var chunkType = Encoding.UTF8.GetString(byteStream.ReadBytes(4));\n            var chunkSize = BitConverter.ToInt32(byteStream.ReadBytes(4).ToArray());\n\n            if (chunkType != \"EXIF\")\n            {\n                // skip chunk data\n                byteStream.BaseStream.Position += chunkSize;\n                continue;\n            }\n\n            var exifStart = byteStream.BaseStream.Position - 8;\n            var exifBytes = byteStream.ReadBytes(chunkSize);\n            Debug.WriteLine($\"Found exif chunk of size {chunkSize}\");\n\n            using var stream = new MemoryStream(exifBytes[6..]);\n            var img = new MyTiffFile(stream, Encoding.UTF8);\n\n            foreach (var (key, value) in exifTagData)\n            {\n                img.Properties.Set(key, value);\n            }\n\n            using var newStream = new MemoryStream();\n            img.Save(newStream);\n            newStream.Seek(0, SeekOrigin.Begin);\n            var newExifBytes = exifBytes[..6].Concat(newStream.ToArray());\n            var newExifSize = newExifBytes.Count();\n            var newChunkSize = BitConverter.GetBytes(newExifSize);\n            var newChunk = \"EXIF\"u8.ToArray().Concat(newChunkSize).Concat(newExifBytes).ToArray();\n\n            var inputEndIndex = (int)exifStart;\n            var newImage = inputImage[..inputEndIndex].Concat(newChunk).ToArray();\n\n            // webp or tiff or something requires even number of bytes\n            if (newImage.Length % 2 != 0)\n            {\n                newImage = newImage.Concat(new byte[] { 0x00 }).ToArray();\n            }\n\n            // no clue why the minus 8 is needed but it is\n            var newImageSize = BitConverter.GetBytes(newImage.Length - 8);\n            newImage[4] = newImageSize[0];\n            newImage[5] = newImageSize[1];\n            newImage[6] = newImageSize[2];\n            newImage[7] = newImageSize[3];\n            return newImage;\n        }\n\n        return Array.Empty<byte>();\n    }\n\n    private static byte[] GetExifChunks(FilePath imagePath)\n    {\n        using var byteStream = new BinaryReader(File.OpenRead(imagePath));\n        byteStream.BaseStream.Position = 0;\n\n        // Read first 8 bytes and make sure they match the RIFF header\n        if (!byteStream.ReadBytes(4).SequenceEqual(Riff))\n        {\n            return Array.Empty<byte>();\n        }\n\n        // skip 4 bytes then read next 4 for webp header\n        byteStream.BaseStream.Position += 4;\n        if (!byteStream.ReadBytes(4).SequenceEqual(Webp))\n        {\n            return Array.Empty<byte>();\n        }\n\n        while (byteStream.BaseStream.Position < byteStream.BaseStream.Length - 4)\n        {\n            var chunkType = Encoding.UTF8.GetString(byteStream.ReadBytes(4));\n            var chunkSize = BitConverter.ToInt32(byteStream.ReadBytes(4).ToArray());\n\n            if (chunkType != \"EXIF\")\n            {\n                // skip chunk data\n                byteStream.BaseStream.Position += chunkSize;\n                continue;\n            }\n\n            var exifStart = byteStream.BaseStream.Position;\n            var exifBytes = byteStream.ReadBytes(chunkSize);\n            var exif = Encoding.UTF8.GetString(exifBytes);\n            Debug.WriteLine($\"Found exif chunk of size {chunkSize}\");\n            return exifBytes;\n        }\n\n        return Array.Empty<byte>();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/LazyInstance.cs",
    "content": "﻿using Microsoft.Extensions.DependencyInjection;\n\nnamespace StabilityMatrix.Core.Helper;\n\n/// <summary>\n/// Lazy instance of a DI service.\n/// </summary>\npublic class LazyInstance<T> : Lazy<T>\n    where T : notnull\n{\n    public LazyInstance(IServiceProvider serviceProvider)\n        : base(serviceProvider.GetRequiredService<T>) { }\n}\n\npublic static class LazyInstanceServiceExtensions\n{\n    /// <summary>\n    /// Register <see cref=\"LazyInstance{T}\"/> to be used when resolving <see cref=\"Lazy{T}\"/> instances.\n    /// </summary>\n    public static IServiceCollection AddLazyInstance(this IServiceCollection services)\n    {\n        services.AddTransient(typeof(Lazy<>), typeof(LazyInstance<>));\n        return services;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/MinimumDelay.cs",
    "content": "﻿using System.Diagnostics;\n\nnamespace StabilityMatrix.Core.Helper;\n\n/// <summary>\n/// Enforces a minimum delay if the function returns too quickly.\n/// Waits during async Dispose.\n/// </summary>\npublic class MinimumDelay : IAsyncDisposable\n{\n    private readonly Stopwatch stopwatch = new();\n    private readonly TimeSpan delay;\n    \n    /// <summary>\n    /// Minimum random delay in milliseconds.\n    /// </summary>\n    public MinimumDelay(int randMin, int randMax)\n    {\n        stopwatch.Start();\n        Random rand = new();\n        delay = TimeSpan.FromMilliseconds(rand.Next(randMin, randMax));\n    }\n    \n    /// <summary>\n    /// Minimum fixed delay in milliseconds.\n    /// </summary>\n    public MinimumDelay(int delayMilliseconds)\n    {\n        stopwatch.Start();\n        delay = TimeSpan.FromMilliseconds(delayMilliseconds);\n    }\n    \n    public async ValueTask DisposeAsync()\n    {\n        stopwatch.Stop();\n        var elapsed = stopwatch.Elapsed;\n        if (elapsed < delay)\n        {\n            await Task.Delay(delay - elapsed);\n        }\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/ModelCompatChecker.cs",
    "content": "﻿using StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic class ModelCompatChecker\n{\n    private readonly Dictionary<string, CivitBaseModelType> baseModelNamesToTypes =\n        Enum.GetValues<CivitBaseModelType>().ToDictionary(x => x.GetStringValue());\n\n    public bool? IsLoraCompatibleWithBaseModel(HybridModelFile? lora, HybridModelFile? baseModel)\n    {\n        // Require connected info for both\n        if (\n            lora?.Local?.ConnectedModelInfo is not { } loraInfo\n            || baseModel?.Local?.ConnectedModelInfo is not { } baseModelInfo\n        )\n            return null;\n\n        if (\n            loraInfo.BaseModel is null\n            || !baseModelNamesToTypes.TryGetValue(loraInfo.BaseModel, out var loraBaseModelType)\n        )\n            return null;\n\n        if (\n            baseModelInfo.BaseModel is null\n            || !baseModelNamesToTypes.TryGetValue(baseModelInfo.BaseModel, out var baseModelType)\n        )\n            return null;\n\n        // Normalize both\n        var normalizedLoraBaseModelType = NormalizeBaseModelType(loraBaseModelType);\n        var normalizedBaseModelType = NormalizeBaseModelType(baseModelType);\n\n        // Ignore if either is \"Other\"\n        if (\n            normalizedLoraBaseModelType == CivitBaseModelType.Other\n            || normalizedBaseModelType == CivitBaseModelType.Other\n        )\n            return null;\n\n        return normalizedLoraBaseModelType == normalizedBaseModelType;\n    }\n\n    // Normalize base model type\n    private static CivitBaseModelType NormalizeBaseModelType(CivitBaseModelType baseModel)\n    {\n        return baseModel switch\n        {\n            CivitBaseModelType.Sdxl09 => CivitBaseModelType.Sdxl10,\n            CivitBaseModelType.Sdxl10Lcm => CivitBaseModelType.Sdxl10,\n            CivitBaseModelType.SdxlDistilled => CivitBaseModelType.Sdxl10,\n            CivitBaseModelType.SdxlHyper => CivitBaseModelType.Sdxl10,\n            CivitBaseModelType.SdxlLightning => CivitBaseModelType.Sdxl10,\n            CivitBaseModelType.SdxlTurbo => CivitBaseModelType.Sdxl10,\n            CivitBaseModelType.Pony => CivitBaseModelType.Sdxl10,\n            CivitBaseModelType.NoobAi => CivitBaseModelType.Sdxl10,\n            CivitBaseModelType.Illustrious => CivitBaseModelType.Sdxl10,\n            _ => baseModel,\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/ModelFinder.cs",
    "content": "﻿using System.Net;\nusing Injectio.Attributes;\nusing NLog;\nusing Refit;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Models.Api;\n\nnamespace StabilityMatrix.Core.Helper;\n\n// return Model, ModelVersion, ModelFile\npublic record struct ModelSearchResult(CivitModel Model, CivitModelVersion ModelVersion, CivitFile ModelFile);\n\n[RegisterSingleton<ModelFinder>]\npublic class ModelFinder\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private readonly ILiteDbContext liteDbContext;\n    private readonly ICivitApi civitApi;\n\n    public ModelFinder(ILiteDbContext liteDbContext, ICivitApi civitApi)\n    {\n        this.liteDbContext = liteDbContext;\n        this.civitApi = civitApi;\n    }\n\n    // Finds a model from the local database using file hash\n    public async Task<ModelSearchResult?> LocalFindModel(string hashBlake3)\n    {\n        var (model, version) = await liteDbContext.FindCivitModelFromFileHashAsync(hashBlake3);\n        if (model == null || version == null)\n        {\n            return null;\n        }\n\n        var file = version.Files!.First(file => file.Hashes.BLAKE3?.ToLowerInvariant() == hashBlake3);\n\n        return new ModelSearchResult(model, version, file);\n    }\n\n    // Finds a model using Civit API using file hash\n    public async Task<ModelSearchResult?> RemoteFindModel(string hashBlake3)\n    {\n        Logger.Info(\"Searching Civit API for model version using hash {Hash}\", hashBlake3);\n        try\n        {\n            var versionResponse = await civitApi.GetModelVersionByHash(hashBlake3);\n\n            Logger.Info(\n                \"Found version {VersionId} with model id {ModelId}\",\n                versionResponse.Id,\n                versionResponse.ModelId\n            );\n            var model = await civitApi.GetModelById(versionResponse.ModelId);\n\n            // VersionResponse is not actually the full data of ModelVersion, so find it again\n            var version = model.ModelVersions!.First(version => version.Id == versionResponse.Id);\n\n            var file = versionResponse.Files.FirstOrDefault(\n                file => hashBlake3.Equals(file.Hashes.BLAKE3, StringComparison.OrdinalIgnoreCase)\n            );\n\n            // Archived models do not have files\n            if (file == null)\n                return null;\n\n            return new ModelSearchResult(model, version, file);\n        }\n        catch (TaskCanceledException e)\n        {\n            Logger.Warn(\n                \"Timed out while finding remote model version using hash {Hash}: {Error}\",\n                hashBlake3,\n                e.Message\n            );\n            return null;\n        }\n        catch (ApiException e)\n        {\n            if (e.StatusCode == HttpStatusCode.NotFound)\n            {\n                Logger.Info(\"Could not find remote model version using hash {Hash}\", hashBlake3);\n            }\n            else\n            {\n                Logger.Warn(\n                    e,\n                    \"Could not find remote model version using hash {Hash}: {Error}\",\n                    hashBlake3,\n                    e.Message\n                );\n            }\n\n            return null;\n        }\n        catch (HttpRequestException e)\n        {\n            Logger.Warn(\n                e,\n                \"Could not connect to api while finding remote model version using hash {Hash}: {Error}\",\n                hashBlake3,\n                e.Message\n            );\n            return null;\n        }\n    }\n\n    public async Task<IEnumerable<CivitModel>> FindRemoteModelsById(IEnumerable<int> ids)\n    {\n        var results = new List<CivitModel>();\n\n        // split ids into batches of 100\n        var batches = ids.Chunk(100);\n\n        foreach (var batch in batches)\n        {\n            try\n            {\n                var response = await civitApi\n                    .GetModels(\n                        new CivitModelsRequest\n                        {\n                            CommaSeparatedModelIds = string.Join(\",\", batch),\n                            Nsfw = \"true\",\n                            Query = string.Empty\n                        }\n                    )\n                    .ConfigureAwait(false);\n\n                if (response.Items == null || response.Items.Count == 0)\n                    continue;\n\n                results.AddRange(response.Items);\n            }\n            catch (Exception e)\n            {\n                Logger.Error(\"Error while finding remote models by id: {Error}\", e.Message);\n            }\n        }\n\n        return results;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/MyTiffFile.cs",
    "content": "﻿using System.Text;\nusing ExifLibrary;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic class MyTiffFile(MemoryStream stream, Encoding encoding) : TIFFFile(stream, encoding);\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/ObjectHash.cs",
    "content": "﻿using System.Security.Cryptography;\nusing System.Text;\nusing System.Text.Json;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic static class ObjectHash\n{\n    /// <summary>\n    /// Return a GUID based on the MD5 hash of the JSON representation of the object.\n    /// </summary>\n    public static Guid GetMd5Guid<T>(T obj)\n    {\n        var json = JsonSerializer.Serialize(obj);\n        var bytes = Encoding.UTF8.GetBytes(json);\n        using var md5 = System.Security.Cryptography.MD5.Create();\n        var hash = md5.ComputeHash(bytes);\n        return new Guid(hash);\n    }\n\n    /// <summary>\n    /// Return a short Sha256 signature of a string\n    /// </summary>\n    public static string GetStringSignature(string? str)\n    {\n        if (str is null)\n        {\n            return \"null\";\n        }\n\n        if (string.IsNullOrEmpty(str))\n        {\n            return \"\";\n        }\n\n        var bytes = Encoding.UTF8.GetBytes(str);\n        var hash = Convert.ToBase64String(SHA256.HashData(bytes));\n\n        return $\"[..{str.Length}, {hash[..7]}]\";\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/PlatformKind.cs",
    "content": "﻿namespace StabilityMatrix.Core.Helper;\n\n[Flags]\npublic enum PlatformKind\n{\n    Unknown = 0,\n    Windows = 1 << 0,\n    Unix = 1 << 1,\n    Linux = Unix | 1 << 2,\n    MacOS = Unix | 1 << 3,\n    Arm = 1 << 20,\n    X64 = 1 << 21,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/ProcessTracker.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Runtime.InteropServices;\nusing System.Runtime.Versioning;\nusing Microsoft.Win32.SafeHandles;\nusing NLog;\n\nnamespace StabilityMatrix.Core.Helper;\n\n/// <summary>\n/// Allows processes to be automatically killed if this parent process unexpectedly quits.\n/// This feature requires Windows 8 or greater. On Windows 7, nothing is done.</summary>\n/// <remarks>References:\n///  https://stackoverflow.com/a/4657392/386091\n///  https://stackoverflow.com/a/9164742/386091 </remarks>\n[SupportedOSPlatform(\"windows\")]\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\npublic static partial class ProcessTracker\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private static readonly Lazy<JobObject?> ProcessTrackerJobLazy = new(() =>\n    {\n        if (!JobObject.IsAvailableOnCurrentPlatform)\n        {\n            return null;\n        }\n\n        // The job name is optional (and can be null) but it helps with diagnostics.\n        //  If it's not null, it has to be unique. Use SysInternals' Handle command-line\n        //  utility: handle -a ChildProcessTracker\n        var jobName = $\"SM_ProcessTracker_{Environment.ProcessId}\";\n\n        Logger.Debug(\"Creating Job Object {Job}\", jobName);\n\n        try\n        {\n            return new JobObject(jobName);\n        }\n        catch (Exception e)\n        {\n            Logger.Error(e, \"Failed to create Job Object, ProcessTracker will be unavailable\");\n            return null;\n        }\n    });\n\n    private static JobObject? ProcessTrackerJob => ProcessTrackerJobLazy.Value;\n\n    /// <summary>\n    /// Add the process to be tracked. If our current process is killed, the child processes\n    /// that we are tracking will be automatically killed, too. If the child process terminates\n    /// first, that's fine, too.\n    ///\n    /// Ignored if the Process has already exited.\n    /// </summary>\n    public static void AddProcess(Process process)\n    {\n        // Skip if no job object\n        if (ProcessTrackerJob is not { } job)\n        {\n            return;\n        }\n\n        // Skip if process already exited, this sometimes throws\n        try\n        {\n            if (process.HasExited)\n            {\n                return;\n            }\n        }\n        catch (InvalidOperationException)\n        {\n            return;\n        }\n\n        try\n        {\n            Logger.Debug(\n                \"Adding Process {Process} [{Id}] to Job Object {Job}\",\n                process.ProcessName,\n                process.Id,\n                job.Name\n            );\n\n            job.AssignProcess(process);\n        }\n        catch (Exception)\n        {\n            // Check again if the process has exited, if it hasn't, rethrow\n            try\n            {\n                if (process.HasExited)\n                {\n                    return;\n                }\n            }\n            catch (InvalidOperationException)\n            {\n                return;\n            }\n\n            throw;\n        }\n    }\n\n    /// <summary>\n    /// Add the process to be tracked in a new job. If our current process is killed, the child processes\n    /// that we are tracking will be automatically killed, too. If the child process terminates\n    /// first, that's fine, too.\n    ///\n    /// Ignored if the Process has already exited.\n    /// </summary>\n    public static void AttachExitHandlerJobToProcess(Process process)\n    {\n        // Skip if job object is not available\n        if (!JobObject.IsAvailableOnCurrentPlatform)\n        {\n            return;\n        }\n\n        // Skip if process already exited, this sometimes throws\n        try\n        {\n            if (process.HasExited)\n            {\n                return;\n            }\n        }\n        catch (InvalidOperationException)\n        {\n            return;\n        }\n\n        // Create a new job object for this process\n        var jobName = $\"SM_ProcessTracker_{Environment.ProcessId}_Instance_{process.Id}\";\n\n        Logger.Debug(\"Creating Instance Job Object {Job}\", jobName);\n\n        var instanceJob = new JobObject(jobName);\n\n        try\n        {\n            Logger.Debug(\n                \"Adding Process {Process} [{Id}] to Job Object {Job}\",\n                process.ProcessName,\n                process.Id,\n                jobName\n            );\n\n            instanceJob.AssignProcess(process);\n\n            // Dispose the instance job when the process exits\n            process.Exited += (_, _) =>\n            {\n                Logger.Debug(\n                    \"Process {Process} [{Id}] exited ({Code}), terminating instance Job Object {Job}\",\n                    process.ProcessName,\n                    process.Id,\n                    process.ExitCode,\n                    jobName\n                );\n\n                // ReSharper disable twice AccessToDisposedClosure\n                if (!instanceJob.IsClosed)\n                {\n                    // Convert from negative to two's complement if needed\n                    var exitCode =\n                        process.ExitCode < 0 ? (uint)(4294967296 + process.ExitCode) : (uint)process.ExitCode;\n\n                    instanceJob.Terminate(exitCode);\n                    instanceJob.Dispose();\n                }\n            };\n        }\n        catch (Exception)\n        {\n            instanceJob.Dispose();\n            throw;\n        }\n    }\n\n    private class JobObject : SafeHandleZeroOrMinusOneIsInvalid\n    {\n        // This feature requires Windows 8 or later\n        public static bool IsAvailableOnCurrentPlatform =>\n            Compat.IsWindows && Environment.OSVersion.Version >= new Version(6, 2);\n\n        public string Name { get; }\n\n        public JobObject(string name)\n            : base(true)\n        {\n            if (!IsAvailableOnCurrentPlatform)\n            {\n                throw new PlatformNotSupportedException(\"This feature requires Windows 8 or later.\");\n            }\n\n            Name = name;\n\n            handle = CreateJobObject(IntPtr.Zero, name);\n\n            var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION\n            {\n                // This is the key flag. When our process is killed, Windows will automatically\n                //  close the job handle, and when that happens, we want the child processes to\n                //  be killed, too.\n                LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,\n            };\n\n            var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));\n            var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION { BasicLimitInformation = info };\n            var extendedInfoPtr = Marshal.AllocHGlobal(length);\n\n            try\n            {\n                Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);\n\n                if (\n                    !SetInformationJobObject(\n                        handle,\n                        JobObjectInfoType.ExtendedLimitInformation,\n                        extendedInfoPtr,\n                        (uint)length\n                    )\n                )\n                {\n                    throw new Win32Exception(Marshal.GetLastWin32Error());\n                }\n            }\n            finally\n            {\n                Marshal.FreeHGlobal(extendedInfoPtr);\n            }\n        }\n\n        public void AssignProcess(Process process)\n        {\n            ObjectDisposedException.ThrowIf(handle == IntPtr.Zero, typeof(JobObject));\n\n            if (!AssignProcessToJobObject(handle, process.Handle))\n            {\n                throw new Win32Exception(Marshal.GetLastWin32Error());\n            }\n        }\n\n        public void AssignProcess(IntPtr processHandle)\n        {\n            ObjectDisposedException.ThrowIf(handle == IntPtr.Zero, typeof(JobObject));\n\n            if (!AssignProcessToJobObject(handle, processHandle))\n            {\n                throw new Win32Exception(Marshal.GetLastWin32Error());\n            }\n        }\n\n        public void Terminate(uint exitCode)\n        {\n            ObjectDisposedException.ThrowIf(handle == IntPtr.Zero, typeof(JobObject));\n\n            if (!TerminateJobObject(handle, exitCode))\n            {\n                throw new Win32Exception(Marshal.GetLastWin32Error());\n            }\n        }\n\n        protected override bool ReleaseHandle()\n        {\n            return CloseHandle(handle);\n        }\n    }\n\n    [LibraryImport(\n        \"kernel32.dll\",\n        EntryPoint = \"CreateJobObjectW\",\n        SetLastError = true,\n        StringMarshalling = StringMarshalling.Utf16\n    )]\n    private static partial IntPtr CreateJobObject(IntPtr lpJobAttributes, string name);\n\n    [LibraryImport(\"kernel32.dll\", SetLastError = true)]\n    [return: MarshalAs(UnmanagedType.Bool)]\n    private static partial bool SetInformationJobObject(\n        IntPtr job,\n        JobObjectInfoType infoType,\n        IntPtr lpJobObjectInfo,\n        uint cbJobObjectInfoLength\n    );\n\n    [LibraryImport(\"kernel32.dll\", SetLastError = true)]\n    [return: MarshalAs(UnmanagedType.Bool)]\n    private static partial bool AssignProcessToJobObject(IntPtr job, IntPtr process);\n\n    [LibraryImport(\"kernel32.dll\", SetLastError = true)]\n    [return: MarshalAs(UnmanagedType.Bool)]\n    private static partial bool CloseHandle(IntPtr hObject);\n\n    [LibraryImport(\"kernel32.dll\", SetLastError = true)]\n    [return: MarshalAs(UnmanagedType.Bool)]\n    private static partial bool TerminateJobObject(IntPtr job, uint exitCode);\n\n    internal enum JobObjectInfoType\n    {\n        AssociateCompletionPortInformation = 7,\n        BasicLimitInformation = 2,\n        BasicUIRestrictions = 4,\n        EndOfJobTimeInformation = 6,\n        ExtendedLimitInformation = 9,\n        SecurityLimitInformation = 5,\n        GroupInformation = 11,\n    }\n\n    [StructLayout(LayoutKind.Sequential)]\n    // ReSharper disable once IdentifierTypo\n    internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION\n    {\n        public Int64 PerProcessUserTimeLimit;\n        public Int64 PerJobUserTimeLimit;\n        public JOBOBJECTLIMIT LimitFlags;\n        public UIntPtr MinimumWorkingSetSize;\n        public UIntPtr MaximumWorkingSetSize;\n        public UInt32 ActiveProcessLimit;\n        public Int64 Affinity;\n        public UInt32 PriorityClass;\n        public UInt32 SchedulingClass;\n    }\n\n    [Flags]\n    // ReSharper disable once IdentifierTypo\n    internal enum JOBOBJECTLIMIT : uint\n    {\n        JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000,\n    }\n\n    [StructLayout(LayoutKind.Sequential)]\n    internal struct IO_COUNTERS\n    {\n        public UInt64 ReadOperationCount;\n        public UInt64 WriteOperationCount;\n        public UInt64 OtherOperationCount;\n        public UInt64 ReadTransferCount;\n        public UInt64 WriteTransferCount;\n        public UInt64 OtherTransferCount;\n    }\n\n    [StructLayout(LayoutKind.Sequential)]\n    // ReSharper disable once IdentifierTypo\n    internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION\n    {\n        public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;\n        public IO_COUNTERS IoInfo;\n        public UIntPtr ProcessMemoryLimit;\n        public UIntPtr JobMemoryLimit;\n        public UIntPtr PeakProcessMemoryUsed;\n        public UIntPtr PeakJobMemoryUsed;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/PropertyComparer.cs",
    "content": "﻿namespace StabilityMatrix.Core.Helper;\n\npublic class PropertyComparer<T> : IEqualityComparer<T> where T : class\n{\n    private Func<T, object> Expr { get; set; }\n    \n    public PropertyComparer(Func<T, object> expr)\n    {\n        Expr = expr;\n    }\n    public bool Equals(T? x, T? y)\n    {\n        if (x == null || y == null) return false;\n        \n        var first = Expr.Invoke(x);\n        var second = Expr.Invoke(y);\n        \n        return first.Equals(second);\n    }\n    public int GetHashCode(T obj)\n    {\n        return obj.GetHashCode();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/ReaderWriterLockAdvanced.cs",
    "content": "namespace StabilityMatrix.Core.Helper;\n\n/// <summary>\n/// Extended <see cref=\"ReaderWriterLockSlim\"/> with support for disposal pattern.\n/// </summary>\npublic class ReaderWriterLockAdvanced : ReaderWriterLockSlim\n{\n    public ReaderWriterLockAdvanced()\n    {\n    }\n    \n    public ReaderWriterLockAdvanced(LockRecursionPolicy recursionPolicy) : base(recursionPolicy)\n    {\n    }\n    \n    public DisposableLock EnterReadContext(TimeSpan timeout = default)\n    {\n        if (!TryEnterReadLock(timeout))\n        {\n            throw new TimeoutException(\"Timeout waiting for read lock\");\n        }\n        return new DisposableLock(this, LockType.Read);\n    }\n    \n    public DisposableLock EnterWriteContext(TimeSpan timeout = default)\n    {\n        if (!TryEnterWriteLock(timeout))\n        {\n            throw new TimeoutException(\"Timeout waiting for write lock\");\n        }\n        return new DisposableLock(this, LockType.Write);\n    }\n    \n    public DisposableLock EnterUpgradeableReadContext(TimeSpan timeout = default)\n    {\n        if (!TryEnterUpgradeableReadLock(timeout))\n        {\n            throw new TimeoutException(\"Timeout waiting for upgradeable read lock\");\n        }\n        return new DisposableLock(this, LockType.UpgradeableRead);\n    }\n}\n\n/// <summary>\n/// Wrapper for disposable lock\n/// </summary>\npublic class DisposableLock : IDisposable\n{\n    private readonly ReaderWriterLockAdvanced readerWriterLock;\n    private readonly LockType lockType;\n    \n    public DisposableLock(ReaderWriterLockAdvanced @lock, LockType lockType)\n    {\n        readerWriterLock = @lock;\n        this.lockType = lockType;\n    }\n\n    public DisposableLock UpgradeToWrite(TimeSpan timeout = default)\n    {\n        if (lockType != LockType.UpgradeableRead)\n        {\n            throw new InvalidOperationException(\"Can only upgrade from upgradeable read lock\");\n        }\n        return readerWriterLock.EnterWriteContext(timeout);\n    }\n\n    public void Dispose()\n    {\n        switch (lockType)\n        {\n            case LockType.Read:\n                readerWriterLock.ExitReadLock();\n                break;\n            case LockType.Write:\n                readerWriterLock.ExitWriteLock();\n                break;\n            case LockType.UpgradeableRead:\n                readerWriterLock.ExitUpgradeableReadLock();\n                break;\n            default:\n                throw new ArgumentOutOfRangeException();\n        }\n        GC.SuppressFinalize(this);\n    }\n}\n\npublic enum LockType\n{\n    Read,\n    Write,\n    UpgradeableRead\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/RemoteModels.cs",
    "content": "﻿using System.Collections.Immutable;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Core.Helper;\n\n/// <summary>\n/// Record of remote model resources\n/// </summary>\npublic static class RemoteModels\n{\n    /// <summary>\n    /// Root of huggingface upscalers\n    /// </summary>\n    private static Uri UpscalersRoot { get; } = new(\"https://huggingface.co/LykosAI/Upscalers/\");\n\n    /// <summary>\n    /// Root of huggingface upscalers at the main branch\n    /// </summary>\n    private static Uri UpscalersRootMain { get; } = UpscalersRoot.Append(\"blob/main/\");\n\n    public static IReadOnlyList<RemoteResource> Upscalers { get; } =\n        new RemoteResource[]\n        {\n            new()\n            {\n                Url = UpscalersRoot.Append(\n                    \"resolve/28a279ec60b5c47c9f39381dcaa997b9402ac09d/RealESRGAN/RealESRGAN_x2plus.pth\"\n                ),\n                HashSha256 = \"49fafd45f8fd7aa8d31ab2a22d14d91b536c34494a5cfe31eb5d89c2fa266abb\",\n                InfoUrl = new Uri(\"https://github.com/xinntao/ESRGAN\"),\n                Author = \"xinntao\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(UpscalersRootMain, \"RealESRGAN/LICENSE.txt\"),\n                ContextType = SharedFolderType.RealESRGAN\n            },\n            new()\n            {\n                Url = UpscalersRoot.Append(\n                    \"resolve/28a279ec60b5c47c9f39381dcaa997b9402ac09d/RealESRGAN/RealESRGAN_x2plus.pth\"\n                ),\n                HashSha256 = \"49fafd45f8fd7aa8d31ab2a22d14d91b536c34494a5cfe31eb5d89c2fa266abb\",\n                InfoUrl = new Uri(\"https://github.com/xinntao/ESRGAN\"),\n                Author = \"xinntao\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(UpscalersRootMain, \"RealESRGAN/LICENSE.txt\"),\n                ContextType = SharedFolderType.RealESRGAN\n            },\n            new()\n            {\n                Url = UpscalersRoot.Append(\n                    \"resolve/28a279ec60b5c47c9f39381dcaa997b9402ac09d/RealESRGAN/RealESRGAN_x4plus.pth\"\n                ),\n                HashSha256 = \"4fa0d38905f75ac06eb49a7951b426670021be3018265fd191d2125df9d682f1\",\n                InfoUrl = new Uri(\"https://github.com/xinntao/ESRGAN\"),\n                Author = \"xinntao\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(UpscalersRootMain, \"RealESRGAN/LICENSE.txt\"),\n                ContextType = SharedFolderType.RealESRGAN\n            },\n            new()\n            {\n                Url = UpscalersRoot.Append(\n                    \"resolve/28a279ec60b5c47c9f39381dcaa997b9402ac09d/RealESRGAN/RealESRGAN_x4plus_anime_6B.pth\"\n                ),\n                HashSha256 = \"f872d837d3c90ed2e05227bed711af5671a6fd1c9f7d7e91c911a61f155e99da\",\n                InfoUrl = new Uri(\"https://github.com/xinntao/ESRGAN\"),\n                Author = \"xinntao\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(UpscalersRootMain, \"RealESRGAN/LICENSE.txt\"),\n                ContextType = SharedFolderType.RealESRGAN\n            },\n            new()\n            {\n                Url = UpscalersRoot.Append(\n                    \"resolve/28a279ec60b5c47c9f39381dcaa997b9402ac09d/RealESRGAN/RealESRGAN_x4plus_anime_6B.pth\"\n                ),\n                HashSha256 = \"f872d837d3c90ed2e05227bed711af5671a6fd1c9f7d7e91c911a61f155e99da\",\n                InfoUrl = new Uri(\"https://github.com/xinntao/ESRGAN\"),\n                Author = \"xinntao\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(UpscalersRootMain, \"RealESRGAN/LICENSE.txt\"),\n                ContextType = SharedFolderType.RealESRGAN\n            },\n            new()\n            {\n                Url = UpscalersRoot.Append(\n                    \"resolve/4f813cc283de5cd66fe253bc05e66ca76bf68c51/SwinIR/SwinIR_4x.pth\"\n                ),\n                HashSha256 = \"99adfa91350a84c99e946c1eb3d8fce34bc28f57d807b09dc8fe40a316328c0a\",\n                InfoUrl = new Uri(\"https://github.com/JingyunLiang/SwinIR\"),\n                Author = \"JingyunLiang\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(UpscalersRootMain, \"SwinIR/LICENSE.txt\"),\n                ContextType = SharedFolderType.SwinIR\n            },\n            new()\n            {\n                Url = UpscalersRoot.Append(\n                    \"resolve/6a1d697dd18f8c4ff031f26e6dc523ada419517d/UltraMix/4x-UltraMix_Smooth.pth\"\n                ),\n                HashSha256 = \"7deeeac95ce7c28d616933b789f51642d169b200a6638edfb1c57ccecd903cd0\",\n                InfoUrl = new Uri(\"https://github.com/Kim2091\"),\n                Author = \"Kim2091\",\n                LicenseType = \"CC BY-NC-SA 4.0\",\n                LicenseUrl = new Uri(UpscalersRootMain, \"UltraMix/LICENSE.txt\"),\n                ContextType = SharedFolderType.ESRGAN\n            },\n            new()\n            {\n                Url = UpscalersRoot.Append(\n                    \"resolve/6a1d697dd18f8c4ff031f26e6dc523ada419517d/UltraMix/4x-UltraMix_Restore.pth\"\n                ),\n                HashSha256 = \"e8982b435557baeea5a08066aede41a9b3c8a6512c8688dab6d326e91ba82fa3\",\n                InfoUrl = new Uri(\"https://github.com/Kim2091\"),\n                Author = \"Kim2091\",\n                LicenseType = \"CC BY-NC-SA 4.0\",\n                LicenseUrl = new Uri(UpscalersRootMain, \"UltraMix/LICENSE.txt\"),\n                ContextType = SharedFolderType.ESRGAN\n            },\n            new()\n            {\n                Url = UpscalersRoot.Append(\n                    \"resolve/6a1d697dd18f8c4ff031f26e6dc523ada419517d/UltraMix/4x-UltraMix_Balanced.pth\"\n                ),\n                HashSha256 = \"e23ca000107aae95ec9b8d7d1bf150f7884f1361cd9d669bdf824d72529f0e26\",\n                InfoUrl = new Uri(\"https://github.com/Kim2091\"),\n                Author = \"Kim2091\",\n                LicenseType = \"CC BY-NC-SA 4.0\",\n                LicenseUrl = new Uri(UpscalersRootMain, \"UltraMix/LICENSE.txt\"),\n                ContextType = SharedFolderType.ESRGAN\n            }\n        };\n\n    private static Uri ControlNetRoot { get; } = new(\"https://huggingface.co/lllyasviel/ControlNet/\");\n\n    private static RemoteResource ControlNetCommon(string path, string sha256)\n    {\n        const string commit = \"38a62cbf79862c1bac73405ec8dc46133aee3e36\";\n\n        return new RemoteResource\n        {\n            Url = ControlNetRoot.Append($\"resolve/{commit}/\").Append(path),\n            HashSha256 = sha256,\n            InfoUrl = ControlNetRoot,\n            Author = \"lllyasviel\",\n            LicenseType = \"OpenRAIL\",\n            LicenseUrl = ControlNetRoot,\n            ContextType = SharedFolderType.ControlNet\n        };\n    }\n\n    public static IReadOnlyList<RemoteResource> ControlNets { get; } =\n        new[]\n        {\n            ControlNetCommon(\n                \"models/control_sd15_canny.pth\",\n                \"4de384b16bc2d7a1fb258ca0cbd941d7dd0a721ae996aff89f905299d6923f45\"\n            ),\n            ControlNetCommon(\n                \"models/control_sd15_depth.pth\",\n                \"726cd0b472c4b5c0341b01afcb7fdc4a7b4ab7c37fe797fd394c9805cbef60bf\"\n            ),\n            ControlNetCommon(\n                \"models/control_sd15_openpose.pth\",\n                \"d19ffffeeaff6d9feb2204b234c3e1b9aec039ab3e63fca07f4fe5646f2ef591\"\n            )\n        };\n\n    public static HybridModelFile ControlNetReferenceOnlyModel { get; } =\n        HybridModelFile.FromRemote(\"@ReferenceOnly\");\n\n    public static IReadOnlyList<HybridModelFile> ControlNetModels { get; } =\n        ControlNets\n            .Select(HybridModelFile.FromDownloadable)\n            .Concat([ControlNetReferenceOnlyModel])\n            .ToImmutableArray();\n\n    private static IEnumerable<RemoteResource> PromptExpansions =>\n        [\n            new RemoteResource\n            {\n                Url = new Uri(\"https://cdn.lykos.ai/models/GPT-Prompt-Expansion-Fooocus-v2.zip\"),\n                HashSha256 = \"82e69311787c0bb6736389710d80c0a2b653ed9bbe6ea6e70c6b90820fe42d88\",\n                InfoUrl = new Uri(\"https://huggingface.co/LykosAI/GPT-Prompt-Expansion-Fooocus-v2\"),\n                Author = \"lllyasviel, LykosAI\",\n                LicenseType = \"GPLv3\",\n                LicenseUrl = new Uri(\"https://github.com/lllyasviel/Fooocus/blob/main/LICENSE\"),\n                ContextType = SharedFolderType.PromptExpansion,\n                AutoExtractArchive = true,\n                ExtractRelativePath = \"GPT-Prompt-Expansion-Fooocus-v2\"\n            }\n        ];\n\n    public static IEnumerable<HybridModelFile> PromptExpansionModels =>\n        PromptExpansions.Select(HybridModelFile.FromDownloadable);\n\n    private static IEnumerable<RemoteResource> UltralyticsModels =>\n        [\n            new RemoteResource\n            {\n                Url = new Uri(\"https://huggingface.co/Bingsu/adetailer/resolve/main/face_yolov8m.pt\"),\n                HashSha256 = \"f02b8a23e6f12bd2c1b1f6714f66f984c728fa41ed749d033e7d6dea511ef70c\",\n                InfoUrl = new Uri(\"https://huggingface.co/Bingsu/adetailer\"),\n                Author = \"Bingsu\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(\n                    \"https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/apache-2.0.md\"\n                ),\n                ContextType = SharedFolderType.Ultralytics,\n                RelativeDirectory = \"bbox\"\n            },\n            new RemoteResource\n            {\n                Url = new Uri(\"https://huggingface.co/Bingsu/adetailer/resolve/main/hand_yolov8s.pt\"),\n                HashSha256 = \"5c4faf8d17286ace2c3d3346c6d0d4a0c8d62404955263a7ae95c1dd7eb877af\",\n                InfoUrl = new Uri(\"https://huggingface.co/Bingsu/adetailer\"),\n                Author = \"Bingsu\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(\n                    \"https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/apache-2.0.md\"\n                ),\n                ContextType = SharedFolderType.Ultralytics,\n                RelativeDirectory = \"bbox\"\n            },\n            new RemoteResource\n            {\n                Url = new Uri(\"https://huggingface.co/Bingsu/adetailer/resolve/main/person_yolov8m-seg.pt\"),\n                HashSha256 = \"9d881ec50b831f546e37977081b18f4e3bf65664aec163f97a311b0955499795\",\n                InfoUrl = new Uri(\"https://huggingface.co/Bingsu/adetailer\"),\n                Author = \"Bingsu\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(\n                    \"https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/apache-2.0.md\"\n                ),\n                ContextType = SharedFolderType.Ultralytics,\n                RelativeDirectory = \"segm\"\n            },\n            new RemoteResource\n            {\n                Url = new Uri(\"https://huggingface.co/Bingsu/adetailer/resolve/main/person_yolov8s-seg.pt\"),\n                HashSha256 = \"b5684835e79fd8b805459e0f7a0f9daa437e421cb4a214fff45ec4ac61767ef9\",\n                InfoUrl = new Uri(\"https://huggingface.co/Bingsu/adetailer\"),\n                Author = \"Bingsu\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(\n                    \"https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/apache-2.0.md\"\n                ),\n                ContextType = SharedFolderType.Ultralytics,\n                RelativeDirectory = \"segm\"\n            }\n        ];\n\n    public static IEnumerable<HybridModelFile> UltralyticsModelFiles =>\n        UltralyticsModels.Select(HybridModelFile.FromDownloadable);\n\n    private static IEnumerable<RemoteResource> SamModels =>\n        [\n            new RemoteResource\n            {\n                Url = new Uri(\"https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth\"),\n                InfoUrl = new Uri(\"https://github.com/facebookresearch/segment-anything\"),\n                Author = \"Facebook Research\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(\n                    \"https://github.com/facebookresearch/segment-anything/blob/main/LICENSE\"\n                ),\n                ContextType = SharedFolderType.Sams\n            },\n            new RemoteResource\n            {\n                Url = new Uri(\"https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth\"),\n                InfoUrl = new Uri(\"https://github.com/facebookresearch/segment-anything\"),\n                Author = \"Facebook Research\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(\n                    \"https://github.com/facebookresearch/segment-anything/blob/main/LICENSE\"\n                ),\n                ContextType = SharedFolderType.Sams\n            },\n            new RemoteResource\n            {\n                Url = new Uri(\"https://dl.fbaipublicfiles.com/segment_anything/sam_vit_l_0b3195.pth\"),\n                InfoUrl = new Uri(\"https://github.com/facebookresearch/segment-anything\"),\n                Author = \"Facebook Research\",\n                LicenseType = \"Apache 2.0\",\n                LicenseUrl = new Uri(\n                    \"https://github.com/facebookresearch/segment-anything/blob/main/LICENSE\"\n                ),\n                ContextType = SharedFolderType.Sams\n            }\n        ];\n\n    public static IEnumerable<HybridModelFile> SamModelFiles =>\n        SamModels.Select(HybridModelFile.FromDownloadable);\n\n    private static IEnumerable<RemoteResource> ClipModels =>\n        [\n            new()\n            {\n                Url = new Uri(\n                    \"https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/clip_l.safetensors\"\n                ),\n                InfoUrl = new Uri(\"https://huggingface.co/comfyanonymous/flux_text_encoders\"),\n                HashSha256 = \"660c6f5b1abae9dc498ac2d21e1347d2abdb0cf6c0c0c8576cd796491d9a6cdd\",\n                Author = \"OpenAI\",\n                LicenseType = \"MIT\",\n                ContextType = SharedFolderType.TextEncoders,\n            },\n            new()\n            {\n                Url = new Uri(\n                    \"https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp16.safetensors\"\n                ),\n                InfoUrl = new Uri(\"https://huggingface.co/comfyanonymous/flux_text_encoders\"),\n                HashSha256 = \"6e480b09fae049a72d2a8c5fbccb8d3e92febeb233bbe9dfe7256958a9167635\",\n                Author = \"Google\",\n                LicenseType = \"Apache 2.0\",\n                ContextType = SharedFolderType.TextEncoders,\n            },\n            new()\n            {\n                Url = new Uri(\n                    \"https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp8_e4m3fn.safetensors\"\n                ),\n                InfoUrl = new Uri(\"https://huggingface.co/comfyanonymous/flux_text_encoders\"),\n                HashSha256 = \"7d330da4816157540d6bb7838bf63a0f02f573fc48ca4d8de34bb0cbfd514f09\",\n                Author = \"Google\",\n                LicenseType = \"Apache 2.0\",\n                ContextType = SharedFolderType.TextEncoders,\n            },\n            new()\n            {\n                Url = new Uri(\n                    \"https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors\"\n                ),\n                InfoUrl = new Uri(\"https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged\"),\n                HashSha256 = \"c3355d30191f1f066b26d93fba017ae9809dce6c627dda5f6a66eaa651204f68\",\n                Author = \"Google\",\n                LicenseType = \"Apache 2.0\",\n                ContextType = SharedFolderType.TextEncoders,\n            },\n            new()\n            {\n                Url = new Uri(\n                    \"https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp16.safetensors\"\n                ),\n                InfoUrl = new Uri(\"https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged\"),\n                HashSha256 = \"7b8850f1961e1cf8a77cca4c964a358d303f490833c6c087d0cff4b2f99db2af\",\n                Author = \"Google\",\n                LicenseType = \"Apache 2.0\",\n                ContextType = SharedFolderType.TextEncoders,\n            },\n            new()\n            {\n                Url = new Uri(\n                    \"https://huggingface.co/Comfy-Org/HiDream-I1_ComfyUI/resolve/main/split_files/text_encoders/clip_l_hidream.safetensors\"\n                ),\n                InfoUrl = new Uri(\"https://huggingface.co/Comfy-Org/HiDream-I1_ComfyUI\"),\n                HashSha256 = \"706fdb88e22e18177b207837c02f4b86a652abca0302821f2bfa24ac6aea4f71\",\n                Author = \"OpenAI\",\n                LicenseType = \"MIT\",\n                ContextType = SharedFolderType.TextEncoders\n            },\n            new()\n            {\n                Url = new Uri(\n                    \"https://huggingface.co/Comfy-Org/HiDream-I1_ComfyUI/resolve/main/split_files/text_encoders/clip_g_hidream.safetensors\"\n                ),\n                InfoUrl = new Uri(\"https://huggingface.co/Comfy-Org/HiDream-I1_ComfyUI\"),\n                HashSha256 = \"3771e70e36450e5199f30bad61a53faae85a2e02606974bcda0a6a573c0519d5\",\n                Author = \"OpenAI\",\n                LicenseType = \"MIT\",\n                ContextType = SharedFolderType.TextEncoders\n            },\n            new()\n            {\n                Url = new Uri(\n                    \"https://huggingface.co/Comfy-Org/HiDream-I1_ComfyUI/resolve/main/split_files/text_encoders/llama_3.1_8b_instruct_fp8_scaled.safetensors\"\n                ),\n                InfoUrl = new Uri(\"https://huggingface.co/Comfy-Org/HiDream-I1_ComfyUI\"),\n                HashSha256 = \"9f86897bbeb933ef4fd06297740edb8dd962c94efcd92b373a11460c33765ea6\",\n                Author = \"Meta\",\n                LicenseType = \"llama3.1\",\n                ContextType = SharedFolderType.TextEncoders\n            }\n        ];\n\n    public static IEnumerable<HybridModelFile> ClipModelFiles =>\n        ClipModels.Select(HybridModelFile.FromDownloadable);\n\n    private static IEnumerable<RemoteResource> ClipVisionModels =>\n        [\n            new()\n            {\n                Url = new Uri(\n                    \"https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/clip_vision/clip_vision_h.safetensors\"\n                ),\n                InfoUrl = new Uri(\"https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged\"),\n                HashSha256 = \"64a7ef761bfccbadbaa3da77366aac4185a6c58fa5de5f589b42a65bcc21f161\",\n                Author = \"OpenAI\",\n                LicenseType = \"MIT\",\n                ContextType = SharedFolderType.ClipVision,\n            }\n        ];\n\n    public static IEnumerable<HybridModelFile> ClipVisionModelFiles =>\n        ClipVisionModels.Select(HybridModelFile.FromDownloadable);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/SharedFolders.cs",
    "content": "﻿using Injectio.Attributes;\nusing NLog;\nusing OneOf.Types;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.ReparsePoints;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Helper;\n\n[RegisterSingleton<ISharedFolders, SharedFolders>]\n[RegisterSingleton<IAsyncDisposable, SharedFolders>]\npublic class SharedFolders(ISettingsManager settingsManager, IPackageFactory packageFactory)\n    : ISharedFolders,\n        IAsyncDisposable\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    // mapping is old:new\n    private static readonly Dictionary<string, string> LegacySharedFolderMapping = new()\n    {\n        { \"CLIP\", \"TextEncoders\" },\n        { \"Unet\", \"DiffusionModels\" },\n        { \"InvokeClipVision\", \"ClipVision\" },\n        { \"InvokeIpAdapters15\", \"IpAdapters15\" },\n        { \"InvokeIpAdaptersXl\", \"IpAdaptersXl\" },\n        { \"TextualInversion\", \"Embeddings\" },\n    };\n\n    public bool IsDisposed { get; private set; }\n\n    /// <summary>\n    /// Platform redirect for junctions / symlinks\n    /// </summary>\n    private static void CreateLinkOrJunction(string junctionDir, string targetDir, bool overwrite)\n    {\n        if (Compat.IsWindows)\n        {\n            Junction.Create(junctionDir, targetDir, overwrite);\n        }\n        else\n        {\n            // Create parent directory if it doesn't exist, since CreateSymbolicLink doesn't seem to\n            new DirectoryPath(junctionDir).Parent?.Create();\n            Directory.CreateSymbolicLink(junctionDir, targetDir);\n        }\n    }\n\n    /// <summary>\n    /// Creates or updates junction link from the source to the destination.\n    /// Moves destination files to source if they exist.\n    /// </summary>\n    /// <param name=\"sourceDir\">Shared source (i.e. \"Models/\")</param>\n    /// <param name=\"destinationDir\">Destination (i.e. \"webui/models/lora\")</param>\n    /// <param name=\"overwrite\">Whether to overwrite the destination if it exists</param>\n    /// <param name=\"recursiveDelete\">Whether to recursively delete the directory after moving data out of it</param>\n    public static async Task CreateOrUpdateLink(\n        DirectoryPath sourceDir,\n        DirectoryPath destinationDir,\n        bool overwrite = false,\n        bool recursiveDelete = false\n    )\n    {\n        // Create source folder if it doesn't exist\n        if (!sourceDir.Exists)\n        {\n            Logger.Info($\"Creating junction source {sourceDir}\");\n            sourceDir.Create();\n        }\n\n        var destAsFile = new FilePath(destinationDir.ToString());\n        if (destAsFile.Exists)\n        {\n            await destAsFile.DeleteAsync().ConfigureAwait(false);\n        }\n\n        if (destinationDir.Exists)\n        {\n            // Existing dest is a link\n            if (destinationDir.IsSymbolicLink)\n            {\n                // If link is already the same, just skip\n                if (destinationDir.Info.LinkTarget == sourceDir)\n                {\n                    Logger.Info($\"Skipped updating matching folder link ({destinationDir} -> ({sourceDir})\");\n                    return;\n                }\n\n                // Otherwise delete the link\n                Logger.Info($\"Deleting existing junction at target {destinationDir}\");\n                destinationDir.Info.Attributes = FileAttributes.Normal;\n                await destinationDir.DeleteAsync(false).ConfigureAwait(false);\n            }\n            else\n            {\n                // Move all files if not empty\n                if (destinationDir.Info.EnumerateFileSystemInfos().Any())\n                {\n                    Logger.Info($\"Moving files from {destinationDir} to {sourceDir}\");\n                    await FileTransfers\n                        .MoveAllFilesAndDirectories(\n                            destinationDir,\n                            sourceDir,\n                            overwriteIfHashMatches: true,\n                            overwrite: overwrite,\n                            deleteSymlinks: true\n                        )\n                        .ConfigureAwait(false);\n                }\n\n                Logger.Info($\"Deleting existing empty folder at target {destinationDir}\");\n                await destinationDir.DeleteAsync(recursiveDelete).ConfigureAwait(false);\n            }\n        }\n\n        Logger.Info($\"Updating junction link from {sourceDir} to {destinationDir}\");\n        CreateLinkOrJunction(destinationDir, sourceDir, true);\n    }\n\n    [Obsolete(\"Use static methods instead\")]\n    public void SetupLinksForPackage(BasePackage basePackage, DirectoryPath installDirectory)\n    {\n        var modelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory);\n        var sharedFolders = basePackage.SharedFolders;\n        if (sharedFolders == null)\n            return;\n        UpdateLinksForPackage(sharedFolders, modelsDirectory, installDirectory).GetAwaiter().GetResult();\n    }\n\n    /// <summary>\n    /// Updates or creates shared links for a package.\n    /// Will attempt to move files from the destination to the source if the destination is not empty.\n    /// </summary>\n    public static async Task UpdateLinksForPackage<T>(\n        Dictionary<T, IReadOnlyList<string>> sharedFolders,\n        DirectoryPath modelsDirectory,\n        DirectoryPath installDirectory,\n        bool recursiveDelete = false\n    )\n        where T : Enum\n    {\n        foreach (var (folderType, relativePaths) in sharedFolders)\n        {\n            foreach (var relativePath in relativePaths)\n            {\n                var sourceDir = new DirectoryPath(modelsDirectory, folderType.GetStringValue());\n                var destinationDir = installDirectory.JoinDir(relativePath);\n\n                // Check and remove destinationDir parent if it's a link\n                if (destinationDir.Parent is { IsSymbolicLink: true } parentLink)\n                {\n                    Logger.Info(\"Deleting parent junction at target {Path}\", parentLink.ToString());\n\n                    await parentLink.DeleteAsync(false).ConfigureAwait(false);\n\n                    // Recreate\n                    parentLink.Create();\n                }\n\n                await CreateOrUpdateLink(sourceDir, destinationDir, recursiveDelete: recursiveDelete)\n                    .ConfigureAwait(false);\n            }\n        }\n    }\n\n    public static void RemoveLinksForPackage<T>(\n        Dictionary<T, IReadOnlyList<string>>? sharedFolders,\n        DirectoryPath installPath\n    )\n        where T : Enum\n    {\n        if (sharedFolders == null)\n        {\n            return;\n        }\n\n        foreach (var (_, relativePaths) in sharedFolders)\n        {\n            foreach (var relativePath in relativePaths)\n            {\n                var destination = Path.GetFullPath(Path.Combine(installPath, relativePath));\n                // Delete the destination folder if it exists\n                if (!Directory.Exists(destination))\n                    continue;\n\n                Logger.Info($\"Deleting junction target {destination}\");\n                Directory.Delete(destination, false);\n            }\n        }\n    }\n\n    public void RemoveLinksForAllPackages()\n    {\n        var packages = settingsManager.Settings.InstalledPackages;\n        foreach (var package in packages)\n        {\n            try\n            {\n                if (\n                    packageFactory.FindPackageByName(package.PackageName) is not { } basePackage\n                    || package.FullPath is null\n                )\n                {\n                    continue;\n                }\n\n                var sharedFolderMethod =\n                    package.PreferredSharedFolderMethod ?? basePackage.RecommendedSharedFolderMethod;\n                basePackage\n                    .RemoveModelFolderLinks(package.FullPath, sharedFolderMethod)\n                    .GetAwaiter()\n                    .GetResult();\n\n                // Remove output folder links if enabled\n                if (package.UseSharedOutputFolder)\n                {\n                    basePackage.RemoveOutputFolderLinks(package.FullPath).GetAwaiter().GetResult();\n                }\n            }\n            catch (Exception e)\n            {\n                Logger.Warn(\n                    \"Failed to remove links for package {Package} \" + \"({DisplayName}): {Message}\",\n                    package.PackageName,\n                    package.DisplayName,\n                    e.Message\n                );\n            }\n        }\n    }\n\n    public static void SetupSharedModelFolders(DirectoryPath rootModelsDir)\n    {\n        if (string.IsNullOrWhiteSpace(rootModelsDir))\n            return;\n\n        Directory.CreateDirectory(rootModelsDir);\n        var allSharedFolderTypes = Enum.GetValues<SharedFolderType>();\n        foreach (var sharedFolder in allSharedFolderTypes)\n        {\n            if (sharedFolder == SharedFolderType.Unknown)\n                continue;\n\n            var dir = new DirectoryPath(rootModelsDir, sharedFolder.GetStringValue());\n            dir.Create();\n\n            if (sharedFolder == SharedFolderType.Ultralytics)\n            {\n                var bboxDir = new DirectoryPath(dir, \"bbox\");\n                var segmDir = new DirectoryPath(dir, \"segm\");\n                bboxDir.Create();\n                segmDir.Create();\n            }\n        }\n\n        MigrateOldSharedFolderPaths(rootModelsDir);\n    }\n\n    private static void MigrateOldSharedFolderPaths(DirectoryPath rootModelsDir)\n    {\n        foreach (var (legacyFolderName, newFolderName) in LegacySharedFolderMapping)\n        {\n            var fullPath = rootModelsDir.JoinDir(legacyFolderName);\n            if (!fullPath.Exists)\n                continue;\n\n            foreach (var file in fullPath.EnumerateFiles(searchOption: SearchOption.AllDirectories))\n            {\n                var relativePath = file.RelativeTo(fullPath);\n                var newPath = rootModelsDir.JoinFile(newFolderName, relativePath);\n                newPath.Directory?.Create();\n                file.MoveTo(newPath);\n            }\n        }\n\n        // delete the old directories *only if they're empty*\n        foreach (\n            var fullPath in from legacyFolderName in LegacySharedFolderMapping.Keys\n            select rootModelsDir.JoinDir(legacyFolderName) into fullPath\n            where fullPath.Exists\n            where !fullPath.EnumerateFiles(searchOption: SearchOption.AllDirectories).Any()\n            select fullPath\n        )\n        {\n            fullPath.Delete(true);\n        }\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        if (IsDisposed)\n        {\n            return;\n        }\n\n        // Skip if library dir is not set or remove folder links on shutdown is disabled\n        if (!settingsManager.IsLibraryDirSet || !settingsManager.Settings.RemoveFolderLinksOnShutdown)\n        {\n            return;\n        }\n\n        // Remove all package junctions\n        Logger.Debug(\"SharedFolders Dispose: Removing package junctions\");\n\n        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));\n\n        try\n        {\n            await Task.Run(RemoveLinksForAllPackages, cts.Token).WaitAsync(cts.Token).ConfigureAwait(false);\n        }\n        catch (OperationCanceledException)\n        {\n            Logger.Warn(\"SharedFolders Dispose: Timeout removing package junctions\");\n        }\n        catch (Exception e)\n        {\n            Logger.Error(e, \"SharedFolders Dispose: Failed to remove package junctions\");\n        }\n\n        Logger.Debug(\"SharedFolders Dispose: Finished removing package junctions\");\n\n        IsDisposed = true;\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/SharedFoldersConfigHelper.cs",
    "content": "﻿using StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Packages.Config;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic static class SharedFoldersConfigHelper\n{\n    // Cache strategies to avoid repeated instantiation\n    private static readonly Dictionary<ConfigFileType, IConfigSharingStrategy> Strategies =\n        new()\n        {\n            { ConfigFileType.Json, new JsonConfigSharingStrategy() },\n            { ConfigFileType.Yaml, new YamlConfigSharingStrategy() },\n            { ConfigFileType.Fds, new FdsConfigSharingStrategy() }\n            // Add more strategies here as needed\n        };\n\n    /// <summary>\n    /// Updates a config file with shared folder layout rules, using the SourceTypes,\n    /// converted to absolute paths using the sharedModelsDirectory.\n    /// </summary>\n    public static async Task UpdateConfigFileForSharedAsync(\n        SharedFolderLayout layout,\n        string packageRootDirectory,\n        string sharedModelsDirectory,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var configPath = Path.Combine(\n            packageRootDirectory,\n            layout.RelativeConfigPath ?? throw new InvalidOperationException(\"RelativeConfigPath is null\")\n        );\n        Directory.CreateDirectory(Path.GetDirectoryName(configPath)!); // Ensure directory exists\n\n        // Using FileStream ensures we handle file locking and async correctly\n        await using var stream = new FileStream(\n            configPath,\n            FileMode.OpenOrCreate,\n            FileAccess.ReadWrite,\n            FileShare.None\n        );\n\n        await UpdateConfigFileForSharedAsync(\n                layout,\n                packageRootDirectory,\n                sharedModelsDirectory,\n                stream,\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n    }\n\n    /// <summary>\n    /// Updates a config file with shared folder layout rules, using the SourceTypes,\n    /// converted to absolute paths using the sharedModelsDirectory.\n    /// </summary>\n    public static async Task UpdateConfigFileForSharedAsync(\n        SharedFolderLayout layout,\n        string packageRootDirectory,\n        string sharedModelsDirectory,\n        Stream stream,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var fileType = layout.ConfigFileType ?? throw new InvalidOperationException(\"ConfigFileType is null\");\n        var options = layout.ConfigSharingOptions;\n\n        if (!Strategies.TryGetValue(fileType, out var strategy))\n        {\n            throw new NotSupportedException($\"Configuration file type '{fileType}' is not supported.\");\n        }\n\n        await strategy\n            .UpdateAndWriteAsync(\n                stream,\n                layout,\n                rule =>\n                {\n                    // Handle Root, just use models directory (e.g., Swarm)\n                    if (rule.IsRoot)\n                    {\n                        return [sharedModelsDirectory];\n                    }\n\n                    var paths = rule.SourceTypes.Select(type => type.GetStringValue()) // Get the enum string value (e.g., \"StableDiffusion\")\n                        .Where(folderName => !string.IsNullOrEmpty(folderName)) // Filter out potentially empty mappings\n                        .Select(folderName => Path.Combine(sharedModelsDirectory, folderName)); // Combine with base models dir\n\n                    // If sub-path provided, add to all paths\n                    if (!string.IsNullOrEmpty(rule.SourceSubPath))\n                    {\n                        paths = paths.Select(path => Path.Combine(path, rule.SourceSubPath));\n                    }\n\n                    return paths;\n                },\n                [],\n                options,\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n    }\n\n    /// <summary>\n    /// Updates a config file with shared folder layout rules, using the TargetRelativePaths,\n    /// converted to absolute paths using the packageRootDirectory (restores default paths).\n    /// </summary>\n    public static async Task UpdateConfigFileForDefaultAsync(\n        SharedFolderLayout layout,\n        string packageRootDirectory,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var configPath = Path.Combine(\n            packageRootDirectory,\n            layout.RelativeConfigPath ?? throw new InvalidOperationException(\"RelativeConfigPath is null\")\n        );\n        Directory.CreateDirectory(Path.GetDirectoryName(configPath)!); // Ensure directory exists\n\n        // Using FileStream ensures we handle file locking and async correctly\n        await using var stream = new FileStream(\n            configPath,\n            FileMode.OpenOrCreate,\n            FileAccess.ReadWrite,\n            FileShare.None\n        );\n\n        await UpdateConfigFileForDefaultAsync(layout, packageRootDirectory, stream, cancellationToken)\n            .ConfigureAwait(false);\n    }\n\n    /// <summary>\n    /// Updates a config file with shared folder layout rules, using the TargetRelativePaths,\n    /// converted to absolute paths using the packageRootDirectory (restores default paths).\n    /// </summary>\n    public static async Task UpdateConfigFileForDefaultAsync(\n        SharedFolderLayout layout,\n        string packageRootDirectory,\n        Stream stream,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var fileType = layout.ConfigFileType ?? throw new InvalidOperationException(\"ConfigFileType is null\");\n        var options = layout.ConfigSharingOptions;\n\n        if (!Strategies.TryGetValue(fileType, out var strategy))\n        {\n            throw new NotSupportedException($\"Configuration file type '{fileType}' is not supported.\");\n        }\n\n        var clearPaths = new List<string>();\n\n        // If using clear root option, add the root key\n        if (options.ConfigDefaultType is ConfigDefaultType.ClearRoot)\n        {\n            clearPaths.Add(options.RootKey ?? \"\");\n        }\n\n        await strategy\n            .UpdateAndWriteAsync(\n                stream,\n                layout,\n                rule =>\n                    rule.TargetRelativePaths.Select(\n                        path => Path.Combine(packageRootDirectory, NormalizePathSlashes(path))\n                    ), // Combine relative with package root\n                clearPaths,\n                options,\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n    }\n\n    // Keep path normalization logic accessible if needed elsewhere, or inline it if only used here.\n    private static string NormalizePathSlashes(string path)\n    {\n        // Replace forward slashes with backslashes on Windows, otherwise use forward slashes.\n        return path.Replace(Compat.IsWindows ? '/' : '\\\\', Path.DirectorySeparatorChar);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/SharedFoldersConfigOptions.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic class SharedFoldersConfigOptions\n{\n    public static SharedFoldersConfigOptions Default => new();\n\n    public bool AlwaysWriteArray { get; set; } = false;\n\n    public JsonSerializerOptions JsonSerializerOptions { get; set; } =\n        new() { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault };\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Size.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\n\nnamespace StabilityMatrix.Core.Helper;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic static class Size\n{\n    public const ulong KiB = 1024;\n    public const ulong MiB = KiB * 1024;\n    public const ulong GiB = MiB * 1024;\n\n    private static string TrimZero(string value)\n    {\n        return value.TrimEnd('0').TrimEnd('.');\n    }\n\n    public static string FormatBytes(ulong bytes, bool trimZero = false)\n    {\n        return bytes switch\n        {\n            < KiB => $\"{bytes:0} Bytes\",\n            < MiB\n                => (trimZero ? $\"{bytes / (double)KiB:0.0}\".TrimEnd('0').TrimEnd('.') : $\"{bytes / (double)KiB:0.0}\")\n                    + \" KiB\",\n            < GiB\n                => (trimZero ? $\"{bytes / (double)MiB:0.0}\".TrimEnd('0').TrimEnd('.') : $\"{bytes / (double)MiB:0.0}\")\n                    + \" MiB\",\n            _\n                => (trimZero ? $\"{bytes / (double)GiB:0.0}\".TrimEnd('0').TrimEnd('.') : $\"{bytes / (double)GiB:0.0}\")\n                    + \" GiB\"\n        };\n    }\n\n    public static string FormatBase10Bytes(ulong bytes, bool trimZero = false)\n    {\n        return bytes switch\n        {\n            < KiB => $\"{bytes:0} Bytes\",\n            < MiB\n                => (trimZero ? $\"{bytes / (double)KiB:0.0}\".TrimEnd('0').TrimEnd('.') : $\"{bytes / (double)KiB:0.0}\")\n                    + \" KB\",\n            < GiB\n                => (trimZero ? $\"{bytes / (double)MiB:0.0}\".TrimEnd('0').TrimEnd('.') : $\"{bytes / (double)MiB:0.0}\")\n                    + \" MB\",\n            _\n                => (trimZero ? $\"{bytes / (double)GiB:0.00}\".TrimEnd('0').TrimEnd('.') : $\"{bytes / (double)GiB:0.00}\")\n                    + \" GB\"\n        };\n    }\n\n    public static string FormatBase10Bytes(long bytes)\n    {\n        return FormatBase10Bytes(Convert.ToUInt64(bytes));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/SystemInfo.cs",
    "content": "﻿using System.Runtime.InteropServices;\nusing NLog;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic static class SystemInfo\n{\n    public const long Gibibyte = 1024 * 1024 * 1024;\n    public const long Mebibyte = 1024 * 1024;\n\n    [DllImport(\"UXTheme.dll\", SetLastError = true, EntryPoint = \"#138\")]\n    public static extern bool ShouldUseDarkMode();\n\n    public static long? GetDiskFreeSpaceBytes(string path)\n    {\n        try\n        {\n            var drive = new DriveInfo(path);\n            return drive.AvailableFreeSpace;\n        }\n        catch (Exception e)\n        {\n            LogManager.GetCurrentClassLogger().Error(e);\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Utilities.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Reflection;\nusing System.Text.RegularExpressions;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Core.Helper;\n\npublic static partial class Utilities\n{\n    public static string GetAppVersion()\n    {\n        var assembly = Assembly.GetExecutingAssembly();\n        var version = assembly.GetName().Version;\n        return version == null\n            ? \"(Unknown)\"\n            : $\"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}\";\n    }\n\n    public static void CopyDirectory(\n        string sourceDir,\n        string destinationDir,\n        bool recursive,\n        bool includeReparsePoints = false\n    )\n    {\n        // Get information about the source directory\n        var dir = new DirectoryInfo(sourceDir);\n\n        // Check if the source directory exists\n        if (!dir.Exists)\n            throw new DirectoryNotFoundException($\"Source directory not found: {dir.FullName}\");\n\n        // Cache directories before we start copying\n        var dirs = includeReparsePoints\n            ? dir.GetDirectories()\n            : dir.GetDirectories().Where(d => !d.Attributes.HasFlag(FileAttributes.ReparsePoint));\n\n        // Create the destination directory\n        Directory.CreateDirectory(destinationDir);\n\n        // Get the files in the source directory and copy to the destination directory\n        foreach (var file in dir.GetFiles())\n        {\n            var targetFilePath = Path.Combine(destinationDir, file.Name);\n            if (file.FullName == targetFilePath)\n                continue;\n            file.CopyTo(targetFilePath, true);\n        }\n\n        if (!recursive)\n            return;\n\n        // If recursive and copying subdirectories, recursively call this method\n        foreach (var subDir in dirs)\n        {\n            var newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n            CopyDirectory(subDir.FullName, newDestinationDir, true);\n        }\n    }\n\n    public static MemoryStream? GetMemoryStreamFromFile(string filePath)\n    {\n        var fileBytes = File.ReadAllBytes(filePath);\n        var stream = new MemoryStream(fileBytes);\n        stream.Position = 0;\n\n        return stream;\n    }\n\n    public static async Task<string> WhichAsync(string arg)\n    {\n        using var process = new Process();\n        process.StartInfo.FileName = Compat.IsWindows ? \"where.exe\" : \"which\";\n        process.StartInfo.Arguments = arg;\n        process.StartInfo.UseShellExecute = false;\n        process.StartInfo.CreateNoWindow = true;\n        process.StartInfo.RedirectStandardOutput = true;\n\n        process.Start();\n        await process.WaitForExitAsync().ConfigureAwait(false);\n\n        return await process.StandardOutput.ReadToEndAsync().ConfigureAwait(false);\n    }\n\n    public static int GetNumDaysTilBeginningOfNextMonth()\n    {\n        var now = DateTimeOffset.UtcNow;\n        var firstDayOfNextMonth = new DateTime(now.Year, now.Month, 1).AddMonths(1);\n        var daysUntilNextMonth = (firstDayOfNextMonth - now).Days;\n        return daysUntilNextMonth;\n    }\n\n    public static string RemoveHtml(string? stringWithHtml)\n    {\n        var pruned =\n            stringWithHtml\n                ?.Replace(\"<br/>\", $\"{Environment.NewLine}{Environment.NewLine}\")\n                .Replace(\"<br />\", $\"{Environment.NewLine}{Environment.NewLine}\")\n                .Replace(\"</p>\", $\"{Environment.NewLine}{Environment.NewLine}\")\n                .Replace(\"</h1>\", $\"{Environment.NewLine}{Environment.NewLine}\")\n                .Replace(\"</h2>\", $\"{Environment.NewLine}{Environment.NewLine}\")\n                .Replace(\"</h3>\", $\"{Environment.NewLine}{Environment.NewLine}\")\n                .Replace(\"</h4>\", $\"{Environment.NewLine}{Environment.NewLine}\")\n                .Replace(\"</h5>\", $\"{Environment.NewLine}{Environment.NewLine}\")\n                .Replace(\"</h6>\", $\"{Environment.NewLine}{Environment.NewLine}\") ?? string.Empty;\n        pruned = HtmlRegex().Replace(pruned, string.Empty);\n        return pruned;\n    }\n\n    /// <summary>\n    /// Try to find pyvenv.cfg in common locations and parse its version into PyVersion.\n    /// </summary>\n    public static bool TryGetPyVenvVersion(string packageLocation, out PyVersion version)\n    {\n        version = default;\n\n        if (string.IsNullOrWhiteSpace(packageLocation))\n            return false;\n\n        // Common placements\n        var candidates = new[]\n        {\n            Path.Combine(packageLocation, \"pyvenv.cfg\"),\n            Path.Combine(packageLocation, \"venv\", \"pyvenv.cfg\"),\n            Path.Combine(packageLocation, \".venv\", \"pyvenv.cfg\"),\n        };\n\n        foreach (var candidate in candidates)\n        {\n            if (!File.Exists(candidate))\n                continue;\n\n            if (!TryReadVersionFromCfg(candidate, out var pyv))\n                continue;\n\n            version = pyv;\n            return true;\n        }\n\n        return false;\n    }\n\n    private static bool TryReadVersionFromCfg(string cfgFile, out PyVersion version)\n    {\n        version = default;\n\n        var kv = ReadKeyValues(cfgFile);\n\n        // Prefer version_info (e.g., \"3.10.11.final.0\"), fall back to version (e.g., \"3.12.0\")\n        if (!kv.TryGetValue(\"version_info\", out var raw) || string.IsNullOrWhiteSpace(raw))\n            kv.TryGetValue(\"version\", out raw);\n\n        if (string.IsNullOrWhiteSpace(raw))\n            return false;\n\n        // Grab the first 1–3 dot-separated integers and ignore anything after (e.g., \".final.0\", \".rc1\")\n        // Examples matched: \"3\", \"3.12\", \"3.10.11\" (from \"3.10.11.final.0\")\n        var m = Regex.Match(raw, @\"(?<!\\d)(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?\");\n        if (!m.Success)\n            return false;\n\n        var major = int.Parse(m.Groups[1].Value);\n        var minor = m.Groups[2].Success ? int.Parse(m.Groups[2].Value) : 0;\n        var micro = m.Groups[3].Success ? int.Parse(m.Groups[3].Value) : 0;\n\n        version = new PyVersion(major, minor, micro);\n        return true;\n    }\n\n    private static Dictionary<string, string> ReadKeyValues(string path)\n    {\n        var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);\n        foreach (var line in File.ReadAllLines(path))\n        {\n            var trimmed = line.Trim();\n            if (trimmed.Length == 0 || trimmed.StartsWith(\"#\"))\n                continue;\n\n            var idx = trimmed.IndexOf('=');\n            if (idx <= 0)\n                continue;\n\n            var key = trimmed[..idx].Trim();\n            var val = trimmed[(idx + 1)..].Trim();\n            dict[key] = val;\n        }\n        return dict;\n    }\n\n    /// <summary>\n    /// Returns the simplified aspect ratio as a tuple: (widthRatio, heightRatio).\n    /// e.g. GetAspectRatio(1920,1080) -> (16,9)\n    /// </summary>\n    public static (int widthRatio, int heightRatio) GetAspectRatio(int width, int height)\n    {\n        if (width <= 0 || height <= 0)\n            throw new ArgumentException(\"Width and height must be positive.\");\n\n        var gcd = Gcd(width, height);\n        return (width / gcd, height / gcd);\n    }\n\n    // Euclidean GCD\n    private static int Gcd(int a, int b)\n    {\n        a = Math.Abs(a);\n        b = Math.Abs(b);\n        while (b != 0)\n        {\n            var rem = a % b;\n            a = b;\n            b = rem;\n        }\n        return a;\n    }\n\n    [GeneratedRegex(\"<[^>]+>\")]\n    private static partial Regex HtmlRegex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Helper/Webp/WebpReader.cs",
    "content": "﻿using System.Text;\n\nnamespace StabilityMatrix.Core.Helper.Webp;\n\npublic class WebpReader(Stream stream) : BinaryReader(stream, Encoding.ASCII, true)\n{\n    private uint headerFileSize;\n\n    public bool GetIsAnimatedFlag()\n    {\n        ReadHeader();\n\n        while (BaseStream.Position < headerFileSize)\n        {\n            if (ReadVoidChunk() is \"ANMF\" or \"ANIM\")\n            {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    private void ReadHeader()\n    {\n        // RIFF\n        var riff = ReadBytes(4);\n        if (!riff.SequenceEqual([..\"RIFF\"u8]))\n        {\n            throw new InvalidDataException(\"Invalid RIFF header\");\n        }\n\n        // Size: uint32\n        headerFileSize = ReadUInt32();\n\n        // WEBP\n        var webp = ReadBytes(4);\n        if (!webp.SequenceEqual([..\"WEBP\"u8]))\n        {\n            throw new InvalidDataException(\"Invalid WEBP header\");\n        }\n    }\n\n    // Read a single chunk and discard its contents\n    private string ReadVoidChunk()\n    {\n        // FourCC: 4 bytes in ASCII\n        var result = ReadBytes(4);\n\n        // Size: uint32\n        var size = ReadUInt32();\n\n        BaseStream.Seek(size, SeekOrigin.Current);\n\n        return Encoding.ASCII.GetString(result);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Inference/ComfyClient.cs",
    "content": "﻿using System.Collections.Concurrent;\nusing System.Collections.Immutable;\nusing System.Net.WebSockets;\nusing System.Text.Json;\nusing NLog;\nusing Polly.Contrib.WaitAndRetry;\nusing Refit;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Converters.Json;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Comfy;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\nusing StabilityMatrix.Core.Models.Api.Comfy.WebSocketData;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing Websocket.Client;\nusing Websocket.Client.Exceptions;\nusing Yoh.Text.Json.NamingPolicies;\n\nnamespace StabilityMatrix.Core.Inference;\n\npublic class ComfyClient : InferenceClientBase\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private readonly WebsocketClient webSocketClient;\n    private readonly IComfyApi comfyApi;\n    private bool isDisposed;\n\n    private readonly JsonSerializerOptions jsonSerializerOptions =\n        new()\n        {\n            PropertyNamingPolicy = JsonNamingPolicies.SnakeCaseLower,\n            Converters =\n            {\n                new NodeConnectionBaseJsonConverter(),\n                new OneOfJsonConverter<string, StringNodeConnection>()\n            }\n        };\n\n    // ReSharper disable once MemberCanBePrivate.Global\n    public string ClientId { get; } = Guid.NewGuid().ToString();\n\n    public Uri BaseAddress { get; }\n\n    /// <summary>\n    /// If available, the local path to the server root directory.\n    /// </summary>\n    public DirectoryPath? LocalServerPath { get; set; }\n\n    /// <summary>\n    /// If available, the local server package pair\n    /// </summary>\n    public PackagePair? LocalServerPackage { get; set; }\n\n    /// <summary>\n    /// Path to the \"output\" folder from LocalServerPath\n    /// </summary>\n    public DirectoryPath? OutputImagesDir => LocalServerPath?.JoinDir(\"output\");\n\n    /// <summary>\n    /// Path to the \"input\" folder from LocalServerPath\n    /// </summary>\n    public DirectoryPath? InputImagesDir => LocalServerPath?.JoinDir(\"input\");\n\n    /// <summary>\n    /// Dictionary of ongoing prompt execution tasks\n    /// </summary>\n    public ConcurrentDictionary<string, ComfyTask> PromptTasks { get; } = new();\n\n    /// <summary>\n    /// Current running prompt task\n    /// </summary>\n    private ComfyTask? currentPromptTask;\n\n    /// <summary>\n    /// Event raised when a progress update is received from the server\n    /// </summary>\n    public event EventHandler<ComfyWebSocketProgressData>? ProgressUpdateReceived;\n\n    /// <summary>\n    /// Event raised when a status update is received from the server\n    /// </summary>\n    public event EventHandler<ComfyWebSocketStatusData>? StatusUpdateReceived;\n\n    /// <summary>\n    /// Event raised when a executing update is received from the server\n    /// </summary>\n    public event EventHandler<ComfyWebSocketExecutingData>? ExecutingUpdateReceived;\n\n    /// <summary>\n    /// Event raised when a preview image is received from the server\n    /// </summary>\n    public event EventHandler<ComfyWebSocketImageData>? PreviewImageReceived;\n\n    public ComfyClient(IApiFactory apiFactory, Uri baseAddress)\n    {\n        comfyApi = apiFactory.CreateRefitClient<IComfyApi>(\n            baseAddress,\n            new RefitSettings\n            {\n                ContentSerializer = new SystemTextJsonContentSerializer(jsonSerializerOptions),\n            }\n        );\n        BaseAddress = baseAddress;\n\n        // Setup websocket client\n        var wsUri = new UriBuilder(baseAddress)\n        {\n            Scheme = \"ws\",\n            Path = \"/ws\",\n            Query = $\"clientId={ClientId}\"\n        }.Uri;\n\n        webSocketClient = new WebsocketClient(wsUri)\n        {\n            Name = nameof(ComfyClient),\n            ReconnectTimeout = TimeSpan.FromSeconds(30)\n        };\n\n        webSocketClient.DisconnectionHappened.Subscribe(\n            info => Logger.Info(\"Websocket Disconnected, ({Type})\", info.Type)\n        );\n        webSocketClient.ReconnectionHappened.Subscribe(\n            info => Logger.Info(\"Websocket Reconnected, ({Type})\", info.Type)\n        );\n\n        webSocketClient.MessageReceived.Subscribe(OnMessageReceived);\n    }\n\n    private void OnMessageReceived(ResponseMessage message)\n    {\n        switch (message.MessageType)\n        {\n            case WebSocketMessageType.Text:\n                HandleTextMessage(message.Text);\n                break;\n            case WebSocketMessageType.Binary:\n                HandleBinaryMessage(message.Binary);\n                break;\n            case WebSocketMessageType.Close:\n                Logger.Trace(\"Received ws close message: {Text}\", message.Text);\n                break;\n            default:\n                throw new ArgumentOutOfRangeException(nameof(message));\n        }\n    }\n\n    private void HandleTextMessage(string text)\n    {\n        ComfyWebSocketResponse? json;\n        try\n        {\n            json = JsonSerializer.Deserialize<ComfyWebSocketResponse>(text, jsonSerializerOptions);\n        }\n        catch (JsonException e)\n        {\n            Logger.Warn($\"Failed to parse json {text} ({e}), skipping\");\n            return;\n        }\n\n        if (json is null)\n        {\n            Logger.Warn($\"Could not parse json {text}, skipping\");\n            return;\n        }\n\n        Logger.Trace(\"Received json message: (Type = {Type}, Data = {Data})\", json.Type, json.Data);\n\n        if (json.Type == ComfyWebSocketResponseType.Executing)\n        {\n            var executingData = json.GetDataAsType<ComfyWebSocketExecutingData>(jsonSerializerOptions);\n            if (executingData?.PromptId is null)\n            {\n                Logger.Warn($\"Could not parse executing data {json.Data}, skipping\");\n                return;\n            }\n\n            // When Node property is null, it means the prompt has finished executing\n            // remove the task from the dictionary and set the result\n            if (executingData.Node is null)\n            {\n                if (PromptTasks.TryRemove(executingData.PromptId, out var task))\n                {\n                    task.RunningNode = null;\n                    task.SetResult();\n                    currentPromptTask = null;\n                }\n                else\n                {\n                    Logger.Warn($\"Could not find task for prompt {executingData.PromptId}, skipping\");\n                }\n            }\n            // Otherwise set the task's active node to the one received\n            else\n            {\n                if (PromptTasks.TryGetValue(executingData.PromptId, out var task))\n                {\n                    task.RunningNode = executingData.Node;\n                }\n            }\n\n            ExecutingUpdateReceived?.Invoke(this, executingData);\n        }\n        else if (json.Type == ComfyWebSocketResponseType.Status)\n        {\n            var statusData = json.GetDataAsType<ComfyWebSocketStatusData>(jsonSerializerOptions);\n            if (statusData is null)\n            {\n                Logger.Warn($\"Could not parse status data {json.Data}, skipping\");\n                return;\n            }\n\n            StatusUpdateReceived?.Invoke(this, statusData);\n        }\n        else if (json.Type == ComfyWebSocketResponseType.Progress)\n        {\n            var progressData = json.GetDataAsType<ComfyWebSocketProgressData>(jsonSerializerOptions);\n            if (progressData is null)\n            {\n                Logger.Warn($\"Could not parse progress data {json.Data}, skipping\");\n                return;\n            }\n\n            // Set for the current prompt task\n            currentPromptTask?.OnProgressUpdate(progressData);\n\n            ProgressUpdateReceived?.Invoke(this, progressData);\n        }\n        else if (json.Type == ComfyWebSocketResponseType.ExecutionError)\n        {\n            if (\n                json.GetDataAsType<ComfyWebSocketExecutionErrorData>(jsonSerializerOptions)\n                is not { } errorData\n            )\n            {\n                Logger.Warn($\"Could not parse ExecutionError data {json.Data}, skipping\");\n                return;\n            }\n\n            // Set error status\n            if (PromptTasks.TryRemove(errorData.PromptId, out var task))\n            {\n                task.RunningNode = null;\n                task.SetException(\n                    new ComfyNodeException { ErrorData = errorData, JsonData = json.Data.ToString() }\n                );\n                currentPromptTask = null;\n            }\n            else\n            {\n                Logger.Warn($\"Could not find task for prompt {errorData.PromptId}, skipping\");\n            }\n        }\n        else\n        {\n            Logger.Warn($\"Unknown message type {json.Type} ({json.Data}), skipping\");\n        }\n    }\n\n    /// <summary>\n    /// Parses binary data (previews) into image streams\n    /// https://github.com/comfyanonymous/ComfyUI/blob/master/server.py#L518\n    /// </summary>\n    private void HandleBinaryMessage(byte[] data)\n    {\n        if (data is not { Length: > 4 })\n        {\n            Logger.Warn(\"The input data is null or not long enough.\");\n            return;\n        }\n\n        // The first 4 bytes is int32 of the message type\n        // Subsequent 4 bytes following is int32 of the image format\n        // The rest is the image data\n\n        // Read the image type from the first 4 bytes of the data.\n        // Python's struct.pack(\">I\", type_num) will pack the data as a big-endian unsigned int\n        /*var typeBytes = new byte[4];\n        stream.Read(typeBytes, 0, 4);\n        var imageType = BitConverter.ToInt32(typeBytes, 0);*/\n\n        /*if (!BitConverter.IsLittleEndian)\n        {\n            Array.Reverse(typeBytes);\n        }*/\n\n        PreviewImageReceived?.Invoke(this, new ComfyWebSocketImageData { ImageBytes = data[8..], });\n    }\n\n    public override async Task ConnectAsync(CancellationToken cancellationToken = default)\n    {\n        var delays = Backoff\n            .DecorrelatedJitterBackoffV2(TimeSpan.FromMilliseconds(500), retryCount: 5)\n            .ToImmutableArray();\n\n        foreach (var (i, retryDelay) in delays.Enumerate())\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            try\n            {\n                await webSocketClient.StartOrFail().ConfigureAwait(false);\n                return;\n            }\n            catch (WebsocketException e)\n            {\n                Logger.Info(\n                    \"Failed to connect to websocket, retrying in {RetryDelay} ({Message})\",\n                    retryDelay,\n                    e.Message\n                );\n\n                if (i == delays.Length - 1)\n                {\n                    throw;\n                }\n            }\n        }\n    }\n\n    public override async Task CloseAsync(CancellationToken cancellationToken = default)\n    {\n        await webSocketClient.Stop(WebSocketCloseStatus.NormalClosure, string.Empty).ConfigureAwait(false);\n    }\n\n    public async Task<ComfyTask> QueuePromptAsync(\n        Dictionary<string, ComfyNode> nodes,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var request = new ComfyPromptRequest { ClientId = ClientId, Prompt = nodes };\n        var result = await comfyApi.PostPrompt(request, cancellationToken).ConfigureAwait(false);\n\n        // Add task to dictionary and set it as the current task\n        var task = new ComfyTask(result.PromptId);\n        PromptTasks.TryAdd(result.PromptId, task);\n        currentPromptTask = task;\n\n        return task;\n    }\n\n    public async Task InterruptPromptAsync(CancellationToken cancellationToken = default)\n    {\n        await comfyApi.PostInterrupt(cancellationToken).ConfigureAwait(false);\n\n        // Set the current task to null, and remove it from the dictionary\n        if (currentPromptTask is { } task)\n        {\n            PromptTasks.TryRemove(task.Id, out _);\n            task.TrySetCanceled(cancellationToken);\n            task.Dispose();\n            currentPromptTask = null;\n        }\n    }\n\n    // Upload images\n    public Task<ComfyUploadImageResponse> UploadImageAsync(\n        Stream image,\n        string fileName,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var streamPart = new StreamPart(image, fileName);\n        return comfyApi.PostUploadImage(streamPart, \"true\", \"input\", \"Inference\", cancellationToken);\n    }\n\n    /// <summary>\n    /// Upload a file to the server at the given relative path from server's root\n    /// </summary>\n    public async Task UploadFileAsync(\n        string sourcePath,\n        string destinationRelativePath,\n        CancellationToken cancellationToken = default\n    )\n    {\n        cancellationToken.ThrowIfCancellationRequested();\n\n        // Currently there is no api, so we do a local file copy\n        if (LocalServerPath is null)\n        {\n            throw new InvalidOperationException(\"LocalServerPath is not set\");\n        }\n\n        var sourceFile = new FilePath(sourcePath);\n        var destFile = LocalServerPath.JoinFile(destinationRelativePath);\n\n        Logger.Info(\"Copying file from {Source} to {Dest}\", sourcePath, destFile);\n\n        if (!sourceFile.Exists)\n        {\n            throw new FileNotFoundException(\"Source file does not exist\", sourcePath);\n        }\n\n        destFile.Directory?.Create();\n\n        await sourceFile.CopyToAsync(destFile, true).ConfigureAwait(false);\n    }\n\n    public async Task<Dictionary<string, List<ComfyImage>?>> GetImagesForExecutedPromptAsync(\n        string promptId,\n        CancellationToken cancellationToken = default\n    )\n    {\n        // Get history for images\n        var history = await comfyApi.GetHistory(promptId, cancellationToken).ConfigureAwait(false);\n\n        // Get the current prompt history\n        var current = history[promptId];\n\n        var dict = new Dictionary<string, List<ComfyImage>?>();\n        foreach (var (nodeKey, output) in current.Outputs)\n        {\n            dict[nodeKey] = output.Images;\n        }\n        return dict;\n    }\n\n    public async Task<Stream> GetImageStreamAsync(\n        ComfyImage comfyImage,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var response = await comfyApi\n            .GetImage(comfyImage.FileName, comfyImage.SubFolder, comfyImage.Type, cancellationToken)\n            .ConfigureAwait(false);\n        return response;\n    }\n\n    /// <summary>\n    /// Get a list of strings representing available model names\n    /// </summary>\n    public Task<List<string>?> GetModelNamesAsync(CancellationToken cancellationToken = default)\n    {\n        return GetNodeOptionNamesAsync(\"CheckpointLoaderSimple\", \"ckpt_name\", cancellationToken);\n    }\n\n    /// <summary>\n    /// Get a list of strings representing available sampler names\n    /// </summary>\n    public Task<List<string>?> GetSamplerNamesAsync(CancellationToken cancellationToken = default)\n    {\n        return GetNodeOptionNamesAsync(\"KSampler\", \"sampler_name\", cancellationToken);\n    }\n\n    /// <summary>\n    /// Get a list of strings representing available options of a given node\n    /// </summary>\n    public async Task<List<string>?> GetNodeOptionNamesAsync(\n        string nodeName,\n        string optionName,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var response = await comfyApi.GetObjectInfo(nodeName, cancellationToken).ConfigureAwait(false);\n\n        var info = response[nodeName];\n        return info.Input.GetRequiredValueAsNestedList(optionName);\n    }\n\n    /// <summary>\n    /// Get a list of strings representing available optional options of a given node\n    /// </summary>\n    public async Task<List<string>?> GetOptionalNodeOptionNamesAsync(\n        string nodeName,\n        string optionName,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var response = await comfyApi.GetObjectInfo(nodeName, cancellationToken).ConfigureAwait(false);\n\n        var info = response.GetValueOrDefault(nodeName);\n\n        return info?.Input.GetOptionalValueAsNestedList(optionName);\n    }\n\n    public async Task<List<string>?> GetRequiredNodeOptionNamesFromOptionalNodeAsync(\n        string nodeName,\n        string optionName,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var response = await comfyApi.GetObjectInfo(nodeName, cancellationToken).ConfigureAwait(false);\n\n        var info = response.GetValueOrDefault(nodeName);\n\n        return info?.Input.GetRequiredValueAsNestedList(optionName);\n    }\n\n    protected override void Dispose(bool disposing)\n    {\n        if (isDisposed)\n            return;\n        webSocketClient.Dispose();\n        isDisposed = true;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Inference/ComfyProgressUpdateEventArgs.cs",
    "content": "﻿namespace StabilityMatrix.Core.Inference;\n\npublic readonly record struct ComfyProgressUpdateEventArgs(\n    int Value, \n    int Maximum, \n    string? TaskId,\n    string? RunningNode);\n"
  },
  {
    "path": "StabilityMatrix.Core/Inference/ComfyTask.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Api.Comfy.WebSocketData;\n\nnamespace StabilityMatrix.Core.Inference;\n\npublic class ComfyTask : TaskCompletionSource, IDisposable\n{\n    public string Id { get; }\n\n    private string? runningNode;\n    public string? RunningNode\n    {\n        get => runningNode;\n        set\n        {\n            runningNode = value;\n            RunningNodeChanged?.Invoke(this, value);\n            if (value != null)\n            {\n                RunningNodesHistory.Push(value);\n            }\n        }\n    }\n\n    public Stack<string> RunningNodesHistory { get; } = new();\n\n    public ComfyProgressUpdateEventArgs? LastProgressUpdate { get; private set; }\n\n    public bool HasProgressUpdateStarted => LastProgressUpdate != null;\n\n    public EventHandler<ComfyProgressUpdateEventArgs>? ProgressUpdate;\n\n    public event EventHandler<string?>? RunningNodeChanged;\n\n    public ComfyTask(string id)\n    {\n        Id = id;\n    }\n\n    /// <summary>\n    /// Handler for progress updates\n    /// </summary>\n    public void OnProgressUpdate(ComfyWebSocketProgressData update)\n    {\n        RunningNodesHistory.TryPeek(out var lastNode);\n        var args = new ComfyProgressUpdateEventArgs(update.Value, update.Max, Id, lastNode);\n        ProgressUpdate?.Invoke(this, args);\n        LastProgressUpdate = args;\n    }\n\n    /// <inheritdoc />\n    public void Dispose()\n    {\n        ProgressUpdate = null;\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Inference/InferenceClientBase.cs",
    "content": "﻿namespace StabilityMatrix.Core.Inference;\n\npublic abstract class InferenceClientBase : IDisposable\n{\n    /// <summary>\n    /// Start the connection\n    /// </summary>\n    public virtual Task ConnectAsync(CancellationToken cancellationToken = default)\n    {\n        return Task.CompletedTask;\n    }\n\n    /// <summary>\n    /// Close the connection to remote resources\n    /// </summary>\n    public virtual Task CloseAsync(CancellationToken cancellationToken = default)\n    {\n        return Task.CompletedTask;\n    }\n    \n    protected virtual void Dispose(bool disposing)\n    {\n    }\n\n    public void Dispose()\n    {\n        Dispose(true);\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/A3Options.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\n[SuppressMessage(\"ReSharper\", \"IdentifierTypo\")]\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\npublic class A3Options\n{\n    [JsonPropertyName(\"samples_save\")]\n    public bool? SamplesSave { get; set; }\n\n    [JsonPropertyName(\"samples_format\")]\n    public string? SamplesFormat { get; set; }\n\n    [JsonPropertyName(\"samples_filename_pattern\")]\n    public string? SamplesFilenamePattern { get; set; }\n\n    [JsonPropertyName(\"save_images_add_number\")]\n    public bool? SaveImagesAddNumber { get; set; }\n\n    [JsonPropertyName(\"grid_save\")]\n    public bool? GridSave { get; set; }\n\n    [JsonPropertyName(\"grid_format\")]\n    public string? GridFormat { get; set; }\n\n    [JsonPropertyName(\"grid_extended_filename\")]\n    public bool? GridExtendedFilename { get; set; }\n\n    [JsonPropertyName(\"grid_only_if_multiple\")]\n    public bool? GridOnlyIfMultiple { get; set; }\n\n    [JsonPropertyName(\"grid_prevent_empty_spots\")]\n    public bool? GridPreventEmptySpots { get; set; }\n\n    [JsonPropertyName(\"grid_zip_filename_pattern\")]\n    public string? GridZipFilenamePattern { get; set; }\n\n    [JsonPropertyName(\"n_rows\")]\n    public int? NRows { get; set; }\n\n    [JsonPropertyName(\"enable_pnginfo\")]\n    public bool? EnablePnginfo { get; set; }\n\n    [JsonPropertyName(\"save_txt\")]\n    public bool? SaveTxt { get; set; }\n\n    [JsonPropertyName(\"save_images_before_face_restoration\")]\n    public bool? SaveImagesBeforeFaceRestoration { get; set; }\n\n    [JsonPropertyName(\"save_images_before_highres_fix\")]\n    public bool? SaveImagesBeforeHighresFix { get; set; }\n\n    [JsonPropertyName(\"save_images_before_color_correction\")]\n    public bool? SaveImagesBeforeColorCorrection { get; set; }\n\n    [JsonPropertyName(\"save_mask\")]\n    public bool? SaveMask { get; set; }\n\n    [JsonPropertyName(\"save_mask_composite\")]\n    public bool? SaveMaskComposite { get; set; }\n\n    [JsonPropertyName(\"jpeg_quality\")]\n    public int? JpegQuality { get; set; }\n\n    [JsonPropertyName(\"webp_lossless\")]\n    public bool? WebpLossless { get; set; }\n\n    [JsonPropertyName(\"export_for_4chan\")]\n    public bool? ExportFor4chan { get; set; }\n\n    [JsonPropertyName(\"img_downscale_threshold\")]\n    public int? ImgDownscaleThreshold { get; set; }\n\n    [JsonPropertyName(\"target_side_length\")]\n    public int? TargetSideLength { get; set; }\n\n    [JsonPropertyName(\"img_max_size_mp\")]\n    public int? ImgMaxSizeMp { get; set; }\n\n    [JsonPropertyName(\"use_original_name_batch\")]\n    public bool? UseOriginalNameBatch { get; set; }\n\n    [JsonPropertyName(\"use_upscaler_name_as_suffix\")]\n    public bool? UseUpscalerNameAsSuffix { get; set; }\n\n    [JsonPropertyName(\"save_selected_only\")]\n    public bool? SaveSelectedOnly { get; set; }\n\n    [JsonPropertyName(\"save_init_img\")]\n    public bool? SaveInitImg { get; set; }\n\n    [JsonPropertyName(\"temp_dir\")]\n    public string? TempDir { get; set; }\n\n    [JsonPropertyName(\"clean_temp_dir_at_start\")]\n    public bool? CleanTempDirAtStart { get; set; }\n\n    [JsonPropertyName(\"outdir_samples\")]\n    public string? OutdirSamples { get; set; }\n\n    [JsonPropertyName(\"outdir_txt2img_samples\")]\n    public string? OutdirTxt2ImgSamples { get; set; }\n\n    [JsonPropertyName(\"outdir_img2img_samples\")]\n    public string? OutdirImg2ImgSamples { get; set; }\n\n    [JsonPropertyName(\"outdir_extras_samples\")]\n    public string? OutdirExtrasSamples { get; set; }\n\n    [JsonPropertyName(\"outdir_grids\")]\n    public string? OutdirGrids { get; set; }\n\n    [JsonPropertyName(\"outdir_txt2img_grids\")]\n    public string? OutdirTxt2ImgGrids { get; set; }\n\n    [JsonPropertyName(\"outdir_img2img_grids\")]\n    public string? OutdirImg2ImgGrids { get; set; }\n\n    [JsonPropertyName(\"outdir_save\")]\n    public string? OutdirSave { get; set; }\n\n    [JsonPropertyName(\"outdir_init_images\")]\n    public string? OutdirInitImages { get; set; }\n\n    [JsonPropertyName(\"save_to_dirs\")]\n    public bool? SaveToDirs { get; set; }\n\n    [JsonPropertyName(\"grid_save_to_dirs\")]\n    public bool? GridSaveToDirs { get; set; }\n\n    [JsonPropertyName(\"use_save_to_dirs_for_ui\")]\n    public bool? UseSaveToDirsForUi { get; set; }\n\n    [JsonPropertyName(\"directories_filename_pattern\")]\n    public string? DirectoriesFilenamePattern { get; set; }\n\n    [JsonPropertyName(\"directories_max_prompt_words\")]\n    public int? DirectoriesMaxPromptWords { get; set; }\n\n    [JsonPropertyName(\"ESRGAN_tile\")]\n    public int? ESRGANTile { get; set; }\n\n    [JsonPropertyName(\"ESRGAN_tile_overlap\")]\n    public int? ESRGANTileOverlap { get; set; }\n\n    [JsonPropertyName(\"realesrgan_enabled_models\")]\n    public List<string>? RealEsrganEnabledModels { get; set; }\n\n    [JsonPropertyName(\"upscaler_for_img2img\")]\n    public string? UpscalerForImg2img { get; set; }\n\n    [JsonPropertyName(\"face_restoration_model\")]\n    public string? FaceRestorationModel { get; set; }\n\n    [JsonPropertyName(\"code_former_weight\")]\n    public double? CodeFormerWeight { get; set; }\n\n    [JsonPropertyName(\"face_restoration_unload\")]\n    public bool? FaceRestorationUnload { get; set; }\n\n    [JsonPropertyName(\"show_warnings\")]\n    public bool? ShowWarnings { get; set; }\n\n    [JsonPropertyName(\"memmon_poll_rate\")]\n    public int? MemmonPollRate { get; set; }\n\n    [JsonPropertyName(\"samples_log_stdout\")]\n    public bool? SamplesLogStdout { get; set; }\n\n    [JsonPropertyName(\"multiple_tqdm\")]\n    public bool? MultipleTqdm { get; set; }\n\n    [JsonPropertyName(\"print_hypernet_extra\")]\n    public bool? PrintHypernetExtra { get; set; }\n\n    [JsonPropertyName(\"list_hidden_files\")]\n    public bool? ListHiddenFiles { get; set; }\n\n    [JsonPropertyName(\"unload_models_when_training\")]\n    public bool? UnloadModelsWhenTraining { get; set; }\n\n    [JsonPropertyName(\"pin_memory\")]\n    public bool? PinMemory { get; set; }\n\n    [JsonPropertyName(\"save_optimizer_state\")]\n    public bool? SaveOptimizerState { get; set; }\n\n    [JsonPropertyName(\"save_training_settings_to_txt\")]\n    public bool? SaveTrainingSettingsToTxt { get; set; }\n\n    [JsonPropertyName(\"dataset_filename_word_regex\")]\n    public string? DatasetFilenameWordRegex { get; set; }\n\n    [JsonPropertyName(\"dataset_filename_join_string\")]\n    public string? DatasetFilenameJoinString { get; set; }\n\n    [JsonPropertyName(\"training_image_repeats_per_epoch\")]\n    public int? TrainingImageRepeatsPerEpoch { get; set; }\n\n    [JsonPropertyName(\"training_write_csv_every\")]\n    public int? TrainingWriteCsvEvery { get; set; }\n\n    [JsonPropertyName(\"training_xattention_optimizations\")]\n    public bool? TrainingXattentionOptimizations { get; set; }\n\n    [JsonPropertyName(\"training_enable_tensorboard\")]\n    public bool? TrainingEnableTensorboard { get; set; }\n\n    [JsonPropertyName(\"training_tensorboard_save_images\")]\n    public bool? TrainingTensorboardSaveImages { get; set; }\n\n    [JsonPropertyName(\"training_tensorboard_flush_every\")]\n    public int? TrainingTensorboardFlushEvery { get; set; }\n\n    [JsonPropertyName(\"sd_model_checkpoint\")]\n    public string? SdModelCheckpoint { get; set; }\n\n    [JsonPropertyName(\"sd_checkpoint_cache\")]\n    public int? SdCheckpointCache { get; set; }\n\n    [JsonPropertyName(\"sd_vae_checkpoint_cache\")]\n    public int? SdVaeCheckpointCache { get; set; }\n\n    [JsonPropertyName(\"sd_vae\")]\n    public string? SdVae { get; set; }\n\n    [JsonPropertyName(\"sd_vae_as_default\")]\n    public bool? SdVaeAsDefault { get; set; }\n\n    [JsonPropertyName(\"sd_unet\")]\n    public string? SdUnet { get; set; }\n\n    [JsonPropertyName(\"inpainting_mask_weight\")]\n    public int? InpaintingMaskWeight { get; set; }\n\n    [JsonPropertyName(\"initial_noise_multiplier\")]\n    public int? InitialNoiseMultiplier { get; set; }\n\n    [JsonPropertyName(\"img2img_color_correction\")]\n    public bool? Img2imgColorCorrection { get; set; }\n\n    [JsonPropertyName(\"img2img_fix_steps\")]\n    public bool? Img2imgFixSteps { get; set; }\n\n    [JsonPropertyName(\"img2img_background_color\")]\n    public string? Img2ImgBackgroundColor { get; set; }\n\n    [JsonPropertyName(\"enable_quantization\")]\n    public bool? EnableQuantization { get; set; }\n\n    [JsonPropertyName(\"enable_emphasis\")]\n    public bool? EnableEmphasis { get; set; }\n\n    [JsonPropertyName(\"enable_batch_seeds\")]\n    public bool? EnableBatchSeeds { get; set; }\n\n    [JsonPropertyName(\"comma_padding_backtrack\")]\n    public int? CommaPaddingBacktrack { get; set; }\n\n    [JsonPropertyName(\"CLIP_stop_at_last_layers\")]\n    public int? CLIPStopAtLastLayers { get; set; }\n\n    [JsonPropertyName(\"upcast_attn\")]\n    public bool? UpcastAttn { get; set; }\n\n    [JsonPropertyName(\"randn_source\")]\n    public string? RandNSource { get; set; }\n\n    [JsonPropertyName(\"cross_attention_optimization\")]\n    public string? CrossAttentionOptimization { get; set; }\n\n    [JsonPropertyName(\"s_min_uncond\")]\n    public int? SMinUncond { get; set; }\n\n    [JsonPropertyName(\"token_merging_ratio\")]\n    public int? TokenMergingRatio { get; set; }\n\n    [JsonPropertyName(\"token_merging_ratio_img2img\")]\n    public int? TokenMergingRatioImg2Img { get; set; }\n\n    [JsonPropertyName(\"token_merging_ratio_hr\")]\n    public int? TokenMergingRatioHr { get; set; }\n\n    [JsonPropertyName(\"pad_cond_uncond\")]\n    public bool? PadCondUncond { get; set; }\n\n    [JsonPropertyName(\"experimental_persistent_cond_cache\")]\n    public bool? ExperimentalPersistentCondCache { get; set; }\n\n    [JsonPropertyName(\"use_old_emphasis_implementation\")]\n    public bool? UseOldEmphasisImplementation { get; set; }\n\n    [JsonPropertyName(\"use_old_karras_scheduler_sigmas\")]\n    public bool? UseOldKarrasSchedulerSigmas { get; set; }\n\n    [JsonPropertyName(\"no_dpmpp_sde_batch_determinism\")]\n    public bool? NoDpmppSdeBatchDeterminism { get; set; }\n\n    [JsonPropertyName(\"use_old_hires_fix_width_height\")]\n    public bool? UseOldHiresFixWidthHeight { get; set; }\n\n    [JsonPropertyName(\"dont_fix_second_order_samplers_schedule\")]\n    public bool? DontFixSecondOrderSamplersSchedule { get; set; }\n\n    [JsonPropertyName(\"hires_fix_use_firstpass_conds\")]\n    public bool? HiresFixUseFirstpassConds { get; set; }\n\n    [JsonPropertyName(\"interrogate_keep_models_in_memory\")]\n    public bool? InterrogateKeepModelsInMemory { get; set; }\n\n    [JsonPropertyName(\"interrogate_return_ranks\")]\n    public bool? InterrogateReturnRanks { get; set; }\n\n    [JsonPropertyName(\"interrogate_clip_num_beams\")]\n    public int? InterrogateClipNumBeams { get; set; }\n\n    [JsonPropertyName(\"interrogate_clip_min_length\")]\n    public int? InterrogateClipMinLength { get; set; }\n\n    [JsonPropertyName(\"interrogate_clip_max_length\")]\n    public int? InterrogateClipMaxLength { get; set; }\n\n    [JsonPropertyName(\"interrogate_clip_dict_limit\")]\n    public int? InterrogateClipDictLimit { get; set; }\n\n    [JsonPropertyName(\"interrogate_clip_skip_categories\")]\n    public List<string>? InterrogateClipSkipCategories { get; set; }\n\n    [JsonPropertyName(\"interrogate_deepbooru_score_threshold\")]\n    public double? InterrogateDeepbooruScoreThreshold { get; set; }\n\n    [JsonPropertyName(\"deepbooru_sort_alpha\")]\n    public bool? DeepbooruSortAlpha { get; set; }\n\n    [JsonPropertyName(\"deepbooru_use_spaces\")]\n    public bool? DeepbooruUseSpaces { get; set; }\n\n    [JsonPropertyName(\"deepbooru_escape\")]\n    public bool? DeepbooruEscape { get; set; }\n\n    [JsonPropertyName(\"deepbooru_filter_tags\")]\n    public string? DeepbooruFilterTags { get; set; }\n\n    [JsonPropertyName(\"extra_networks_show_hidden_directories\")]\n    public bool? ExtraNetworksShowHiddenDirectories { get; set; }\n\n    [JsonPropertyName(\"extra_networks_hidden_models\")]\n    public string? ExtraNetworksHiddenModels { get; set; }\n\n    [JsonPropertyName(\"extra_networks_default_view\")]\n    public string? ExtraNetworksDefaultView { get; set; }\n\n    [JsonPropertyName(\"extra_networks_default_multiplier\")]\n    public int? ExtraNetworksDefaultMultiplier { get; set; }\n\n    [JsonPropertyName(\"extra_networks_card_width\")]\n    public int? ExtraNetworksCardWidth { get; set; }\n\n    [JsonPropertyName(\"extra_networks_card_height\")]\n    public int? ExtraNetworksCardHeight { get; set; }\n\n    [JsonPropertyName(\"extra_networks_add_text_separator\")]\n    public string? ExtraNetworksAddTextSeparator { get; set; }\n\n    [JsonPropertyName(\"ui_extra_networks_tab_reorder\")]\n    public string? UiExtraNetworksTabReorder { get; set; }\n\n    [JsonPropertyName(\"sd_hypernetwork\")]\n    public string? SdHypernetwork { get; set; }\n\n    [JsonPropertyName(\"localization\")]\n    public string? Localization { get; set; }\n\n    [JsonPropertyName(\"gradio_theme\")]\n    public string? GradioTheme { get; set; }\n\n    [JsonPropertyName(\"img2img_editor_height\")]\n    public int? Img2ImgEditorHeight { get; set; }\n\n    [JsonPropertyName(\"return_grid\")]\n    public bool? ReturnGrid { get; set; }\n\n    [JsonPropertyName(\"return_mask\")]\n    public bool? ReturnMask { get; set; }\n\n    [JsonPropertyName(\"return_mask_composite\")]\n    public bool? ReturnMaskComposite { get; set; }\n\n    [JsonPropertyName(\"do_not_show_images\")]\n    public bool? DoNotShowImages { get; set; }\n\n    [JsonPropertyName(\"send_seed\")]\n    public bool? SendSeed { get; set; }\n\n    [JsonPropertyName(\"send_size\")]\n    public bool? SendSize { get; set; }\n\n    [JsonPropertyName(\"font\")]\n    public string? Font { get; set; }\n\n    [JsonPropertyName(\"js_modal_lightbox\")]\n    public bool? JsModalLightbox { get; set; }\n\n    [JsonPropertyName(\"js_modal_lightbox_initially_zoomed\")]\n    public bool? JsModalLightboxInitiallyZoomed { get; set; }\n\n    [JsonPropertyName(\"js_modal_lightbox_gamepad\")]\n    public bool? JsModalLightboxGamepad { get; set; }\n\n    [JsonPropertyName(\"js_modal_lightbox_gamepad_repeat\")]\n    public int? JsModalLightboxGamepadRepeat { get; set; }\n\n    [JsonPropertyName(\"show_progress_in_title\")]\n    public bool? ShowProgressInTitle { get; set; }\n\n    [JsonPropertyName(\"samplers_in_dropdown\")]\n    public bool? SamplersInDropdown { get; set; }\n\n    [JsonPropertyName(\"dimensions_and_batch_together\")]\n    public bool? DimensionsAndBatchTogether { get; set; }\n\n    [JsonPropertyName(\"keyedit_precision_attention\")]\n    public double? KeyEditPrecisionAttention { get; set; }\n\n    [JsonPropertyName(\"keyedit_precision_extra\")]\n    public double? KeyEditPrecisionExtra { get; set; }\n\n    [JsonPropertyName(\"keyedit_delimiters\")]\n    public string? KeyEditDelimiters { get; set; }\n\n    [JsonPropertyName(\"hires_fix_show_sampler\")]\n    public bool? HiresFixShowSampler { get; set; }\n\n    [JsonPropertyName(\"hires_fix_show_prompts\")]\n    public bool? HiresFixShowPrompts { get; set; }\n\n    [JsonPropertyName(\"disable_token_counters\")]\n    public bool? DisableTokenCounters { get; set; }\n\n    [JsonPropertyName(\"add_model_hash_to_info\")]\n    public bool? AddModelHashToInfo { get; set; }\n\n    [JsonPropertyName(\"add_model_name_to_info\")]\n    public bool? AddModelNameToInfo { get; set; }\n\n    [JsonPropertyName(\"add_version_to_infotext\")]\n    public bool? AddVersionToInfotext { get; set; }\n\n    [JsonPropertyName(\"disable_weights_auto_swap\")]\n    public bool? DisableWeightsAutoSwap { get; set; }\n\n    [JsonPropertyName(\"infotext_styles\")]\n    public string? InfotextStyles { get; set; }\n\n    [JsonPropertyName(\"show_progressbar\")]\n    public bool? ShowProgressbar { get; set; }\n\n    [JsonPropertyName(\"live_previews_enable\")]\n    public bool? LivePreviewsEnable { get; set; }\n\n    [JsonPropertyName(\"live_previews_image_format\")]\n    public string? LivePreviewsImageFormat { get; set; }\n\n    [JsonPropertyName(\"show_progress_grid\")]\n    public bool? ShowProgressGrid { get; set; }\n\n    [JsonPropertyName(\"show_progress_every_n_steps\")]\n    public int? ShowProgressEveryNSteps { get; set; }\n\n    [JsonPropertyName(\"show_progress_type\")]\n    public string? ShowProgressType { get; set; }\n\n    [JsonPropertyName(\"live_preview_content\")]\n    public string? LivePreviewContent { get; set; }\n\n    [JsonPropertyName(\"live_preview_refresh_period\")]\n    public int? LivePreviewRefreshPeriod { get; set; }\n\n    // TODO: hide_samplers\n\n    [JsonPropertyName(\"eta_ddim\")]\n    public int? EtaDdim { get; set; }\n\n    [JsonPropertyName(\"eta_ancestral\")]\n    public int? EtaAncestral { get; set; }\n\n    [JsonPropertyName(\"ddim_discretize\")]\n    public string? DDIMDiscretize { get; set; }\n\n    [JsonPropertyName(\"s_churn\")]\n    public int? SChurn { get; set; }\n\n    [JsonPropertyName(\"s_tmin\")]\n    public int? STmin { get; set; }\n\n    [JsonPropertyName(\"s_noise\")]\n    public int? SNoise { get; set; }\n\n    [JsonPropertyName(\"k_sched_type\")]\n    public string? KSchedType { get; set; }\n\n    [JsonPropertyName(\"sigma_min\")]\n    public int? SigmaMin { get; set; }\n\n    [JsonPropertyName(\"sigma_max\")]\n    public int? SigmaMax { get; set; }\n\n    [JsonPropertyName(\"rho\")]\n    public int? Rho { get; set; }\n\n    [JsonPropertyName(\"eta_noise_seed_delta\")]\n    public int? EtaNoiseSeedDelta { get; set; }\n\n    [JsonPropertyName(\"always_discard_next_to_last_sigma\")]\n    public bool? AlwaysDiscardNextToLastSigma { get; set; }\n\n    [JsonPropertyName(\"uni_pc_variant\")]\n    public string? UniPcVariant { get; set; }\n\n    [JsonPropertyName(\"uni_pc_skip_type\")]\n    public string? UniPcSkipType { get; set; }\n\n    [JsonPropertyName(\"uni_pc_order\")]\n    public int? UniPcOrder { get; set; }\n\n    [JsonPropertyName(\"uni_pc_lower_order_final\")]\n    public bool? UniPcLowerOrderFinal { get; set; }\n\n    // TODO: postprocessing_enable_in_main_ui\n    // TODO: postprocessing_operation_order\n\n    [JsonPropertyName(\"upscaling_max_images_in_cache\")]\n    public int? UpscalingMaxImagesInCache { get; set; }\n\n    [JsonPropertyName(\"disabled_extensions\")]\n    public List<string>? DisabledExtensions { get; set; }\n\n    [JsonPropertyName(\"disable_all_extensions\")]\n    public string? DisableAllExtensions { get; set; }\n\n    [JsonPropertyName(\"restore_config_state_file\")]\n    public string? RestoreConfigStateFile { get; set; }\n\n    [JsonPropertyName(\"sd_checkpoint_hash\")]\n    public string? SdCheckpointHash { get; set; }\n\n    [JsonPropertyName(\"sd_lora\")]\n    public string? SdLora { get; set; }\n\n    [JsonPropertyName(\"lora_preferred_name\")]\n    public string? LoraPreferredName { get; set; }\n\n    [JsonPropertyName(\"lora_add_hashes_to_infotext\")]\n    public bool? LoraAddHashesToInfotext { get; set; }\n\n    [JsonPropertyName(\"lora_functional\")]\n    public bool? LoraFunctional { get; set; }\n\n    [JsonPropertyName(\"canvas_hotkey_move\")]\n    public string? CanvasHotkeyMove { get; set; }\n\n    [JsonPropertyName(\"canvas_hotkey_fullscreen\")]\n    public string? CanvasHotkeyFullscreen { get; set; }\n\n    [JsonPropertyName(\"canvas_hotkey_reset\")]\n    public string? CanvasHotkeyReset { get; set; }\n\n    [JsonPropertyName(\"canvas_hotkey_overlap\")]\n    public string? CanvasHotkeyOverlap { get; set; }\n\n    [JsonPropertyName(\"canvas_show_tooltip\")]\n    public bool? CanvasShowTooltip { get; set; }\n\n    [JsonPropertyName(\"canvas_swap_controls\")]\n    public bool? CanvasSwapControls { get; set; }\n    \n    // TODO: List<object> ExtraOptions\n\n    [JsonPropertyName(\"extra_options_accordion\")]\n    public bool? ExtraOptionsAccordion { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitAccountStatusUpdateEventArgs.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Api.CivitTRPC;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic class CivitAccountStatusUpdateEventArgs : EventArgs\n{\n    public static CivitAccountStatusUpdateEventArgs Disconnected { get; } = new();\n\n    public bool IsConnected { get; init; }\n\n    public CivitUserProfileResponse? UserProfile { get; init; }\n\n    public string? UsernameWithParentheses =>\n        string.IsNullOrEmpty(UserProfile?.Username) ? null : $\"({UserProfile.Username})\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitBaseModelType.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\n[JsonConverter(typeof(JsonStringEnumConverter<CivitBaseModelType>))]\npublic enum CivitBaseModelType\n{\n    All,\n\n    [StringValue(\"AuraFlow\")]\n    AuraFlow,\n\n    [StringValue(\"CogVideoX\")]\n    CogVideoX,\n\n    [StringValue(\"Flux.1 S\")]\n    Flux1S,\n\n    [StringValue(\"Flux.1 D\")]\n    Flux1D,\n\n    [StringValue(\"HiDream\")]\n    HiDream,\n\n    [StringValue(\"Hunyuan 1\")]\n    Hunyuan1,\n\n    [StringValue(\"Hunyuan Video\")]\n    HunyuanVideo,\n\n    [StringValue(\"Illustrious\")]\n    Illustrious,\n\n    [StringValue(\"Kolors\")]\n    Kolors,\n\n    [StringValue(\"LTXV\")]\n    Ltxv,\n\n    [StringValue(\"Lumina\")]\n    Lumina,\n\n    [StringValue(\"Mochi\")]\n    Mochi,\n\n    [StringValue(\"NoobAI\")]\n    NoobAi,\n\n    [StringValue(\"ODOR\")]\n    Odor,\n\n    [StringValue(\"PixArt a\")]\n    PixArtA,\n\n    [StringValue(\"PixArt E\")]\n    PixArtE,\n\n    [StringValue(\"Playground v2\")]\n    PlaygroundV2,\n\n    [StringValue(\"Pony\")]\n    Pony,\n\n    [StringValue(\"SD 1.4\")]\n    Sd14,\n\n    [StringValue(\"SD 1.5\")]\n    Sd15,\n\n    [StringValue(\"SD 1.5 Hyper\")]\n    Sd15Hyper,\n\n    [StringValue(\"SD 1.5 LCM\")]\n    Sd15Lcm,\n\n    [StringValue(\"SD 2.0\")]\n    Sd20,\n\n    [StringValue(\"SD 2.0 768\")]\n    Sd20_768,\n\n    [StringValue(\"SD 2.1\")]\n    Sd21,\n\n    [StringValue(\"SD 2.1 768\")]\n    Sd21_768,\n\n    [StringValue(\"SD 2.1 Unclip\")]\n    Sd21Unclip,\n\n    [StringValue(\"SD 3\")]\n    Sd3,\n\n    [StringValue(\"SD 3.5\")]\n    Sd35,\n\n    [StringValue(\"SD 3.5 Large\")]\n    Sd35Large,\n\n    [StringValue(\"SD 3.5 Large Turbo\")]\n    Sd35LargeTurbo,\n\n    [StringValue(\"SD 3.5 Medium\")]\n    Sd35Medium,\n\n    [StringValue(\"SDXL 0.9\")]\n    Sdxl09,\n\n    [StringValue(\"SDXL 1.0\")]\n    Sdxl10,\n\n    [StringValue(\"SDXL 1.0 LCM\")]\n    Sdxl10Lcm,\n\n    [StringValue(\"SDXL Distilled\")]\n    SdxlDistilled,\n\n    [StringValue(\"SDXL Hyper\")]\n    SdxlHyper,\n\n    [StringValue(\"SDXL Lightning\")]\n    SdxlLightning,\n\n    [StringValue(\"SDXL Turbo\")]\n    SdxlTurbo,\n\n    [StringValue(\"SVD\")]\n    SVD,\n\n    [StringValue(\"SVD XT\")]\n    SvdXt,\n\n    [StringValue(\"Stable Cascade\")]\n    StableCascade,\n\n    [StringValue(\"Wan Video\")]\n    WanVideo,\n\n    Other,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitCommercialUse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\n[JsonConverter(typeof(JsonStringEnumConverter<CivitCommercialUse>))]\npublic enum CivitCommercialUse\n{\n    None,\n    Image,\n    Rent,\n    Sell\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitCreator.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic record CivitCreator\n{\n    [JsonPropertyName(\"username\")]\n    public string? Username { get; init; }\n\n    [JsonPropertyName(\"image\")]\n    public string? Image { get; init; }\n\n    [JsonIgnore]\n    public string? ProfileUrl => Username is null ? null : $\"https://civitai.com/user/{Username}\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitFile.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic class CivitFile\n{\n    [JsonPropertyName(\"id\")]\n    public int Id { get; set; }\n\n    [JsonPropertyName(\"sizeKB\")]\n    public double SizeKb { get; set; }\n\n    [JsonPropertyName(\"pickleScanResult\")]\n    public string PickleScanResult { get; set; }\n\n    [JsonPropertyName(\"virusScanResult\")]\n    public string VirusScanResult { get; set; }\n\n    [JsonPropertyName(\"scannedAt\")]\n    public DateTime? ScannedAt { get; set; }\n\n    [JsonPropertyName(\"metadata\")]\n    public CivitFileMetadata Metadata { get; set; }\n\n    [JsonPropertyName(\"name\")]\n    public string Name { get; set; }\n\n    [JsonPropertyName(\"downloadUrl\")]\n    public string DownloadUrl { get; set; }\n\n    [JsonPropertyName(\"hashes\")]\n    public CivitFileHashes Hashes { get; set; }\n\n    [JsonPropertyName(\"type\")]\n    public CivitFileType Type { get; set; }\n\n    [JsonPropertyName(\"primary\")]\n    public bool IsPrimary { get; set; }\n\n    private FileSizeType? fullFilesSize;\n    public FileSizeType FullFilesSize\n    {\n        get\n        {\n            if (fullFilesSize != null)\n                return fullFilesSize;\n            fullFilesSize = new FileSizeType(SizeKb);\n            return fullFilesSize;\n        }\n    }\n\n    public string DisplayName => Path.GetFileNameWithoutExtension(Name);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitFileHashes.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic record CivitFileHashes\n{\n    public string? SHA256 { get; set; }\n\n    public string? CRC32 { get; set; }\n\n    public string? BLAKE3 { get; set; }\n\n    [JsonIgnore]\n    public string ShortSha256 => SHA256?[..8] ?? string.Empty;\n\n    [JsonIgnore]\n    public string ShortBlake3 => BLAKE3?[..8] ?? string.Empty;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitFileMetadata.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic record CivitFileMetadata\n{\n    [JsonPropertyName(\"fp\")]\n    public string? Fp { get; set; }\n\n    [JsonPropertyName(\"size\")]\n    public string? Size { get; set; }\n\n    [JsonPropertyName(\"format\")]\n    public CivitModelFormat? Format { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitFileType.cs",
    "content": "﻿using System.Runtime.Serialization;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\n[JsonConverter(typeof(DefaultUnknownEnumConverter<CivitFileType>))]\npublic enum CivitFileType\n{\n    Unknown,\n    Model,\n    VAE,\n    Config,\n    Archive,\n\n    [EnumMember(Value = \"Pruned Model\")]\n    PrunedModel,\n\n    [EnumMember(Value = \"Training Data\")]\n    TrainingData\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitImage.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic class CivitImage\n{\n    [JsonPropertyName(\"url\")]\n    public string Url { get; set; }\n\n    [JsonPropertyName(\"nsfwLevel\")]\n    public int? NsfwLevel { get; set; }\n\n    [JsonPropertyName(\"width\")]\n    public int Width { get; set; }\n\n    [JsonPropertyName(\"height\")]\n    public int Height { get; set; }\n\n    [JsonPropertyName(\"hash\")]\n    public string Hash { get; set; }\n\n    [JsonPropertyName(\"type\")]\n    public string Type { get; set; }\n\n    // TODO: \"meta\" ( object? )\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitMetadata.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic class CivitMetadata\n{\n    [JsonPropertyName(\"totalItems\")]\n    public int TotalItems { get; set; }\n\n    [JsonPropertyName(\"currentPage\")]\n    public int CurrentPage { get; set; }\n\n    [JsonPropertyName(\"pageSize\")]\n    public int PageSize { get; set; }\n\n    [JsonPropertyName(\"totalPages\")]\n    public int TotalPages { get; set; }\n\n    [JsonPropertyName(\"nextPage\")]\n    public string? NextPage { get; set; }\n\n    [JsonPropertyName(\"prevPage\")]\n    public string? PrevPage { get; set; }\n\n    [JsonPropertyName(\"nextCursor\")]\n    public string? NextCursor { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitMode.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\n[JsonConverter(typeof(JsonStringEnumConverter<CivitMode>))]\npublic enum CivitMode\n{\n    Archived,\n    TakenDown\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitModel.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing LiteDB;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic class CivitModel\n{\n    [JsonPropertyName(\"id\")]\n    public int Id { get; set; }\n\n    [JsonPropertyName(\"name\")]\n    public string Name { get; set; }\n\n    [JsonPropertyName(\"description\")]\n    public string? Description { get; set; }\n\n    [JsonPropertyName(\"type\")]\n    public CivitModelType Type { get; set; }\n\n    [JsonPropertyName(\"nsfw\")]\n    public bool Nsfw { get; set; }\n\n    [JsonPropertyName(\"tags\")]\n    public string[] Tags { get; set; }\n\n    [JsonPropertyName(\"mode\")]\n    public CivitMode? Mode { get; set; }\n\n    [JsonPropertyName(\"creator\")]\n    public CivitCreator? Creator { get; set; }\n\n    [JsonPropertyName(\"stats\")]\n    public CivitModelStats Stats { get; set; }\n\n    [BsonRef(\"ModelVersions\")]\n    [JsonPropertyName(\"modelVersions\")]\n    public List<CivitModelVersion>? ModelVersions { get; set; }\n\n    private FileSizeType? fullFilesSize;\n    public FileSizeType FullFilesSize\n    {\n        get\n        {\n            if (fullFilesSize != null)\n                return fullFilesSize;\n            var kbs = 0.0;\n\n            var latestVersion = ModelVersions?.FirstOrDefault();\n            if (latestVersion?.Files != null && latestVersion.Files.Any())\n            {\n                var latestModelFile = latestVersion.Files.FirstOrDefault(x => x.Type == CivitFileType.Model);\n                kbs = latestModelFile?.SizeKb ?? 0;\n            }\n            fullFilesSize = new FileSizeType(kbs);\n            return fullFilesSize;\n        }\n    }\n\n    public string LatestModelVersionName =>\n        ModelVersions is { Count: > 0 } ? ModelVersions[0].Name : string.Empty;\n\n    public string? BaseModelType =>\n        ModelVersions is { Count: > 0 } ? ModelVersions[0].BaseModel?.Replace(\"SD\", \"\").Trim() : string.Empty;\n\n    public CivitModelStats ModelVersionStats =>\n        ModelVersions is { Count: > 0 } ? ModelVersions[0].Stats : new CivitModelStats();\n\n    public string LatestVersionCreatedAt =>\n        (\n            ModelVersions is { Count: > 0 }\n                ? ModelVersions[0].PublishedAt ?? DateTimeOffset.MinValue\n                : DateTimeOffset.MinValue\n        ).ToString(\"d\", System.Globalization.CultureInfo.CurrentCulture);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitModelFormat.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\n[JsonConverter(typeof(DefaultUnknownEnumConverter<CivitModelFormat>))]\npublic enum CivitModelFormat\n{\n    Unknown,\n    SafeTensor,\n    PickleTensor,\n    Diffusers,\n    GGUF,\n    Other\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitModelFpType.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\n[JsonConverter(typeof(JsonStringEnumConverter<CivitModelFpType>))]\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\npublic enum CivitModelFpType\n{\n    bf16,\n    fp16,\n    fp32,\n    tf32,\n    fp8,\n    nf4,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitModelSize.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\n[JsonConverter(typeof(JsonStringEnumConverter<CivitModelSize>))]\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\npublic enum CivitModelSize\n{\n    full,\n    pruned,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitModelStats.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic record CivitModelStats : CivitStats\n{\n    [JsonPropertyName(\"favoriteCount\")]\n    public int FavoriteCount { get; set; }\n\n    [JsonPropertyName(\"commentCount\")]\n    public int CommentCount { get; set; }\n\n    [JsonPropertyName(\"thumbsUpCount\")]\n    public int ThumbsUpCount { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitModelType.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\n[JsonConverter(typeof(DefaultUnknownEnumConverter<CivitModelType>))]\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\npublic enum CivitModelType\n{\n    Unknown,\n\n    [ConvertTo<SharedFolderType>(SharedFolderType.StableDiffusion)]\n    Checkpoint,\n\n    [ConvertTo<SharedFolderType>(SharedFolderType.Embeddings)]\n    TextualInversion,\n\n    [ConvertTo<SharedFolderType>(SharedFolderType.Hypernetwork)]\n    Hypernetwork,\n\n    [ConvertTo<SharedFolderType>(SharedFolderType.Lora)]\n    DoRA,\n\n    [ConvertTo<SharedFolderType>(SharedFolderType.Lora)]\n    LORA,\n\n    [ConvertTo<SharedFolderType>(SharedFolderType.ControlNet)]\n    Controlnet,\n\n    [ConvertTo<SharedFolderType>(SharedFolderType.LyCORIS)]\n    LoCon,\n\n    [ConvertTo<SharedFolderType>(SharedFolderType.VAE)]\n    VAE,\n\n    // Unused/obsolete/unknown/meta options\n    AestheticGradient,\n    Model,\n    MotionModule,\n    Poses,\n\n    [ConvertTo<SharedFolderType>(SharedFolderType.ESRGAN)]\n    Upscaler,\n\n    Wildcards,\n    Workflows,\n    Other,\n    All\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitModelVersion.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic class CivitModelVersion\n{\n    [JsonPropertyName(\"id\")]\n    public int Id { get; set; }\n\n    [JsonPropertyName(\"name\")]\n    public string Name { get; set; }\n\n    [JsonPropertyName(\"description\")]\n    public string Description { get; set; }\n\n    [JsonPropertyName(\"downloadUrl\")]\n    public string DownloadUrl { get; set; }\n\n    [JsonPropertyName(\"trainedWords\")]\n    public string[] TrainedWords { get; set; }\n\n    [JsonPropertyName(\"baseModel\")]\n    public string? BaseModel { get; set; }\n\n    [JsonPropertyName(\"availability\")]\n    public string? Availability { get; set; }\n\n    [JsonPropertyName(\"files\")]\n    public List<CivitFile>? Files { get; set; }\n\n    [JsonPropertyName(\"images\")]\n    public List<CivitImage>? Images { get; set; }\n\n    [JsonPropertyName(\"stats\")]\n    public CivitModelStats Stats { get; set; }\n\n    [JsonPropertyName(\"publishedAt\")]\n    public DateTimeOffset? PublishedAt { get; set; }\n\n    [JsonIgnore]\n    public bool IsEarlyAccess =>\n        Availability?.Equals(\"EarlyAccess\", StringComparison.OrdinalIgnoreCase) ?? false;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitModelVersionResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic record CivitModelVersionResponse(\n    [property: JsonPropertyName(\"id\")]\n    int Id,\n    \n    [property: JsonPropertyName(\"modelId\")]\n    int ModelId,\n    \n    [property: JsonPropertyName(\"name\")]\n    string Name,\n    \n    [property: JsonPropertyName(\"baseModel\")]\n    string BaseModel,\n    \n    [property: JsonPropertyName(\"files\")]\n    List<CivitFile> Files,\n    \n    [property: JsonPropertyName(\"images\")]\n    List<CivitImage> Images,\n    \n    [property: JsonPropertyName(\"downloadUrl\")]\n    string DownloadUrl\n);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitModelsRequest.cs",
    "content": "﻿using Refit;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic class CivitModelsRequest\n{\n    /// <summary>\n    /// The number of results to be returned per page. This can be a number between 1 and 200. By default, each page will return 100 results\n    /// </summary>\n    [AliasAs(\"limit\")]\n    public int? Limit { get; set; }\n\n    /// <summary>\n    /// The page from which to start fetching models\n    /// </summary>\n    [AliasAs(\"page\")]\n    public int? Page { get; set; }\n\n    /// <summary>\n    /// Search query to filter models by name\n    /// </summary>\n    [AliasAs(\"query\")]\n    public string? Query { get; set; }\n\n    /// <summary>\n    /// Search query to filter models by tag\n    /// </summary>\n    [AliasAs(\"tag\")]\n    public string? Tag { get; set; }\n\n    /// <summary>\n    /// Search query to filter models by user\n    /// </summary>\n    [AliasAs(\"username\")]\n    public string? Username { get; set; }\n\n    /// <summary>\n    /// The type of model you want to filter with. If none is specified, it will return all types\n    /// </summary>\n    [AliasAs(\"types\")]\n    public CivitModelType[]? Types { get; set; }\n\n    /// <summary>\n    /// The order in which you wish to sort the results\n    /// </summary>\n    [AliasAs(\"sort\")]\n    public CivitSortMode? Sort { get; set; }\n\n    /// <summary>\n    /// The time frame in which the models will be sorted\n    /// </summary>\n    [AliasAs(\"period\")]\n    public CivitPeriod? Period { get; set; }\n\n    /// <summary>\n    /// The rating you wish to filter the models with. If none is specified, it will return models with any rating\n    /// </summary>\n    [AliasAs(\"rating\")]\n    public int? Rating { get; set; }\n\n    /// <summary>\n    /// Filter to models that require or don't require crediting the creator\n    /// <remarks>Requires Authentication</remarks>\n    /// </summary>\n    [AliasAs(\"favorites\")]\n    public bool? Favorites { get; set; }\n\n    /// <summary>\n    /// Filter to hidden models of the authenticated user\n    /// <remarks>Requires Authentication</remarks>\n    /// </summary>\n    [AliasAs(\"hidden\")]\n    public bool? Hidden { get; set; }\n\n    /// <summary>\n    /// Only include the primary file for each model (This will use your preferred format options if you use an API token or session cookie)\n    /// </summary>\n    [AliasAs(\"primaryFileOnly\")]\n    public bool? PrimaryFileOnly { get; set; }\n\n    /// <summary>\n    /// Filter to models that allow or don't allow creating derivatives\n    /// </summary>\n    [AliasAs(\"allowDerivatives\")]\n    public bool? AllowDerivatives { get; set; }\n\n    /// <summary>\n    /// Filter to models that allow or don't allow derivatives to have a different license\n    /// </summary>\n    [AliasAs(\"allowDifferentLicenses\")]\n    public bool? AllowDifferentLicenses { get; set; }\n\n    /// <summary>\n    /// Filter to models based on their commercial permissions\n    /// </summary>\n    [AliasAs(\"allowCommercialUse\")]\n    public CivitCommercialUse? AllowCommercialUse { get; set; }\n\n    /// <summary>\n    /// If false, will return safer images and hide models that don't have safe images\n    /// </summary>\n    [AliasAs(\"nsfw\")]\n    public string? Nsfw { get; set; }\n\n    /// <summary>\n    /// options: <br/>\n    /// SD 1.4 <br/>\n    /// SD 1.5 <br/>\n    /// SD 2.0 <br/>\n    /// SD 2.0 768 <br/>\n    /// SD 2.1 <br/>\n    /// SD 2.1 768 <br/>\n    /// SD 2.1 Unclip <br/>\n    /// SDXL 0.9 <br/>\n    /// SDXL 1.0\n    /// </summary>\n    [AliasAs(\"baseModels\")]\n    [Query(CollectionFormat.Multi)]\n    public string[]? BaseModels { get; set; }\n\n    [AliasAs(\"ids\")]\n    public string CommaSeparatedModelIds { get; set; }\n\n    [AliasAs(\"cursor\")]\n    public string? Cursor { get; set; }\n\n    public override string ToString()\n    {\n        return $\"Page: {Page}, \"\n            + $\"Query: {Query}, \"\n            + $\"Tag: {Tag}, \"\n            + $\"Username: {Username}, \"\n            + $\"Types: {Types}, \"\n            + $\"Sort: {Sort}, \"\n            + $\"Period: {Period}, \"\n            + $\"Rating: {Rating}, \"\n            + $\"Nsfw: {Nsfw}, \"\n            + $\"BaseModel: {string.Join(\",\", BaseModels ?? [])}, \"\n            + $\"CommaSeparatedModelIds: {CommaSeparatedModelIds}\";\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitModelsResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic class CivitModelsResponse\n{\n    [JsonPropertyName(\"items\")]\n    public List<CivitModel>? Items { get; set; }\n    \n    [JsonPropertyName(\"metadata\")]\n    public CivitMetadata? Metadata { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitPeriod.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\n[JsonConverter(typeof(JsonStringEnumConverter<CivitPeriod>))]\npublic enum CivitPeriod\n{\n    AllTime,\n    Year,\n    Month,\n    Week,\n    Day\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitSortMode.cs",
    "content": "﻿using System.Runtime.Serialization;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\n[JsonConverter(typeof(JsonStringEnumConverter<CivitSortMode>))]\npublic enum CivitSortMode\n{\n    [EnumMember(Value = \"Highest Rated\")]\n    HighestRated,\n\n    [EnumMember(Value = \"Most Downloaded\")]\n    MostDownloaded,\n\n    [EnumMember(Value = \"Newest\")]\n    Newest,\n\n    [EnumMember(Value = \"Installed\")]\n    Installed,\n\n    [EnumMember(Value = \"Favorites\")]\n    Favorites,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitStats.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic record CivitStats\n{\n    [JsonPropertyName(\"downloadCount\")]\n    public int DownloadCount { get; set; }\n\n    [JsonPropertyName(\"ratingCount\")]\n    public int RatingCount { get; set; }\n\n    [JsonPropertyName(\"rating\")]\n    public double Rating { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitTRPC/CivitApiTokens.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.CivitTRPC;\n\npublic record CivitApiTokens(string ApiToken, string Username);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitTRPC/CivitGetUserByIdRequest.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.CivitTRPC;\n\npublic record CivitGetUserByIdRequest : IFormattable\n{\n    [JsonPropertyName(\"id\")]\n    public required int Id { get; set; }\n\n    /// <inheritdoc />\n    public string ToString(string? format, IFormatProvider? formatProvider)\n    {\n        return JsonSerializer.Serialize(new { json = this });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitTRPC/CivitGetUserByIdResponse.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.CivitTRPC;\n\npublic record CivitGetUserByIdResponse(int Id, string Username, string? Image);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitTRPC/CivitImageGenerationDataResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.CivitTRPC;\n\npublic class CivitImageGenerationDataResponse\n{\n    [JsonPropertyName(\"process\")]\n    public string? Process { get; set; }\n\n    [JsonPropertyName(\"meta\")]\n    public CivitImageMetadata? Metadata { get; set; }\n\n    [JsonPropertyName(\"resources\")]\n    public List<CivitImageResource>? Resources { get; set; }\n\n    [JsonIgnore]\n    public IReadOnlyDictionary<string, string>? OtherMetadata { get; set; }\n}\n\npublic class CivitImageMetadata\n{\n    [JsonPropertyName(\"prompt\")]\n    public string? Prompt { get; set; }\n\n    [JsonPropertyName(\"negativePrompt\")]\n    public string? NegativePrompt { get; set; }\n\n    [JsonPropertyName(\"cfgScale\")]\n    public double? CfgScale { get; set; }\n\n    [JsonPropertyName(\"steps\")]\n    public int? Steps { get; set; }\n\n    [JsonPropertyName(\"sampler\")]\n    public string? Sampler { get; set; }\n\n    [JsonPropertyName(\"seed\")]\n    public long? Seed { get; set; }\n\n    [JsonPropertyName(\"Eta\")]\n    public string? Eta { get; set; }\n\n    [JsonPropertyName(\"RNG\")]\n    public string? Rng { get; set; }\n\n    [JsonPropertyName(\"ENSD\")]\n    public string? Ensd { get; set; }\n\n    [JsonPropertyName(\"Size\")]\n    public string? Size { get; set; }\n\n    [JsonPropertyName(\"width\")]\n    public int? Width { get; set; }\n\n    [JsonPropertyName(\"height\")]\n    public int? Height { get; set; }\n\n    [JsonPropertyName(\"Model\")]\n    public string? Model { get; set; }\n\n    [JsonPropertyName(\"Version\")]\n    public string? Version { get; set; }\n\n    [JsonPropertyName(\"resources\")]\n    public List<CivitImageResource>? Resources { get; set; }\n\n    [JsonPropertyName(\"ModelHash\")]\n    public string? ModelHash { get; set; }\n\n    [JsonPropertyName(\"Hires steps\")]\n    public string? HiresSteps { get; set; }\n\n    [JsonPropertyName(\"Hires upscale\")]\n    public string? HiresUpscaleAmount { get; set; }\n\n    [JsonPropertyName(\"Schedule type\")]\n    public string? ScheduleType { get; set; }\n\n    [JsonPropertyName(\"Hires upscaler\")]\n    public string? HiresUpscaler { get; set; }\n\n    [JsonPropertyName(\"Denoising strength\")]\n    public string? DenoisingStrength { get; set; }\n\n    [JsonPropertyName(\"clipSkip\")]\n    public int? ClipSkip { get; set; }\n\n    [JsonPropertyName(\"scheduler\")]\n    public string? Scheduler { get; set; }\n\n    [JsonIgnore]\n    public string Dimensions => string.IsNullOrWhiteSpace(Size) ? $\"{Width}x{Height}\" : Size;\n}\n\npublic class CivitImageResource\n{\n    public string? Hash { get; set; }\n    public string? Name { get; set; }\n    public string? Type { get; set; }\n    public int ModelId { get; set; }\n    public string? ModelType { get; set; }\n    public string? ModelName { get; set; }\n    public int VersionId { get; set; }\n    public string? VersionName { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitTRPC/CivitUserAccountResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.CivitTRPC;\n\npublic record CivitUserAccountResponse(int Id, int Balance, int LifetimeBalance);\n\npublic record CivitTrpcResponse<T>\n{\n    [JsonPropertyName(\"result\")]\n    public required CivitTrpcResponseData<T> Result { get; set; }\n\n    public record CivitTrpcResponseData<TData>\n    {\n        [JsonPropertyName(\"data\")]\n        public required CivitTrpcResponseDataJson<TData> Data { get; set; }\n    }\n\n    public record CivitTrpcResponseDataJson<TJson>\n    {\n        [JsonPropertyName(\"Json\")]\n        public required TJson Json { get; set; }\n    }\n}\n\n/// <summary>\n/// Like CivitTrpcResponse, but wrapped as the first item of an array.\n/// </summary>\n/// <typeparam name=\"T\"></typeparam>\npublic record CivitTrpcArrayResponse<T>\n{\n    [JsonPropertyName(\"result\")]\n    public required CivitTrpcResponseData<T> Result { get; set; }\n\n    [JsonIgnore]\n    public T? InnerJson => Result.Data.Json.FirstOrDefault();\n\n    public record CivitTrpcResponseData<TData>\n    {\n        [JsonPropertyName(\"data\")]\n        public required CivitTrpcResponseDataJson<TData> Data { get; set; }\n    }\n\n    public record CivitTrpcResponseDataJson<TJson>\n    {\n        [JsonPropertyName(\"Json\")]\n        public required List<TJson> Json { get; set; }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitTRPC/CivitUserProfileRequest.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Serialization;\nusing System.Web;\n\nnamespace StabilityMatrix.Core.Models.Api.CivitTRPC;\n\npublic record CivitUserProfileRequest : IFormattable\n{\n    [JsonPropertyName(\"username\")]\n    public required string Username { get; set; }\n\n    [JsonPropertyName(\"authed\")]\n    public bool Authed { get; set; }\n\n    /// <inheritdoc />\n    public string ToString(string? format, IFormatProvider? formatProvider)\n    {\n        return JsonSerializer.Serialize(new { json = this });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitTRPC/CivitUserProfileResponse.cs",
    "content": "﻿using System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.CivitTRPC;\n\n/*\n * Example:\n * {\n    \"result\": {\n        \"data\": {\n            \"json\": {\n                \"id\": 1020931,\n                \"username\": \"owo\",\n                \"deletedAt\": null,\n                \"image\": \"https://lh3.googleusercontent.com/a/...\",\n                \"leaderboardShowcase\": null,\n                \"createdAt\": \"2023-02-01T21:05:31.125Z\",\n                \"cosmetics\": [],\n                \"links\": [],\n                \"rank\": null,\n                \"stats\": null,\n                \"profile\": {\n                    \"bio\": null,\n                    \"coverImageId\": null,\n                    \"coverImage\": null,\n                    \"message\": null,\n                    \"messageAddedAt\": null,\n                    \"profileSectionsSettings\": [\n                        {\n                            \"key\": \"showcase\",\n                            \"enabled\": true\n                        },\n                        {\n                            \"key\": \"popularModels\",\n                            \"enabled\": true\n                        },\n                        {\n                            \"key\": \"popularArticles\",\n                            \"enabled\": true\n                        },\n                        {\n                            \"key\": \"modelsOverview\",\n                            \"enabled\": true\n                        },\n                        {\n                            \"key\": \"imagesOverview\",\n                            \"enabled\": true\n                        },\n                        {\n                            \"key\": \"recentReviews\",\n                            \"enabled\": true\n                        }\n                    ],\n                    \"privacySettings\": {\n                        \"showFollowerCount\": true,\n                        \"showReviewsRating\": true,\n                        \"showFollowingCount\": true\n                    },\n                    \"showcaseItems\": [],\n                    \"location\": null,\n                    \"nsfw\": false,\n                    \"userId\": 1020931\n                }\n            },\n            \"meta\": {\n                \"values\": {\n                    \"createdAt\": [\n                        \"Date\"\n                    ]\n                }\n            }\n        }\n    }\n}\n *\n*/\n\npublic record CivitUserProfileResponse\n{\n    [JsonPropertyName(\"result\")]\n    public required JsonObject Result { get; init; }\n\n    public int? UserId => Result[\"data\"]?[\"json\"]?[\"id\"]?.GetValue<int>();\n\n    public string? Username => Result[\"data\"]?[\"json\"]?[\"username\"]?.GetValue<string>();\n\n    public string? ImageUrl => Result[\"data\"]?[\"json\"]?[\"image\"]?.GetValue<string>();\n\n    public DateTimeOffset? CreatedAt =>\n        Result[\"data\"]?[\"json\"]?[\"createdAt\"]?.GetValue<DateTimeOffset>();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/CivitTRPC/CivitUserToggleFavoriteModelRequest.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.CivitTRPC;\n\npublic record CivitUserToggleFavoriteModelRequest : IFormattable\n{\n    [JsonPropertyName(\"modelId\")]\n    public required int ModelId { get; set; }\n\n    [JsonPropertyName(\"authed\")]\n    public bool Authed { get; set; } = true;\n\n    /// <inheritdoc />\n    public string ToString(string? format, IFormatProvider? formatProvider)\n    {\n        return JsonSerializer.Serialize(new { json = this });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyAuxPreprocessor.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing JetBrains.Annotations;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\n/// <summary>\n/// Collection of preprocessors included in\n/// </summary>\n/// <param name=\"Value\"></param>\n[PublicAPI]\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\npublic record ComfyAuxPreprocessor(string Value) : StringValue(Value)\n{\n    public static ComfyAuxPreprocessor None { get; } = new(\"none\");\n    public static ComfyAuxPreprocessor AnimeFaceSemSegPreprocessor { get; } =\n        new(\"AnimeFace_SemSegPreprocessor\");\n    public static ComfyAuxPreprocessor BinaryPreprocessor { get; } = new(\"BinaryPreprocessor\");\n    public static ComfyAuxPreprocessor CannyEdgePreprocessor { get; } = new(\"CannyEdgePreprocessor\");\n    public static ComfyAuxPreprocessor ColorPreprocessor { get; } = new(\"ColorPreprocessor\");\n    public static ComfyAuxPreprocessor DensePosePreprocessor { get; } = new(\"DensePosePreprocessor\");\n    public static ComfyAuxPreprocessor DepthAnythingPreprocessor { get; } = new(\"DepthAnythingPreprocessor\");\n    public static ComfyAuxPreprocessor ZoeDepthAnythingPreprocessor { get; } =\n        new(\"Zoe_DepthAnythingPreprocessor\");\n    public static ComfyAuxPreprocessor DiffusionEdgePreprocessor { get; } = new(\"DiffusionEdge_Preprocessor\");\n    public static ComfyAuxPreprocessor DWPreprocessor { get; } = new(\"DWPreprocessor\");\n    public static ComfyAuxPreprocessor AnimalPosePreprocessor { get; } = new(\"AnimalPosePreprocessor\");\n    public static ComfyAuxPreprocessor HEDPreprocessor { get; } = new(\"HEDPreprocessor\");\n    public static ComfyAuxPreprocessor FakeScribblePreprocessor { get; } = new(\"FakeScribblePreprocessor\");\n    public static ComfyAuxPreprocessor LeReSDepthMapPreprocessor { get; } = new(\"LeReS-DepthMapPreprocessor\");\n    public static ComfyAuxPreprocessor LineArtPreprocessor { get; } = new(\"LineArtPreprocessor\");\n    public static ComfyAuxPreprocessor AnimeLineArtPreprocessor { get; } = new(\"AnimeLineArtPreprocessor\");\n    public static ComfyAuxPreprocessor LineartStandardPreprocessor { get; } =\n        new(\"LineartStandardPreprocessor\");\n    public static ComfyAuxPreprocessor Manga2AnimeLineArtPreprocessor { get; } =\n        new(\"Manga2Anime_LineArt_Preprocessor\");\n    public static ComfyAuxPreprocessor MediaPipeFaceMeshPreprocessor { get; } =\n        new(\"MediaPipe-FaceMeshPreprocessor\");\n    public static ComfyAuxPreprocessor MeshGraphormerDepthMapPreprocessor { get; } =\n        new(\"MeshGraphormer-DepthMapPreprocessor\");\n    public static ComfyAuxPreprocessor MiDaSNormalMapPreprocessor { get; } =\n        new(\"MiDaS-NormalMapPreprocessor\");\n    public static ComfyAuxPreprocessor MiDaSDepthMapPreprocessor { get; } = new(\"MiDaS-DepthMapPreprocessor\");\n    public static ComfyAuxPreprocessor MLSDPreprocessor { get; } = new(\"M-LSDPreprocessor\");\n    public static ComfyAuxPreprocessor BAENormalMapPreprocessor { get; } = new(\"BAE-NormalMapPreprocessor\");\n    public static ComfyAuxPreprocessor OneFormerCOCOSemSegPreprocessor { get; } =\n        new(\"OneFormer-COCO-SemSegPreprocessor\");\n    public static ComfyAuxPreprocessor OneFormerADE20KSemSegPreprocessor { get; } =\n        new(\"OneFormer-ADE20K-SemSegPreprocessor\");\n    public static ComfyAuxPreprocessor OpenposePreprocessor { get; } = new(\"OpenposePreprocessor\");\n    public static ComfyAuxPreprocessor PiDiNetPreprocessor { get; } = new(\"PiDiNetPreprocessor\");\n    public static ComfyAuxPreprocessor SavePoseKpsAsJsonFile { get; } = new(\"SavePoseKpsAsJsonFile\");\n    public static ComfyAuxPreprocessor FacialPartColoringFromPoseKps { get; } =\n        new(\"FacialPartColoringFromPoseKps\");\n    public static ComfyAuxPreprocessor ImageLuminanceDetector { get; } = new(\"ImageLuminanceDetector\");\n    public static ComfyAuxPreprocessor ImageIntensityDetector { get; } = new(\"ImageIntensityDetector\");\n    public static ComfyAuxPreprocessor ScribblePreprocessor { get; } = new(\"ScribblePreprocessor\");\n    public static ComfyAuxPreprocessor ScribbleXDoGPreprocessor { get; } = new(\"Scribble_XDoG_Preprocessor\");\n    public static ComfyAuxPreprocessor SAMPreprocessor { get; } = new(\"SAMPreprocessor\");\n    public static ComfyAuxPreprocessor ShufflePreprocessor { get; } = new(\"ShufflePreprocessor\");\n    public static ComfyAuxPreprocessor TEEDPreprocessor { get; } = new(\"TEEDPreprocessor\");\n    public static ComfyAuxPreprocessor TilePreprocessor { get; } = new(\"TilePreprocessor\");\n    public static ComfyAuxPreprocessor UniFormerSemSegPreprocessor { get; } =\n        new(\"UniFormer-SemSegPreprocessor\");\n    public static ComfyAuxPreprocessor SemSegPreprocessor { get; } = new(\"SemSegPreprocessor\");\n    public static ComfyAuxPreprocessor UnimatchOptFlowPreprocessor { get; } =\n        new(\"Unimatch_OptFlowPreprocessor\");\n    public static ComfyAuxPreprocessor MaskOptFlow { get; } = new(\"MaskOptFlow\");\n    public static ComfyAuxPreprocessor ZoeDepthMapPreprocessor { get; } = new(\"Zoe-DepthMapPreprocessor\");\n\n    private static Dictionary<ComfyAuxPreprocessor, string> DisplayNamesMapping { get; } =\n        new()\n        {\n            [None] = \"None\",\n            [AnimeFaceSemSegPreprocessor] = \"Anime Face SemSeg Preprocessor\",\n            [BinaryPreprocessor] = \"Binary Preprocessor\",\n            [CannyEdgePreprocessor] = \"Canny Edge Preprocessor\",\n            [ColorPreprocessor] = \"Color Preprocessor\",\n            [DensePosePreprocessor] = \"DensePose Preprocessor\",\n            [DepthAnythingPreprocessor] = \"Depth Anything Preprocessor\",\n            [ZoeDepthAnythingPreprocessor] = \"Zoe Depth Anything Preprocessor\",\n            [DiffusionEdgePreprocessor] = \"Diffusion Edge Preprocessor\",\n            [DWPreprocessor] = \"DW Preprocessor\",\n            [AnimalPosePreprocessor] = \"Animal Pose Preprocessor\",\n            [HEDPreprocessor] = \"HED Preprocessor\",\n            [FakeScribblePreprocessor] = \"Fake Scribble Preprocessor\",\n            [LeReSDepthMapPreprocessor] = \"LeReS-DepthMap Preprocessor\",\n            [LineArtPreprocessor] = \"LineArt Preprocessor\",\n            [AnimeLineArtPreprocessor] = \"Anime LineArt Preprocessor\",\n            [LineartStandardPreprocessor] = \"Lineart Standard Preprocessor\",\n            [Manga2AnimeLineArtPreprocessor] = \"Manga2Anime LineArt Preprocessor\",\n            [MediaPipeFaceMeshPreprocessor] = \"MediaPipe FaceMesh Preprocessor\",\n            [MeshGraphormerDepthMapPreprocessor] = \"MeshGraphormer DepthMap Preprocessor\",\n            [MiDaSNormalMapPreprocessor] = \"MiDaS NormalMap Preprocessor\",\n            [MiDaSDepthMapPreprocessor] = \"MiDaS DepthMap Preprocessor\",\n            [MLSDPreprocessor] = \"M-LSD Preprocessor\",\n            [BAENormalMapPreprocessor] = \"BAE NormalMap Preprocessor\",\n            [OneFormerCOCOSemSegPreprocessor] = \"OneFormer COCO SemSeg Preprocessor\",\n            [OneFormerADE20KSemSegPreprocessor] = \"OneFormer ADE20K SemSeg Preprocessor\",\n            [OpenposePreprocessor] = \"Openpose Preprocessor\",\n            [PiDiNetPreprocessor] = \"PiDiNet Preprocessor\",\n            [SavePoseKpsAsJsonFile] = \"Save Pose Kps As Json File\",\n            [FacialPartColoringFromPoseKps] = \"Facial Part Coloring From Pose Kps\",\n            [ImageLuminanceDetector] = \"Image Luminance Detector\",\n            [ImageIntensityDetector] = \"Image Intensity Detector\",\n            [ScribblePreprocessor] = \"Scribble Preprocessor\",\n            [ScribbleXDoGPreprocessor] = \"Scribble XDoG Preprocessor\",\n            [SAMPreprocessor] = \"SAM Preprocessor\",\n            [ShufflePreprocessor] = \"Shuffle Preprocessor\",\n            [TEEDPreprocessor] = \"TEED Preprocessor\",\n            [TilePreprocessor] = \"Tile Preprocessor\",\n            [UniFormerSemSegPreprocessor] = \"UniFormer SemSeg Preprocessor\",\n            [SemSegPreprocessor] = \"SemSeg Preprocessor\",\n            [UnimatchOptFlowPreprocessor] = \"Unimatch OptFlow Preprocessor\",\n            [MaskOptFlow] = \"Mask OptFlow\",\n            [ZoeDepthMapPreprocessor] = \"Zoe DepthMap Preprocessor\"\n        };\n\n    public static IEnumerable<ComfyAuxPreprocessor> Defaults => DisplayNamesMapping.Keys;\n\n    public string DisplayName => DisplayNamesMapping.GetValueOrDefault(this, Value);\n\n    /// <inheritdoc />\n    public override string ToString() => Value;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyHistoryOutput.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\npublic class ComfyHistoryOutput\n{\n    [JsonPropertyName(\"images\")]\n    public List<ComfyImage>? Images { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyHistoryResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\npublic class ComfyHistoryResponse\n{\n    [JsonPropertyName(\"outputs\")]\n    public required Dictionary<string, ComfyHistoryOutput> Outputs { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyImage.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing System.Web;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\npublic class ComfyImage\n{\n    [JsonPropertyName(\"filename\")]\n    public required string FileName { get; set; }\n    \n    [JsonPropertyName(\"subfolder\")]\n    public required string SubFolder { get; set; }\n    \n    [JsonPropertyName(\"type\")]\n    public required string Type { get; set; }\n\n    public Uri ToUri(Uri baseAddress)\n    {\n        var query = HttpUtility.ParseQueryString(string.Empty);\n        query[\"filename\"] = FileName;\n        query[\"subfolder\"] = SubFolder;\n        query[\"type\"] = Type;\n        \n        return new UriBuilder(baseAddress)\n        {\n            Path = \"/view\",\n            Query = query.ToString()\n        }.Uri;\n    }\n    \n    public FilePath ToFilePath(DirectoryPath outputDir)\n    {\n        return new FilePath(outputDir, SubFolder, FileName);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyInputInfo.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\npublic class ComfyInputInfo\n{\n    [JsonPropertyName(\"required\")]\n    public Dictionary<string, JsonNode>? Required { get; set; }\n\n    [JsonPropertyName(\"optional\")]\n    public Dictionary<string, JsonNode>? Optional { get; set; }\n\n    public List<string>? GetRequiredValueAsNestedList(string key)\n    {\n        var value = Required?[key];\n\n        // value usually is a [[\"a\", \"b\"]] array\n        // but can also be [[\"a\", \"b\"], {\"x\": \"y\"}] array\n        // or sometimes [\"COMBO\", {\"options\": [\"a\", \"b\"]}] array\n\n        var outerArray = value?.Deserialize<JsonArray>();\n\n        if (\n            outerArray?.Count > 1\n            && outerArray.FirstOrDefault() is JsonValue jsonValue\n            && jsonValue.ToString().Equals(\"COMBO\")\n        )\n        {\n            var options = outerArray[1]?[\"options\"];\n            if (options is JsonArray optionsArray)\n            {\n                return optionsArray.Deserialize<List<string>>();\n            }\n        }\n\n        if (outerArray?.FirstOrDefault() is not { } innerNode)\n        {\n            return null;\n        }\n\n        var innerList = innerNode.Deserialize<List<string>>();\n        return innerList;\n    }\n\n    public List<string>? GetOptionalValueAsNestedList(string key)\n    {\n        var value = Optional?[key];\n\n        // value usually is a [[\"a\", \"b\"]] array\n        // but can also be [[\"a\", \"b\"], {\"x\": \"y\"}] array\n\n        var outerArray = value?.Deserialize<JsonArray>();\n\n        if (outerArray?.FirstOrDefault() is not { } innerNode)\n        {\n            return null;\n        }\n\n        var innerList = innerNode.Deserialize<List<string>>();\n        return innerList;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyObjectInfo.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\npublic class ComfyObjectInfo\n{\n    [JsonPropertyName(\"name\")]\n    public string? Name { get; set; }\n    \n    [JsonPropertyName(\"display_name\")]\n    public string? DisplayName { get; set; }\n    \n    [JsonPropertyName(\"description\")]\n    public string? Description { get; set; }\n    \n    [JsonPropertyName(\"category\")]\n    public string? Category { get; set; }\n    \n    [JsonPropertyName(\"output_node\")]\n    public bool IsOutputNode { get; set; }\n    \n    /// <summary>\n    /// Input info\n    /// </summary>\n    [JsonPropertyName(\"input\")]\n    public required ComfyInputInfo Input { get; set; }\n    \n    /// <summary>\n    /// List of output point types\n    /// i.e. [\"MODEL\", \"CLIP\", \"VAE\"]\n    /// </summary>\n    [JsonPropertyName(\"output\")]\n    public required List<string> Output { get; set; }\n    \n    /// <summary>\n    /// List of output point display names\n    /// i.e. [\"MODEL\", \"CLIP\", \"VAE\"]\n    /// </summary>\n    [JsonPropertyName(\"output_name\")]\n    public required List<string> OutputName { get; set; }\n    \n    /// <summary>\n    /// List of whether the indexed output is a list\n    /// i.e. [false, false, false]\n    /// </summary>\n    [JsonPropertyName(\"output_is_list\")]\n    public required List<bool> OutputIsList { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyPromptRequest.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\npublic class ComfyPromptRequest\n{\n    [JsonPropertyName(\"client_id\")]\n    public required string ClientId { get; set; }\n    \n    [JsonPropertyName(\"prompt\")]\n    public required Dictionary<string, ComfyNode> Prompt { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyPromptResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\n// ReSharper disable once ClassNeverInstantiated.Global\npublic class ComfyPromptResponse\n{\n    [JsonPropertyName(\"prompt_id\")]\n    public required string PromptId { get; set; }\n\n    [JsonPropertyName(\"number\")]\n    public required int Number { get; set; }\n    \n    [JsonPropertyName(\"node_errors\")]\n    public required Dictionary<string, object?> NodeErrors { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfySampler.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Diagnostics.CodeAnalysis;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\n[SuppressMessage(\"ReSharper\", \"StringLiteralTypo\")]\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n[SuppressMessage(\"ReSharper\", \"IdentifierTypo\")]\npublic readonly record struct ComfySampler(string Name)\n{\n    public static ComfySampler Euler { get; } = new(\"euler\");\n    public static ComfySampler EulerAncestral { get; } = new(\"euler_ancestral\");\n    public static ComfySampler Heun { get; } = new(\"heun\");\n    public static ComfySampler HeunPp2 { get; } = new(\"heunpp2\");\n    public static ComfySampler Dpm2 { get; } = new(\"dpm_2\");\n    public static ComfySampler Dpm2Ancestral { get; } = new(\"dpm_2_ancestral\");\n    public static ComfySampler LMS { get; } = new(\"lms\");\n    public static ComfySampler DpmFast { get; } = new(\"dpm_fast\");\n    public static ComfySampler DpmAdaptive { get; } = new(\"dpm_adaptive\");\n    public static ComfySampler Dpmpp2SAncestral { get; } = new(\"dpmpp_2s_ancestral\");\n    public static ComfySampler DpmppSde { get; } = new(\"dpmpp_sde\");\n    public static ComfySampler DpmppSdeGpu { get; } = new(\"dpmpp_sde_gpu\");\n    public static ComfySampler Dpmpp2M { get; } = new(\"dpmpp_2m\");\n    public static ComfySampler Dpmpp2MSde { get; } = new(\"dpmpp_2m_sde\");\n    public static ComfySampler Dpmpp2MSdeGpu { get; } = new(\"dpmpp_2m_sde_gpu\");\n    public static ComfySampler Dpmpp3M { get; } = new(\"dpmpp_3m\");\n    public static ComfySampler Dpmpp3MSde { get; } = new(\"dpmpp_3m_sde\");\n    public static ComfySampler Dpmpp3MSdeGpu { get; } = new(\"dpmpp_3m_sde_gpu\");\n    public static ComfySampler DDIM { get; } = new(\"ddim\");\n    public static ComfySampler DDPM { get; } = new(\"ddpm\");\n    public static ComfySampler UniPC { get; } = new(\"uni_pc\");\n    public static ComfySampler UniPCBh2 { get; } = new(\"uni_pc_bh2\");\n    public static ComfySampler LCM { get; } = new(\"lcm\");\n\n    private static Dictionary<ComfySampler, string> ConvertDict { get; } =\n        new()\n        {\n            [Euler] = \"Euler\",\n            [EulerAncestral] = \"Euler Ancestral\",\n            [Heun] = \"Heun\",\n            [HeunPp2] = \"Heun++ 2\",\n            [Dpm2] = \"DPM 2\",\n            [Dpm2Ancestral] = \"DPM 2 Ancestral\",\n            [LMS] = \"LMS\",\n            [DpmFast] = \"DPM Fast\",\n            [DpmAdaptive] = \"DPM Adaptive\",\n            [Dpmpp2SAncestral] = \"DPM++ 2S Ancestral\",\n            [DpmppSde] = \"DPM++ SDE\",\n            [DpmppSdeGpu] = \"DPM++ SDE GPU\",\n            [Dpmpp2M] = \"DPM++ 2M\",\n            [Dpmpp2MSde] = \"DPM++ 2M SDE\",\n            [Dpmpp2MSdeGpu] = \"DPM++ 2M SDE GPU\",\n            [Dpmpp3M] = \"DPM++ 3M\",\n            [Dpmpp3MSde] = \"DPM++ 3M SDE\",\n            [Dpmpp3MSdeGpu] = \"DPM++ 3M SDE GPU\",\n            [DDIM] = \"DDIM\",\n            [DDPM] = \"DDPM\",\n            [UniPC] = \"UniPC\",\n            [UniPCBh2] = \"UniPC BH2\",\n            [LCM] = \"LCM\"\n        };\n\n    public static IReadOnlyList<ComfySampler> Defaults { get; } = ConvertDict.Keys.ToImmutableArray();\n\n    public string DisplayName => ConvertDict.GetValueOrDefault(this, Name);\n\n    /// <inheritdoc />\n    public bool Equals(ComfySampler other)\n    {\n        return Name == other.Name;\n    }\n\n    /// <inheritdoc />\n    public override int GetHashCode()\n    {\n        return Name.GetHashCode();\n    }\n\n    private sealed class NameEqualityComparer : IEqualityComparer<ComfySampler>\n    {\n        public bool Equals(ComfySampler x, ComfySampler y)\n        {\n            return x.Name == y.Name;\n        }\n\n        public int GetHashCode(ComfySampler obj)\n        {\n            return obj.Name.GetHashCode();\n        }\n    }\n\n    public static IEqualityComparer<ComfySampler> Comparer { get; } = new NameEqualityComparer();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfySamplerScheduler.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Comfy;\n\n/// <summary>\n/// Pair of <see cref=\"ComfySampler\"/> and <see cref=\"ComfyScheduler\"/>\n/// </summary>\npublic readonly record struct ComfySamplerScheduler(ComfySampler Sampler, ComfyScheduler Scheduler)\n{\n    /// <inheritdoc />\n    public bool Equals(ComfySamplerScheduler other)\n    {\n        return Sampler.Equals(other.Sampler) && Scheduler.Equals(other.Scheduler);\n    }\n\n    /// <inheritdoc />\n    public override int GetHashCode()\n    {\n        return HashCode.Combine(Sampler, Scheduler);\n    }\n\n    // Implicit conversion from (ComfySampler, ComfyScheduler)\n    public static implicit operator ComfySamplerScheduler((ComfySampler, ComfyScheduler) tuple)\n    {\n        return new ComfySamplerScheduler(tuple.Item1, tuple.Item2);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyScheduler.cs",
    "content": "﻿using System.Collections.Immutable;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\npublic readonly record struct ComfyScheduler(string Name)\n{\n    public static ComfyScheduler Normal { get; } = new(\"normal\");\n    public static ComfyScheduler Karras { get; } = new(\"karras\");\n    public static ComfyScheduler Exponential { get; } = new(\"exponential\");\n    public static ComfyScheduler SDTurbo { get; } = new(\"sd_turbo\");\n    public static ComfyScheduler Simple { get; } = new(\"simple\");\n    public static ComfyScheduler Beta { get; } = new(\"beta\");\n    public static ComfyScheduler AlignYourSteps { get; } = new(\"align_your_steps\");\n    public static ComfyScheduler LinearQuadratic { get; } = new(\"linear_quadratic\");\n    public static ComfyScheduler KLOptimal { get; } = new(\"kl_optimal\");\n    public static ComfyScheduler FaceDetailerAlignYourStepsSD1 { get; } = new(\"AYS SD1\");\n    public static ComfyScheduler FaceDetailerAlignYourStepsSDXL { get; } = new(\"AYS SDXL\");\n    public static ComfyScheduler FaceDetailerGits { get; } = new(\"GITS[coeff=1.2]\");\n    public static ComfyScheduler FaceDetailerLtxv { get; } = new(\"LTXV[default]\");\n\n    private static Dictionary<string, string> ConvertDict { get; } =\n        new()\n        {\n            [Normal.Name] = \"Normal\",\n            [Karras.Name] = \"Karras\",\n            [Exponential.Name] = \"Exponential\",\n            [\"sgm_uniform\"] = \"SGM Uniform\",\n            [Simple.Name] = \"Simple\",\n            [\"ddim_uniform\"] = \"DDIM Uniform\",\n            [SDTurbo.Name] = \"SD Turbo\",\n            [Beta.Name] = \"Beta\",\n            [AlignYourSteps.Name] = \"Align Your Steps\",\n            [LinearQuadratic.Name] = \"Linear Quadratic\",\n            [KLOptimal.Name] = \"KL Optimal\"\n        };\n\n    private static Dictionary<string, string> FaceDetailerConvertDict { get; } =\n        new()\n        {\n            [FaceDetailerAlignYourStepsSD1.Name] = \"Align Your Steps SD1\",\n            [FaceDetailerAlignYourStepsSDXL.Name] = \"Align Your Steps SDXL\",\n            [FaceDetailerGits.Name] = \"GITS[coeff=1.2]\",\n            [FaceDetailerLtxv.Name] = \"LTXV[default]\"\n        };\n\n    public static IReadOnlyList<ComfyScheduler> Defaults { get; } =\n        ConvertDict.Keys.Select(k => new ComfyScheduler(k)).ToImmutableArray();\n\n    public static IReadOnlyList<ComfyScheduler> FaceDetailerDefaults { get; } =\n        Defaults\n            .Except([AlignYourSteps])\n            .Concat(FaceDetailerConvertDict.Keys.Select(k => new ComfyScheduler(k)))\n            .ToImmutableArray();\n\n    public string DisplayName => ConvertDict.GetValueOrDefault(Name, Name);\n\n    private sealed class NameEqualityComparer : IEqualityComparer<ComfyScheduler>\n    {\n        public bool Equals(ComfyScheduler x, ComfyScheduler y)\n        {\n            return x.Name == y.Name;\n        }\n\n        public int GetHashCode(ComfyScheduler obj)\n        {\n            return obj.Name.GetHashCode();\n        }\n    }\n\n    public static IEqualityComparer<ComfyScheduler> Comparer { get; } = new NameEqualityComparer();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyUploadImageResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\npublic record ComfyUploadImageResponse\n{\n    [JsonPropertyName(\"name\")]\n    public required string Name { get; set; }\n\n    [JsonPropertyName(\"type\")]\n    public required string Type { get; set; }\n\n    [JsonPropertyName(\"subfolder\")]\n    public required string SubFolder { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyUpscaler.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\npublic readonly record struct ComfyUpscaler(string Name, ComfyUpscalerType Type) : IDownloadableResource\n{\n    public static ComfyUpscaler NearestExact { get; } = new(\"nearest-exact\", ComfyUpscalerType.Latent);\n\n    private static Dictionary<string, string> ConvertDict { get; } =\n        new()\n        {\n            [\"nearest-exact\"] = \"Nearest Exact\",\n            [\"bilinear\"] = \"Bilinear\",\n            [\"area\"] = \"Area\",\n            [\"bicubic\"] = \"Bicubic\",\n            [\"bislerp\"] = \"Bislerp\",\n        };\n\n    public static IReadOnlyList<ComfyUpscaler> Defaults { get; } =\n        ConvertDict.Keys.Select(k => new ComfyUpscaler(k, ComfyUpscalerType.Latent)).ToImmutableArray();\n\n    public static ComfyUpscaler FromDownloadable(RemoteResource resource)\n    {\n        return new ComfyUpscaler(resource.FileName, ComfyUpscalerType.DownloadableModel)\n        {\n            DownloadableResource = resource\n        };\n    }\n\n    /// <summary>\n    /// Downloadable model information.\n    /// If this is set, <see cref=\"Type\"/> should be <see cref=\"ComfyUpscalerType.DownloadableModel\"/>.\n    /// </summary>\n    public RemoteResource? DownloadableResource { get; init; }\n\n    [MemberNotNullWhen(true, nameof(DownloadableResource))]\n    public bool IsDownloadable => DownloadableResource != null;\n\n    [JsonIgnore]\n    public string DisplayType\n    {\n        get\n        {\n            return Type switch\n            {\n                ComfyUpscalerType.Latent => \"Latent\",\n                ComfyUpscalerType.ESRGAN => \"ESRGAN\",\n                ComfyUpscalerType.DownloadableModel => \"Downloadable\",\n                ComfyUpscalerType.None => \"None\",\n                _ => throw new ArgumentOutOfRangeException(nameof(Type), Type, null)\n            };\n        }\n    }\n\n    [JsonIgnore]\n    public string DisplayName\n    {\n        get\n        {\n            if (Type == ComfyUpscalerType.Latent)\n            {\n                return ConvertDict.TryGetValue(Name, out var displayName) ? displayName : Name;\n            }\n\n            if (Type is ComfyUpscalerType.ESRGAN or ComfyUpscalerType.DownloadableModel)\n            {\n                // Remove file extensions\n                return Path.GetFileNameWithoutExtension(Name);\n            }\n\n            return Name;\n        }\n    }\n\n    [JsonIgnore]\n    public string ShortDisplayName\n    {\n        get\n        {\n            if (Type != ComfyUpscalerType.Latent)\n            {\n                // Remove file extensions\n                return Path.GetFileNameWithoutExtension(Name);\n            }\n\n            return DisplayName;\n        }\n    }\n\n    /// <summary>\n    /// Default remote downloadable models\n    /// </summary>\n    public static IReadOnlyList<ComfyUpscaler> DefaultDownloadableModels { get; } =\n        RemoteModels.Upscalers.Select(FromDownloadable).ToImmutableArray();\n\n    private sealed class NameTypeEqualityComparer : IEqualityComparer<ComfyUpscaler>\n    {\n        public bool Equals(ComfyUpscaler x, ComfyUpscaler y)\n        {\n            return x.Name == y.Name && x.Type == y.Type;\n        }\n\n        public int GetHashCode(ComfyUpscaler obj)\n        {\n            return HashCode.Combine(obj.Name, (int)obj.Type);\n        }\n    }\n\n    public static IEqualityComparer<ComfyUpscaler> Comparer { get; } = new NameTypeEqualityComparer();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyUpscalerType.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\npublic enum ComfyUpscalerType\n{\n    None,\n    Latent,\n    ESRGAN,\n    DownloadableModel\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyWebSocketResponse.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Models.Api.Comfy.WebSocketData;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\npublic class ComfyWebSocketResponse\n{\n    [JsonPropertyName(\"type\")]\n    public required ComfyWebSocketResponseType Type { get; set; }\n\n    /// <summary>\n    /// Depending on the value of <see cref=\"Type\"/>,\n    /// this property will be one of these types\n    /// <list type=\"bullet\">\n    /// <item>Status - <see cref=\"ComfyWebSocketStatusData\"/></item>\n    /// <item>Progress - <see cref=\"ComfyWebSocketProgressData\"/></item>\n    /// <item>Executing - <see cref=\"ComfyWebSocketExecutingData\"/></item>\n    /// </list>\n    /// </summary>\n    [JsonPropertyName(\"data\")]\n    public required JsonObject Data { get; set; }\n\n    public T? GetDataAsType<T>(JsonSerializerOptions? options = null)\n        where T : class\n    {\n        return Data.Deserialize<T>(options);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyWebSocketResponseType.cs",
    "content": "﻿using System.Runtime.Serialization;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\n[JsonConverter(typeof(DefaultUnknownEnumConverter<ComfyWebSocketResponseType>))]\npublic enum ComfyWebSocketResponseType\n{\n    Unknown,\n\n    [EnumMember(Value = \"status\")]\n    Status,\n\n    [EnumMember(Value = \"execution_start\")]\n    ExecutionStart,\n\n    [EnumMember(Value = \"execution_cached\")]\n    ExecutionCached,\n\n    [EnumMember(Value = \"execution_error\")]\n    ExecutionError,\n\n    [EnumMember(Value = \"executing\")]\n    Executing,\n\n    [EnumMember(Value = \"progress\")]\n    Progress,\n\n    [EnumMember(Value = \"executed\")]\n    Executed,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/ComfyWebSocketResponseUnion.cs",
    "content": "﻿using System.Net.WebSockets;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy;\n\npublic record ComfyWebSocketResponseUnion\n{\n    public WebSocketMessageType MessageType { get; set; }\n    public ComfyWebSocketResponse? Json { get; set; }\n    public byte[]? Bytes { get; set; }\n};\n\n\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ConditioningConnections.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\n/// <summary>\n/// Combination of the positive and negative conditioning connections.\n/// </summary>\npublic record ConditioningConnections(ConditioningNodeConnection Positive, ConditioningNodeConnection Negative)\n{\n    // Implicit from tuple\n    public static implicit operator ConditioningConnections(\n        (ConditioningNodeConnection Positive, ConditioningNodeConnection Negative) value\n    ) => new(value.Positive, value.Negative);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/ModelConnections.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\n/// <summary>\n/// Connections from a loaded model\n/// </summary>\npublic record ModelConnections(string Name)\n{\n    public ModelConnections(ModelConnections other)\n    {\n        Name = other.Name;\n        Model = other.Model;\n        VAE = other.VAE;\n        Clip = other.Clip;\n        Conditioning = other.Conditioning;\n        ClipVision = other.ClipVision;\n    }\n\n    public ModelNodeConnection? Model { get; set; }\n\n    public VAENodeConnection? VAE { get; set; }\n\n    public ClipNodeConnection? Clip { get; set; }\n\n    public ConditioningConnections? Conditioning { get; set; }\n\n    public ClipVisionNodeConnection? ClipVision { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/NodeConnectionBase.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\n[JsonConverter(typeof(NodeConnectionBaseJsonConverter))]\npublic abstract class NodeConnectionBase\n{\n    /// <summary>\n    /// Array data for the connection.\n    /// [(string) Node Name, (int) Connection Index]\n    /// </summary>\n    public object[]? Data { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/NodeConnections.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\npublic class LatentNodeConnection : NodeConnectionBase;\n\npublic class VAENodeConnection : NodeConnectionBase;\n\npublic class ImageNodeConnection : NodeConnectionBase;\n\npublic class ImageMaskConnection : NodeConnectionBase;\n\npublic class UpscaleModelNodeConnection : NodeConnectionBase;\n\npublic class ModelNodeConnection : NodeConnectionBase;\n\npublic class ConditioningNodeConnection : NodeConnectionBase;\n\npublic class ClipNodeConnection : NodeConnectionBase;\n\npublic class ControlNetNodeConnection : NodeConnectionBase;\n\npublic class ClipVisionNodeConnection : NodeConnectionBase;\n\npublic class ClipVisionOutputNodeConnection : NodeConnectionBase;\n\npublic class SamplerNodeConnection : NodeConnectionBase;\n\npublic class SigmasNodeConnection : NodeConnectionBase;\n\npublic class StringNodeConnection : NodeConnectionBase;\n\npublic class BboxDetectorNodeConnection : NodeConnectionBase;\n\npublic class SegmDetectorNodeConnection : NodeConnectionBase;\n\npublic class SamModelNodeConnection : NodeConnectionBase;\n\npublic class GuiderNodeConnection : NodeConnectionBase;\n\npublic class NoiseNodeConnection : NodeConnectionBase;\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/NodeTypes/PrimaryNodeConnection.cs",
    "content": "﻿using OneOf;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\n/// <summary>\n/// Union for the primary Image or Latent node connection\n/// </summary>\n[GenerateOneOf]\npublic partial class PrimaryNodeConnection\n    : OneOfBase<LatentNodeConnection, ImageNodeConnection> { }\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNode.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\n[JsonSerializable(typeof(ComfyNode))]\n[SuppressMessage(\"ReSharper\", \"CollectionNeverQueried.Global\")]\n[SuppressMessage(\"ReSharper\", \"UnusedAutoPropertyAccessor.Global\")]\npublic record ComfyNode\n{\n    [JsonPropertyName(\"class_type\")]\n    public required string ClassType { get; init; }\n    \n    [JsonPropertyName(\"inputs\")]\n    public required Dictionary<string, object?> Inputs { get; init; }\n    \n    public NamedComfyNode ToNamedNode(string name)\n    {\n        return new NamedComfyNode(name)\n        {\n            ClassType = ClassType,\n            Inputs = Inputs\n        };\n    } \n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyNodeBuilder.cs",
    "content": "﻿using System.ComponentModel;\nusing System.ComponentModel.DataAnnotations;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Drawing;\nusing System.Text.Json.Serialization;\nusing OneOf;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.Inference;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\n/// <summary>\n/// Builder functions for comfy nodes\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\n[Localizable(false)]\npublic class ComfyNodeBuilder\n{\n    public NodeDictionary Nodes { get; } = new();\n\n    private static string GetRandomPrefix() => Guid.NewGuid().ToString()[..8];\n\n    private const int MaxResolution = 16384;\n\n    private string GetUniqueName(string nameBase)\n    {\n        var name = $\"{nameBase}_1\";\n        for (var i = 0; Nodes.ContainsKey(name); i++)\n        {\n            if (i > 1_000_000)\n            {\n                throw new InvalidOperationException($\"Could not find unique name for base {nameBase}\");\n            }\n\n            name = $\"{nameBase}_{i + 1}\";\n        }\n\n        return name;\n    }\n\n    public record VAEEncode : ComfyTypedNodeBase<LatentNodeConnection>\n    {\n        public required ImageNodeConnection Pixels { get; init; }\n        public required VAENodeConnection Vae { get; init; }\n    }\n\n    public record VAEEncodeForInpaint : ComfyTypedNodeBase<LatentNodeConnection>\n    {\n        public required ImageNodeConnection Pixels { get; init; }\n        public required VAENodeConnection Vae { get; init; }\n        public required ImageMaskConnection Mask { get; init; }\n\n        [Range(0, 64)]\n        public int GrowMaskBy { get; init; } = 6;\n    }\n\n    public record VAEDecode : ComfyTypedNodeBase<ImageNodeConnection>\n    {\n        public required LatentNodeConnection Samples { get; init; }\n        public required VAENodeConnection Vae { get; init; }\n    }\n\n    [TypedNodeOptions(Name = \"VAEDecodeTiled\")]\n    public record TiledVAEDecode : ComfyTypedNodeBase<ImageNodeConnection>\n    {\n        public required LatentNodeConnection Samples { get; init; }\n        public required VAENodeConnection Vae { get; init; }\n\n        [Range(64, 4096)]\n        [JsonPropertyName(\"tile_size\")]\n        public int TileSize { get; init; } = 512;\n\n        [Range(0, 4096)]\n        [JsonPropertyName(\"overlap\")]\n        public int Overlap { get; init; } = 64;\n\n        [Range(8, 4096)]\n        [JsonPropertyName(\"temporal_size\")]\n        public int TemporalSize { get; init; } = 64;\n\n        [Range(4, 4096)]\n        [JsonPropertyName(\"temporal_overlap\")]\n        public int TemporalOverlap { get; init; } = 8;\n    }\n\n    public record KSampler : ComfyTypedNodeBase<LatentNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n        public required ulong Seed { get; init; }\n        public required int Steps { get; init; }\n        public required double Cfg { get; init; }\n        public required string SamplerName { get; init; }\n        public required string Scheduler { get; init; }\n        public required ConditioningNodeConnection Positive { get; init; }\n        public required ConditioningNodeConnection Negative { get; init; }\n        public required LatentNodeConnection LatentImage { get; init; }\n        public required double Denoise { get; init; }\n    }\n\n    public record KSamplerAdvanced : ComfyTypedNodeBase<LatentNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n\n        [BoolStringMember(\"enable\", \"disable\")]\n        public required bool AddNoise { get; init; }\n        public required ulong NoiseSeed { get; init; }\n        public required int Steps { get; init; }\n        public required double Cfg { get; init; }\n        public required string SamplerName { get; init; }\n        public required string Scheduler { get; init; }\n        public required ConditioningNodeConnection Positive { get; init; }\n        public required ConditioningNodeConnection Negative { get; init; }\n        public required LatentNodeConnection LatentImage { get; init; }\n        public required int StartAtStep { get; init; }\n        public required int EndAtStep { get; init; }\n\n        [BoolStringMember(\"enable\", \"disable\")]\n        public bool ReturnWithLeftoverNoise { get; init; }\n    }\n\n    public record SamplerCustom : ComfyTypedNodeBase<LatentNodeConnection, LatentNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n        public required bool AddNoise { get; init; }\n        public required ulong NoiseSeed { get; init; }\n\n        [Range(0d, 100d)]\n        public required double Cfg { get; init; }\n\n        public required ConditioningNodeConnection Positive { get; init; }\n        public required ConditioningNodeConnection Negative { get; init; }\n        public required SamplerNodeConnection Sampler { get; init; }\n        public required SigmasNodeConnection Sigmas { get; init; }\n        public required LatentNodeConnection LatentImage { get; init; }\n    }\n\n    public record KSamplerSelect : ComfyTypedNodeBase<SamplerNodeConnection>\n    {\n        public required string SamplerName { get; init; }\n    }\n\n    public record SDTurboScheduler : ComfyTypedNodeBase<SigmasNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n\n        [Range(1, 10)]\n        public required int Steps { get; init; }\n\n        [Range(0, 1.0)]\n        public required double Denoise { get; init; }\n    }\n\n    public record EmptyLatentImage : ComfyTypedNodeBase<LatentNodeConnection>\n    {\n        public required int BatchSize { get; init; }\n        public required int Height { get; init; }\n        public required int Width { get; init; }\n    }\n\n    public record EmptyHunyuanLatentVideo : ComfyTypedNodeBase<LatentNodeConnection>\n    {\n        public required int Width { get; init; }\n        public required int Height { get; init; }\n        public required int Length { get; init; }\n        public required int BatchSize { get; init; }\n    }\n\n    public record CLIPSetLastLayer : ComfyTypedNodeBase<ClipNodeConnection>\n    {\n        public required ClipNodeConnection Clip { get; init; }\n\n        [Range(-24, -1)]\n        public int StopAtClipLayer { get; init; } = -1;\n    }\n\n    public record LatentFromBatch : ComfyTypedNodeBase<LatentNodeConnection>\n    {\n        public required LatentNodeConnection Samples { get; init; }\n\n        [Range(0, 63)]\n        public int BatchIndex { get; init; } = 0;\n\n        [Range(1, 64)]\n        public int Length { get; init; } = 1;\n    }\n\n    public record RepeatLatentBatch : ComfyTypedNodeBase<LatentNodeConnection>\n    {\n        public required LatentNodeConnection Samples { get; init; }\n\n        [Range(1, 64)]\n        public int Amount { get; init; } = 1;\n    }\n\n    public record LatentBlend : ComfyTypedNodeBase<LatentNodeConnection>\n    {\n        public required LatentNodeConnection Samples1 { get; init; }\n\n        public required LatentNodeConnection Samples2 { get; init; }\n\n        [Range(0d, 1d)]\n        public double BlendFactor { get; init; } = 0.5;\n    }\n\n    public record ModelMergeSimple : ComfyTypedNodeBase<ModelNodeConnection>\n    {\n        public required ModelNodeConnection Model1 { get; init; }\n\n        public required ModelNodeConnection Model2 { get; init; }\n\n        [Range(0d, 1d)]\n        public double Ratio { get; init; } = 1;\n    }\n\n    public static NamedComfyNode<ImageNodeConnection> ImageUpscaleWithModel(\n        string name,\n        UpscaleModelNodeConnection upscaleModel,\n        ImageNodeConnection image\n    )\n    {\n        return new NamedComfyNode<ImageNodeConnection>(name)\n        {\n            ClassType = \"ImageUpscaleWithModel\",\n            Inputs = new Dictionary<string, object?>\n            {\n                [\"upscale_model\"] = upscaleModel.Data,\n                [\"image\"] = image.Data,\n            },\n        };\n    }\n\n    public static NamedComfyNode<UpscaleModelNodeConnection> UpscaleModelLoader(string name, string modelName)\n    {\n        return new NamedComfyNode<UpscaleModelNodeConnection>(name)\n        {\n            ClassType = \"UpscaleModelLoader\",\n            Inputs = new Dictionary<string, object?> { [\"model_name\"] = modelName },\n        };\n    }\n\n    public static NamedComfyNode<ImageNodeConnection> ImageScale(\n        string name,\n        ImageNodeConnection image,\n        string method,\n        int height,\n        int width,\n        bool crop\n    )\n    {\n        return new NamedComfyNode<ImageNodeConnection>(name)\n        {\n            ClassType = \"ImageScale\",\n            Inputs = new Dictionary<string, object?>\n            {\n                [\"image\"] = image.Data,\n                [\"upscale_method\"] = method,\n                [\"height\"] = height,\n                [\"width\"] = width,\n                [\"crop\"] = crop ? \"center\" : \"disabled\",\n            },\n        };\n    }\n\n    public record VAELoader : ComfyTypedNodeBase<VAENodeConnection>\n    {\n        public required string VaeName { get; init; }\n    }\n\n    public static NamedComfyNode<ModelNodeConnection, ClipNodeConnection> LoraLoader(\n        string name,\n        ModelNodeConnection model,\n        ClipNodeConnection clip,\n        string loraName,\n        double strengthModel,\n        double strengthClip\n    )\n    {\n        return new NamedComfyNode<ModelNodeConnection, ClipNodeConnection>(name)\n        {\n            ClassType = \"LoraLoader\",\n            Inputs = new Dictionary<string, object?>\n            {\n                [\"model\"] = model.Data,\n                [\"clip\"] = clip.Data,\n                [\"lora_name\"] = loraName,\n                [\"strength_model\"] = strengthModel,\n                [\"strength_clip\"] = strengthClip,\n            },\n        };\n    }\n\n    public record CheckpointLoader\n        : ComfyTypedNodeBase<ModelNodeConnection, ClipNodeConnection, VAENodeConnection>\n    {\n        public required string ConfigName { get; init; }\n        public required string CkptName { get; init; }\n    }\n\n    public record CheckpointLoaderSimple\n        : ComfyTypedNodeBase<ModelNodeConnection, ClipNodeConnection, VAENodeConnection>\n    {\n        public required string CkptName { get; init; }\n    }\n\n    public record ImageOnlyCheckpointLoader\n        : ComfyTypedNodeBase<ModelNodeConnection, ClipVisionNodeConnection, VAENodeConnection>\n    {\n        public required string CkptName { get; init; }\n    }\n\n    public record FreeU : ComfyTypedNodeBase<ModelNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n        public required double B1 { get; init; }\n        public required double B2 { get; init; }\n        public required double S1 { get; init; }\n        public required double S2 { get; init; }\n    }\n\n    [SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n    public record CLIPTextEncode : ComfyTypedNodeBase<ConditioningNodeConnection>\n    {\n        public required ClipNodeConnection Clip { get; init; }\n        public required OneOf<string, StringNodeConnection> Text { get; init; }\n    }\n\n    public record LoadImage : ComfyTypedNodeBase<ImageNodeConnection, ImageMaskConnection>\n    {\n        /// <summary>\n        /// Path relative to the Comfy input directory\n        /// </summary>\n        public required string Image { get; init; }\n    }\n\n    public record LoadImageMask : ComfyTypedNodeBase<ImageMaskConnection>\n    {\n        /// <summary>\n        /// Path relative to the Comfy input directory\n        /// </summary>\n        public required string Image { get; init; }\n\n        /// <summary>\n        /// Color channel to use as mask.\n        /// (\"alpha\", \"red\", \"green\", \"blue\")\n        /// </summary>\n        public string Channel { get; init; } = \"alpha\";\n    }\n\n    public record PreviewImage : ComfyTypedNodeBase\n    {\n        public required ImageNodeConnection Images { get; init; }\n    }\n\n    public record ImageSharpen : ComfyTypedNodeBase<ImageNodeConnection>\n    {\n        public required ImageNodeConnection Image { get; init; }\n        public required int SharpenRadius { get; init; }\n        public required double Sigma { get; init; }\n        public required double Alpha { get; init; }\n    }\n\n    public record ControlNetLoader : ComfyTypedNodeBase<ControlNetNodeConnection>\n    {\n        public required string ControlNetName { get; init; }\n    }\n\n    public record ControlNetApplyAdvanced\n        : ComfyTypedNodeBase<ConditioningNodeConnection, ConditioningNodeConnection>\n    {\n        public required ConditioningNodeConnection Positive { get; init; }\n        public required ConditioningNodeConnection Negative { get; init; }\n        public required ControlNetNodeConnection ControlNet { get; init; }\n        public required ImageNodeConnection Image { get; init; }\n        public required double Strength { get; init; }\n        public required double StartPercent { get; init; }\n        public required double EndPercent { get; init; }\n    }\n\n    public record SVD_img2vid_Conditioning\n        : ComfyTypedNodeBase<ConditioningNodeConnection, ConditioningNodeConnection, LatentNodeConnection>\n    {\n        public required ClipVisionNodeConnection ClipVision { get; init; }\n        public required ImageNodeConnection InitImage { get; init; }\n        public required VAENodeConnection Vae { get; init; }\n        public required int Width { get; init; }\n        public required int Height { get; init; }\n        public required int VideoFrames { get; init; }\n        public required int MotionBucketId { get; init; }\n        public required int Fps { get; set; }\n        public required double AugmentationLevel { get; init; }\n    }\n\n    public record VideoLinearCFGGuidance : ComfyTypedNodeBase<ModelNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n        public required double MinCfg { get; init; }\n    }\n\n    public record SaveAnimatedWEBP : ComfyTypedNodeBase\n    {\n        public required ImageNodeConnection Images { get; init; }\n        public required string FilenamePrefix { get; init; }\n        public required double Fps { get; init; }\n        public required bool Lossless { get; init; }\n        public required int Quality { get; init; }\n        public required string Method { get; init; }\n    }\n\n    public record UNETLoader : ComfyTypedNodeBase<ModelNodeConnection>\n    {\n        public required string UnetName { get; init; }\n\n        /// <summary>\n        /// possible values: \"default\", \"fp8_e4m3fn\", \"fp8_e5m2\"\n        /// </summary>\n        public required string WeightDtype { get; init; }\n    }\n\n    public record CLIPLoader : ComfyTypedNodeBase<ClipNodeConnection>\n    {\n        public required string ClipName { get; init; }\n\n        /// <summary>\n        /// possible values: \"stable_diffusion\", \"stable_cascade\", \"sd3\", \"stable_audio\", \"mochi\"\n        /// </summary>\n        public required string Type { get; init; }\n    }\n\n    public record DualCLIPLoader : ComfyTypedNodeBase<ClipNodeConnection>\n    {\n        public required string ClipName1 { get; init; }\n        public required string ClipName2 { get; init; }\n\n        /// <summary>\n        /// possible values: \"sdxl\", \"sd3\", \"flux\"\n        /// </summary>\n        public required string Type { get; init; }\n    }\n\n    public record TripleCLIPLoader : ComfyTypedNodeBase<ClipNodeConnection>\n    {\n        public required string ClipName1 { get; init; }\n        public required string ClipName2 { get; init; }\n        public required string ClipName3 { get; init; }\n\n        // no type, always sd3 I guess?\n    }\n\n    public record QuadrupleCLIPLoader : ComfyTypedNodeBase<ClipNodeConnection>\n    {\n        public required string ClipName1 { get; init; }\n        public required string ClipName2 { get; init; }\n        public required string ClipName3 { get; init; }\n        public required string ClipName4 { get; init; }\n\n        // no type, always HiDream I guess?\n    }\n\n    public record CLIPVisionLoader : ComfyTypedNodeBase<ClipVisionNodeConnection>\n    {\n        public required string ClipName { get; init; }\n    }\n\n    public record CLIPVisionEncode : ComfyTypedNodeBase<ClipVisionOutputNodeConnection>\n    {\n        public required ClipVisionNodeConnection ClipVision { get; init; }\n        public required ImageNodeConnection Image { get; init; }\n        public required string Crop { get; set; }\n    }\n\n    public record FluxGuidance : ComfyTypedNodeBase<ConditioningNodeConnection>\n    {\n        public required ConditioningNodeConnection Conditioning { get; init; }\n\n        [Range(0.0d, 100.0d)]\n        public required double Guidance { get; init; }\n    }\n\n    public record BasicGuider : ComfyTypedNodeBase<GuiderNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n        public required ConditioningNodeConnection Conditioning { get; init; }\n    }\n\n    public record EmptySD3LatentImage : ComfyTypedNodeBase<LatentNodeConnection>\n    {\n        [Range(16, MaxResolution)]\n        public int Width { get; init; } = 1024;\n\n        [Range(16, MaxResolution)]\n        public int Height { get; init; } = 1024;\n\n        [Range(1, 4096)]\n        public int BatchSize { get; init; } = 1;\n    }\n\n    public record RandomNoise : ComfyTypedNodeBase<NoiseNodeConnection>\n    {\n        [Range(0, int.MaxValue)]\n        public ulong NoiseSeed { get; init; }\n    }\n\n    public record BasicScheduler : ComfyTypedNodeBase<SigmasNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n        public required string Scheduler { get; init; }\n\n        [Range(1, 10000)]\n        public int Steps { get; init; } = 20;\n\n        [Range(0.0d, 1.0d)]\n        public double Denoise { get; init; } = 1.0;\n    }\n\n    public record SamplerCustomAdvanced : ComfyTypedNodeBase<LatentNodeConnection, LatentNodeConnection>\n    {\n        public required NoiseNodeConnection Noise { get; init; }\n        public required GuiderNodeConnection Guider { get; init; }\n        public required SamplerNodeConnection Sampler { get; init; }\n        public required SigmasNodeConnection Sigmas { get; init; }\n        public required LatentNodeConnection LatentImage { get; init; }\n    }\n\n    public record ModelSamplingDiscrete : ComfyTypedNodeBase<ModelNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n\n        /// <summary>\n        /// Options: \"eps\", \"v_prediction\", \"lcm\", \"x0\"\n        /// </summary>\n        public required string Sampling { get; set; }\n        public required bool Zsnr { get; init; }\n    }\n\n    public record ModelSamplingSD3 : ComfyTypedNodeBase<ModelNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n\n        [Range(0, 100)]\n        public required double Shift { get; init; }\n    }\n\n    public record RescaleCFG : ComfyTypedNodeBase<ModelNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n        public required double Multiplier { get; init; }\n    }\n\n    public record SetLatentNoiseMask : ComfyTypedNodeBase<LatentNodeConnection>\n    {\n        public required LatentNodeConnection Samples { get; init; }\n        public required ImageMaskConnection Mask { get; init; }\n    }\n\n    public record AlignYourStepsScheduler : ComfyTypedNodeBase<SigmasNodeConnection>\n    {\n        /// <summary>\n        /// options: SD1, SDXL, SVD\n        /// </summary>\n        public required string ModelType { get; init; }\n\n        [Range(1, 10000)]\n        public required int Steps { get; init; }\n\n        [Range(0.0d, 1.0d)]\n        public required double Denoise { get; init; }\n    }\n\n    public record CFGGuider : ComfyTypedNodeBase<GuiderNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; set; }\n        public required ConditioningNodeConnection Positive { get; set; }\n        public required ConditioningNodeConnection Negative { get; set; }\n\n        [Range(0.0d, 100.0d)]\n        public required double Cfg { get; set; }\n    }\n\n    /// <summary>\n    /// outputs: positive, negative, latent\n    /// </summary>\n    public record WanImageToVideo\n        : ComfyTypedNodeBase<ConditioningNodeConnection, ConditioningNodeConnection, LatentNodeConnection>\n    {\n        public required ConditioningNodeConnection Positive { get; init; }\n        public required ConditioningNodeConnection Negative { get; init; }\n        public required VAENodeConnection Vae { get; init; }\n        public required ClipVisionOutputNodeConnection ClipVisionOutput { get; init; }\n        public required ImageNodeConnection StartImage { get; init; }\n        public required int Width { get; init; }\n        public required int Height { get; init; }\n        public required int Length { get; init; }\n        public required int BatchSize { get; init; }\n    }\n\n    [TypedNodeOptions(\n        Name = \"CheckpointLoaderNF4\",\n        RequiredExtensions = [\"https://github.com/comfyanonymous/ComfyUI_bitsandbytes_NF4\"]\n    )]\n    public record CheckpointLoaderNF4\n        : ComfyTypedNodeBase<ModelNodeConnection, ClipNodeConnection, VAENodeConnection>\n    {\n        public required string CkptName { get; init; }\n    }\n\n    [TypedNodeOptions(\n        Name = \"UnetLoaderGGUF\",\n        RequiredExtensions = [\"https://github.com/city96/ComfyUI-GGUF\"]\n    )]\n    public record UnetLoaderGGUF : ComfyTypedNodeBase<ModelNodeConnection>\n    {\n        public required string UnetName { get; init; }\n    }\n\n    [TypedNodeOptions(\n        Name = \"Inference_Core_PromptExpansion\",\n        RequiredExtensions = [\"https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes >= 0.2.0\"]\n    )]\n    public record PromptExpansion : ComfyTypedNodeBase<StringNodeConnection>\n    {\n        public required string ModelName { get; init; }\n        public required OneOf<string, StringNodeConnection> Text { get; init; }\n        public required ulong Seed { get; init; }\n        public bool LogPrompt { get; init; }\n    }\n\n    [TypedNodeOptions(\n        Name = \"Inference_Core_AIO_Preprocessor\",\n        RequiredExtensions = [\"https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes >= 0.2.0\"]\n    )]\n    public record AIOPreprocessor : ComfyTypedNodeBase<ImageNodeConnection>\n    {\n        public required ImageNodeConnection Image { get; init; }\n\n        public required string Preprocessor { get; init; }\n\n        [Range(64, 16384)]\n        public int Resolution { get; init; } = 512;\n    }\n\n    [TypedNodeOptions(\n        Name = \"Inference_Core_ReferenceOnlySimple\",\n        RequiredExtensions = [\"https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes >= 0.3.0\"]\n    )]\n    public record ReferenceOnlySimple : ComfyTypedNodeBase<ModelNodeConnection, LatentNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n\n        public required LatentNodeConnection Reference { get; init; }\n\n        [Range(1, 64)]\n        public int BatchSize { get; init; } = 1;\n    }\n\n    [TypedNodeOptions(\n        Name = \"Inference_Core_LayeredDiffusionApply\",\n        RequiredExtensions = [\"https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes >= 0.4.0\"]\n    )]\n    public record LayeredDiffusionApply : ComfyTypedNodeBase<ModelNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n\n        /// <summary>\n        /// Available configs:\n        /// <para>SD15, Attention Injection, attn_sharing</para>\n        /// <para>SDXL, Conv Injection</para>\n        /// <para>SDXL, Attention Injection</para>\n        /// </summary>\n        public required string Config { get; init; }\n\n        [Range(-1d, 3d)]\n        public double Weight { get; init; } = 1.0;\n    }\n\n    [TypedNodeOptions(\n        Name = \"Inference_Core_LayeredDiffusionDecodeRGBA\",\n        RequiredExtensions = [\"https://github.com/LykosAI/ComfyUI-Inference-Core-Nodes >= 0.4.0\"]\n    )]\n    public record LayeredDiffusionDecodeRgba : ComfyTypedNodeBase<ImageNodeConnection>\n    {\n        public required LatentNodeConnection Samples { get; init; }\n\n        public required ImageNodeConnection Images { get; init; }\n\n        /// <summary>\n        /// Either \"SD15\" or \"SDXL\"\n        /// </summary>\n        public required string SdVersion { get; init; }\n\n        [Range(1, 4096)]\n        public int SubBatchSize { get; init; } = 16;\n    }\n\n    [TypedNodeOptions(\n        Name = \"UltralyticsDetectorProvider\",\n        RequiredExtensions = [\n            \"https://github.com/ltdrdata/ComfyUI-Impact-Pack\",\n            \"https://github.com/ltdrdata/ComfyUI-Impact-Subpack\",\n        ]\n    )]\n    public record UltralyticsDetectorProvider\n        : ComfyTypedNodeBase<BboxDetectorNodeConnection, SegmDetectorNodeConnection>\n    {\n        public required string ModelName { get; init; }\n    }\n\n    [TypedNodeOptions(\n        Name = \"SAMLoader\",\n        RequiredExtensions = [\n            \"https://github.com/ltdrdata/ComfyUI-Impact-Pack\",\n            \"https://github.com/ltdrdata/ComfyUI-Impact-Subpack\",\n        ]\n    )]\n    public record SamLoader : ComfyTypedNodeBase<SamModelNodeConnection>\n    {\n        public required string ModelName { get; init; }\n\n        /// <summary>\n        /// options: AUTO, Prefer GPU, CPU\n        /// </summary>\n        public required string DeviceMode { get; init; }\n    }\n\n    [TypedNodeOptions(\n        Name = \"FaceDetailer\",\n        RequiredExtensions = [\n            \"https://github.com/ltdrdata/ComfyUI-Impact-Pack\",\n            \"https://github.com/ltdrdata/ComfyUI-Impact-Subpack\",\n        ]\n    )]\n    public record FaceDetailer : ComfyTypedNodeBase<ImageNodeConnection>\n    {\n        public required ImageNodeConnection Image { get; init; }\n        public required ModelNodeConnection Model { get; init; }\n        public required ClipNodeConnection Clip { get; init; }\n        public required VAENodeConnection Vae { get; init; }\n        public required ConditioningNodeConnection Positive { get; init; }\n        public required ConditioningNodeConnection Negative { get; init; }\n        public required BboxDetectorNodeConnection BboxDetector { get; init; }\n        public required double GuideSize { get; init; } = 512.0;\n\n        /// <summary>\n        /// true: 'bbox'\n        /// false: 'crop_region'\n        /// </summary>\n        public required bool GuideSizeFor { get; init; } = true;\n        public required double MaxSize { get; init; } = 1024.0;\n        public required ulong Seed { get; init; }\n        public required int Steps { get; init; } = 20;\n        public required double Cfg { get; init; } = 8.0d;\n        public required string SamplerName { get; init; }\n        public required string Scheduler { get; init; }\n        public required double Denoise { get; init; } = 0.5d;\n        public required int Feather { get; init; } = 5;\n        public required bool NoiseMask { get; init; } = true;\n        public required bool ForceInpaint { get; init; } = true;\n\n        [Range(0.0, 1.0)]\n        public required double BboxThreshold { get; init; } = 0.5d;\n\n        [Range(-512, 512)]\n        public required int BboxDilation { get; init; } = 10;\n\n        [Range(1.0, 10.0)]\n        public required double BboxCropFactor { get; init; } = 3.0d;\n\n        /// <summary>\n        /// options: [\"center-1\", \"horizontal-2\", \"vertical-2\", \"rect-4\", \"diamond-4\", \"mask-area\", \"mask-points\", \"mask-point-bbox\", \"none\"]\n        /// </summary>\n        public required string SamDetectionHint { get; init; }\n\n        [Range(-512, 512)]\n        public required int SamDilation { get; init; }\n\n        [Range(0.0d, 1.0d)]\n        public required double SamThreshold { get; init; } = 0.93d;\n\n        [Range(0, 1000)]\n        public required int SamBboxExpansion { get; init; }\n\n        [Range(0.0d, 1.0d)]\n        public required double SamMaskHintThreshold { get; init; } = 0.7d;\n\n        /// <summary>\n        /// options: [\"False\", \"Small\", \"Outter\"]\n        /// </summary>\n        public required string SamMaskHintUseNegative { get; init; } = \"False\";\n\n        public required string Wildcard { get; init; }\n\n        [Range(1, 32768)]\n        public required int DropSize { get; init; } = 10;\n\n        [Range(1, 10)]\n        public required int Cycle { get; init; } = 1;\n\n        public SamModelNodeConnection? SamModelOpt { get; set; }\n        public SegmDetectorNodeConnection? SegmDetectorOpt { get; set; }\n        public bool TiledEncode { get; init; }\n        public bool TiledDecode { get; init; }\n    }\n\n    /// <summary>\n    /// Plasma Noise generation node (Lykos_JDC_Plasma)\n    /// </summary>\n    [TypedNodeOptions(\n        Name = \"Lykos_JDC_Plasma\",\n        RequiredExtensions = [\"https://github.com/LykosAI/inference-comfy-plasma\"]\n    )] // Name corrected, Extensions added\n    public record PlasmaNoise : ComfyTypedNodeBase<ImageNodeConnection>\n    {\n        [Range(128, 8192)]\n        public required int Width { get; init; } = 512;\n\n        [Range(128, 8192)]\n        public required int Height { get; init; } = 512;\n\n        [Range(0.5d, 32.0d)]\n        public required double Turbulence { get; init; } = 2.75;\n\n        [Range(-1, 255)]\n        public required int ValueMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int ValueMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int RedMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int RedMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int GreenMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int GreenMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int BlueMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int BlueMax { get; init; } = -1;\n\n        [Range(0UL, ulong.MaxValue)] // Match Python's max int size\n        public required ulong Seed { get; init; } = 0;\n    }\n\n    /// <summary>\n    /// Random Noise generation node (Lykos_JDC_RandNoise)\n    /// </summary>\n    [TypedNodeOptions(\n        Name = \"Lykos_JDC_RandNoise\",\n        RequiredExtensions = [\"https://github.com/LykosAI/inference-comfy-plasma\"]\n    )] // Name corrected, Extensions added\n    public record RandNoise : ComfyTypedNodeBase<ImageNodeConnection>\n    {\n        [Range(128, 8192)]\n        public required int Width { get; init; } = 512;\n\n        [Range(128, 8192)]\n        public required int Height { get; init; } = 512;\n\n        [Range(-1, 255)]\n        public required int ValueMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int ValueMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int RedMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int RedMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int GreenMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int GreenMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int BlueMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int BlueMax { get; init; } = -1;\n\n        [Range(0UL, ulong.MaxValue)]\n        public required ulong Seed { get; init; } = 0;\n    }\n\n    /// <summary>\n    /// Greyscale Noise generation node (Lykos_JDC_GreyNoise)\n    /// </summary>\n    [TypedNodeOptions(\n        Name = \"Lykos_JDC_GreyNoise\",\n        RequiredExtensions = [\"https://github.com/LykosAI/inference-comfy-plasma\"]\n    )] // Name corrected, Extensions added\n    public record GreyNoise : ComfyTypedNodeBase<ImageNodeConnection>\n    {\n        [Range(128, 8192)]\n        public required int Width { get; init; } = 512;\n\n        [Range(128, 8192)]\n        public required int Height { get; init; } = 512;\n\n        [Range(-1, 255)]\n        public required int ValueMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int ValueMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int RedMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int RedMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int GreenMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int GreenMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int BlueMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int BlueMax { get; init; } = -1;\n\n        [Range(0UL, ulong.MaxValue)]\n        public required ulong Seed { get; init; } = 0;\n    }\n\n    /// <summary>\n    /// Pink Noise generation node (Lykos_JDC_PinkNoise)\n    /// </summary>\n    [TypedNodeOptions(\n        Name = \"Lykos_JDC_PinkNoise\",\n        RequiredExtensions = [\"https://github.com/LykosAI/inference-comfy-plasma\"]\n    )] // Name corrected, Extensions added\n    public record PinkNoise : ComfyTypedNodeBase<ImageNodeConnection>\n    {\n        [Range(128, 8192)]\n        public required int Width { get; init; } = 512;\n\n        [Range(128, 8192)]\n        public required int Height { get; init; } = 512;\n\n        [Range(-1, 255)]\n        public required int ValueMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int ValueMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int RedMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int RedMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int GreenMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int GreenMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int BlueMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int BlueMax { get; init; } = -1;\n\n        [Range(0UL, ulong.MaxValue)]\n        public required ulong Seed { get; init; } = 0;\n    }\n\n    /// <summary>\n    /// Brown Noise generation node (Lykos_JDC_BrownNoise)\n    /// </summary>\n    [TypedNodeOptions(\n        Name = \"Lykos_JDC_BrownNoise\",\n        RequiredExtensions = new[] { \"https://github.com/LykosAI/inference-comfy-plasma\" }\n    )] // Name corrected, Extensions added\n    public record BrownNoise : ComfyTypedNodeBase<ImageNodeConnection>\n    {\n        [Range(128, 8192)]\n        public required int Width { get; init; } = 512;\n\n        [Range(128, 8192)]\n        public required int Height { get; init; } = 512;\n\n        [Range(-1, 255)]\n        public required int ValueMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int ValueMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int RedMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int RedMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int GreenMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int GreenMax { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int BlueMin { get; init; } = -1;\n\n        [Range(-1, 255)]\n        public required int BlueMax { get; init; } = -1;\n\n        [Range(0UL, ulong.MaxValue)]\n        public required ulong Seed { get; init; } = 0;\n    }\n\n    /// <summary>\n    /// CUDNN Toggle node for controlling CUDA Deep Neural Network library settings (CUDNNToggleAutoPassthrough)\n    /// </summary>\n    [TypedNodeOptions(Name = \"CUDNNToggleAutoPassthrough\")]\n    public record CUDNNToggleAutoPassthrough\n        : ComfyTypedNodeBase<ModelNodeConnection, ConditioningNodeConnection, LatentNodeConnection>\n    {\n        public ModelNodeConnection? Model { get; init; }\n        public ConditioningNodeConnection? Conditioning { get; init; }\n        public LatentNodeConnection? Latent { get; init; }\n        public required bool EnableCudnn { get; init; } = false;\n        public required bool CudnnBenchmark { get; init; } = false;\n    }\n\n    /// <summary>\n    /// Custom KSampler node using alternative noise distribution (Lykos_JDC_PlasmaSampler)\n    /// </summary>\n    [TypedNodeOptions(\n        Name = \"Lykos_JDC_PlasmaSampler\",\n        RequiredExtensions = [\"https://github.com/LykosAI/inference-comfy-plasma\"]\n    )] // Name corrected, Extensions added\n    public record PlasmaSampler : ComfyTypedNodeBase<LatentNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n\n        [Range(0UL, ulong.MaxValue)]\n        public required ulong NoiseSeed { get; init; } = 0;\n\n        [Range(1, 10000)]\n        public required int Steps { get; init; } = 20;\n\n        [Range(0.0d, 100.0d)]\n        public required double Cfg { get; init; } = 7.0;\n\n        [Range(0.0d, 1.0d)]\n        public required double Denoise { get; init; } = 0.9; // Default from Python code\n\n        [Range(0.0d, 1.0d)]\n        public required double LatentNoise { get; init; } = 0.05; // Default from Python code\n\n        /// <summary>\n        /// Noise distribution type. Expected values: \"default\", \"rand\".\n        /// Validation should ensure one of these values is passed.\n        /// </summary>\n        public required string DistributionType { get; init; } = \"rand\";\n\n        /// <summary>\n        /// Name of the KSampler sampler (e.g., \"euler\", \"dpmpp_2m_sde\").\n        /// Should correspond to available samplers in comfy.samplers.KSampler.SAMPLERS.\n        /// </summary>\n        public required string SamplerName { get; init; } // No default in Python, must be provided\n\n        /// <summary>\n        /// Name of the KSampler scheduler (e.g., \"normal\", \"karras\", \"sgm_uniform\").\n        /// Should correspond to available schedulers in comfy.samplers.KSampler.SCHEDULERS.\n        /// </summary>\n        public required string Scheduler { get; init; } // No default in Python, must be provided\n\n        public required ConditioningNodeConnection Positive { get; init; }\n        public required ConditioningNodeConnection Negative { get; init; }\n        public required LatentNodeConnection LatentImage { get; init; }\n    }\n\n    [TypedNodeOptions(\n        Name = \"NRS\",\n        RequiredExtensions = [\"https://github.com/Reithan/negative_rejection_steering\"]\n    )]\n    public record NRS : ComfyTypedNodeBase<ModelNodeConnection>\n    {\n        public required ModelNodeConnection Model { get; init; }\n\n        [Range(-30.0f, 30.0f)]\n        public required double Skew { get; set; }\n\n        [Range(-30.0f, 30.0f)]\n        public required double Stretch { get; set; }\n\n        [Range(0f, 1f)]\n        public required double Squash { get; set; }\n    }\n\n    public ImageNodeConnection Lambda_LatentToImage(LatentNodeConnection latent, VAENodeConnection vae)\n    {\n        var name = GetUniqueName(\"VAEDecode\");\n        return Nodes\n            .AddTypedNode(\n                new VAEDecode\n                {\n                    Name = name,\n                    Samples = latent,\n                    Vae = vae,\n                }\n            )\n            .Output;\n    }\n\n    public LatentNodeConnection Lambda_ImageToLatent(ImageNodeConnection pixels, VAENodeConnection vae)\n    {\n        var name = GetUniqueName(\"VAEEncode\");\n        return Nodes\n            .AddTypedNode(\n                new VAEEncode\n                {\n                    Name = name,\n                    Pixels = pixels,\n                    Vae = vae,\n                }\n            )\n            .Output;\n    }\n\n    /// <summary>\n    /// Create a group node that upscales a given image with a given model\n    /// </summary>\n    public NamedComfyNode<ImageNodeConnection> Group_UpscaleWithModel(\n        string name,\n        string modelName,\n        ImageNodeConnection image\n    )\n    {\n        var modelLoader = Nodes.AddNamedNode(UpscaleModelLoader($\"{name}_UpscaleModelLoader\", modelName));\n\n        var upscaler = Nodes.AddNamedNode(\n            ImageUpscaleWithModel($\"{name}_ImageUpscaleWithModel\", modelLoader.Output, image)\n        );\n\n        return upscaler;\n    }\n\n    /// <summary>\n    /// Create a group node that scales a given image to image output\n    /// </summary>\n    public PrimaryNodeConnection Group_Upscale(\n        string name,\n        PrimaryNodeConnection primary,\n        VAENodeConnection vae,\n        ComfyUpscaler upscaleInfo,\n        int width,\n        int height\n    )\n    {\n        if (upscaleInfo.Type == ComfyUpscalerType.Latent)\n        {\n            return primary.Match<PrimaryNodeConnection>(\n                latent =>\n                    Nodes\n                        .AddNamedNode(\n                            new NamedComfyNode<LatentNodeConnection>($\"{name}_LatentUpscale\")\n                            {\n                                ClassType = \"LatentUpscale\",\n                                Inputs = new Dictionary<string, object?>\n                                {\n                                    [\"upscale_method\"] = upscaleInfo.Name,\n                                    [\"width\"] = width,\n                                    [\"height\"] = height,\n                                    [\"crop\"] = \"disabled\",\n                                    [\"samples\"] = latent.Data,\n                                },\n                            }\n                        )\n                        .Output,\n                image =>\n                    Nodes\n                        .AddNamedNode(\n                            ImageScale($\"{name}_ImageUpscale\", image, upscaleInfo.Name, height, width, false)\n                        )\n                        .Output\n            );\n        }\n\n        if (upscaleInfo.Type == ComfyUpscalerType.ESRGAN)\n        {\n            // Convert to image space if needed\n            var samplerImage = GetPrimaryAsImage(primary, vae);\n\n            // Do group upscale\n            var modelUpscaler = Group_UpscaleWithModel(\n                $\"{name}_ModelUpscale\",\n                upscaleInfo.Name,\n                samplerImage\n            );\n\n            // Since the model upscale is fixed to model (2x/4x), scale it again to the requested size\n            var resizedScaled = Nodes.AddNamedNode(\n                ImageScale($\"{name}_ImageScale\", modelUpscaler.Output, \"bilinear\", height, width, false)\n            );\n\n            return resizedScaled.Output;\n        }\n\n        throw new InvalidOperationException($\"Unknown upscaler type: {upscaleInfo.Type}\");\n    }\n\n    /// <summary>\n    /// Create a group node that scales a given image to a given size\n    /// </summary>\n    public NamedComfyNode<LatentNodeConnection> Group_UpscaleToLatent(\n        string name,\n        LatentNodeConnection latent,\n        VAENodeConnection vae,\n        ComfyUpscaler upscaleInfo,\n        int width,\n        int height\n    )\n    {\n        if (upscaleInfo.Type == ComfyUpscalerType.Latent)\n        {\n            return Nodes.AddNamedNode(\n                new NamedComfyNode<LatentNodeConnection>($\"{name}_LatentUpscale\")\n                {\n                    ClassType = \"LatentUpscale\",\n                    Inputs = new Dictionary<string, object?>\n                    {\n                        [\"upscale_method\"] = upscaleInfo.Name,\n                        [\"width\"] = width,\n                        [\"height\"] = height,\n                        [\"crop\"] = \"disabled\",\n                        [\"samples\"] = latent.Data,\n                    },\n                }\n            );\n        }\n\n        if (upscaleInfo.Type == ComfyUpscalerType.ESRGAN)\n        {\n            // Convert to image space\n            var samplerImage = Nodes.AddTypedNode(\n                new VAEDecode\n                {\n                    Name = $\"{name}_VAEDecode\",\n                    Samples = latent,\n                    Vae = vae,\n                }\n            );\n\n            // Do group upscale\n            var modelUpscaler = Group_UpscaleWithModel(\n                $\"{name}_ModelUpscale\",\n                upscaleInfo.Name,\n                samplerImage.Output\n            );\n\n            // Since the model upscale is fixed to model (2x/4x), scale it again to the requested size\n            var resizedScaled = Nodes.AddNamedNode(\n                ImageScale($\"{name}_ImageScale\", modelUpscaler.Output, \"bilinear\", height, width, false)\n            );\n\n            // Convert back to latent space\n            return Nodes.AddTypedNode(\n                new VAEEncode\n                {\n                    Name = $\"{name}_VAEEncode\",\n                    Pixels = resizedScaled.Output,\n                    Vae = vae,\n                }\n            );\n        }\n\n        throw new InvalidOperationException($\"Unknown upscaler type: {upscaleInfo.Type}\");\n    }\n\n    /// <summary>\n    /// Create a group node that scales a given image to image output\n    /// </summary>\n    public NamedComfyNode<ImageNodeConnection> Group_LatentUpscaleToImage(\n        string name,\n        LatentNodeConnection latent,\n        VAENodeConnection vae,\n        ComfyUpscaler upscaleInfo,\n        int width,\n        int height\n    )\n    {\n        if (upscaleInfo.Type == ComfyUpscalerType.Latent)\n        {\n            var latentUpscale = Nodes.AddNamedNode(\n                new NamedComfyNode<LatentNodeConnection>($\"{name}_LatentUpscale\")\n                {\n                    ClassType = \"LatentUpscale\",\n                    Inputs = new Dictionary<string, object?>\n                    {\n                        [\"upscale_method\"] = upscaleInfo.Name,\n                        [\"width\"] = width,\n                        [\"height\"] = height,\n                        [\"crop\"] = \"disabled\",\n                        [\"samples\"] = latent.Data,\n                    },\n                }\n            );\n\n            // Convert to image space\n            return Nodes.AddTypedNode(\n                new VAEDecode\n                {\n                    Name = $\"{name}_VAEDecode\",\n                    Samples = latentUpscale.Output,\n                    Vae = vae,\n                }\n            );\n        }\n\n        if (upscaleInfo.Type == ComfyUpscalerType.ESRGAN)\n        {\n            // Convert to image space\n            var samplerImage = Nodes.AddTypedNode(\n                new VAEDecode\n                {\n                    Name = $\"{name}_VAEDecode\",\n                    Samples = latent,\n                    Vae = vae,\n                }\n            );\n\n            // Do group upscale\n            var modelUpscaler = Group_UpscaleWithModel(\n                $\"{name}_ModelUpscale\",\n                upscaleInfo.Name,\n                samplerImage.Output\n            );\n\n            // Since the model upscale is fixed to model (2x/4x), scale it again to the requested size\n            var resizedScaled = Nodes.AddNamedNode(\n                ImageScale($\"{name}_ImageScale\", modelUpscaler.Output, \"bilinear\", height, width, false)\n            );\n\n            // No need to convert back to latent space\n            return resizedScaled;\n        }\n\n        throw new InvalidOperationException($\"Unknown upscaler type: {upscaleInfo.Type}\");\n    }\n\n    /// <summary>\n    /// Create a group node that scales a given image to image output\n    /// </summary>\n    public NamedComfyNode<ImageNodeConnection> Group_UpscaleToImage(\n        string name,\n        ImageNodeConnection image,\n        ComfyUpscaler upscaleInfo,\n        int width,\n        int height\n    )\n    {\n        if (upscaleInfo.Type == ComfyUpscalerType.Latent)\n        {\n            return Nodes.AddNamedNode(\n                new NamedComfyNode<ImageNodeConnection>($\"{name}_LatentUpscale\")\n                {\n                    ClassType = \"ImageScale\",\n                    Inputs = new Dictionary<string, object?>\n                    {\n                        [\"image\"] = image,\n                        [\"upscale_method\"] = upscaleInfo.Name,\n                        [\"width\"] = width,\n                        [\"height\"] = height,\n                        [\"crop\"] = \"disabled\",\n                    },\n                }\n            );\n        }\n\n        if (upscaleInfo.Type == ComfyUpscalerType.ESRGAN)\n        {\n            // Do group upscale\n            var modelUpscaler = Group_UpscaleWithModel($\"{name}_ModelUpscale\", upscaleInfo.Name, image);\n\n            // Since the model upscale is fixed to model (2x/4x), scale it again to the requested size\n            var resizedScaled = Nodes.AddNamedNode(\n                ImageScale($\"{name}_ImageScale\", modelUpscaler.Output, \"bilinear\", height, width, false)\n            );\n\n            // No need to convert back to latent space\n            return resizedScaled;\n        }\n\n        throw new InvalidOperationException($\"Unknown upscaler type: {upscaleInfo.Type}\");\n    }\n\n    /// <summary>\n    /// Create a group node that loads multiple Lora's in series\n    /// </summary>\n    public NamedComfyNode<ModelNodeConnection, ClipNodeConnection> Group_LoraLoadMany(\n        string name,\n        ModelNodeConnection model,\n        ClipNodeConnection clip,\n        IEnumerable<(string FileName, double? ModelWeight, double? ClipWeight)> loras\n    )\n    {\n        NamedComfyNode<ModelNodeConnection, ClipNodeConnection>? currentNode = null;\n\n        foreach (var (i, loraNetwork) in loras.Enumerate())\n        {\n            currentNode = Nodes.AddNamedNode(\n                LoraLoader(\n                    $\"{name}_LoraLoader_{i + 1}\",\n                    model,\n                    clip,\n                    loraNetwork.FileName,\n                    loraNetwork.ModelWeight ?? 1,\n                    loraNetwork.ClipWeight ?? 1\n                )\n            );\n\n            // Connect to previous node\n            model = currentNode.Output1;\n            clip = currentNode.Output2;\n        }\n\n        return currentNode ?? throw new InvalidOperationException(\"No lora networks given\");\n    }\n\n    /// <summary>\n    /// Create a group node that loads multiple Lora's in series\n    /// </summary>\n    public NamedComfyNode<ModelNodeConnection, ClipNodeConnection> Group_LoraLoadMany(\n        string name,\n        ModelNodeConnection model,\n        ClipNodeConnection clip,\n        IEnumerable<(LocalModelFile ModelFile, double? ModelWeight, double? ClipWeight)> loras\n    )\n    {\n        NamedComfyNode<ModelNodeConnection, ClipNodeConnection>? currentNode = null;\n\n        foreach (var (i, loraNetwork) in loras.Enumerate())\n        {\n            currentNode = Nodes.AddNamedNode(\n                LoraLoader(\n                    $\"{name}_LoraLoader_{i + 1}\",\n                    model,\n                    clip,\n                    loraNetwork.ModelFile.RelativePathFromSharedFolder,\n                    loraNetwork.ModelWeight ?? 1,\n                    loraNetwork.ClipWeight ?? 1\n                )\n            );\n\n            // Connect to previous node\n            model = currentNode.Output1;\n            clip = currentNode.Output2;\n        }\n\n        return currentNode ?? throw new InvalidOperationException(\"No lora networks given\");\n    }\n\n    /// <summary>\n    /// Get or convert latest primary connection to latent\n    /// </summary>\n    public LatentNodeConnection GetPrimaryAsLatent()\n    {\n        if (Connections.Primary?.IsT0 == true)\n        {\n            return Connections.Primary.AsT0;\n        }\n\n        return GetPrimaryAsLatent(\n            Connections.Primary ?? throw new NullReferenceException(\"No primary connection\"),\n            Connections.GetDefaultVAE()\n        );\n    }\n\n    /// <summary>\n    /// Get or convert latest primary connection to latent\n    /// </summary>\n    public LatentNodeConnection GetPrimaryAsLatent(PrimaryNodeConnection primary, VAENodeConnection vae)\n    {\n        return primary.Match(latent => latent, image => Lambda_ImageToLatent(image, vae));\n    }\n\n    /// <summary>\n    /// Get or convert latest primary connection to latent\n    /// </summary>\n    public LatentNodeConnection GetPrimaryAsLatent(VAENodeConnection vae)\n    {\n        if (Connections.Primary?.IsT0 == true)\n        {\n            return Connections.Primary.AsT0;\n        }\n\n        return GetPrimaryAsLatent(\n            Connections.Primary ?? throw new NullReferenceException(\"No primary connection\"),\n            vae\n        );\n    }\n\n    /// <summary>\n    /// Get or convert latest primary connection to image\n    /// </summary>\n    public ImageNodeConnection GetPrimaryAsImage()\n    {\n        if (Connections.Primary?.IsT1 == true)\n        {\n            return Connections.Primary.AsT1;\n        }\n\n        return GetPrimaryAsImage(\n            Connections.Primary ?? throw new NullReferenceException(\"No primary connection\"),\n            Connections.GetDefaultVAE()\n        );\n    }\n\n    /// <summary>\n    /// Get or convert latest primary connection to image\n    /// </summary>\n    public ImageNodeConnection GetPrimaryAsImage(PrimaryNodeConnection primary, VAENodeConnection vae)\n    {\n        return primary.Match(latent => Lambda_LatentToImage(latent, vae), image => image);\n    }\n\n    /// <summary>\n    /// Get or convert latest primary connection to image\n    /// </summary>\n    public ImageNodeConnection GetPrimaryAsImage(VAENodeConnection vae)\n    {\n        if (Connections.Primary?.IsT1 == true)\n        {\n            return Connections.Primary.AsT1;\n        }\n\n        return GetPrimaryAsImage(\n            Connections.Primary ?? throw new NullReferenceException(\"No primary connection\"),\n            vae\n        );\n    }\n\n    /// <summary>\n    /// Convert to a NodeDictionary\n    /// </summary>\n    public NodeDictionary ToNodeDictionary()\n    {\n        Nodes.NormalizeConnectionTypes();\n        return Nodes;\n    }\n\n    public class NodeBuilderConnections\n    {\n        public ulong Seed { get; set; }\n\n        public int BatchSize { get; set; } = 1;\n        public int? BatchIndex { get; set; }\n        public int? PrimarySteps { get; set; }\n        public double? PrimaryCfg { get; set; }\n        public string? PrimaryModelType { get; set; }\n\n        public OneOf<string, StringNodeConnection> PositivePrompt { get; set; }\n        public OneOf<string, StringNodeConnection> NegativePrompt { get; set; }\n\n        public ClipNodeConnection? BaseClip { get; set; }\n        public ClipVisionNodeConnection? BaseClipVision { get; set; }\n\n        public Dictionary<string, ModelConnections> Models { get; } =\n            new() { [\"Base\"] = new ModelConnections(\"Base\"), [\"Refiner\"] = new ModelConnections(\"Refiner\") };\n\n        /// <summary>\n        /// ModelConnections from <see cref=\"Models\"/> with <see cref=\"ModelConnections.Model\"/> set\n        /// </summary>\n        public IEnumerable<ModelConnections> LoadedModels => Models.Values.Where(m => m.Model is not null);\n\n        public ModelConnections Base => Models[\"Base\"];\n        public ModelConnections Refiner => Models[\"Refiner\"];\n\n        public Dictionary<string, ModuleApplyStepTemporaryArgs?> SamplerTemporaryArgs { get; } = new();\n\n        public ModuleApplyStepTemporaryArgs? BaseSamplerTemporaryArgs\n        {\n            get => SamplerTemporaryArgs.GetValueOrDefault(\"Base\");\n            set => SamplerTemporaryArgs[\"Base\"] = value;\n        }\n\n        /// <summary>\n        /// The last primary set latent value, updated when <see cref=\"Primary\"/> is set to a latent value.\n        /// </summary>\n        public LatentNodeConnection? LastPrimaryLatent { get; private set; }\n\n        private PrimaryNodeConnection? primary;\n\n        public PrimaryNodeConnection? Primary\n        {\n            get => primary;\n            set\n            {\n                if (value?.IsT0 == true)\n                {\n                    LastPrimaryLatent = value.AsT0;\n                }\n                primary = value;\n            }\n        }\n\n        public VAENodeConnection? PrimaryVAE { get; set; }\n        public Size PrimarySize { get; set; }\n\n        public ComfySampler? PrimarySampler { get; set; }\n        public ComfyScheduler? PrimaryScheduler { get; set; }\n\n        public GuiderNodeConnection PrimaryGuider { get; set; }\n        public NoiseNodeConnection PrimaryNoise { get; set; }\n        public SigmasNodeConnection PrimarySigmas { get; set; }\n        public SamplerNodeConnection PrimarySamplerNode { get; set; }\n\n        public List<NamedComfyNode> OutputNodes { get; } = new();\n\n        public IEnumerable<string> OutputNodeNames => OutputNodes.Select(n => n.Name);\n\n        public ModelNodeConnection GetRefinerOrBaseModel()\n        {\n            return Refiner.Model\n                ?? Base.Model\n                ?? throw new NullReferenceException(\"No Refiner or Base Model\");\n        }\n\n        public ConditioningConnections GetRefinerOrBaseConditioning()\n        {\n            return Refiner.Conditioning\n                ?? Base.Conditioning\n                ?? throw new NullReferenceException(\"No Refiner or Base Conditioning\");\n        }\n\n        public VAENodeConnection GetDefaultVAE()\n        {\n            return PrimaryVAE ?? Refiner.VAE ?? Base.VAE ?? throw new NullReferenceException(\"No VAE\");\n        }\n    }\n\n    public NodeBuilderConnections Connections { get; } = new();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/Nodes/ComfyTypedNodeBase.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Reflection;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\nusing Yoh.Text.Json.NamingPolicies;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\npublic abstract record ComfyTypedNodeBase\n{\n    [Localizable(false)]\n    protected virtual string ClassType\n    {\n        get\n        {\n            var type = GetType();\n\n            // Use options name if available\n            if (type.GetCustomAttribute<TypedNodeOptionsAttribute>() is { } options)\n            {\n                if (!string.IsNullOrEmpty(options.Name))\n                {\n                    return options.Name;\n                }\n            }\n\n            // Otherwise use class name\n            return type.Name;\n        }\n    }\n\n    [Localizable(false)]\n    [JsonIgnore]\n    public required string Name { get; init; }\n\n    protected NamedComfyNode ToNamedNode()\n    {\n        var inputs = new Dictionary<string, object?>();\n\n        // Loop through all properties, key is property name as snake_case, or JsonPropertyName\n        var namingPolicy = JsonNamingPolicies.SnakeCaseLower;\n\n        foreach (var property in GetType().GetProperties())\n        {\n            if (property.Name == nameof(Name) || property.GetValue(this) is not { } value)\n                continue;\n\n            // Skip JsonIgnore\n            if (property.GetCustomAttribute<JsonIgnoreAttribute>() is not null)\n                continue;\n\n            var key =\n                property.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name\n                ?? namingPolicy.ConvertName(property.Name);\n\n            // If theres a BoolStringMember attribute, convert to one of the strings\n            if (property.GetCustomAttribute<BoolStringMemberAttribute>() is { } converter)\n            {\n                if (value is bool boolValue)\n                {\n                    inputs.Add(key, boolValue ? converter.TrueString : converter.FalseString);\n                }\n                else\n                {\n                    throw new InvalidOperationException(\n                        $\"Property {property.Name} is not a bool, but has a BoolStringMember attribute\"\n                    );\n                }\n\n                continue;\n            }\n\n            // For connection types, use data property\n            if (value is NodeConnectionBase connection)\n            {\n                inputs.Add(key, connection.Data);\n            }\n            else\n            {\n                inputs.Add(key, value);\n            }\n        }\n\n        return new NamedComfyNode(Name) { ClassType = ClassType, Inputs = inputs };\n    }\n\n    // Implicit conversion to NamedComfyNode\n    public static implicit operator NamedComfyNode(ComfyTypedNodeBase node) => node.ToNamedNode();\n}\n\npublic abstract record ComfyTypedNodeBase<TOutput> : ComfyTypedNodeBase\n    where TOutput : NodeConnectionBase, new()\n{\n    [JsonIgnore]\n    public TOutput Output => new() { Data = new object[] { Name, 0 } };\n\n    public static implicit operator NamedComfyNode<TOutput>(ComfyTypedNodeBase<TOutput> node) =>\n        (NamedComfyNode<TOutput>)node.ToNamedNode();\n}\n\npublic abstract record ComfyTypedNodeBase<TOutput1, TOutput2> : ComfyTypedNodeBase\n    where TOutput1 : NodeConnectionBase, new()\n    where TOutput2 : NodeConnectionBase, new()\n{\n    [JsonIgnore]\n    public TOutput1 Output1 => new() { Data = new object[] { Name, 0 } };\n\n    [JsonIgnore]\n    public TOutput2 Output2 => new() { Data = new object[] { Name, 1 } };\n\n    public static implicit operator NamedComfyNode<TOutput1, TOutput2>(\n        ComfyTypedNodeBase<TOutput1, TOutput2> node\n    ) => (NamedComfyNode<TOutput1, TOutput2>)node.ToNamedNode();\n}\n\npublic abstract record ComfyTypedNodeBase<TOutput1, TOutput2, TOutput3> : ComfyTypedNodeBase\n    where TOutput1 : NodeConnectionBase, new()\n    where TOutput2 : NodeConnectionBase, new()\n    where TOutput3 : NodeConnectionBase, new()\n{\n    [JsonIgnore]\n    public TOutput1 Output1 => new() { Data = new object[] { Name, 0 } };\n\n    [JsonIgnore]\n    public TOutput2 Output2 => new() { Data = new object[] { Name, 1 } };\n\n    [JsonIgnore]\n    public TOutput3 Output3 => new() { Data = new object[] { Name, 2 } };\n\n    public static implicit operator NamedComfyNode<TOutput1, TOutput2, TOutput3>(\n        ComfyTypedNodeBase<TOutput1, TOutput2, TOutput3> node\n    ) => (NamedComfyNode<TOutput1, TOutput2, TOutput3>)node.ToNamedNode();\n}\n\npublic abstract record ComfyTypedNodeBase<TOutput1, TOutput2, TOutput3, TOutput4> : ComfyTypedNodeBase\n    where TOutput1 : NodeConnectionBase, new()\n    where TOutput2 : NodeConnectionBase, new()\n    where TOutput3 : NodeConnectionBase, new()\n    where TOutput4 : NodeConnectionBase, new()\n{\n    [JsonIgnore]\n    public TOutput1 Output1 => new() { Data = new object[] { Name, 0 } };\n\n    [JsonIgnore]\n    public TOutput2 Output2 => new() { Data = new object[] { Name, 1 } };\n\n    [JsonIgnore]\n    public TOutput3 Output3 => new() { Data = new object[] { Name, 2 } };\n\n    [JsonIgnore]\n    public TOutput4 Output4 => new() { Data = new object[] { Name, 3 } };\n\n    public static implicit operator NamedComfyNode<TOutput1, TOutput2, TOutput3, TOutput4>(\n        ComfyTypedNodeBase<TOutput1, TOutput2, TOutput3, TOutput4> node\n    ) => (NamedComfyNode<TOutput1, TOutput2, TOutput3, TOutput4>)node.ToNamedNode();\n}\n\npublic abstract record ComfyTypedNodeBase<TOutput1, TOutput2, TOutput3, TOutput4, TOutput5>\n    : ComfyTypedNodeBase\n    where TOutput1 : NodeConnectionBase, new()\n    where TOutput2 : NodeConnectionBase, new()\n    where TOutput3 : NodeConnectionBase, new()\n    where TOutput4 : NodeConnectionBase, new()\n    where TOutput5 : NodeConnectionBase, new()\n{\n    [JsonIgnore]\n    public TOutput1 Output1 => new() { Data = new object[] { Name, 0 } };\n\n    [JsonIgnore]\n    public TOutput2 Output2 => new() { Data = new object[] { Name, 1 } };\n\n    [JsonIgnore]\n    public TOutput3 Output3 => new() { Data = new object[] { Name, 2 } };\n\n    [JsonIgnore]\n    public TOutput4 Output4 => new() { Data = new object[] { Name, 3 } };\n\n    [JsonIgnore]\n    public TOutput5 Output5 => new() { Data = new object[] { Name, 4 } };\n\n    public static implicit operator NamedComfyNode<TOutput1, TOutput2, TOutput3, TOutput4, TOutput5>(\n        ComfyTypedNodeBase<TOutput1, TOutput2, TOutput3, TOutput4, TOutput5> node\n    ) => (NamedComfyNode<TOutput1, TOutput2, TOutput3, TOutput4, TOutput5>)node.ToNamedNode();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/Nodes/IOutputNode.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\npublic interface IOutputNode\n{\n    /// <summary>\n    /// Returns { Name, index } for use as a node connection\n    /// </summary>\n    public object[] GetOutput(int index);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/Nodes/NamedComfyNode.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\n[JsonSerializable(typeof(NamedComfyNode))]\npublic record NamedComfyNode([property: JsonIgnore] string Name) : ComfyNode, IOutputNode\n{\n    /// <summary>\n    /// Returns { Name, index } for use as a node connection\n    /// </summary>\n    public object[] GetOutput(int index)\n    {\n        return new object[] { Name, index };\n    }\n\n    /// <summary>\n    /// Returns typed { Name, index } for use as a node connection\n    /// </summary>\n    public TOutput GetOutput<TOutput>(int index)\n        where TOutput : NodeConnectionBase, new()\n    {\n        return new TOutput { Data = GetOutput(index) };\n    }\n}\n\n[JsonSerializable(typeof(NamedComfyNode<>))]\npublic record NamedComfyNode<TOutput>(string Name) : NamedComfyNode(Name)\n    where TOutput : NodeConnectionBase, new()\n{\n    public TOutput Output => new TOutput { Data = GetOutput(0) };\n}\n\n[JsonSerializable(typeof(NamedComfyNode<>))]\npublic record NamedComfyNode<TOutput1, TOutput2>(string Name) : NamedComfyNode(Name)\n    where TOutput1 : NodeConnectionBase, new()\n    where TOutput2 : NodeConnectionBase, new()\n{\n    public TOutput1 Output1 => new() { Data = GetOutput(0) };\n\n    public TOutput2 Output2 => new() { Data = GetOutput(1) };\n}\n\n[JsonSerializable(typeof(NamedComfyNode<>))]\npublic record NamedComfyNode<TOutput1, TOutput2, TOutput3>(string Name) : NamedComfyNode(Name)\n    where TOutput1 : NodeConnectionBase, new()\n    where TOutput2 : NodeConnectionBase, new()\n    where TOutput3 : NodeConnectionBase, new()\n{\n    public TOutput1 Output1 => new() { Data = GetOutput(0) };\n\n    public TOutput2 Output2 => new() { Data = GetOutput(1) };\n\n    public TOutput3 Output3 => new() { Data = GetOutput(2) };\n}\n\n[JsonSerializable(typeof(NamedComfyNode<>))]\npublic record NamedComfyNode<TOutput1, TOutput2, TOutput3, TOutput4>(string Name) : NamedComfyNode(Name)\n    where TOutput1 : NodeConnectionBase, new()\n    where TOutput2 : NodeConnectionBase, new()\n    where TOutput3 : NodeConnectionBase, new()\n    where TOutput4 : NodeConnectionBase, new()\n{\n    public TOutput1 Output1 => new() { Data = GetOutput(0) };\n    public TOutput2 Output2 => new() { Data = GetOutput(1) };\n    public TOutput3 Output3 => new() { Data = GetOutput(2) };\n    public TOutput4 Output4 => new() { Data = GetOutput(3) };\n}\n\n[JsonSerializable(typeof(NamedComfyNode<>))]\npublic record NamedComfyNode<TOutput1, TOutput2, TOutput3, TOutput4, TOutput5>(string Name)\n    : NamedComfyNode(Name)\n    where TOutput1 : NodeConnectionBase, new()\n    where TOutput2 : NodeConnectionBase, new()\n    where TOutput3 : NodeConnectionBase, new()\n    where TOutput4 : NodeConnectionBase, new()\n    where TOutput5 : NodeConnectionBase, new()\n{\n    public TOutput1 Output1 => new() { Data = GetOutput(0) };\n    public TOutput2 Output2 => new() { Data = GetOutput(1) };\n    public TOutput3 Output3 => new() { Data = GetOutput(2) };\n    public TOutput4 Output4 => new() { Data = GetOutput(3) };\n    public TOutput5 Output5 => new() { Data = GetOutput(4) };\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/Nodes/NodeDictionary.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Reflection;\nusing System.Text.Json.Serialization;\nusing KGySoft.CoreLibraries;\nusing OneOf;\nusing StabilityMatrix.Core.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\npublic class NodeDictionary : Dictionary<string, ComfyNode>\n{\n    /// <summary>\n    /// Tracks base names and their highest index resulting from <see cref=\"GetUniqueName\"/>\n    /// </summary>\n    private readonly Dictionary<string, int> _baseNameIndex = new();\n\n    /// <summary>\n    /// When inserting TypedNodes, this holds a mapping of ClassType to required extensions\n    /// </summary>\n    [JsonIgnore]\n    public Dictionary<string, ExtensionSpecifier[]> ClassTypeRequiredExtensions { get; } = new();\n\n    public IEnumerable<ExtensionSpecifier> RequiredExtensions =>\n        ClassTypeRequiredExtensions.Values.SelectMany(x => x);\n\n    /// <summary>\n    /// Finds a unique node name given a base name, by appending _2, _3, etc.\n    /// </summary>\n    public string GetUniqueName([Localizable(false)] string nameBase)\n    {\n        if (_baseNameIndex.TryGetValue(nameBase, out var index))\n        {\n            var newIndex = checked(index + 1);\n            _baseNameIndex[nameBase] = newIndex;\n            return $\"{nameBase}_{newIndex}\";\n        }\n\n        // Ensure new name does not exist\n        if (ContainsKey(nameBase))\n        {\n            throw new InvalidOperationException($\"Initial unique name already exists for base {nameBase}\");\n        }\n\n        _baseNameIndex.Add(nameBase, 1);\n\n        return nameBase;\n    }\n\n    public TNamedNode AddNamedNode<TNamedNode>(TNamedNode node)\n        where TNamedNode : NamedComfyNode\n    {\n        Add(node.Name, node);\n        return node;\n    }\n\n    public TTypedNode AddTypedNode<TTypedNode>(TTypedNode node)\n        where TTypedNode : ComfyTypedNodeBase\n    {\n        var namedNode = (NamedComfyNode)node;\n\n        Add(node.Name, namedNode);\n\n        // Check statically annotated stuff for TypedNodeOptionsAttribute\n        if (node.GetType().GetCustomAttribute<TypedNodeOptionsAttribute>() is { } options)\n        {\n            if (options.RequiredExtensions != null)\n            {\n                ClassTypeRequiredExtensions.AddOrUpdate(\n                    namedNode.ClassType,\n                    _ => options.GetRequiredExtensionSpecifiers().ToArray(),\n                    (_, specifiers) => options.GetRequiredExtensionSpecifiers().Concat(specifiers).ToArray()\n                );\n            }\n        }\n\n        return node;\n    }\n\n    public void NormalizeConnectionTypes()\n    {\n        using var _ = CodeTimer.StartDebug();\n\n        // Convert all node inputs containing NodeConnectionBase objects to their Data property\n        foreach (var node in Values)\n        {\n            lock (node.Inputs)\n            {\n                foreach (var (key, input) in node.Inputs)\n                {\n                    if (input is NodeConnectionBase connection)\n                    {\n                        node.Inputs[key] = connection.Data;\n                    }\n                    else if (input is IOneOf { Value: NodeConnectionBase oneOfConnection })\n                    {\n                        node.Inputs[key] = oneOfConnection.Data;\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/Nodes/RerouteNode.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\n/// <summary>\n/// Skeleton node that relays the output of another node\n/// </summary>\npublic record RerouteNode(object[] Connection) : IOutputNode\n{\n    /// <inheritdoc />\n    public object[] GetOutput(int index)\n    {\n        if (index != 0)\n        {\n            throw new ArgumentOutOfRangeException(nameof(index));\n        }\n        return Connection;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/WebSocketData/ComfyStatus.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy.WebSocketData;\n\npublic record ComfyStatus\n{\n    [JsonPropertyName(\"exec_info\")]\n    public required ComfyStatusExecInfo ExecInfo { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/WebSocketData/ComfyStatusExecInfo.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy.WebSocketData;\n\npublic record ComfyStatusExecInfo\n{\n    [JsonPropertyName(\"queue_remaining\")]\n    public required int QueueRemaining { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/WebSocketData/ComfyWebSocketExecutingData.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy.WebSocketData;\n\npublic class ComfyWebSocketExecutingData\n{\n    [JsonPropertyName(\"prompt_id\")]\n    public string? PromptId { get; set; }\n\n    /// <summary>\n    /// When this is null it indicates completed\n    /// </summary>\n    [JsonPropertyName(\"node\")]\n    public string? Node { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/WebSocketData/ComfyWebSocketExecutionErrorData.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Comfy.WebSocketData;\n\npublic record ComfyWebSocketExecutionErrorData\n{\n    public required string PromptId { get; set; }\n    public string? NodeId { get; set; }\n    public string? NodeType { get; set; }\n    public string? ExceptionMessage { get; set; }\n    public string? ExceptionType { get; set; }\n    public string[]? Traceback { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/WebSocketData/ComfyWebSocketImageData.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Comfy.WebSocketData;\n\npublic readonly record struct ComfyWebSocketImageData(byte[] ImageBytes, string? MimeType = null);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/WebSocketData/ComfyWebSocketProgressData.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy.WebSocketData;\n\npublic record ComfyWebSocketProgressData\n{\n    [JsonPropertyName(\"value\")]\n    public required int Value { get; set; }\n    \n    [JsonPropertyName(\"max\")]\n    public required int Max { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Comfy/WebSocketData/ComfyWebSocketStatusData.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Comfy.WebSocketData;\n\npublic record ComfyWebSocketStatusData\n{\n    [JsonPropertyName(\"status\")]\n    public required ComfyStatus Status { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/HuggingFace/HuggingFaceUser.cs",
    "content": "using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.HuggingFace;\n\npublic record HuggingFaceUser\n{\n    [JsonPropertyName(\"name\")]\n    public string? Name { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/HuggingFaceAccountStatusUpdateEventArgs.cs",
    "content": "namespace StabilityMatrix.Core.Models.Api; // Or StabilityMatrix.Core.Models.Api.HuggingFace\n\npublic class HuggingFaceAccountStatusUpdateEventArgs : EventArgs\n{\n    public bool IsConnected { get; init; }\n    public string? Username { get; init; } // Optional: if we decide to fetch/display username\n    public string? ErrorMessage { get; init; }\n\n    public static HuggingFaceAccountStatusUpdateEventArgs Disconnected => new(false, null);\n\n    // Constructor to allow initialization, matching the usage in AccountSettingsViewModel\n    public HuggingFaceAccountStatusUpdateEventArgs() { }\n\n    public HuggingFaceAccountStatusUpdateEventArgs(bool isConnected, string? username, string? errorMessage = null)\n    {\n        IsConnected = isConnected;\n        Username = username;\n        ErrorMessage = errorMessage;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/ImageResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic class ImageResponse\n{\n    [JsonPropertyName(\"images\")]\n    public string[] Images { get; set; }\n\n    [JsonPropertyName(\"info\")]\n    public string? Info { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Invoke/InstallModelRequest.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Invoke;\n\npublic class InstallModelRequest\n{\n    public string Name { get; set; }\n    public string Description { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Invoke/ModelInstallResult.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Invoke;\n\npublic class ModelInstallResult\n{\n    [JsonPropertyName(\"id\")]\n    public long Id { get; set; }\n\n    [JsonPropertyName(\"status\")]\n    public string? Status { get; set; }\n\n    [JsonPropertyName(\"error_reason\")]\n    public string? ErrorReason { get; set; }\n\n    [JsonPropertyName(\"inplace\")]\n    public bool Inplace { get; set; }\n\n    [JsonPropertyName(\"local_path\")]\n    public string? LocalPath { get; set; }\n\n    [JsonPropertyName(\"bytes\")]\n    public long Bytes { get; set; }\n\n    [JsonPropertyName(\"total_bytes\")]\n    public long TotalBytes { get; set; }\n\n    [JsonPropertyName(\"error\")]\n    public string? Error { get; set; }\n\n    [JsonPropertyName(\"error_traceback\")]\n    public string? ErrorTraceback { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Invoke/ScanFolderResult.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Invoke;\n\npublic class ScanFolderResult\n{\n    public string Path { get; set; }\n\n    [JsonPropertyName(\"is_installed\")]\n    public bool IsInstalled { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/Analytics/AnalyticsRequest.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Lykos.Analytics;\n\npublic class AnalyticsRequest\n{\n    [JsonPropertyName(\"type\")]\n    public virtual string Type { get; set; } = \"unknown\";\n\n    public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.UtcNow;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/Analytics/FirstTimeInstallAnalytics.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Lykos.Analytics;\n\npublic class FirstTimeInstallAnalytics : AnalyticsRequest\n{\n    public string? SelectedPackageName { get; set; }\n\n    public IEnumerable<string>? SelectedRecommendedModels { get; set; }\n\n    public bool FirstTimeSetupSkipped { get; set; }\n\n    public override string Type { get; set; } = \"first-time-install\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/Analytics/LaunchAnalyticsRequest.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Lykos.Analytics;\n\npublic class LaunchAnalyticsRequest : AnalyticsRequest\n{\n    public string? Version { get; set; }\n\n    public string? RuntimeIdentifier { get; set; }\n\n    public string? OsDescription { get; set; }\n\n    public override string Type { get; set; } = \"launch\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/Analytics/PackageInstallAnalyticsRequest.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.Lykos.Analytics;\n\npublic class PackageInstallAnalyticsRequest : AnalyticsRequest\n{\n    public required string PackageName { get; set; }\n\n    public required string PackageVersion { get; set; }\n\n    public bool IsSuccess { get; set; }\n\n    [JsonPropertyName(\"type\")]\n    public override string Type { get; set; } = \"package-install\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/GetDownloadResponse.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Lykos;\n\npublic record GetFilesDownloadResponse\n{\n    public required Uri DownloadUrl { get; set; }\n\n    public DateTimeOffset? ExpiresAt { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/GetRecommendedModelsResponse.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Lykos;\n\npublic class GetRecommendedModelsResponse\n{\n    public required ModelLists Sd15 { get; set; }\n    public required ModelLists Sdxl { get; set; }\n    public required ModelLists Decoders { get; set; }\n}\n\npublic class ModelLists\n{\n    public IEnumerable<int>? CivitAi { get; set; }\n    public IEnumerable<string>? HuggingFace { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/GetUserResponse.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Lykos;\n\npublic record GetUserResponse\n{\n    public required string Id { get; init; }\n    public required LykosAccount Account { get; init; }\n    public required HashSet<LykosRole> UserRoles { get; init; }\n    public string? PatreonId { get; init; }\n    public bool IsEmailVerified { get; init; }\n    public bool CanHasDevBuild { get; init; }\n    public bool CanHasPreviewBuild { get; init; }\n\n    public bool IsActiveSupporter => CanHasDevBuild || CanHasPreviewBuild;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/GoogleOAuthResponse.cs",
    "content": "﻿using System.Web;\n\nnamespace StabilityMatrix.Core.Models.Api.Lykos;\n\npublic class GoogleOAuthResponse\n{\n    public string? Code { get; init; }\n\n    public string? State { get; init; }\n\n    public string? Nonce { get; init; }\n\n    public string? Error { get; init; }\n\n    public static GoogleOAuthResponse ParseFromQueryString(string query)\n    {\n        var queryCollection = HttpUtility.ParseQueryString(query);\n\n        return new GoogleOAuthResponse\n        {\n            Code = queryCollection.Get(\"code\"),\n            State = queryCollection.Get(\"state\"),\n            Nonce = queryCollection.Get(\"nonce\"),\n            Error = queryCollection.Get(\"error\")\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/LykosAccount.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Lykos;\n\npublic record LykosAccount(string Id, string Name);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/LykosAccountStatusUpdateEventArgs.cs",
    "content": "﻿using System.Security.Claims;\nusing OpenIddict.Abstractions;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Api.LykosAuthApi;\n\nnamespace StabilityMatrix.Core.Models.Api.Lykos;\n\npublic class LykosAccountStatusUpdateEventArgs : EventArgs\n{\n    public static LykosAccountStatusUpdateEventArgs Disconnected { get; } = new();\n\n    public bool IsConnected { get; init; }\n\n    public ClaimsPrincipal? Principal { get; init; }\n\n    public AccountResponse? User { get; init; }\n\n    public string? Id => Principal?.GetClaim(OpenIddictConstants.Claims.Subject);\n\n    public string? DisplayName =>\n        Principal?.GetClaim(OpenIddictConstants.Claims.PreferredUsername) ?? Principal?.Identity?.Name;\n\n    public string? Email => Principal?.GetClaim(OpenIddictConstants.Claims.Email);\n\n    public bool IsPatreonConnected => User?.PatreonId != null;\n\n    public bool IsActiveSupporter => User?.Roles.Contains(\"ActivePatron\") == true;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/LykosAccountV1Tokens.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Lykos;\n\n[Obsolete(\"Use LykosAccountV2Tokens instead\")]\npublic record LykosAccountV1Tokens(string AccessToken, string RefreshToken);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/LykosAccountV2Tokens.cs",
    "content": "﻿using System.Security.Claims;\nusing Microsoft.IdentityModel.JsonWebTokens;\n\nnamespace StabilityMatrix.Core.Models.Api.Lykos;\n\npublic record LykosAccountV2Tokens(string AccessToken, string? RefreshToken, string? IdentityToken)\n{\n    public JsonWebToken? GetDecodedIdentityToken()\n    {\n        if (string.IsNullOrWhiteSpace(IdentityToken))\n        {\n            return null;\n        }\n\n        var handler = new JsonWebTokenHandler();\n        return handler.ReadJsonWebToken(IdentityToken);\n    }\n\n    public ClaimsPrincipal? GetIdentityTokenPrincipal()\n    {\n        if (GetDecodedIdentityToken() is not { } token)\n        {\n            return null;\n        }\n\n        return new ClaimsPrincipal(new ClaimsIdentity(token.Claims, \"IdentityToken\"));\n    }\n\n    public DateTimeOffset? GetIdentityTokenExpiration()\n    {\n        if (GetDecodedIdentityToken() is not { } token)\n        {\n            return null;\n        }\n\n        return token.ValidTo;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/LykosRole.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Lykos;\n\npublic enum LykosRole\n{\n    Unknown = -1,\n    Basic = 0,\n    Supporter = 1,\n    PatreonSupporter = 2,\n    Insider = 3,\n\n    // 4 and 5 have been retired 🫡\n    [Obsolete(\"Roles restructured, use the new BetaTester role\")]\n    OLD_BetaTester = 4,\n\n    [Obsolete(\"Roles restructured, use the new Developer role\")]\n    OLD_Developer = 5,\n    Pioneer = 6,\n    Visionary = 7,\n    BetaTester = 100,\n    Translator = 101,\n    Developer = 900,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/PostAccountRequest.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Lykos;\n\npublic record PostAccountRequest(\n    string Email,\n    string Password,\n    string ConfirmPassword,\n    string AccountName\n);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/PostLoginRefreshRequest.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Lykos;\n\npublic record PostLoginRefreshRequest(string RefreshToken);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/PostLoginRequest.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Lykos;\n\npublic record PostLoginRequest(string Email, string Password);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Lykos/RecommendedModelsV2Response.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Lykos;\n\npublic class RecommendedModelsV2Response\n{\n    public Dictionary<string, List<CivitModel>> RecommendedModelsByCategory { get; set; } = new();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenArt/NodesCount.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenArt;\n\npublic class NodesCount\n{\n    [JsonPropertyName(\"total\")]\n    public long Total { get; set; }\n\n    [JsonPropertyName(\"primitive\")]\n    public long Primitive { get; set; }\n\n    [JsonPropertyName(\"custom\")]\n    public long Custom { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenArt/OpenArtCreator.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenArt;\n\npublic class OpenArtCreator\n{\n    [JsonPropertyName(\"uid\")]\n    public string Uid { get; set; }\n\n    [JsonPropertyName(\"name\")]\n    public string Name { get; set; }\n\n    [JsonPropertyName(\"bio\")]\n    public string Bio { get; set; }\n\n    [JsonPropertyName(\"avatar\")]\n    public Uri Avatar { get; set; }\n\n    [JsonPropertyName(\"username\")]\n    public string Username { get; set; }\n\n    [JsonPropertyName(\"dev_profile_url\")]\n    public string DevProfileUrl { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDateTime.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenArt;\n\npublic class OpenArtDateTime\n{\n    [JsonPropertyName(\"_seconds\")]\n    public long Seconds { get; set; }\n\n    public DateTimeOffset ToDateTimeOffset()\n    {\n        return DateTimeOffset.FromUnixTimeSeconds(Seconds);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDownloadRequest.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing Refit;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenArt;\n\npublic class OpenArtDownloadRequest\n{\n    [AliasAs(\"workflow_id\")]\n    [JsonPropertyName(\"workflow_id\")]\n    public required string WorkflowId { get; set; }\n\n    [AliasAs(\"version_tag\")]\n    [JsonPropertyName(\"version_tag\")]\n    public string VersionTag { get; set; } = \"latest\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenArt/OpenArtDownloadResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenArt;\n\npublic class OpenArtDownloadResponse\n{\n    [JsonPropertyName(\"filename\")]\n    public string Filename { get; set; }\n\n    [JsonPropertyName(\"payload\")]\n    public string Payload { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenArt/OpenArtFeedRequest.cs",
    "content": "﻿using Refit;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenArt;\n\n/// <summary>\n/// Note that parameters Category, Custom Node and Sort should be used separately\n/// </summary>\npublic class OpenArtFeedRequest\n{\n    [AliasAs(\"category\")]\n    public string Category { get; set; }\n\n    [AliasAs(\"sort\")]\n    public string Sort { get; set; }\n\n    [AliasAs(\"custom_node\")]\n    public string CustomNode { get; set; }\n\n    [AliasAs(\"cursor\")]\n    public string Cursor { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchRequest.cs",
    "content": "﻿using Refit;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenArt;\n\npublic class OpenArtSearchRequest\n{\n    [AliasAs(\"keyword\")]\n    public required string Keyword { get; set; }\n\n    [AliasAs(\"pageSize\")]\n    public int PageSize { get; set; } = 30;\n\n    /// <summary>\n    /// 0-based index of the page to retrieve\n    /// </summary>\n    [AliasAs(\"currentPage\")]\n    public int CurrentPage { get; set; } = 0;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenArt;\n\npublic class OpenArtSearchResponse\n{\n    [JsonPropertyName(\"items\")]\n    public IEnumerable<OpenArtSearchResult> Items { get; set; }\n\n    [JsonPropertyName(\"total\")]\n    public int Total { get; set; }\n\n    [JsonPropertyName(\"nextCursor\")]\n    public string? NextCursor { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenArt/OpenArtSearchResult.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenArt;\n\npublic class OpenArtSearchResult\n{\n    [JsonPropertyName(\"id\")]\n    public string Id { get; set; }\n\n    [JsonPropertyName(\"creator\")]\n    public OpenArtCreator Creator { get; set; }\n\n    [JsonPropertyName(\"stats\")]\n    public OpenArtStats Stats { get; set; }\n\n    [JsonPropertyName(\"nodes_index\")]\n    public IEnumerable<string> NodesIndex { get; set; }\n\n    [JsonPropertyName(\"name\")]\n    public string Name { get; set; }\n\n    [JsonPropertyName(\"description\")]\n    public string Description { get; set; }\n\n    [JsonPropertyName(\"categories\")]\n    public IEnumerable<string> Categories { get; set; }\n\n    [JsonPropertyName(\"thumbnails\")]\n    public List<OpenArtThumbnail> Thumbnails { get; set; }\n\n    [JsonPropertyName(\"nodes_count\")]\n    public NodesCount NodesCount { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenArt/OpenArtStats.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenArt;\n\npublic class OpenArtStats\n{\n    [JsonPropertyName(\"num_shares\")]\n    public int NumShares { get; set; }\n\n    [JsonPropertyName(\"num_bookmarks\")]\n    public int NumBookmarks { get; set; }\n\n    [JsonPropertyName(\"num_reviews\")]\n    public int NumReviews { get; set; }\n\n    [JsonPropertyName(\"rating\")]\n    public double Rating { get; set; }\n\n    [JsonPropertyName(\"num_comments\")]\n    public int NumComments { get; set; }\n\n    [JsonPropertyName(\"num_likes\")]\n    public int NumLikes { get; set; }\n\n    [JsonPropertyName(\"num_downloads\")]\n    public int NumDownloads { get; set; }\n\n    [JsonPropertyName(\"num_runs\")]\n    public int NumRuns { get; set; }\n\n    [JsonPropertyName(\"num_views\")]\n    public int NumViews { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenArt/OpenArtThumbnail.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenArt;\n\npublic class OpenArtThumbnail\n{\n    [JsonPropertyName(\"width\")]\n    public int Width { get; set; }\n\n    [JsonPropertyName(\"url\")]\n    public Uri Url { get; set; }\n\n    [JsonPropertyName(\"height\")]\n    public int Height { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbArchitecture.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\npublic class OpenModelDbArchitecture\n{\n    public string? Name { get; set; }\n    public string? Input { get; set; }\n    public string[]? CompatiblePlatforms { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbArchitecturesResponse.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\npublic class OpenModelDbArchitecturesResponse : Dictionary<string, OpenModelDbArchitecture>;\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbImage.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\n/// <summary>\n/// Apparently all the urls can be either standalone or paired.\n/// </summary>\n[JsonPolymorphic(TypeDiscriminatorPropertyName = \"type\", IgnoreUnrecognizedTypeDiscriminators = true)]\n[JsonDerivedType(typeof(Paired), \"paired\")]\n[JsonDerivedType(typeof(Standalone), \"standalone\")]\npublic class OpenModelDbImage\n{\n    /*{\n        \"type\": \"paired\",\n        \"LR\": \"https://images2.imgbox.com/09/3f/ZcIq3bwn_o.jpeg\",\n        \"SR\": \"https://images2.imgbox.com/c7/dd/lIHpU4PZ_o.png\",\n        \"thumbnail\": \"/thumbs/small/6e36848722bccb84eca5232a.jpg\"\n    }*/\n    /*{\n        \"type\": \"paired\",\n        \"LR\": \"/thumbs/573c7a3162c9831716c3bb35.jpg\",\n        \"SR\": \"/thumbs/9a7cb6631356006c30f177d1.jpg\",\n        \"LRSize\": {\n            \"width\": 366,\n            \"height\": 296\n        },\n        \"SRSize\": {\n            \"width\": 366,\n            \"height\": 296\n        }\n    }*/\n    public class Paired : OpenModelDbImage\n    {\n        [JsonPropertyName(\"LR\")]\n        public Uri? Lr { get; set; }\n\n        [JsonPropertyName(\"SR\")]\n        public Uri? Sr { get; set; }\n\n        public Uri? Thumbnail { get; set; }\n\n        [JsonIgnore]\n        public Uri? SrAbsoluteUri => ToAbsoluteUri(Sr);\n\n        [JsonIgnore]\n        public Uri? LrAbsoluteUri => ToAbsoluteUri(Lr);\n\n        public override IEnumerable<Uri> GetImageAbsoluteUris()\n        {\n            var hasHq = false;\n            if (ToAbsoluteUri(Sr) is { } srUri)\n            {\n                hasHq = true;\n                yield return srUri;\n            }\n            if (ToAbsoluteUri(Lr) is { } lrUri)\n            {\n                hasHq = true;\n                yield return lrUri;\n            }\n            if (!hasHq && ToAbsoluteUri(Thumbnail) is { } thumbnail)\n            {\n                yield return thumbnail;\n            }\n        }\n    }\n\n    /*\n     {\n        \"type\": \"standalone\",\n        \"url\": \"https://i.slow.pics/rE3PKKTD.webp\",\n        \"thumbnail\": \"/thumbs/small/85e62ea0e6801e7a0bf5acb6.jpg\"\n    }\n    */\n    public class Standalone : OpenModelDbImage\n    {\n        public Uri? Url { get; set; }\n\n        public Uri? Thumbnail { get; set; }\n\n        public override IEnumerable<Uri> GetImageAbsoluteUris()\n        {\n            if (ToAbsoluteUri(Url) is { } url)\n            {\n                yield return url;\n            }\n            else if (ToAbsoluteUri(Thumbnail) is { } thumbnail)\n            {\n                yield return thumbnail;\n            }\n        }\n    }\n\n    private static Uri? ToAbsoluteUri(Uri? url)\n    {\n        if (url is null)\n        {\n            return null;\n        }\n\n        if (url.IsAbsoluteUri)\n        {\n            return url;\n        }\n\n        var baseUri = new Uri(\"https://openmodeldb.info/\");\n\n        return new Uri(baseUri, url);\n    }\n\n    public virtual IEnumerable<Uri> GetImageAbsoluteUris()\n    {\n        return [];\n    }\n}\n\npublic static class OpenModelDbImageEnumerableExtensions\n{\n    public static IEnumerable<Uri> SelectImageAbsoluteUris(this IEnumerable<OpenModelDbImage> images)\n    {\n        return images.SelectMany(image => image.GetImageAbsoluteUris());\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbKeyedModel.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\npublic record OpenModelDbKeyedModel : OpenModelDbModel\n{\n    public required string Id { get; set; }\n\n    public OpenModelDbKeyedModel() { }\n\n    public OpenModelDbKeyedModel(OpenModelDbModel model)\n        : base(model) { }\n\n    public OpenModelDbKeyedModel(OpenModelDbKeyedModel model)\n        : base(model)\n    {\n        Id = model.Id;\n    }\n\n    public SharedFolderType? GetSharedFolderType()\n    {\n        return Architecture?.ToLowerInvariant() switch\n        {\n            \"swinir\" => SharedFolderType.SwinIR,\n            _ => SharedFolderType.ESRGAN\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModel.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing OneOf;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\npublic record OpenModelDbModel\n{\n    public string? Name { get; set; }\n\n    [JsonConverter(typeof(OneOfJsonConverter<string, string[]>))]\n    public OneOf<string, string[]>? Author { get; set; }\n    public string? License { get; set; }\n    public List<string>? Tags { get; set; }\n    public string? Description { get; set; }\n    public DateOnly? Date { get; set; }\n    public string? Architecture { get; set; }\n    public List<string>? Size { get; set; }\n    public int? Scale { get; set; }\n    public int? InputChannels { get; set; }\n    public int? OutputChannels { get; set; }\n    public List<OpenModelDbResource>? Resources { get; set; }\n    public List<OpenModelDbImage>? Images { get; set; }\n    public OpenModelDbImage? Thumbnail { get; set; }\n\n    public OpenModelDbModel(OpenModelDbModel model)\n    {\n        Name = model.Name;\n        Author = model.Author;\n        License = model.License;\n        Tags = model.Tags;\n        Description = model.Description;\n        Date = model.Date;\n        Architecture = model.Architecture;\n        Size = model.Size;\n        Scale = model.Scale;\n        InputChannels = model.InputChannels;\n        OutputChannels = model.OutputChannels;\n        Resources = model.Resources;\n        Images = model.Images;\n        Thumbnail = model.Thumbnail;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbModelsResponse.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\npublic class OpenModelDbModelsResponse : Dictionary<string, OpenModelDbModel>\n{\n    public ParallelQuery<OpenModelDbKeyedModel> GetKeyedModels()\n    {\n        return this.AsParallel().Select(kv => new OpenModelDbKeyedModel(kv.Value) { Id = kv.Key });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbResource.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\npublic class OpenModelDbResource\n{\n    public string? Platform { get; set; }\n    public string? Type { get; set; }\n    public long Size { get; set; }\n    public string? Sha256 { get; set; }\n    public List<string>? Urls { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbTag.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\npublic class OpenModelDbTag\n{\n    public string? Name { get; set; }\n    public string? Description { get; set; }\n    public string[]? Implies { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/OpenModelsDb/OpenModelDbTagsResponse.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\npublic class OpenModelDbTagsResponse : Dictionary<string, OpenModelDbTag>;\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/ProgressRequest.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic class ProgressRequest\n{\n    [JsonPropertyName(\"skip_current_image\")]\n    public bool? SkipCurrentImage { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/ProgressResponse.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\npublic class ProgressResponse\n{\n    // Range from 0 to 1\n    [JsonPropertyName(\"progress\")]\n    public float Progress { get; set; }\n    \n    // ETA in seconds\n    [JsonPropertyName(\"eta_relative\")]\n    public float EtaRelative { get; set; }\n    \n    // state: dict\n    \n    // The current image in base64 format. opts.show_progress_every_n_steps is required for this to work\n    [JsonPropertyName(\"current_image\")]\n    public string? CurrentImage { get; set; }\n    \n    [JsonPropertyName(\"textinfo\")]\n    public string? TextInfo { get; set; }\n    \n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Pypi/PyPiReleaseFile.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Pypi;\n\npublic class PyPiReleaseFile { }\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/Pypi/PyPiResponse.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Api.Pypi;\n\npublic class PyPiResponse\n{\n    public Dictionary<string, List<PyPiReleaseFile>>? Releases { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Api/TextToImageRequest.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Api;\n\n\npublic class TextToImageRequest\n{\n    [JsonPropertyName(\"enable_hr\")]\n    public bool? EnableHr { get; set; }\n\n    [JsonPropertyName(\"denoising_strength\")]\n    public int? DenoisingStrength { get; set; }\n\n    [JsonPropertyName(\"firstphase_width\")]\n    public int? FirstPhaseWidth { get; set; }\n\n    [JsonPropertyName(\"firstphase_height\")]\n    public int? FirstPhaseHeight { get; set; }\n\n    [JsonPropertyName(\"hr_scale\")]\n    public int? HrScale { get; set; }\n\n    [JsonPropertyName(\"hr_upscaler\")]\n    public string? HrUpscaler { get; set; }\n\n    [JsonPropertyName(\"hr_second_pass_steps\")]\n    public int? HrSecondPassSteps { get; set; }\n\n    [JsonPropertyName(\"hr_resize_x\")]\n    public int? HrResizeX { get; set; }\n\n    [JsonPropertyName(\"hr_resize_y\")]\n    public int? HrResizeY { get; set; }\n\n    [JsonPropertyName(\"prompt\")]\n    public string Prompt { get; set; }\n\n    [JsonPropertyName(\"styles\")]\n    public string?[] Styles { get; set; }\n\n    [JsonPropertyName(\"seed\")]\n    public int? Seed { get; set; }\n\n    [JsonPropertyName(\"subseed\")]\n    public int? Subseed { get; set; }\n\n    [JsonPropertyName(\"subseed_strength\")]\n    public int? SubseedStrength { get; set; }\n\n    [JsonPropertyName(\"seed_resize_from_h\")]\n    public int? SeedResizeFromH { get; set; }\n\n    [JsonPropertyName(\"seed_resize_from_w\")]\n    public int? SeedResizeFromW { get; set; }\n\n    [JsonPropertyName(\"sampler_name\")]\n    public string? SamplerName { get; set; }\n\n    [JsonPropertyName(\"batch_size\")]\n    public int? BatchSize { get; set; }\n\n    [JsonPropertyName(\"n_iter\")]\n    public int? NIter { get; set; }\n\n    [JsonPropertyName(\"steps\")]\n    public int? Steps { get; set; }\n\n    [JsonPropertyName(\"cfg_scale\")]\n    public int? CfgScale { get; set; }\n\n    [JsonPropertyName(\"width\")]\n    public int? Width { get; set; }\n\n    [JsonPropertyName(\"height\")]\n    public int? Height { get; set; }\n\n    [JsonPropertyName(\"restore_faces\")]\n    public bool? RestoreFaces { get; set; }\n\n    [JsonPropertyName(\"tiling\")]\n    public bool? Tiling { get; set; }\n\n    [JsonPropertyName(\"do_not_save_samples\")]\n    public bool? DoNotSaveSamples { get; set; }\n\n    [JsonPropertyName(\"do_not_save_grid\")]\n    public bool? DoNotSaveGrid { get; set; }\n\n    [JsonPropertyName(\"negative_prompt\")]\n    public string? NegativePrompt { get; set; }\n\n    [JsonPropertyName(\"eta\")]\n    public int? Eta { get; set; }\n\n    [JsonPropertyName(\"s_min_uncond\")]\n    public int? SMinUncond { get; set; }\n\n    [JsonPropertyName(\"s_churn\")]\n    public int? SChurn { get; set; }\n\n    [JsonPropertyName(\"s_tmax\")]\n    public int? STmax { get; set; }\n\n    [JsonPropertyName(\"s_tmin\")]\n    public int? STmin { get; set; }\n\n    [JsonPropertyName(\"s_noise\")]\n    public int? SNoise { get; set; }\n\n    [JsonPropertyName(\"override_settings\")]\n    public Dictionary<string, string>? OverrideSettings { get; set; }\n\n    [JsonPropertyName(\"override_settings_restore_afterwards\")]\n    public bool? OverrideSettingsRestoreAfterwards { get; set; }\n\n    [JsonPropertyName(\"script_args\")]\n    public string[]? ScriptArgs { get; set; }\n\n    [JsonPropertyName(\"sampler_index\")]\n    public string? SamplerIndex { get; set; }\n\n    [JsonPropertyName(\"script_name\")]\n    public string? ScriptName { get; set; }\n\n    [JsonPropertyName(\"send_images\")]\n    public bool? SendImages { get; set; }\n\n    [JsonPropertyName(\"save_images\")]\n    public bool? SaveImages { get; set; }\n\n    [JsonPropertyName(\"alwayson_scripts\")]\n    public Dictionary<string, string>? AlwaysOnScripts { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Base/StringValue.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Base;\n\n/// <summary>\n/// Base class for a string value object\n/// </summary>\n/// <param name=\"Value\">String value</param>\npublic abstract record StringValue(string Value)\n{\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        return Value;\n    }\n\n    public static implicit operator string(StringValue stringValue) => stringValue.ToString();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/CheckpointSortMode.cs",
    "content": "﻿using StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic enum CheckpointSortMode\n{\n    [StringValue(\"Base Model\")]\n    BaseModel,\n\n    [StringValue(\"Date Created\")]\n    Created,\n\n    [StringValue(\"Date Last Modified\")]\n    LastModified,\n\n    [StringValue(\"File Name\")]\n    FileName,\n\n    [StringValue(\"File Size\")]\n    FileSize,\n\n    [StringValue(\"Title\")]\n    Title,\n\n    [StringValue(\"Type\")]\n    SharedFolderType,\n\n    [StringValue(\"Update Available\")]\n    UpdateAvailable,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/CheckpointSortOptions.cs",
    "content": "﻿using System.ComponentModel;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class CheckpointSortOptions\n{\n    public CheckpointSortMode SortMode { get; set; } = CheckpointSortMode.SharedFolderType;\n    public ListSortDirection SortDirection { get; set; } = ListSortDirection.Descending;\n    public bool SortConnectedModelsFirst { get; set; } = true;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/CivitPostDownloadContextAction.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Text.Json;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class CivitPostDownloadContextAction : IContextAction\n{\n    /// <inheritdoc />\n    public object? Context { get; set; }\n\n    public static CivitPostDownloadContextAction FromCivitFile(CivitFile file)\n    {\n        return new CivitPostDownloadContextAction { Context = file.Hashes.BLAKE3 };\n    }\n\n    public void Invoke(ISettingsManager settingsManager, IModelIndexService modelIndexService)\n    {\n        var result = Context as string;\n\n        if (Context is JsonElement jsonElement)\n        {\n            result = jsonElement.GetString();\n        }\n\n        if (result is null)\n        {\n            Debug.WriteLine($\"Context {Context} is not a string.\");\n            return;\n        }\n\n        Debug.WriteLine($\"Adding {result} to installed models.\");\n\n        // Also request reindex\n        modelIndexService.BackgroundRefreshIndex();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/CivitaiResource.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic class CivitaiResource\n{\n    public string Type { get; set; }\n    public int ModelVersionId { get; set; }\n    public string ModelName { get; set; }\n    public string ModelVersionName { get; set; }\n    public double? Weight { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/ComfyNodeMap.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Nodes;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class ComfyNodeMap\n{\n    private static Dictionary<string, string> lookup;\n\n    public static Dictionary<string, string> Lookup\n    {\n        get => lookup ??= Deserialize();\n    }\n\n    public static Dictionary<string, string> Deserialize()\n    {\n        var json = JsonSerializer.Deserialize<Dictionary<string, JsonArray>>(Json);\n\n        return json!\n            .Select(\n                pair =>\n                    (\n                        Name: (pair.Value[1] as JsonObject)?[\"title_aux\"]?.ToString() ?? string.Empty,\n                        Url: pair.Key\n                    )\n            )\n            .DistinctBy(x => x.Name)\n            .ToDictionary(pair => pair.Name, pair => pair.Url);\n    }\n\n    private const string Json = \"\"\"\n                            {\n                                \"https://gist.github.com/alkemann/7361b8eb966f29c8238fd323409efb68/raw/f9605be0b38d38d3e3a2988f89248ff557010076/alkemann.py\": [\n                                    [\n                                        \"Int to Text\",\n                                        \"Save A1 Image\",\n                                        \"Seed With Text\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"alkemann nodes\"\n                                    }\n                                ],\n                                \"https://git.mmaker.moe/mmaker/sd-webui-color-enhance\": [\n                                    [\n                                        \"MMakerColorBlend\",\n                                        \"MMakerColorEnhance\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Color Enhance\"\n                                    }\n                                ],\n                                \"https://github.com/0xbitches/ComfyUI-LCM\": [\n                                    [\n                                        \"LCM_Sampler\",\n                                        \"LCM_Sampler_Advanced\",\n                                        \"LCM_img2img_Sampler\",\n                                        \"LCM_img2img_Sampler_Advanced\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Latent Consistency Model for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/1shadow1/hayo_comfyui_nodes/raw/main/LZCNodes.py\": [\n                                    [\n                                        \"LoadPILImages\",\n                                        \"MergeImages\",\n                                        \"make_transparentmask\",\n                                        \"tensor_trans_pil\",\n                                        \"words_generatee\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Hayo comfyui nodes\"\n                                    }\n                                ],\n                                \"https://github.com/42lux/ComfyUI-safety-checker\": [\n                                    [\n                                        \"Safety Checker\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-safety-checker\"\n                                    }\n                                ],\n                                \"https://github.com/54rt1n/ComfyUI-DareMerge\": [\n                                    [\n                                        \"DM_AdvancedDareModelMerger\",\n                                        \"DM_AdvancedModelMerger\",\n                                        \"DM_AttentionGradient\",\n                                        \"DM_BlockGradient\",\n                                        \"DM_BlockModelMerger\",\n                                        \"DM_DareClipMerger\",\n                                        \"DM_DareModelMergerBlock\",\n                                        \"DM_DareModelMergerElement\",\n                                        \"DM_DareModelMergerMBW\",\n                                        \"DM_GradientEdit\",\n                                        \"DM_GradientOperations\",\n                                        \"DM_GradientReporting\",\n                                        \"DM_InjectNoise\",\n                                        \"DM_LoRALoaderTags\",\n                                        \"DM_LoRAReporting\",\n                                        \"DM_MBWGradient\",\n                                        \"DM_MagnitudeMasker\",\n                                        \"DM_MaskEdit\",\n                                        \"DM_MaskOperations\",\n                                        \"DM_MaskReporting\",\n                                        \"DM_ModelReporting\",\n                                        \"DM_NormalizeModel\",\n                                        \"DM_QuadMasker\",\n                                        \"DM_ShellGradient\",\n                                        \"DM_SimpleMasker\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-DareMerge\"\n                                    }\n                                ],\n                                \"https://github.com/80sVectorz/ComfyUI-Static-Primitives\": [\n                                    [\n                                        \"FloatStaticPrimitive\",\n                                        \"IntStaticPrimitive\",\n                                        \"StringMlStaticPrimitive\",\n                                        \"StringStaticPrimitive\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Static-Primitives\"\n                                    }\n                                ],\n                                \"https://github.com/AInseven/ComfyUI-fastblend\": [\n                                    [\n                                        \"FillDarkMask\",\n                                        \"InterpolateKeyFrame\",\n                                        \"MaskListcaptoBatch\",\n                                        \"MyOpenPoseNode\",\n                                        \"SmoothVideo\",\n                                        \"reBatchImage\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-fastblend\"\n                                    }\n                                ],\n                                \"https://github.com/AIrjen/OneButtonPrompt\": [\n                                    [\n                                        \"AutoNegativePrompt\",\n                                        \"CreatePromptVariant\",\n                                        \"OneButtonPreset\",\n                                        \"OneButtonPrompt\",\n                                        \"SavePromptToFile\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"One Button Prompt\"\n                                    }\n                                ],\n                                \"https://github.com/AbdullahAlfaraj/Comfy-Photoshop-SD\": [\n                                    [\n                                        \"APS_LatentBatch\",\n                                        \"APS_Seed\",\n                                        \"ContentMaskLatent\",\n                                        \"ControlNetScript\",\n                                        \"ControlnetUnit\",\n                                        \"GaussianLatentImage\",\n                                        \"GetConfig\",\n                                        \"LoadImageBase64\",\n                                        \"LoadImageWithMetaData\",\n                                        \"LoadLorasFromPrompt\",\n                                        \"MaskExpansion\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfy-Photoshop-SD\"\n                                    }\n                                ],\n                                \"https://github.com/AbyssYuan0/ComfyUI_BadgerTools\": [\n                                    [\n                                        \"ApplyMaskToImage-badger\",\n                                        \"CropImageByMask-badger\",\n                                        \"ExpandImageWithColor-badger\",\n                                        \"FindThickLinesFromCanny-badger\",\n                                        \"FloatToInt-badger\",\n                                        \"FloatToString-badger\",\n                                        \"FrameToVideo-badger\",\n                                        \"GarbageCollect-badger\",\n                                        \"GetColorFromBorder-badger\",\n                                        \"GetDirName-badger\",\n                                        \"GetUUID-badger\",\n                                        \"IdentifyBorderColorToMask-badger\",\n                                        \"IdentifyColorToMask-badger\",\n                                        \"ImageNormalization-badger\",\n                                        \"ImageOverlap-badger\",\n                                        \"ImageScaleToSide-badger\",\n                                        \"IntToString-badger\",\n                                        \"SegmentToMaskByPoint-badger\",\n                                        \"StringToFizz-badger\",\n                                        \"TextListToString-badger\",\n                                        \"TrimTransparentEdges-badger\",\n                                        \"VideoCutFromDir-badger\",\n                                        \"VideoToFrame-badger\",\n                                        \"deleteDir-badger\",\n                                        \"findCenterOfMask-badger\",\n                                        \"getImageSide-badger\",\n                                        \"getParentDir-badger\",\n                                        \"mkdir-badger\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_BadgerTools\"\n                                    }\n                                ],\n                                \"https://github.com/Acly/comfyui-inpaint-nodes\": [\n                                    [\n                                        \"INPAINT_ApplyFooocusInpaint\",\n                                        \"INPAINT_InpaintWithModel\",\n                                        \"INPAINT_LoadFooocusInpaint\",\n                                        \"INPAINT_LoadInpaintModel\",\n                                        \"INPAINT_MaskedBlur\",\n                                        \"INPAINT_MaskedFill\",\n                                        \"INPAINT_VAEEncodeInpaintConditioning\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Inpaint Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/Acly/comfyui-tooling-nodes\": [\n                                    [\n                                        \"ETN_ApplyMaskToImage\",\n                                        \"ETN_CropImage\",\n                                        \"ETN_LoadImageBase64\",\n                                        \"ETN_LoadMaskBase64\",\n                                        \"ETN_SendImageWebSocket\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Nodes for External Tooling\"\n                                    }\n                                ],\n                                \"https://github.com/Amorano/Jovimetrix\": [\n                                    [],\n                                    {\n                                        \"author\": \"amorano\",\n                                        \"description\": \"Webcams, GLSL shader, Media Streaming, Tick animation, Image manipulation,\",\n                                        \"nodename_pattern\": \" \\\\(jov\\\\)$\",\n                                        \"title\": \"Jovimetrix\",\n                                        \"title_aux\": \"Jovimetrix Composition Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/ArtBot2023/CharacterFaceSwap\": [\n                                    [\n                                        \"Color Blend\",\n                                        \"Crop Face\",\n                                        \"Exclude Facial Feature\",\n                                        \"Generation Parameter Input\",\n                                        \"Generation Parameter Output\",\n                                        \"Image Full BBox\",\n                                        \"Load BiseNet\",\n                                        \"Load RetinaFace\",\n                                        \"Mask Contour\",\n                                        \"Segment Face\",\n                                        \"Uncrop Face\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Character Face Swap\"\n                                    }\n                                ],\n                                \"https://github.com/ArtVentureX/comfyui-animatediff\": [\n                                    [\n                                        \"AnimateDiffCombine\",\n                                        \"AnimateDiffLoraLoader\",\n                                        \"AnimateDiffModuleLoader\",\n                                        \"AnimateDiffSampler\",\n                                        \"AnimateDiffSlidingWindowOptions\",\n                                        \"ImageSizeAndBatchSize\",\n                                        \"LoadVideo\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"AnimateDiff\"\n                                    }\n                                ],\n                                \"https://github.com/AustinMroz/ComfyUI-SpliceTools\": [\n                                    [\n                                        \"LogSigmas\",\n                                        \"RerangeSigmas\",\n                                        \"SpliceDenoised\",\n                                        \"SpliceLatents\",\n                                        \"TemporalSplice\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SpliceTools\"\n                                    }\n                                ],\n                                \"https://github.com/BadCafeCode/masquerade-nodes-comfyui\": [\n                                    [\n                                        \"Blur\",\n                                        \"Change Channel Count\",\n                                        \"Combine Masks\",\n                                        \"Constant Mask\",\n                                        \"Convert Color Space\",\n                                        \"Create QR Code\",\n                                        \"Create Rect Mask\",\n                                        \"Cut By Mask\",\n                                        \"Get Image Size\",\n                                        \"Image To Mask\",\n                                        \"Make Image Batch\",\n                                        \"Mask By Text\",\n                                        \"Mask Morphology\",\n                                        \"Mask To Region\",\n                                        \"MasqueradeIncrementer\",\n                                        \"Mix Color By Mask\",\n                                        \"Mix Images By Mask\",\n                                        \"Paste By Mask\",\n                                        \"Prune By Mask\",\n                                        \"Separate Mask Components\",\n                                        \"Unary Image Op\",\n                                        \"Unary Mask Op\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Masquerade Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/Beinsezii/bsz-cui-extras\": [\n                                    [\n                                        \"BSZAbsoluteHires\",\n                                        \"BSZAspectHires\",\n                                        \"BSZColoredLatentImageXL\",\n                                        \"BSZCombinedHires\",\n                                        \"BSZHueChromaXL\",\n                                        \"BSZInjectionKSampler\",\n                                        \"BSZLatentDebug\",\n                                        \"BSZLatentFill\",\n                                        \"BSZLatentGradient\",\n                                        \"BSZLatentHSVAImage\",\n                                        \"BSZLatentOffsetXL\",\n                                        \"BSZLatentRGBAImage\",\n                                        \"BSZLatentbuster\",\n                                        \"BSZPixelbuster\",\n                                        \"BSZPixelbusterHelp\",\n                                        \"BSZPrincipledConditioning\",\n                                        \"BSZPrincipledSampler\",\n                                        \"BSZPrincipledScale\",\n                                        \"BSZStrangeResample\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"bsz-cui-extras\"\n                                    }\n                                ],\n                                \"https://github.com/BennyKok/comfyui-deploy\": [\n                                    [\n                                        \"ComfyUIDeployExternalCheckpoint\",\n                                        \"ComfyUIDeployExternalImage\",\n                                        \"ComfyUIDeployExternalImageAlpha\",\n                                        \"ComfyUIDeployExternalLora\",\n                                        \"ComfyUIDeployExternalNumber\",\n                                        \"ComfyUIDeployExternalNumberInt\",\n                                        \"ComfyUIDeployExternalText\"\n                                    ],\n                                    {\n                                        \"author\": \"BennyKok\",\n                                        \"description\": \"\",\n                                        \"nickname\": \"Comfy Deploy\",\n                                        \"title\": \"comfyui-deploy\",\n                                        \"title_aux\": \"ComfyUI Deploy\"\n                                    }\n                                ],\n                                \"https://github.com/Bikecicle/ComfyUI-Waveform-Extensions/raw/main/EXT_AudioManipulation.py\": [\n                                    [\n                                        \"BatchJoinAudio\",\n                                        \"CutAudio\",\n                                        \"DuplicateAudio\",\n                                        \"JoinAudio\",\n                                        \"ResampleAudio\",\n                                        \"ReverseAudio\",\n                                        \"StretchAudio\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Waveform Extensions\"\n                                    }\n                                ],\n                                \"https://github.com/Billius-AI/ComfyUI-Path-Helper\": [\n                                    [\n                                        \"Add File Name Prefix\",\n                                        \"Add File Name Prefix Advanced\",\n                                        \"Add Folder\",\n                                        \"Add Folder Advanced\",\n                                        \"Create Project Root\",\n                                        \"Join Variables\",\n                                        \"Show Path\",\n                                        \"Show String\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Path-Helper\"\n                                    }\n                                ],\n                                \"https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb\": [\n                                    [\n                                        \"BNK_AddCLIPSDXLParams\",\n                                        \"BNK_AddCLIPSDXLRParams\",\n                                        \"BNK_CLIPTextEncodeAdvanced\",\n                                        \"BNK_CLIPTextEncodeSDXLAdvanced\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Advanced CLIP Text Encode\"\n                                    }\n                                ],\n                                \"https://github.com/BlenderNeko/ComfyUI_Cutoff\": [\n                                    [\n                                        \"BNK_CutoffBasePrompt\",\n                                        \"BNK_CutoffRegionsToConditioning\",\n                                        \"BNK_CutoffRegionsToConditioning_ADV\",\n                                        \"BNK_CutoffSetRegions\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Cutoff\"\n                                    }\n                                ],\n                                \"https://github.com/BlenderNeko/ComfyUI_Noise\": [\n                                    [\n                                        \"BNK_DuplicateBatchIndex\",\n                                        \"BNK_GetSigma\",\n                                        \"BNK_InjectNoise\",\n                                        \"BNK_NoisyLatentImage\",\n                                        \"BNK_SlerpLatent\",\n                                        \"BNK_Unsampler\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Noise\"\n                                    }\n                                ],\n                                \"https://github.com/BlenderNeko/ComfyUI_SeeCoder\": [\n                                    [\n                                        \"ConcatConditioning\",\n                                        \"SEECoderImageEncode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SeeCoder [WIP]\"\n                                    }\n                                ],\n                                \"https://github.com/BlenderNeko/ComfyUI_TiledKSampler\": [\n                                    [\n                                        \"BNK_TiledKSampler\",\n                                        \"BNK_TiledKSamplerAdvanced\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Tiled sampling for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/CYBERLOOM-INC/ComfyUI-nodes-hnmr\": [\n                                    [\n                                        \"CLIPIter\",\n                                        \"Dict2Model\",\n                                        \"GridImage\",\n                                        \"ImageBlend2\",\n                                        \"KSamplerOverrided\",\n                                        \"KSamplerSetting\",\n                                        \"KSamplerXYZ\",\n                                        \"LatentToHist\",\n                                        \"LatentToImage\",\n                                        \"ModelIter\",\n                                        \"RandomLatentImage\",\n                                        \"SaveStateDict\",\n                                        \"SaveText\",\n                                        \"StateDictLoader\",\n                                        \"StateDictMerger\",\n                                        \"StateDictMergerBlockWeighted\",\n                                        \"StateDictMergerBlockWeightedMulti\",\n                                        \"VAEDecodeBatched\",\n                                        \"VAEEncodeBatched\",\n                                        \"VAEIter\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-nodes-hnmr\"\n                                    }\n                                ],\n                                \"https://github.com/CaptainGrock/ComfyUIInvisibleWatermark/raw/main/Invisible%20Watermark.py\": [\n                                    [\n                                        \"Apply Invisible Watermark\",\n                                        \"Extract Watermark\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUIInvisibleWatermark\"\n                                    }\n                                ],\n                                \"https://github.com/Chan-0312/ComfyUI-IPAnimate\": [\n                                    [\n                                        \"IPAdapterAnimate\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-IPAnimate\"\n                                    }\n                                ],\n                                \"https://github.com/Chaoses-Ib/ComfyUI_Ib_CustomNodes\": [\n                                    [\n                                        \"ImageToPIL\",\n                                        \"LoadImageFromPath\",\n                                        \"PILToImage\",\n                                        \"PILToMask\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_Ib_CustomNodes\"\n                                    }\n                                ],\n                                \"https://github.com/Clybius/ComfyUI-Extra-Samplers\": [\n                                    [\n                                        \"SamplerCLYB_4M_SDE_Momentumized\",\n                                        \"SamplerCustomModelMixtureDuo\",\n                                        \"SamplerCustomNoise\",\n                                        \"SamplerCustomNoiseDuo\",\n                                        \"SamplerDPMPP_DualSDE_Momentumized\",\n                                        \"SamplerEulerAncestralDancing_Experimental\",\n                                        \"SamplerLCMCustom\",\n                                        \"SamplerRES_Momentumized\",\n                                        \"SamplerTTM\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Extra Samplers\"\n                                    }\n                                ],\n                                \"https://github.com/Clybius/ComfyUI-Latent-Modifiers\": [\n                                    [\n                                        \"Latent Diffusion Mega Modifier\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Latent-Modifiers\"\n                                    }\n                                ],\n                                \"https://github.com/CosmicLaca/ComfyUI_Primere_Nodes\": [\n                                    [\n                                        \"PrimereAnyDetailer\",\n                                        \"PrimereAnyOutput\",\n                                        \"PrimereCKPT\",\n                                        \"PrimereCKPTLoader\",\n                                        \"PrimereCLIPEncoder\",\n                                        \"PrimereClearPrompt\",\n                                        \"PrimereDynamicParser\",\n                                        \"PrimereEmbedding\",\n                                        \"PrimereEmbeddingHandler\",\n                                        \"PrimereEmbeddingKeywordMerger\",\n                                        \"PrimereHypernetwork\",\n                                        \"PrimereImageSegments\",\n                                        \"PrimereKSampler\",\n                                        \"PrimereLCMSelector\",\n                                        \"PrimereLORA\",\n                                        \"PrimereLYCORIS\",\n                                        \"PrimereLatentNoise\",\n                                        \"PrimereLoraKeywordMerger\",\n                                        \"PrimereLoraStackMerger\",\n                                        \"PrimereLycorisKeywordMerger\",\n                                        \"PrimereLycorisStackMerger\",\n                                        \"PrimereMetaCollector\",\n                                        \"PrimereMetaRead\",\n                                        \"PrimereMetaSave\",\n                                        \"PrimereMidjourneyStyles\",\n                                        \"PrimereModelConceptSelector\",\n                                        \"PrimereModelKeyword\",\n                                        \"PrimereNetworkTagLoader\",\n                                        \"PrimerePrompt\",\n                                        \"PrimerePromptSwitch\",\n                                        \"PrimereRefinerPrompt\",\n                                        \"PrimereResolution\",\n                                        \"PrimereResolutionMultiplier\",\n                                        \"PrimereResolutionMultiplierMPX\",\n                                        \"PrimereSamplers\",\n                                        \"PrimereSamplersSteps\",\n                                        \"PrimereSeed\",\n                                        \"PrimereStepsCfg\",\n                                        \"PrimereStyleLoader\",\n                                        \"PrimereStylePile\",\n                                        \"PrimereTextOutput\",\n                                        \"PrimereVAE\",\n                                        \"PrimereVAELoader\",\n                                        \"PrimereVAESelector\",\n                                        \"PrimereVisualCKPT\",\n                                        \"PrimereVisualEmbedding\",\n                                        \"PrimereVisualHypernetwork\",\n                                        \"PrimereVisualLORA\",\n                                        \"PrimereVisualLYCORIS\",\n                                        \"PrimereVisualStyle\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Primere nodes for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/Danand/ComfyUI-ComfyCouple\": [\n                                    [\n                                        \"Attention couple\",\n                                        \"Comfy Couple\"\n                                    ],\n                                    {\n                                        \"author\": \"Rei D.\",\n                                        \"description\": \"If you want to draw two different characters together without blending their features, so you could try to check out this custom node.\",\n                                        \"nickname\": \"Danand\",\n                                        \"title\": \"Comfy Couple\",\n                                        \"title_aux\": \"ComfyUI-ComfyCouple\"\n                                    }\n                                ],\n                                \"https://github.com/Davemane42/ComfyUI_Dave_CustomNode\": [\n                                    [\n                                        \"ABGRemover\",\n                                        \"ConditioningStretch\",\n                                        \"ConditioningUpscale\",\n                                        \"MultiAreaConditioning\",\n                                        \"MultiLatentComposite\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Visual Area Conditioning / Latent composition\"\n                                    }\n                                ],\n                                \"https://github.com/Derfuu/Derfuu_ComfyUI_ModdedNodes\": [\n                                    [\n                                        \"ABSNode_DF\",\n                                        \"Absolute value\",\n                                        \"Ceil\",\n                                        \"CeilNode_DF\",\n                                        \"Conditioning area scale by ratio\",\n                                        \"ConditioningSetArea with tuples\",\n                                        \"ConditioningSetAreaEXT_DF\",\n                                        \"ConditioningSetArea_DF\",\n                                        \"CosNode_DF\",\n                                        \"Cosines\",\n                                        \"Divide\",\n                                        \"DivideNode_DF\",\n                                        \"EmptyLatentImage_DF\",\n                                        \"Float\",\n                                        \"Float debug print\",\n                                        \"Float2Tuple_DF\",\n                                        \"FloatDebugPrint_DF\",\n                                        \"FloatNode_DF\",\n                                        \"Floor\",\n                                        \"FloorNode_DF\",\n                                        \"Get image size\",\n                                        \"Get latent size\",\n                                        \"GetImageSize_DF\",\n                                        \"GetLatentSize_DF\",\n                                        \"Image scale by ratio\",\n                                        \"Image scale to side\",\n                                        \"ImageScale_Ratio_DF\",\n                                        \"ImageScale_Side_DF\",\n                                        \"Int debug print\",\n                                        \"Int to float\",\n                                        \"Int to tuple\",\n                                        \"Int2Float_DF\",\n                                        \"IntDebugPrint_DF\",\n                                        \"Integer\",\n                                        \"IntegerNode_DF\",\n                                        \"Latent Scale by ratio\",\n                                        \"Latent Scale to side\",\n                                        \"LatentComposite with tuples\",\n                                        \"LatentScale_Ratio_DF\",\n                                        \"LatentScale_Side_DF\",\n                                        \"MultilineStringNode_DF\",\n                                        \"Multiply\",\n                                        \"MultiplyNode_DF\",\n                                        \"PowNode_DF\",\n                                        \"Power\",\n                                        \"Random\",\n                                        \"RandomFloat_DF\",\n                                        \"SinNode_DF\",\n                                        \"Sinus\",\n                                        \"SqrtNode_DF\",\n                                        \"Square root\",\n                                        \"String debug print\",\n                                        \"StringNode_DF\",\n                                        \"Subtract\",\n                                        \"SubtractNode_DF\",\n                                        \"Sum\",\n                                        \"SumNode_DF\",\n                                        \"TanNode_DF\",\n                                        \"Tangent\",\n                                        \"Text\",\n                                        \"Text box\",\n                                        \"Tuple\",\n                                        \"Tuple debug print\",\n                                        \"Tuple multiply\",\n                                        \"Tuple swap\",\n                                        \"Tuple to floats\",\n                                        \"Tuple to ints\",\n                                        \"Tuple2Float_DF\",\n                                        \"TupleDebugPrint_DF\",\n                                        \"TupleNode_DF\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Derfuu_ComfyUI_ModdedNodes\"\n                                    }\n                                ],\n                                \"https://github.com/DonBaronFactory/ComfyUI-Cre8it-Nodes\": [\n                                    [\n                                        \"ApplySerialPrompter\",\n                                        \"ImageSizer\",\n                                        \"SerialPrompter\"\n                                    ],\n                                    {\n                                        \"author\": \"CRE8IT GmbH\",\n                                        \"description\": \"This extension offers various nodes.\",\n                                        \"nickname\": \"cre8Nodes\",\n                                        \"title\": \"cr8SerialPrompter\",\n                                        \"title_aux\": \"ComfyUI-Cre8it-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/Electrofried/ComfyUI-OpenAINode\": [\n                                    [\n                                        \"OpenAINode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"OpenAINode\"\n                                    }\n                                ],\n                                \"https://github.com/EllangoK/ComfyUI-post-processing-nodes\": [\n                                    [\n                                        \"ArithmeticBlend\",\n                                        \"AsciiArt\",\n                                        \"Blend\",\n                                        \"Blur\",\n                                        \"CannyEdgeMask\",\n                                        \"ChromaticAberration\",\n                                        \"ColorCorrect\",\n                                        \"ColorTint\",\n                                        \"Dissolve\",\n                                        \"Dither\",\n                                        \"DodgeAndBurn\",\n                                        \"FilmGrain\",\n                                        \"Glow\",\n                                        \"HSVThresholdMask\",\n                                        \"KMeansQuantize\",\n                                        \"KuwaharaBlur\",\n                                        \"Parabolize\",\n                                        \"PencilSketch\",\n                                        \"PixelSort\",\n                                        \"Pixelize\",\n                                        \"Quantize\",\n                                        \"Sharpen\",\n                                        \"SineWave\",\n                                        \"Solarize\",\n                                        \"Vignette\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-post-processing-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/Extraltodeus/ComfyUI-AutomaticCFG\": [\n                                    [\n                                        \"Automatic CFG\",\n                                        \"Automatic CFG channels multipliers\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-AutomaticCFG\"\n                                    }\n                                ],\n                                \"https://github.com/Extraltodeus/LoadLoraWithTags\": [\n                                    [\n                                        \"LoraLoaderTagsQuery\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"LoadLoraWithTags\"\n                                    }\n                                ],\n                                \"https://github.com/Extraltodeus/noise_latent_perlinpinpin\": [\n                                    [\n                                        \"NoisyLatentPerlin\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"noise latent perlinpinpin\"\n                                    }\n                                ],\n                                \"https://github.com/Extraltodeus/sigmas_tools_and_the_golden_scheduler\": [\n                                    [\n                                        \"Get sigmas as float\",\n                                        \"Graph sigmas\",\n                                        \"Manual scheduler\",\n                                        \"Merge sigmas by average\",\n                                        \"Merge sigmas gradually\",\n                                        \"Multiply sigmas\",\n                                        \"Split and concatenate sigmas\",\n                                        \"The Golden Scheduler\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"sigmas_tools_and_the_golden_scheduler\"\n                                    }\n                                ],\n                                \"https://github.com/Fannovel16/ComfyUI-Frame-Interpolation\": [\n                                    [\n                                        \"AMT VFI\",\n                                        \"CAIN VFI\",\n                                        \"EISAI VFI\",\n                                        \"FILM VFI\",\n                                        \"FLAVR VFI\",\n                                        \"GMFSS Fortuna VFI\",\n                                        \"IFRNet VFI\",\n                                        \"IFUnet VFI\",\n                                        \"KSampler Gradually Adding More Denoise (efficient)\",\n                                        \"M2M VFI\",\n                                        \"Make Interpolation State List\",\n                                        \"RIFE VFI\",\n                                        \"STMFNet VFI\",\n                                        \"Sepconv VFI\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Frame Interpolation\"\n                                    }\n                                ],\n                                \"https://github.com/Fannovel16/ComfyUI-Loopchain\": [\n                                    [\n                                        \"EmptyLatentImageLoop\",\n                                        \"FolderToImageStorage\",\n                                        \"ImageStorageExportLoop\",\n                                        \"ImageStorageImport\",\n                                        \"ImageStorageReset\",\n                                        \"LatentStorageExportLoop\",\n                                        \"LatentStorageImport\",\n                                        \"LatentStorageReset\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Loopchain\"\n                                    }\n                                ],\n                                \"https://github.com/Fannovel16/ComfyUI-MotionDiff\": [\n                                    [\n                                        \"EmptyMotionData\",\n                                        \"ExportSMPLTo3DSoftware\",\n                                        \"MotionCLIPTextEncode\",\n                                        \"MotionDataVisualizer\",\n                                        \"MotionDiffLoader\",\n                                        \"MotionDiffSimpleSampler\",\n                                        \"RenderSMPLMesh\",\n                                        \"SMPLLoader\",\n                                        \"SaveSMPL\",\n                                        \"SmplifyMotionData\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI MotionDiff\"\n                                    }\n                                ],\n                                \"https://github.com/Fannovel16/ComfyUI-Video-Matting\": [\n                                    [\n                                        \"BRIAAI Matting\",\n                                        \"Robust Video Matting\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Video-Matting\"\n                                    }\n                                ],\n                                \"https://github.com/Fannovel16/comfyui_controlnet_aux\": [\n                                    [\n                                        \"AIO_Preprocessor\",\n                                        \"AnimalPosePreprocessor\",\n                                        \"AnimeFace_SemSegPreprocessor\",\n                                        \"AnimeLineArtPreprocessor\",\n                                        \"BAE-NormalMapPreprocessor\",\n                                        \"BinaryPreprocessor\",\n                                        \"CannyEdgePreprocessor\",\n                                        \"ColorPreprocessor\",\n                                        \"DWPreprocessor\",\n                                        \"DensePosePreprocessor\",\n                                        \"DepthAnythingPreprocessor\",\n                                        \"DiffusionEdge_Preprocessor\",\n                                        \"FacialPartColoringFromPoseKps\",\n                                        \"FakeScribblePreprocessor\",\n                                        \"HEDPreprocessor\",\n                                        \"HintImageEnchance\",\n                                        \"ImageGenResolutionFromImage\",\n                                        \"ImageGenResolutionFromLatent\",\n                                        \"ImageIntensityDetector\",\n                                        \"ImageLuminanceDetector\",\n                                        \"InpaintPreprocessor\",\n                                        \"LeReS-DepthMapPreprocessor\",\n                                        \"LineArtPreprocessor\",\n                                        \"LineartStandardPreprocessor\",\n                                        \"M-LSDPreprocessor\",\n                                        \"Manga2Anime_LineArt_Preprocessor\",\n                                        \"MaskOptFlow\",\n                                        \"MediaPipe-FaceMeshPreprocessor\",\n                                        \"MeshGraphormer-DepthMapPreprocessor\",\n                                        \"MiDaS-DepthMapPreprocessor\",\n                                        \"MiDaS-NormalMapPreprocessor\",\n                                        \"OneFormer-ADE20K-SemSegPreprocessor\",\n                                        \"OneFormer-COCO-SemSegPreprocessor\",\n                                        \"OpenposePreprocessor\",\n                                        \"PiDiNetPreprocessor\",\n                                        \"PixelPerfectResolution\",\n                                        \"SAMPreprocessor\",\n                                        \"SavePoseKpsAsJsonFile\",\n                                        \"ScribblePreprocessor\",\n                                        \"Scribble_XDoG_Preprocessor\",\n                                        \"SemSegPreprocessor\",\n                                        \"ShufflePreprocessor\",\n                                        \"TEEDPreprocessor\",\n                                        \"TilePreprocessor\",\n                                        \"UniFormer-SemSegPreprocessor\",\n                                        \"Unimatch_OptFlowPreprocessor\",\n                                        \"Zoe-DepthMapPreprocessor\",\n                                        \"Zoe_DepthAnythingPreprocessor\"\n                                    ],\n                                    {\n                                        \"author\": \"tstandley\",\n                                        \"title_aux\": \"ComfyUI's ControlNet Auxiliary Preprocessors\"\n                                    }\n                                ],\n                                \"https://github.com/Feidorian/feidorian-ComfyNodes\": [\n                                    [],\n                                    {\n                                        \"nodename_pattern\": \"^Feidorian_\",\n                                        \"title_aux\": \"feidorian-ComfyNodes\"\n                                    }\n                                ],\n                                \"https://github.com/Fictiverse/ComfyUI_Fictiverse\": [\n                                    [\n                                        \"Add Noise to Image with Mask\",\n                                        \"Color correction\",\n                                        \"Displace Image with Depth\",\n                                        \"Displace Images with Mask\",\n                                        \"Zoom Image with Depth\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Fictiverse Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/FizzleDorf/ComfyUI-AIT\": [\n                                    [\n                                        \"AIT_Unet_Loader\",\n                                        \"AIT_VAE_Encode_Loader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-AIT\"\n                                    }\n                                ],\n                                \"https://github.com/FizzleDorf/ComfyUI_FizzNodes\": [\n                                    [\n                                        \"AbsCosWave\",\n                                        \"AbsSinWave\",\n                                        \"BatchGLIGENSchedule\",\n                                        \"BatchPromptSchedule\",\n                                        \"BatchPromptScheduleEncodeSDXL\",\n                                        \"BatchPromptScheduleLatentInput\",\n                                        \"BatchPromptScheduleNodeFlowEnd\",\n                                        \"BatchPromptScheduleSDXLLatentInput\",\n                                        \"BatchStringSchedule\",\n                                        \"BatchValueSchedule\",\n                                        \"BatchValueScheduleLatentInput\",\n                                        \"CalculateFrameOffset\",\n                                        \"ConcatStringSingle\",\n                                        \"CosWave\",\n                                        \"FizzFrame\",\n                                        \"FizzFrameConcatenate\",\n                                        \"ImageBatchFromValueSchedule\",\n                                        \"Init FizzFrame\",\n                                        \"InvCosWave\",\n                                        \"InvSinWave\",\n                                        \"Lerp\",\n                                        \"PromptSchedule\",\n                                        \"PromptScheduleEncodeSDXL\",\n                                        \"PromptScheduleNodeFlow\",\n                                        \"PromptScheduleNodeFlowEnd\",\n                                        \"SawtoothWave\",\n                                        \"SinWave\",\n                                        \"SquareWave\",\n                                        \"StringConcatenate\",\n                                        \"StringSchedule\",\n                                        \"TriangleWave\",\n                                        \"ValueSchedule\",\n                                        \"convertKeyframeKeysToBatchKeys\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"FizzNodes\"\n                                    }\n                                ],\n                                \"https://github.com/FlyingFireCo/tiled_ksampler\": [\n                                    [\n                                        \"Asymmetric Tiled KSampler\",\n                                        \"Circular VAEDecode\",\n                                        \"Tiled KSampler\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"tiled_ksampler\"\n                                    }\n                                ],\n                                \"https://github.com/Franck-Demongin/NX_PromptStyler\": [\n                                    [\n                                        \"NX_PromptStyler\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"NX_PromptStyler\"\n                                    }\n                                ],\n                                \"https://github.com/GMapeSplat/ComfyUI_ezXY\": [\n                                    [\n                                        \"ConcatenateString\",\n                                        \"ItemFromDropdown\",\n                                        \"IterationDriver\",\n                                        \"JoinImages\",\n                                        \"LineToConsole\",\n                                        \"NumberFromList\",\n                                        \"NumbersToList\",\n                                        \"PlotImages\",\n                                        \"StringFromList\",\n                                        \"StringToLabel\",\n                                        \"StringsToList\",\n                                        \"ezMath\",\n                                        \"ezXY_AssemblePlot\",\n                                        \"ezXY_Driver\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ezXY scripts and nodes\"\n                                    }\n                                ],\n                                \"https://github.com/GTSuya-Studio/ComfyUI-Gtsuya-Nodes\": [\n                                    [\n                                        \"Danbooru (ID)\",\n                                        \"Danbooru (Random)\",\n                                        \"Random File From Path\",\n                                        \"Replace Strings\",\n                                        \"Simple Wildcards\",\n                                        \"Simple Wildcards (Dir.)\",\n                                        \"Wildcards Nodes\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-GTSuya-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/Gourieff/comfyui-reactor-node\": [\n                                    [\n                                        \"ReActorFaceSwap\",\n                                        \"ReActorLoadFaceModel\",\n                                        \"ReActorRestoreFace\",\n                                        \"ReActorSaveFaceModel\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ReActor Node for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/HAL41/ComfyUI-aichemy-nodes\": [\n                                    [\n                                        \"aichemyYOLOv8Segmentation\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI aichemy nodes\"\n                                    }\n                                ],\n                                \"https://github.com/Hangover3832/ComfyUI-Hangover-Moondream\": [\n                                    [\n                                        \"Moondream Interrogator (NO COMMERCIAL USE)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Hangover-Moondream\"\n                                    }\n                                ],\n                                \"https://github.com/Hangover3832/ComfyUI-Hangover-Nodes\": [\n                                    [\n                                        \"Image Scale Bounding Box\",\n                                        \"MS kosmos-2 Interrogator\",\n                                        \"Make Inpaint Model\",\n                                        \"Save Image w/o Metadata\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Hangover-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/Haoming02/comfyui-diffusion-cg\": [\n                                    [\n                                        \"Normalization\",\n                                        \"NormalizationXL\",\n                                        \"Recenter\",\n                                        \"Recenter XL\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Diffusion Color Grading\"\n                                    }\n                                ],\n                                \"https://github.com/Haoming02/comfyui-floodgate\": [\n                                    [\n                                        \"FloodGate\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Floodgate\"\n                                    }\n                                ],\n                                \"https://github.com/HaydenReeve/ComfyUI-Better-Strings\": [\n                                    [\n                                        \"BetterString\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Better Strings\"\n                                    }\n                                ],\n                                \"https://github.com/HebelHuber/comfyui-enhanced-save-node\": [\n                                    [\n                                        \"EnhancedSaveNode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-enhanced-save-node\"\n                                    }\n                                ],\n                                \"https://github.com/Hiero207/ComfyUI-Hiero-Nodes\": [\n                                    [\n                                        \"Post to Discord w/ Webhook\"\n                                    ],\n                                    {\n                                        \"author\": \"Hiero\",\n                                        \"description\": \"Just some nodes that I wanted/needed, so I made them.\",\n                                        \"nickname\": \"HNodes\",\n                                        \"title\": \"Hiero-Nodes\",\n                                        \"title_aux\": \"ComfyUI-Hiero-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/IDGallagher/ComfyUI-IG-Nodes\": [\n                                    [\n                                        \"IG Analyze SSIM\",\n                                        \"IG Cross Fade Images\",\n                                        \"IG Explorer\",\n                                        \"IG Float\",\n                                        \"IG Folder\",\n                                        \"IG Int\",\n                                        \"IG Load Image\",\n                                        \"IG Load Images\",\n                                        \"IG Multiply\",\n                                        \"IG Path Join\",\n                                        \"IG String\",\n                                        \"IG ZFill\"\n                                    ],\n                                    {\n                                        \"author\": \"IDGallagher\",\n                                        \"description\": \"Custom nodes to aid in the exploration of Latent Space\",\n                                        \"nickname\": \"IG Interpolation Nodes\",\n                                        \"title\": \"IG Interpolation Nodes\",\n                                        \"title_aux\": \"IG Interpolation Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/Inzaniak/comfyui-ranbooru\": [\n                                    [\n                                        \"PromptBackground\",\n                                        \"PromptLimit\",\n                                        \"PromptMix\",\n                                        \"PromptRandomWeight\",\n                                        \"PromptRemove\",\n                                        \"Ranbooru\",\n                                        \"RanbooruURL\",\n                                        \"RandomPicturePath\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Ranbooru for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/JPS-GER/ComfyUI_JPS-Nodes\": [\n                                    [\n                                        \"Conditioning Switch (JPS)\",\n                                        \"ControlNet Switch (JPS)\",\n                                        \"Crop Image Pipe (JPS)\",\n                                        \"Crop Image Settings (JPS)\",\n                                        \"Crop Image Square (JPS)\",\n                                        \"Crop Image TargetSize (JPS)\",\n                                        \"CtrlNet CannyEdge Pipe (JPS)\",\n                                        \"CtrlNet CannyEdge Settings (JPS)\",\n                                        \"CtrlNet MiDaS Pipe (JPS)\",\n                                        \"CtrlNet MiDaS Settings (JPS)\",\n                                        \"CtrlNet OpenPose Pipe (JPS)\",\n                                        \"CtrlNet OpenPose Settings (JPS)\",\n                                        \"CtrlNet ZoeDepth Pipe (JPS)\",\n                                        \"CtrlNet ZoeDepth Settings (JPS)\",\n                                        \"Disable Enable Switch (JPS)\",\n                                        \"Enable Disable Switch (JPS)\",\n                                        \"Generation TXT IMG Settings (JPS)\",\n                                        \"Get Date Time String (JPS)\",\n                                        \"Get Image Size (JPS)\",\n                                        \"IP Adapter Settings (JPS)\",\n                                        \"IP Adapter Settings Pipe (JPS)\",\n                                        \"IP Adapter Single Settings (JPS)\",\n                                        \"IP Adapter Single Settings Pipe (JPS)\",\n                                        \"IPA Switch (JPS)\",\n                                        \"Image Switch (JPS)\",\n                                        \"ImageToImage Pipe (JPS)\",\n                                        \"ImageToImage Settings (JPS)\",\n                                        \"Images Masks MultiPipe (JPS)\",\n                                        \"Integer Switch (JPS)\",\n                                        \"Largest Int (JPS)\",\n                                        \"Latent Switch (JPS)\",\n                                        \"Lora Loader (JPS)\",\n                                        \"Mask Switch (JPS)\",\n                                        \"Model Switch (JPS)\",\n                                        \"Multiply Float Float (JPS)\",\n                                        \"Multiply Int Float (JPS)\",\n                                        \"Multiply Int Int (JPS)\",\n                                        \"Resolution Multiply (JPS)\",\n                                        \"Revision Settings (JPS)\",\n                                        \"Revision Settings Pipe (JPS)\",\n                                        \"SDXL Basic Settings (JPS)\",\n                                        \"SDXL Basic Settings Pipe (JPS)\",\n                                        \"SDXL Fundamentals MultiPipe (JPS)\",\n                                        \"SDXL Prompt Handling (JPS)\",\n                                        \"SDXL Prompt Handling Plus (JPS)\",\n                                        \"SDXL Prompt Styler (JPS)\",\n                                        \"SDXL Recommended Resolution Calc (JPS)\",\n                                        \"SDXL Resolutions (JPS)\",\n                                        \"Sampler Scheduler Settings (JPS)\",\n                                        \"Save Images Plus (JPS)\",\n                                        \"Substract Int Int (JPS)\",\n                                        \"Text Concatenate (JPS)\",\n                                        \"Text Prompt (JPS)\",\n                                        \"VAE Switch (JPS)\"\n                                    ],\n                                    {\n                                        \"author\": \"JPS\",\n                                        \"description\": \"Various nodes to handle SDXL Resolutions, SDXL Basic Settings, IP Adapter Settings, Revision Settings, SDXL Prompt Styler, Crop Image to Square, Crop Image to Target Size, Get Date-Time String, Resolution Multiply, Largest Integer, 5-to-1 Switches for Integer, Images, Latents, Conditioning, Model, VAE, ControlNet\",\n                                        \"nickname\": \"JPS Custom Nodes\",\n                                        \"title\": \"JPS Custom Nodes for ComfyUI\",\n                                        \"title_aux\": \"JPS Custom Nodes for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/JaredTherriault/ComfyUI-JNodes\": [\n                                    [\n                                        \"JNodes_AddOrSetMetaDataKey\",\n                                        \"JNodes_AnyToString\",\n                                        \"JNodes_AppendReversedFrames\",\n                                        \"JNodes_BooleanSelectorWithString\",\n                                        \"JNodes_CheckpointSelectorWithString\",\n                                        \"JNodes_GetOutputDirectory\",\n                                        \"JNodes_GetParameterFromList\",\n                                        \"JNodes_GetParameterGlobal\",\n                                        \"JNodes_GetTempDirectory\",\n                                        \"JNodes_ImageFormatSelector\",\n                                        \"JNodes_ImageSizeSelector\",\n                                        \"JNodes_LoadVideo\",\n                                        \"JNodes_LoraExtractor\",\n                                        \"JNodes_OutVideoInfo\",\n                                        \"JNodes_ParseDynamicPrompts\",\n                                        \"JNodes_ParseParametersToGlobalList\",\n                                        \"JNodes_ParseWildcards\",\n                                        \"JNodes_PromptBuilderSingleSubject\",\n                                        \"JNodes_RemoveCommentedText\",\n                                        \"JNodes_RemoveMetaDataKey\",\n                                        \"JNodes_RemoveParseableDataForInference\",\n                                        \"JNodes_SamplerSelectorWithString\",\n                                        \"JNodes_SaveImageWithOutput\",\n                                        \"JNodes_SaveVideo\",\n                                        \"JNodes_SchedulerSelectorWithString\",\n                                        \"JNodes_SearchAndReplace\",\n                                        \"JNodes_SearchAndReplaceFromFile\",\n                                        \"JNodes_SearchAndReplaceFromList\",\n                                        \"JNodes_SetNegativePromptInMetaData\",\n                                        \"JNodes_SetPositivePromptInMetaData\",\n                                        \"JNodes_SplitAndJoin\",\n                                        \"JNodes_StringLiteral\",\n                                        \"JNodes_SyncedStringLiteral\",\n                                        \"JNodes_TokenCounter\",\n                                        \"JNodes_TrimAndStrip\",\n                                        \"JNodes_UploadVideo\",\n                                        \"JNodes_VaeSelectorWithString\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-JNodes\"\n                                    }\n                                ],\n                                \"https://github.com/JcandZero/ComfyUI_GLM4Node\": [\n                                    [\n                                        \"GLM3_turbo_CHAT\",\n                                        \"GLM4_CHAT\",\n                                        \"GLM4_Vsion_IMGURL\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_GLM4Node\"\n                                    }\n                                ],\n                                \"https://github.com/Jcd1230/rembg-comfyui-node\": [\n                                    [\n                                        \"Image Remove Background (rembg)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Rembg Background Removal Node for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/JerryOrbachJr/ComfyUI-RandomSize\": [\n                                    [\n                                        \"JOJR_RandomSize\"\n                                    ],\n                                    {\n                                        \"author\": \"JerryOrbachJr\",\n                                        \"description\": \"A ComfyUI custom node that randomly selects a height and width pair from a list in a config file\",\n                                        \"nickname\": \"Random Size\",\n                                        \"title\": \"Random Size\",\n                                        \"title_aux\": \"ComfyUI-RandomSize\"\n                                    }\n                                ],\n                                \"https://github.com/Jordach/comfy-plasma\": [\n                                    [\n                                        \"JDC_AutoContrast\",\n                                        \"JDC_BlendImages\",\n                                        \"JDC_BrownNoise\",\n                                        \"JDC_Contrast\",\n                                        \"JDC_EqualizeGrey\",\n                                        \"JDC_GaussianBlur\",\n                                        \"JDC_GreyNoise\",\n                                        \"JDC_Greyscale\",\n                                        \"JDC_ImageLoader\",\n                                        \"JDC_ImageLoaderMeta\",\n                                        \"JDC_PinkNoise\",\n                                        \"JDC_Plasma\",\n                                        \"JDC_PlasmaSampler\",\n                                        \"JDC_PowerImage\",\n                                        \"JDC_RandNoise\",\n                                        \"JDC_ResizeFactor\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfy-plasma\"\n                                    }\n                                ],\n                                \"https://github.com/Kaharos94/ComfyUI-Saveaswebp\": [\n                                    [\n                                        \"Save_as_webp\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Saveaswebp\"\n                                    }\n                                ],\n                                \"https://github.com/Kangkang625/ComfyUI-paint-by-example\": [\n                                    [\n                                        \"PaintbyExamplePipeLoader\",\n                                        \"PaintbyExampleSampler\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Paint-by-Example\"\n                                    }\n                                ],\n                                \"https://github.com/Kosinkadink/ComfyUI-Advanced-ControlNet\": [\n                                    [\n                                        \"ACN_AdvancedControlNetApply\",\n                                        \"ACN_ControlNetLoaderWithLoraAdvanced\",\n                                        \"ACN_DefaultUniversalWeights\",\n                                        \"ACN_SparseCtrlIndexMethodNode\",\n                                        \"ACN_SparseCtrlLoaderAdvanced\",\n                                        \"ACN_SparseCtrlMergedLoaderAdvanced\",\n                                        \"ACN_SparseCtrlRGBPreprocessor\",\n                                        \"ACN_SparseCtrlSpreadMethodNode\",\n                                        \"ControlNetLoaderAdvanced\",\n                                        \"CustomControlNetWeights\",\n                                        \"CustomT2IAdapterWeights\",\n                                        \"DiffControlNetLoaderAdvanced\",\n                                        \"LatentKeyframe\",\n                                        \"LatentKeyframeBatchedGroup\",\n                                        \"LatentKeyframeGroup\",\n                                        \"LatentKeyframeTiming\",\n                                        \"LoadImagesFromDirectory\",\n                                        \"ScaledSoftControlNetWeights\",\n                                        \"ScaledSoftMaskedUniversalWeights\",\n                                        \"SoftControlNetWeights\",\n                                        \"SoftT2IAdapterWeights\",\n                                        \"TimestepKeyframe\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Advanced-ControlNet\"\n                                    }\n                                ],\n                                \"https://github.com/Kosinkadink/ComfyUI-AnimateDiff-Evolved\": [\n                                    [\n                                        \"ADE_AdjustPEFullStretch\",\n                                        \"ADE_AdjustPEManual\",\n                                        \"ADE_AdjustPESweetspotStretch\",\n                                        \"ADE_AnimateDiffCombine\",\n                                        \"ADE_AnimateDiffKeyframe\",\n                                        \"ADE_AnimateDiffLoRALoader\",\n                                        \"ADE_AnimateDiffLoaderGen1\",\n                                        \"ADE_AnimateDiffLoaderV1Advanced\",\n                                        \"ADE_AnimateDiffLoaderWithContext\",\n                                        \"ADE_AnimateDiffModelSettings\",\n                                        \"ADE_AnimateDiffModelSettingsAdvancedAttnStrengths\",\n                                        \"ADE_AnimateDiffModelSettingsSimple\",\n                                        \"ADE_AnimateDiffModelSettings_Release\",\n                                        \"ADE_AnimateDiffSamplingSettings\",\n                                        \"ADE_AnimateDiffSettings\",\n                                        \"ADE_AnimateDiffUniformContextOptions\",\n                                        \"ADE_AnimateDiffUnload\",\n                                        \"ADE_ApplyAnimateDiffModel\",\n                                        \"ADE_ApplyAnimateDiffModelSimple\",\n                                        \"ADE_BatchedContextOptions\",\n                                        \"ADE_CustomCFG\",\n                                        \"ADE_CustomCFGKeyframe\",\n                                        \"ADE_EmptyLatentImageLarge\",\n                                        \"ADE_IterationOptsDefault\",\n                                        \"ADE_IterationOptsFreeInit\",\n                                        \"ADE_LoadAnimateDiffModel\",\n                                        \"ADE_LoopedUniformContextOptions\",\n                                        \"ADE_LoopedUniformViewOptions\",\n                                        \"ADE_MaskedLoadLora\",\n                                        \"ADE_MultivalDynamic\",\n                                        \"ADE_MultivalScaledMask\",\n                                        \"ADE_NoiseLayerAdd\",\n                                        \"ADE_NoiseLayerAddWeighted\",\n                                        \"ADE_NoiseLayerReplace\",\n                                        \"ADE_RawSigmaSchedule\",\n                                        \"ADE_SigmaSchedule\",\n                                        \"ADE_SigmaScheduleSplitAndCombine\",\n                                        \"ADE_SigmaScheduleWeightedAverage\",\n                                        \"ADE_SigmaScheduleWeightedAverageInterp\",\n                                        \"ADE_StandardStaticContextOptions\",\n                                        \"ADE_StandardStaticViewOptions\",\n                                        \"ADE_StandardUniformContextOptions\",\n                                        \"ADE_StandardUniformViewOptions\",\n                                        \"ADE_UseEvolvedSampling\",\n                                        \"ADE_ViewsOnlyContextOptions\",\n                                        \"AnimateDiffLoaderV1\",\n                                        \"CheckpointLoaderSimpleWithNoiseSelect\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"AnimateDiff Evolved\"\n                                    }\n                                ],\n                                \"https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite\": [\n                                    [\n                                        \"VHS_BatchManager\",\n                                        \"VHS_DuplicateImages\",\n                                        \"VHS_DuplicateLatents\",\n                                        \"VHS_DuplicateMasks\",\n                                        \"VHS_GetImageCount\",\n                                        \"VHS_GetLatentCount\",\n                                        \"VHS_GetMaskCount\",\n                                        \"VHS_LoadAudio\",\n                                        \"VHS_LoadImages\",\n                                        \"VHS_LoadImagesPath\",\n                                        \"VHS_LoadVideo\",\n                                        \"VHS_LoadVideoPath\",\n                                        \"VHS_MergeImages\",\n                                        \"VHS_MergeLatents\",\n                                        \"VHS_MergeMasks\",\n                                        \"VHS_PruneOutputs\",\n                                        \"VHS_SelectEveryNthImage\",\n                                        \"VHS_SelectEveryNthLatent\",\n                                        \"VHS_SelectEveryNthMask\",\n                                        \"VHS_SplitImages\",\n                                        \"VHS_SplitLatents\",\n                                        \"VHS_SplitMasks\",\n                                        \"VHS_VAEDecodeBatched\",\n                                        \"VHS_VAEEncodeBatched\",\n                                        \"VHS_VideoCombine\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-VideoHelperSuite\"\n                                    }\n                                ],\n                                \"https://github.com/LEv145/images-grid-comfy-plugin\": [\n                                    [\n                                        \"GridAnnotation\",\n                                        \"ImageCombine\",\n                                        \"ImagesGridByColumns\",\n                                        \"ImagesGridByRows\",\n                                        \"LatentCombine\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ImagesGrid\"\n                                    }\n                                ],\n                                \"https://github.com/LarryJane491/Image-Captioning-in-ComfyUI\": [\n                                    [\n                                        \"LoRA Caption Load\",\n                                        \"LoRA Caption Save\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Image-Captioning-in-ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/LarryJane491/Lora-Training-in-Comfy\": [\n                                    [\n                                        \"Lora Training in Comfy (Advanced)\",\n                                        \"Lora Training in ComfyUI\",\n                                        \"Tensorboard Access\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Lora-Training-in-Comfy\"\n                                    }\n                                ],\n                                \"https://github.com/Layer-norm/comfyui-lama-remover\": [\n                                    [\n                                        \"LamaRemover\",\n                                        \"LamaRemoverIMG\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfyui lama remover\"\n                                    }\n                                ],\n                                \"https://github.com/Lerc/canvas_tab\": [\n                                    [\n                                        \"Canvas_Tab\",\n                                        \"Send_To_Editor\"\n                                    ],\n                                    {\n                                        \"author\": \"Lerc\",\n                                        \"description\": \"This extension provides a full page image editor with mask support. There are two nodes, one to receive images from the editor and one to send images to the editor.\",\n                                        \"nickname\": \"Canvas Tab\",\n                                        \"title\": \"Canvas Tab\",\n                                        \"title_aux\": \"Canvas Tab\"\n                                    }\n                                ],\n                                \"https://github.com/Limitex/ComfyUI-Calculation\": [\n                                    [\n                                        \"CenterCalculation\",\n                                        \"CreateQRCode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Calculation\"\n                                    }\n                                ],\n                                \"https://github.com/Limitex/ComfyUI-Diffusers\": [\n                                    [\n                                        \"CreateIntListNode\",\n                                        \"DiffusersClipTextEncode\",\n                                        \"DiffusersModelMakeup\",\n                                        \"DiffusersPipelineLoader\",\n                                        \"DiffusersSampler\",\n                                        \"DiffusersSchedulerLoader\",\n                                        \"DiffusersVaeLoader\",\n                                        \"LcmLoraLoader\",\n                                        \"StreamDiffusionCreateStream\",\n                                        \"StreamDiffusionFastSampler\",\n                                        \"StreamDiffusionSampler\",\n                                        \"StreamDiffusionWarmup\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Diffusers\"\n                                    }\n                                ],\n                                \"https://github.com/Loewen-Hob/rembg-comfyui-node-better\": [\n                                    [\n                                        \"Image Remove Background (rembg)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Rembg Background Removal Node for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/LonicaMewinsky/ComfyUI-MakeFrame\": [\n                                    [\n                                        \"BreakFrames\",\n                                        \"BreakGrid\",\n                                        \"GetKeyFrames\",\n                                        \"MakeGrid\",\n                                        \"RandomImageFromDir\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyBreakAnim\"\n                                    }\n                                ],\n                                \"https://github.com/LonicaMewinsky/ComfyUI-RawSaver\": [\n                                    [\n                                        \"SaveTifImage\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-RawSaver\"\n                                    }\n                                ],\n                                \"https://github.com/LyazS/comfyui-anime-seg\": [\n                                    [\n                                        \"Anime Character Seg\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Anime Character Segmentation node for comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/M1kep/ComfyLiterals\": [\n                                    [\n                                        \"Checkpoint\",\n                                        \"Float\",\n                                        \"Int\",\n                                        \"KepStringLiteral\",\n                                        \"Lora\",\n                                        \"Operation\",\n                                        \"String\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyLiterals\"\n                                    }\n                                ],\n                                \"https://github.com/M1kep/ComfyUI-KepOpenAI\": [\n                                    [\n                                        \"KepOpenAI_ImageWithPrompt\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-KepOpenAI\"\n                                    }\n                                ],\n                                \"https://github.com/M1kep/ComfyUI-OtherVAEs\": [\n                                    [\n                                        \"OtherVAE_Taesd\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-OtherVAEs\"\n                                    }\n                                ],\n                                \"https://github.com/M1kep/Comfy_KepKitchenSink\": [\n                                    [\n                                        \"KepRotateImage\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfy_KepKitchenSink\"\n                                    }\n                                ],\n                                \"https://github.com/M1kep/Comfy_KepListStuff\": [\n                                    [\n                                        \"Empty Images\",\n                                        \"Image Overlay\",\n                                        \"ImageListLoader\",\n                                        \"Join Float Lists\",\n                                        \"Join Image Lists\",\n                                        \"KepStringList\",\n                                        \"KepStringListFromNewline\",\n                                        \"Kep_JoinListAny\",\n                                        \"Kep_RepeatList\",\n                                        \"Kep_ReverseList\",\n                                        \"Kep_VariableImageBuilder\",\n                                        \"List Length\",\n                                        \"Range(Num Steps) - Float\",\n                                        \"Range(Num Steps) - Int\",\n                                        \"Range(Step) - Float\",\n                                        \"Range(Step) - Int\",\n                                        \"Stack Images\",\n                                        \"XYAny\",\n                                        \"XYImage\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfy_KepListStuff\"\n                                    }\n                                ],\n                                \"https://github.com/M1kep/Comfy_KepMatteAnything\": [\n                                    [\n                                        \"MatteAnything_DinoBoxes\",\n                                        \"MatteAnything_GenerateVITMatte\",\n                                        \"MatteAnything_InitSamPredictor\",\n                                        \"MatteAnything_LoadDINO\",\n                                        \"MatteAnything_LoadVITMatteModel\",\n                                        \"MatteAnything_SAMLoader\",\n                                        \"MatteAnything_SAMMaskFromBoxes\",\n                                        \"MatteAnything_ToTrimap\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfy_KepMatteAnything\"\n                                    }\n                                ],\n                                \"https://github.com/M1kep/KepPromptLang\": [\n                                    [\n                                        \"Build Gif\",\n                                        \"Special CLIP Loader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"KepPromptLang\"\n                                    }\n                                ],\n                                \"https://github.com/MNeMoNiCuZ/ComfyUI-mnemic-nodes\": [\n                                    [\n                                        \"Save Text File_mne\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-mnemic-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/Mamaaaamooooo/batchImg-rembg-ComfyUI-nodes\": [\n                                    [\n                                        \"Image Remove Background (rembg)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Batch Rembg for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/ManglerFTW/ComfyI2I\": [\n                                    [\n                                        \"Color Transfer\",\n                                        \"Combine and Paste\",\n                                        \"Inpaint Segments\",\n                                        \"Mask Ops\"\n                                    ],\n                                    {\n                                        \"author\": \"ManglerFTW\",\n                                        \"title\": \"ComfyI2I\",\n                                        \"title_aux\": \"ComfyI2I\"\n                                    }\n                                ],\n                                \"https://github.com/MarkoCa1/ComfyUI_Segment_Mask\": [\n                                    [\n                                        \"AutomaticMask(segment anything)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_Segment_Mask\"\n                                    }\n                                ],\n                                \"https://github.com/Miosp/ComfyUI-FBCNN\": [\n                                    [\n                                        \"JPEG artifacts removal FBCNN\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-FBCNN\"\n                                    }\n                                ],\n                                \"https://github.com/MitoshiroPJ/comfyui_slothful_attention\": [\n                                    [\n                                        \"NearSightedAttention\",\n                                        \"NearSightedAttentionSimple\",\n                                        \"NearSightedTile\",\n                                        \"SlothfulAttention\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Slothful Attention\"\n                                    }\n                                ],\n                                \"https://github.com/MrForExample/ComfyUI-3D-Pack\": [\n                                    [],\n                                    {\n                                        \"nodename_pattern\": \"^\\\\[Comfy3D\\\\]\",\n                                        \"title_aux\": \"ComfyUI-3D-Pack\"\n                                    }\n                                ],\n                                \"https://github.com/MrForExample/ComfyUI-AnimateAnyone-Evolved\": [\n                                    [],\n                                    {\n                                        \"nodename_pattern\": \"^\\\\[AnimateAnyone\\\\]\",\n                                        \"title_aux\": \"ComfyUI-AnimateAnyone-Evolved\"\n                                    }\n                                ],\n                                \"https://github.com/NicholasMcCarthy/ComfyUI_TravelSuite\": [\n                                    [\n                                        \"LatentTravel\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_TravelSuite\"\n                                    }\n                                ],\n                                \"https://github.com/NimaNzrii/comfyui-photoshop\": [\n                                    [\n                                        \"PhotoshopToComfyUI\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-photoshop\"\n                                    }\n                                ],\n                                \"https://github.com/NimaNzrii/comfyui-popup_preview\": [\n                                    [\n                                        \"PreviewPopup\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-popup_preview\"\n                                    }\n                                ],\n                                \"https://github.com/Niutonian/ComfyUi-NoodleWebcam\": [\n                                    [\n                                        \"WebcamNode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUi-NoodleWebcam\"\n                                    }\n                                ],\n                                \"https://github.com/Nlar/ComfyUI_CartoonSegmentation\": [\n                                    [\n                                        \"AnimeSegmentation\",\n                                        \"KenBurnsConfigLoader\",\n                                        \"KenBurns_Processor\",\n                                        \"LoadImageFilename\"\n                                    ],\n                                    {\n                                        \"author\": \"Nels Larsen\",\n                                        \"description\": \"This extension offers a front end to the Cartoon Segmentation Project (https://github.com/CartoonSegmentation/CartoonSegmentation)\",\n                                        \"nickname\": \"CfyCS\",\n                                        \"title\": \"ComfyUI_CartoonSegmentation\",\n                                        \"title_aux\": \"ComfyUI_CartoonSegmentation\"\n                                    }\n                                ],\n                                \"https://github.com/NotHarroweD/Harronode\": [\n                                    [\n                                        \"Harronode\"\n                                    ],\n                                    {\n                                        \"author\": \"HarroweD and quadmoon (https://github.com/traugdor)\",\n                                        \"description\": \"This extension to ComfyUI will build a prompt for the Harrlogos LoRA for SDXL.\",\n                                        \"nickname\": \"Harronode\",\n                                        \"nodename_pattern\": \"Harronode\",\n                                        \"title\": \"Harrlogos Prompt Builder Node\",\n                                        \"title_aux\": \"Harronode\"\n                                    }\n                                ],\n                                \"https://github.com/Nourepide/ComfyUI-Allor\": [\n                                    [\n                                        \"AlphaChanelAdd\",\n                                        \"AlphaChanelAddByMask\",\n                                        \"AlphaChanelAsMask\",\n                                        \"AlphaChanelRemove\",\n                                        \"AlphaChanelRestore\",\n                                        \"ClipClamp\",\n                                        \"ClipVisionClamp\",\n                                        \"ClipVisionOutputClamp\",\n                                        \"ConditioningClamp\",\n                                        \"ControlNetClamp\",\n                                        \"GligenClamp\",\n                                        \"ImageBatchCopy\",\n                                        \"ImageBatchFork\",\n                                        \"ImageBatchGet\",\n                                        \"ImageBatchJoin\",\n                                        \"ImageBatchPermute\",\n                                        \"ImageBatchRemove\",\n                                        \"ImageClamp\",\n                                        \"ImageCompositeAbsolute\",\n                                        \"ImageCompositeAbsoluteByContainer\",\n                                        \"ImageCompositeRelative\",\n                                        \"ImageCompositeRelativeByContainer\",\n                                        \"ImageContainer\",\n                                        \"ImageContainerInheritanceAdd\",\n                                        \"ImageContainerInheritanceMax\",\n                                        \"ImageContainerInheritanceScale\",\n                                        \"ImageContainerInheritanceSum\",\n                                        \"ImageDrawArc\",\n                                        \"ImageDrawArcByContainer\",\n                                        \"ImageDrawChord\",\n                                        \"ImageDrawChordByContainer\",\n                                        \"ImageDrawEllipse\",\n                                        \"ImageDrawEllipseByContainer\",\n                                        \"ImageDrawLine\",\n                                        \"ImageDrawLineByContainer\",\n                                        \"ImageDrawPieslice\",\n                                        \"ImageDrawPiesliceByContainer\",\n                                        \"ImageDrawPolygon\",\n                                        \"ImageDrawRectangle\",\n                                        \"ImageDrawRectangleByContainer\",\n                                        \"ImageDrawRectangleRounded\",\n                                        \"ImageDrawRectangleRoundedByContainer\",\n                                        \"ImageEffectsAdjustment\",\n                                        \"ImageEffectsGrayscale\",\n                                        \"ImageEffectsLensBokeh\",\n                                        \"ImageEffectsLensChromaticAberration\",\n                                        \"ImageEffectsLensOpticAxis\",\n                                        \"ImageEffectsLensVignette\",\n                                        \"ImageEffectsLensZoomBurst\",\n                                        \"ImageEffectsNegative\",\n                                        \"ImageEffectsSepia\",\n                                        \"ImageFilterBilateralBlur\",\n                                        \"ImageFilterBlur\",\n                                        \"ImageFilterBoxBlur\",\n                                        \"ImageFilterContour\",\n                                        \"ImageFilterDetail\",\n                                        \"ImageFilterEdgeEnhance\",\n                                        \"ImageFilterEdgeEnhanceMore\",\n                                        \"ImageFilterEmboss\",\n                                        \"ImageFilterFindEdges\",\n                                        \"ImageFilterGaussianBlur\",\n                                        \"ImageFilterGaussianBlurAdvanced\",\n                                        \"ImageFilterMax\",\n                                        \"ImageFilterMedianBlur\",\n                                        \"ImageFilterMin\",\n                                        \"ImageFilterMode\",\n                                        \"ImageFilterRank\",\n                                        \"ImageFilterSharpen\",\n                                        \"ImageFilterSmooth\",\n                                        \"ImageFilterSmoothMore\",\n                                        \"ImageFilterStackBlur\",\n                                        \"ImageNoiseBeta\",\n                                        \"ImageNoiseBinomial\",\n                                        \"ImageNoiseBytes\",\n                                        \"ImageNoiseGaussian\",\n                                        \"ImageSegmentation\",\n                                        \"ImageSegmentationCustom\",\n                                        \"ImageSegmentationCustomAdvanced\",\n                                        \"ImageText\",\n                                        \"ImageTextMultiline\",\n                                        \"ImageTextMultilineOutlined\",\n                                        \"ImageTextOutlined\",\n                                        \"ImageTransformCropAbsolute\",\n                                        \"ImageTransformCropCorners\",\n                                        \"ImageTransformCropRelative\",\n                                        \"ImageTransformPaddingAbsolute\",\n                                        \"ImageTransformPaddingRelative\",\n                                        \"ImageTransformResizeAbsolute\",\n                                        \"ImageTransformResizeClip\",\n                                        \"ImageTransformResizeRelative\",\n                                        \"ImageTransformRotate\",\n                                        \"ImageTransformTranspose\",\n                                        \"LatentClamp\",\n                                        \"MaskClamp\",\n                                        \"ModelClamp\",\n                                        \"StyleModelClamp\",\n                                        \"UpscaleModelClamp\",\n                                        \"VaeClamp\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Allor Plugin\"\n                                    }\n                                ],\n                                \"https://github.com/Nuked88/ComfyUI-N-Nodes\": [\n                                    [\n                                        \"CLIPTextEncodeAdvancedNSuite [n-suite]\",\n                                        \"DynamicPrompt [n-suite]\",\n                                        \"Float Variable [n-suite]\",\n                                        \"FrameInterpolator [n-suite]\",\n                                        \"GPT Loader Simple [n-suite]\",\n                                        \"GPT Sampler [n-suite]\",\n                                        \"ImagePadForOutpaintAdvanced [n-suite]\",\n                                        \"Integer Variable [n-suite]\",\n                                        \"Llava Clip Loader [n-suite]\",\n                                        \"LoadFramesFromFolder [n-suite]\",\n                                        \"LoadVideo [n-suite]\",\n                                        \"SaveVideo [n-suite]\",\n                                        \"SetMetadataForSaveVideo [n-suite]\",\n                                        \"String Variable [n-suite]\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-N-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/Off-Live/ComfyUI-off-suite\": [\n                                    [\n                                        \"Apply CLAHE\",\n                                        \"Cached Image Load From URL\",\n                                        \"Crop Center wigh SEGS\",\n                                        \"Crop Center with SEGS\",\n                                        \"Dilate Mask for Each Face\",\n                                        \"GW Number Formatting\",\n                                        \"Image Crop Fit\",\n                                        \"Image Resize Fit\",\n                                        \"OFF SEGS to Image\",\n                                        \"Paste Face Segment to Image\",\n                                        \"Query Gender and Age\",\n                                        \"SEGS to Face Crop Data\",\n                                        \"Safe Mask to Image\",\n                                        \"VAE Encode For Inpaint V2\",\n                                        \"Watermarking\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-off-suite\"\n                                    }\n                                ],\n                                \"https://github.com/Onierous/QRNG_Node_ComfyUI/raw/main/qrng_node.py\": [\n                                    [\n                                        \"QRNG_Node_CSV\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"QRNG_Node_ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/PCMonsterx/ComfyUI-CSV-Loader\": [\n                                    [\n                                        \"Load Artists CSV\",\n                                        \"Load Artmovements CSV\",\n                                        \"Load Characters CSV\",\n                                        \"Load Colors CSV\",\n                                        \"Load Composition CSV\",\n                                        \"Load Lighting CSV\",\n                                        \"Load Negative CSV\",\n                                        \"Load Positive CSV\",\n                                        \"Load Settings CSV\",\n                                        \"Load Styles CSV\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-CSV-Loader\"\n                                    }\n                                ],\n                                \"https://github.com/ParmanBabra/ComfyUI-Malefish-Custom-Scripts\": [\n                                    [\n                                        \"CSVPromptsLoader\",\n                                        \"CombinePrompt\",\n                                        \"MultiLoraLoader\",\n                                        \"RandomPrompt\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Malefish-Custom-Scripts\"\n                                    }\n                                ],\n                                \"https://github.com/Pfaeff/pfaeff-comfyui\": [\n                                    [\n                                        \"AstropulsePixelDetector\",\n                                        \"BackgroundRemover\",\n                                        \"ImagePadForBetterOutpaint\",\n                                        \"Inpainting\",\n                                        \"InpaintingPipelineLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"pfaeff-comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/QaisMalkawi/ComfyUI-QaisHelper\": [\n                                    [\n                                        \"Bool Binary Operation\",\n                                        \"Bool Unary Operation\",\n                                        \"Item Debugger\",\n                                        \"Item Switch\",\n                                        \"Nearest SDXL Resolution\",\n                                        \"SDXL Resolution\",\n                                        \"Size Swapper\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Qais-Helper\"\n                                    }\n                                ],\n                                \"https://github.com/RenderRift/ComfyUI-RenderRiftNodes\": [\n                                    [\n                                        \"AnalyseMetadata\",\n                                        \"DateIntegerNode\",\n                                        \"DisplayMetaOptions\",\n                                        \"LoadImageWithMeta\",\n                                        \"MetadataOverlayNode\",\n                                        \"VideoPathMetaExtraction\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-RenderRiftNodes\"\n                                    }\n                                ],\n                                \"https://github.com/Ryuukeisyou/comfyui_face_parsing\": [\n                                    [\n                                        \"BBoxListItemSelect(FaceParsing)\",\n                                        \"BBoxResize(FaceParsing)\",\n                                        \"ColorAdjust(FaceParsing)\",\n                                        \"FaceBBoxDetect(FaceParsing)\",\n                                        \"FaceBBoxDetectorLoader(FaceParsing)\",\n                                        \"FaceParse(FaceParsing)\",\n                                        \"FaceParsingModelLoader(FaceParsing)\",\n                                        \"FaceParsingProcessorLoader(FaceParsing)\",\n                                        \"FaceParsingResultsParser(FaceParsing)\",\n                                        \"GuidedFilter(FaceParsing)\",\n                                        \"ImageCropWithBBox(FaceParsing)\",\n                                        \"ImageInsertWithBBox(FaceParsing)\",\n                                        \"ImageListSelect(FaceParsing)\",\n                                        \"ImagePadWithBBox(FaceParsing)\",\n                                        \"ImageResizeCalculator(FaceParsing)\",\n                                        \"ImageResizeWithBBox(FaceParsing)\",\n                                        \"ImageSize(FaceParsing)\",\n                                        \"LatentCropWithBBox(FaceParsing)\",\n                                        \"LatentInsertWithBBox(FaceParsing)\",\n                                        \"LatentSize(FaceParsing)\",\n                                        \"MaskComposite(FaceParsing)\",\n                                        \"MaskListComposite(FaceParsing)\",\n                                        \"MaskListSelect(FaceParsing)\",\n                                        \"MaskToBBox(FaceParsing)\",\n                                        \"SkinDetectTraditional(FaceParsing)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui_face_parsing\"\n                                    }\n                                ],\n                                \"https://github.com/Ryuukeisyou/comfyui_image_io_helpers\": [\n                                    [\n                                        \"ImageLoadAsMaskByPath(ImageIOHelpers)\",\n                                        \"ImageLoadByPath(ImageIOHelpers)\",\n                                        \"ImageLoadFromBase64(ImageIOHelpers)\",\n                                        \"ImageSaveAsBase64(ImageIOHelpers)\",\n                                        \"ImageSaveToPath(ImageIOHelpers)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui_image_io_helpers\"\n                                    }\n                                ],\n                                \"https://github.com/SLAPaper/ComfyUI-Image-Selector\": [\n                                    [\n                                        \"ImageDuplicator\",\n                                        \"ImageSelector\",\n                                        \"LatentDuplicator\",\n                                        \"LatentSelector\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Image-Selector\"\n                                    }\n                                ],\n                                \"https://github.com/SOELexicon/ComfyUI-LexMSDBNodes\": [\n                                    [\n                                        \"MSSqlSelectNode\",\n                                        \"MSSqlTableNode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"LexMSDBNodes\"\n                                    }\n                                ],\n                                \"https://github.com/SOELexicon/ComfyUI-LexTools\": [\n                                    [\n                                        \"AgeClassifierNode\",\n                                        \"ArtOrHumanClassifierNode\",\n                                        \"DocumentClassificationNode\",\n                                        \"FoodCategoryClassifierNode\",\n                                        \"ImageAspectPadNode\",\n                                        \"ImageCaptioning\",\n                                        \"ImageFilterByFloatScoreNode\",\n                                        \"ImageFilterByIntScoreNode\",\n                                        \"ImageQualityScoreNode\",\n                                        \"ImageRankingNode\",\n                                        \"ImageScaleToMin\",\n                                        \"MD5ImageHashNode\",\n                                        \"SamplerPropertiesNode\",\n                                        \"ScoreConverterNode\",\n                                        \"SeedIncrementerNode\",\n                                        \"SegformerNode\",\n                                        \"SegformerNodeMasks\",\n                                        \"SegformerNodeMergeSegments\",\n                                        \"StepCfgIncrementNode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-LexTools\"\n                                    }\n                                ],\n                                \"https://github.com/SadaleNet/CLIPTextEncodeA1111-ComfyUI/raw/master/custom_nodes/clip_text_encoder_a1111.py\": [\n                                    [\n                                        \"CLIPTextEncodeA1111\",\n                                        \"RerouteTextForCLIPTextEncodeA1111\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI A1111-like Prompt Custom Node Solution\"\n                                    }\n                                ],\n                                \"https://github.com/Scholar01/ComfyUI-Keyframe\": [\n                                    [\n                                        \"KeyframeApply\",\n                                        \"KeyframeInterpolationPart\",\n                                        \"KeyframePart\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SComfyUI-Keyframe\"\n                                    }\n                                ],\n                                \"https://github.com/SeargeDP/SeargeSDXL\": [\n                                    [\n                                        \"SeargeAdvancedParameters\",\n                                        \"SeargeCheckpointLoader\",\n                                        \"SeargeConditionMixing\",\n                                        \"SeargeConditioningMuxer2\",\n                                        \"SeargeConditioningMuxer5\",\n                                        \"SeargeConditioningParameters\",\n                                        \"SeargeControlnetAdapterV2\",\n                                        \"SeargeControlnetModels\",\n                                        \"SeargeCustomAfterUpscaling\",\n                                        \"SeargeCustomAfterVaeDecode\",\n                                        \"SeargeCustomPromptMode\",\n                                        \"SeargeDebugPrinter\",\n                                        \"SeargeEnablerInputs\",\n                                        \"SeargeFloatConstant\",\n                                        \"SeargeFloatMath\",\n                                        \"SeargeFloatPair\",\n                                        \"SeargeFreeU\",\n                                        \"SeargeGenerated1\",\n                                        \"SeargeGenerationParameters\",\n                                        \"SeargeHighResolution\",\n                                        \"SeargeImage2ImageAndInpainting\",\n                                        \"SeargeImageAdapterV2\",\n                                        \"SeargeImageSave\",\n                                        \"SeargeImageSaving\",\n                                        \"SeargeInput1\",\n                                        \"SeargeInput2\",\n                                        \"SeargeInput3\",\n                                        \"SeargeInput4\",\n                                        \"SeargeInput5\",\n                                        \"SeargeInput6\",\n                                        \"SeargeInput7\",\n                                        \"SeargeIntegerConstant\",\n                                        \"SeargeIntegerMath\",\n                                        \"SeargeIntegerPair\",\n                                        \"SeargeIntegerScaler\",\n                                        \"SeargeLatentMuxer3\",\n                                        \"SeargeLoraLoader\",\n                                        \"SeargeLoras\",\n                                        \"SeargeMagicBox\",\n                                        \"SeargeModelSelector\",\n                                        \"SeargeOperatingMode\",\n                                        \"SeargeOutput1\",\n                                        \"SeargeOutput2\",\n                                        \"SeargeOutput3\",\n                                        \"SeargeOutput4\",\n                                        \"SeargeOutput5\",\n                                        \"SeargeOutput6\",\n                                        \"SeargeOutput7\",\n                                        \"SeargeParameterProcessor\",\n                                        \"SeargePipelineStart\",\n                                        \"SeargePipelineTerminator\",\n                                        \"SeargePreviewImage\",\n                                        \"SeargePromptAdapterV2\",\n                                        \"SeargePromptCombiner\",\n                                        \"SeargePromptStyles\",\n                                        \"SeargePromptText\",\n                                        \"SeargeSDXLBasePromptEncoder\",\n                                        \"SeargeSDXLImage2ImageSampler\",\n                                        \"SeargeSDXLImage2ImageSampler2\",\n                                        \"SeargeSDXLPromptEncoder\",\n                                        \"SeargeSDXLRefinerPromptEncoder\",\n                                        \"SeargeSDXLSampler\",\n                                        \"SeargeSDXLSampler2\",\n                                        \"SeargeSDXLSamplerV3\",\n                                        \"SeargeSamplerAdvanced\",\n                                        \"SeargeSamplerInputs\",\n                                        \"SeargeSaveFolderInputs\",\n                                        \"SeargeSeparator\",\n                                        \"SeargeStylePreprocessor\",\n                                        \"SeargeTextInputV2\",\n                                        \"SeargeUpscaleModelLoader\",\n                                        \"SeargeUpscaleModels\",\n                                        \"SeargeVAELoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SeargeSDXL\"\n                                    }\n                                ],\n                                \"https://github.com/Ser-Hilary/SDXL_sizing/raw/main/conditioning_sizing_for_SDXL.py\": [\n                                    [\n                                        \"get_aspect_from_image\",\n                                        \"get_aspect_from_ints\",\n                                        \"sizing_node\",\n                                        \"sizing_node_basic\",\n                                        \"sizing_node_unparsed\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SDXL_sizing\"\n                                    }\n                                ],\n                                \"https://github.com/ShmuelRonen/ComfyUI-SVDResizer\": [\n                                    [\n                                        \"SVDRsizer\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-SVDResizer\"\n                                    }\n                                ],\n                                \"https://github.com/Shraknard/ComfyUI-Remover\": [\n                                    [\n                                        \"Remover\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Remover\"\n                                    }\n                                ],\n                                \"https://github.com/Siberpone/lazy-pony-prompter\": [\n                                    [\n                                        \"LPP_Deleter\",\n                                        \"LPP_Derpibooru\",\n                                        \"LPP_E621\",\n                                        \"LPP_Loader_Derpibooru\",\n                                        \"LPP_Loader_E621\",\n                                        \"LPP_Saver\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Lazy Pony Prompter\"\n                                    }\n                                ],\n                                \"https://github.com/Smuzzies/comfyui_chatbox_overlay/raw/main/chatbox_overlay.py\": [\n                                    [\n                                        \"Chatbox Overlay\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Chatbox Overlay node for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/SoftMeng/ComfyUI_Mexx_Poster\": [\n                                    [\n                                        \"ComfyUI_Mexx_Poster\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_Mexx_Poster\"\n                                    }\n                                ],\n                                \"https://github.com/SoftMeng/ComfyUI_Mexx_Styler\": [\n                                    [\n                                        \"MexxSDXLPromptStyler\",\n                                        \"MexxSDXLPromptStylerAdvanced\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_Mexx_Styler\"\n                                    }\n                                ],\n                                \"https://github.com/SpaceKendo/ComfyUI-svd_txt2vid\": [\n                                    [\n                                        \"SVD_txt2vid_ConditioningwithLatent\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Text to video for Stable Video Diffusion in ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/Stability-AI/stability-ComfyUI-nodes\": [\n                                    [\n                                        \"ColorBlend\",\n                                        \"ControlLoraSave\",\n                                        \"GetImageSize\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"stability-ComfyUI-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/StartHua/ComfyUI_Seg_VITON\": [\n                                    [\n                                        \"segformer_agnostic\",\n                                        \"segformer_clothes\",\n                                        \"segformer_remove_bg\",\n                                        \"stabel_vition\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_Seg_VITON\"\n                                    }\n                                ],\n                                \"https://github.com/StartHua/Comfyui_joytag\": [\n                                    [\n                                        \"CXH_JoyTag\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfyui_joytag\"\n                                    }\n                                ],\n                                \"https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes\": [\n                                    [\n                                        \"CR 8 Channel In\",\n                                        \"CR 8 Channel Out\",\n                                        \"CR Apply ControlNet\",\n                                        \"CR Apply LoRA Stack\",\n                                        \"CR Apply Model Merge\",\n                                        \"CR Apply Multi Upscale\",\n                                        \"CR Apply Multi-ControlNet\",\n                                        \"CR Arabic Text RTL\",\n                                        \"CR Aspect Ratio\",\n                                        \"CR Aspect Ratio Banners\",\n                                        \"CR Aspect Ratio SDXL\",\n                                        \"CR Aspect Ratio Social Media\",\n                                        \"CR Batch Images From List\",\n                                        \"CR Batch Process Switch\",\n                                        \"CR Binary Pattern\",\n                                        \"CR Binary To Bit List\",\n                                        \"CR Bit Schedule\",\n                                        \"CR Central Schedule\",\n                                        \"CR Checker Pattern\",\n                                        \"CR Clamp Value\",\n                                        \"CR Clip Input Switch\",\n                                        \"CR Color Bars\",\n                                        \"CR Color Gradient\",\n                                        \"CR Color Panel\",\n                                        \"CR Color Tint\",\n                                        \"CR Combine Prompt\",\n                                        \"CR Combine Schedules\",\n                                        \"CR Comic Panel Templates\",\n                                        \"CR Composite Text\",\n                                        \"CR Conditioning Input Switch\",\n                                        \"CR Conditioning Mixer\",\n                                        \"CR ControlNet Input Switch\",\n                                        \"CR Current Frame\",\n                                        \"CR Cycle Images\",\n                                        \"CR Cycle Images Simple\",\n                                        \"CR Cycle LoRAs\",\n                                        \"CR Cycle Models\",\n                                        \"CR Cycle Text\",\n                                        \"CR Cycle Text Simple\",\n                                        \"CR Data Bus In\",\n                                        \"CR Data Bus Out\",\n                                        \"CR Debatch Frames\",\n                                        \"CR Diamond Panel\",\n                                        \"CR Draw Perspective Text\",\n                                        \"CR Draw Pie\",\n                                        \"CR Draw Shape\",\n                                        \"CR Draw Text\",\n                                        \"CR Encode Scheduled Prompts\",\n                                        \"CR Feathered Border\",\n                                        \"CR Float Range List\",\n                                        \"CR Float To Integer\",\n                                        \"CR Float To String\",\n                                        \"CR Font File List\",\n                                        \"CR Get Parameter From Prompt\",\n                                        \"CR Gradient Float\",\n                                        \"CR Gradient Integer\",\n                                        \"CR Half Drop Panel\",\n                                        \"CR Halftone Filter\",\n                                        \"CR Halftone Grid\",\n                                        \"CR Hires Fix Process Switch\",\n                                        \"CR Image Border\",\n                                        \"CR Image Grid Panel\",\n                                        \"CR Image Input Switch\",\n                                        \"CR Image Input Switch (4 way)\",\n                                        \"CR Image List\",\n                                        \"CR Image List Simple\",\n                                        \"CR Image Output\",\n                                        \"CR Image Panel\",\n                                        \"CR Image Pipe Edit\",\n                                        \"CR Image Pipe In\",\n                                        \"CR Image Pipe Out\",\n                                        \"CR Image Size\",\n                                        \"CR Img2Img Process Switch\",\n                                        \"CR Increment Float\",\n                                        \"CR Increment Integer\",\n                                        \"CR Index\",\n                                        \"CR Index Increment\",\n                                        \"CR Index Multiply\",\n                                        \"CR Index Reset\",\n                                        \"CR Input Text List\",\n                                        \"CR Integer Multiple\",\n                                        \"CR Integer Range List\",\n                                        \"CR Integer To String\",\n                                        \"CR Interpolate Latents\",\n                                        \"CR Intertwine Lists\",\n                                        \"CR Keyframe List\",\n                                        \"CR Latent Batch Size\",\n                                        \"CR Latent Input Switch\",\n                                        \"CR LoRA List\",\n                                        \"CR LoRA Stack\",\n                                        \"CR Load Animation Frames\",\n                                        \"CR Load Flow Frames\",\n                                        \"CR Load GIF As List\",\n                                        \"CR Load Image List\",\n                                        \"CR Load Image List Plus\",\n                                        \"CR Load LoRA\",\n                                        \"CR Load Prompt Style\",\n                                        \"CR Load Schedule From File\",\n                                        \"CR Load Scheduled ControlNets\",\n                                        \"CR Load Scheduled LoRAs\",\n                                        \"CR Load Scheduled Models\",\n                                        \"CR Load Text List\",\n                                        \"CR Mask Text\",\n                                        \"CR Math Operation\",\n                                        \"CR Model Input Switch\",\n                                        \"CR Model List\",\n                                        \"CR Model Merge Stack\",\n                                        \"CR Module Input\",\n                                        \"CR Module Output\",\n                                        \"CR Module Pipe Loader\",\n                                        \"CR Multi Upscale Stack\",\n                                        \"CR Multi-ControlNet Stack\",\n                                        \"CR Multiline Text\",\n                                        \"CR Output Flow Frames\",\n                                        \"CR Output Schedule To File\",\n                                        \"CR Overlay Text\",\n                                        \"CR Overlay Transparent Image\",\n                                        \"CR Page Layout\",\n                                        \"CR Pipe Switch\",\n                                        \"CR Polygons\",\n                                        \"CR Prompt List\",\n                                        \"CR Prompt List Keyframes\",\n                                        \"CR Prompt Scheduler\",\n                                        \"CR Prompt Text\",\n                                        \"CR Radial Gradient\",\n                                        \"CR Random Hex Color\",\n                                        \"CR Random LoRA Stack\",\n                                        \"CR Random Multiline Colors\",\n                                        \"CR Random Multiline Values\",\n                                        \"CR Random Panel Codes\",\n                                        \"CR Random RGB\",\n                                        \"CR Random RGB Gradient\",\n                                        \"CR Random Shape Pattern\",\n                                        \"CR Random Weight LoRA\",\n                                        \"CR Repeater\",\n                                        \"CR SD1.5 Aspect Ratio\",\n                                        \"CR SDXL Aspect Ratio\",\n                                        \"CR SDXL Base Prompt Encoder\",\n                                        \"CR SDXL Prompt Mix Presets\",\n                                        \"CR SDXL Prompt Mixer\",\n                                        \"CR SDXL Style Text\",\n                                        \"CR Save Text To File\",\n                                        \"CR Schedule Input Switch\",\n                                        \"CR Schedule To ScheduleList\",\n                                        \"CR Seamless Checker\",\n                                        \"CR Seed\",\n                                        \"CR Seed to Int\",\n                                        \"CR Select Font\",\n                                        \"CR Select ISO Size\",\n                                        \"CR Select Model\",\n                                        \"CR Select Resize Method\",\n                                        \"CR Set Switch From String\",\n                                        \"CR Set Value On Binary\",\n                                        \"CR Set Value On Boolean\",\n                                        \"CR Set Value on String\",\n                                        \"CR Simple Banner\",\n                                        \"CR Simple Binary Pattern\",\n                                        \"CR Simple Binary Pattern Simple\",\n                                        \"CR Simple Image Compare\",\n                                        \"CR Simple List\",\n                                        \"CR Simple Meme Template\",\n                                        \"CR Simple Prompt List\",\n                                        \"CR Simple Prompt List Keyframes\",\n                                        \"CR Simple Prompt Scheduler\",\n                                        \"CR Simple Schedule\",\n                                        \"CR Simple Text Panel\",\n                                        \"CR Simple Text Scheduler\",\n                                        \"CR Simple Text Watermark\",\n                                        \"CR Simple Titles\",\n                                        \"CR Simple Value Scheduler\",\n                                        \"CR Split String\",\n                                        \"CR Starburst Colors\",\n                                        \"CR Starburst Lines\",\n                                        \"CR String To Boolean\",\n                                        \"CR String To Combo\",\n                                        \"CR String To Number\",\n                                        \"CR Style Bars\",\n                                        \"CR Switch Model and CLIP\",\n                                        \"CR Text\",\n                                        \"CR Text Blacklist\",\n                                        \"CR Text Concatenate\",\n                                        \"CR Text Cycler\",\n                                        \"CR Text Input Switch\",\n                                        \"CR Text Input Switch (4 way)\",\n                                        \"CR Text Length\",\n                                        \"CR Text List\",\n                                        \"CR Text List Simple\",\n                                        \"CR Text List To String\",\n                                        \"CR Text Operation\",\n                                        \"CR Text Replace\",\n                                        \"CR Text Scheduler\",\n                                        \"CR Thumbnail Preview\",\n                                        \"CR Trigger\",\n                                        \"CR Upscale Image\",\n                                        \"CR VAE Decode\",\n                                        \"CR VAE Input Switch\",\n                                        \"CR Value\",\n                                        \"CR Value Cycler\",\n                                        \"CR Value Scheduler\",\n                                        \"CR Vignette Filter\",\n                                        \"CR XY From Folder\",\n                                        \"CR XY Index\",\n                                        \"CR XY Interpolate\",\n                                        \"CR XY List\",\n                                        \"CR XY Product\",\n                                        \"CR XY Save Grid Image\",\n                                        \"CR XYZ Index\",\n                                        \"CR_Aspect Ratio For Print\"\n                                    ],\n                                    {\n                                        \"author\": \"Suzie1\",\n                                        \"description\": \"175 custom nodes for artists, designers and animators.\",\n                                        \"nickname\": \"Comfyroll Studio\",\n                                        \"title\": \"Comfyroll Studio\",\n                                        \"title_aux\": \"ComfyUI_Comfyroll_CustomNodes\"\n                                    }\n                                ],\n                                \"https://github.com/Sxela/ComfyWarp\": [\n                                    [\n                                        \"ExtractOpticalFlow\",\n                                        \"LoadFrame\",\n                                        \"LoadFrameFromDataset\",\n                                        \"LoadFrameFromFolder\",\n                                        \"LoadFramePairFromDataset\",\n                                        \"LoadFrameSequence\",\n                                        \"MakeFrameDataset\",\n                                        \"MixConsistencyMaps\",\n                                        \"OffsetNumber\",\n                                        \"ResizeToFit\",\n                                        \"SaveFrame\",\n                                        \"WarpFrame\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyWarp\"\n                                    }\n                                ],\n                                \"https://github.com/TGu-97/ComfyUI-TGu-utils\": [\n                                    [\n                                        \"MPNReroute\",\n                                        \"MPNSwitch\",\n                                        \"PNSwitch\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"TGu Utilities\"\n                                    }\n                                ],\n                                \"https://github.com/THtianhao/ComfyUI-FaceChain\": [\n                                    [\n                                        \"FC CropAndPaste\",\n                                        \"FC CropBottom\",\n                                        \"FC CropToOrigin\",\n                                        \"FC FaceDetectCrop\",\n                                        \"FC FaceFusion\",\n                                        \"FC FaceSegAndReplace\",\n                                        \"FC FaceSegment\",\n                                        \"FC MaskOP\",\n                                        \"FC RemoveCannyFace\",\n                                        \"FC ReplaceByMask\",\n                                        \"FC StyleLoraLoad\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-FaceChain\"\n                                    }\n                                ],\n                                \"https://github.com/THtianhao/ComfyUI-Portrait-Maker\": [\n                                    [\n                                        \"PM_BoxCropImage\",\n                                        \"PM_ColorTransfer\",\n                                        \"PM_ExpandMaskBox\",\n                                        \"PM_FaceFusion\",\n                                        \"PM_FaceShapMatch\",\n                                        \"PM_FaceSkin\",\n                                        \"PM_GetImageInfo\",\n                                        \"PM_ImageResizeTarget\",\n                                        \"PM_ImageScaleShort\",\n                                        \"PM_MakeUpTransfer\",\n                                        \"PM_MaskDilateErode\",\n                                        \"PM_MaskMerge2Image\",\n                                        \"PM_PortraitEnhancement\",\n                                        \"PM_RatioMerge2Image\",\n                                        \"PM_ReplaceBoxImg\",\n                                        \"PM_RetinaFace\",\n                                        \"PM_Similarity\",\n                                        \"PM_SkinRetouching\",\n                                        \"PM_SuperColorTransfer\",\n                                        \"PM_SuperMakeUpTransfer\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Portrait-Maker\"\n                                    }\n                                ],\n                                \"https://github.com/TRI3D-LC/tri3d-comfyui-nodes\": [\n                                    [\n                                        \"tri3d-adjust-neck\",\n                                        \"tri3d-atr-parse\",\n                                        \"tri3d-atr-parse-batch\",\n                                        \"tri3d-clipdrop-bgremove-api\",\n                                        \"tri3d-dwpose\",\n                                        \"tri3d-extract-hand\",\n                                        \"tri3d-extract-parts-batch\",\n                                        \"tri3d-extract-parts-batch2\",\n                                        \"tri3d-extract-parts-mask-batch\",\n                                        \"tri3d-face-recognise\",\n                                        \"tri3d-float-to-image\",\n                                        \"tri3d-fuzzification\",\n                                        \"tri3d-image-mask-2-box\",\n                                        \"tri3d-image-mask-box-2-image\",\n                                        \"tri3d-interaction-canny\",\n                                        \"tri3d-load-pose-json\",\n                                        \"tri3d-pose-adaption\",\n                                        \"tri3d-pose-to-image\",\n                                        \"tri3d-position-hands\",\n                                        \"tri3d-position-parts-batch\",\n                                        \"tri3d-recolor-mask\",\n                                        \"tri3d-recolor-mask-LAB_space\",\n                                        \"tri3d-recolor-mask-LAB_space_manual\",\n                                        \"tri3d-recolor-mask-RGB_space\",\n                                        \"tri3d-skin-feathered-padded-mask\",\n                                        \"tri3d-swap-pixels\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"tri3d-comfyui-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/Taremin/comfyui-prompt-extranetworks\": [\n                                    [\n                                        \"PromptExtraNetworks\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Prompt ExtraNetworks\"\n                                    }\n                                ],\n                                \"https://github.com/Taremin/comfyui-string-tools\": [\n                                    [\n                                        \"StringToolsBalancedChoice\",\n                                        \"StringToolsConcat\",\n                                        \"StringToolsRandomChoice\",\n                                        \"StringToolsString\",\n                                        \"StringToolsText\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI String Tools\"\n                                    }\n                                ],\n                                \"https://github.com/TeaCrab/ComfyUI-TeaNodes\": [\n                                    [\n                                        \"TC_ColorFill\",\n                                        \"TC_EqualizeCLAHE\",\n                                        \"TC_ImageResize\",\n                                        \"TC_ImageScale\",\n                                        \"TC_RandomColorFill\",\n                                        \"TC_SizeApproximation\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-TeaNodes\"\n                                    }\n                                ],\n                                \"https://github.com/TemryL/ComfyS3\": [\n                                    [\n                                        \"DownloadFileS3\",\n                                        \"LoadImageS3\",\n                                        \"SaveImageS3\",\n                                        \"SaveVideoFilesS3\",\n                                        \"UploadFileS3\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyS3\"\n                                    }\n                                ],\n                                \"https://github.com/TheBarret/ZSuite\": [\n                                    [\n                                        \"ZSuite: Prompter\",\n                                        \"ZSuite: RF Noise\",\n                                        \"ZSuite: SeedMod\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ZSuite\"\n                                    }\n                                ],\n                                \"https://github.com/TinyTerra/ComfyUI_tinyterraNodes\": [\n                                    [\n                                        \"ttN busIN\",\n                                        \"ttN busOUT\",\n                                        \"ttN compareInput\",\n                                        \"ttN concat\",\n                                        \"ttN debugInput\",\n                                        \"ttN float\",\n                                        \"ttN hiresfixScale\",\n                                        \"ttN imageOutput\",\n                                        \"ttN imageREMBG\",\n                                        \"ttN int\",\n                                        \"ttN multiModelMerge\",\n                                        \"ttN pipe2BASIC\",\n                                        \"ttN pipe2DETAILER\",\n                                        \"ttN pipeEDIT\",\n                                        \"ttN pipeEncodeConcat\",\n                                        \"ttN pipeIN\",\n                                        \"ttN pipeKSampler\",\n                                        \"ttN pipeKSamplerAdvanced\",\n                                        \"ttN pipeKSamplerSDXL\",\n                                        \"ttN pipeLoader\",\n                                        \"ttN pipeLoaderSDXL\",\n                                        \"ttN pipeLoraStack\",\n                                        \"ttN pipeOUT\",\n                                        \"ttN seed\",\n                                        \"ttN seedDebug\",\n                                        \"ttN text\",\n                                        \"ttN text3BOX_3WAYconcat\",\n                                        \"ttN text7BOX_concat\",\n                                        \"ttN textDebug\",\n                                        \"ttN xyPlot\"\n                                    ],\n                                    {\n                                        \"author\": \"tinyterra\",\n                                        \"description\": \"This extension offers various pipe nodes, fullscreen image viewer based on node history, dynamic widgets, interface customization, and more.\",\n                                        \"nickname\": \"ttNodes\",\n                                        \"nodename_pattern\": \"^ttN \",\n                                        \"title\": \"tinyterraNodes\",\n                                        \"title_aux\": \"tinyterraNodes\"\n                                    }\n                                ],\n                                \"https://github.com/TripleHeadedMonkey/ComfyUI_MileHighStyler\": [\n                                    [\n                                        \"menus\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_MileHighStyler\"\n                                    }\n                                ],\n                                \"https://github.com/Tropfchen/ComfyUI-Embedding_Picker\": [\n                                    [\n                                        \"EmbeddingPicker\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Embedding Picker\"\n                                    }\n                                ],\n                                \"https://github.com/Tropfchen/ComfyUI-yaResolutionSelector\": [\n                                    [\n                                        \"YARS\",\n                                        \"YARSAdv\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"YARS: Yet Another Resolution Selector\"\n                                    }\n                                ],\n                                \"https://github.com/Trung0246/ComfyUI-0246\": [\n                                    [\n                                        \"0246.Beautify\",\n                                        \"0246.BoxRange\",\n                                        \"0246.CastReroute\",\n                                        \"0246.Cloud\",\n                                        \"0246.Convert\",\n                                        \"0246.Count\",\n                                        \"0246.Highway\",\n                                        \"0246.HighwayBatch\",\n                                        \"0246.Hold\",\n                                        \"0246.Hub\",\n                                        \"0246.Junction\",\n                                        \"0246.JunctionBatch\",\n                                        \"0246.Loop\",\n                                        \"0246.Merge\",\n                                        \"0246.Meta\",\n                                        \"0246.Pick\",\n                                        \"0246.RandomInt\",\n                                        \"0246.Script\",\n                                        \"0246.ScriptNode\",\n                                        \"0246.ScriptPile\",\n                                        \"0246.ScriptRule\",\n                                        \"0246.Stringify\",\n                                        \"0246.Switch\"\n                                    ],\n                                    {\n                                        \"author\": \"Trung0246\",\n                                        \"description\": \"Random nodes for ComfyUI I made to solve my struggle with ComfyUI (ex: pipe, process). Have varying quality.\",\n                                        \"nickname\": \"ComfyUI-0246\",\n                                        \"title\": \"ComfyUI-0246\",\n                                        \"title_aux\": \"ComfyUI-0246\"\n                                    }\n                                ],\n                                \"https://github.com/Ttl/ComfyUi_NNLatentUpscale\": [\n                                    [\n                                        \"NNLatentUpscale\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Neural network latent upscale custom node\"\n                                    }\n                                ],\n                                \"https://github.com/Umikaze-job/select_folder_path_easy\": [\n                                    [\n                                        \"SelectFolderPathEasy\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"select_folder_path_easy\"\n                                    }\n                                ],\n                                \"https://github.com/WASasquatch/ASTERR\": [\n                                    [\n                                        \"ASTERR\",\n                                        \"SaveASTERR\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ASTERR\"\n                                    }\n                                ],\n                                \"https://github.com/WASasquatch/ComfyUI_Preset_Merger\": [\n                                    [\n                                        \"Preset_Model_Merge\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Preset Merger\"\n                                    }\n                                ],\n                                \"https://github.com/WASasquatch/FreeU_Advanced\": [\n                                    [\n                                        \"FreeU (Advanced)\",\n                                        \"FreeU_V2 (Advanced)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"FreeU_Advanced\"\n                                    }\n                                ],\n                                \"https://github.com/WASasquatch/PPF_Noise_ComfyUI\": [\n                                    [\n                                        \"Blend Latents (PPF Noise)\",\n                                        \"Cross-Hatch Power Fractal (PPF Noise)\",\n                                        \"Images as Latents (PPF Noise)\",\n                                        \"Perlin Power Fractal Latent (PPF Noise)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"PPF_Noise_ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/WASasquatch/PowerNoiseSuite\": [\n                                    [\n                                        \"Blend Latents (PPF Noise)\",\n                                        \"Cross-Hatch Power Fractal (PPF Noise)\",\n                                        \"Cross-Hatch Power Fractal Settings (PPF Noise)\",\n                                        \"Images as Latents (PPF Noise)\",\n                                        \"Latent Adjustment (PPF Noise)\",\n                                        \"Latents to CPU (PPF Noise)\",\n                                        \"Linear Cross-Hatch Power Fractal (PPF Noise)\",\n                                        \"Perlin Power Fractal Latent (PPF Noise)\",\n                                        \"Perlin Power Fractal Settings (PPF Noise)\",\n                                        \"Power KSampler Advanced (PPF Noise)\",\n                                        \"Power-Law Noise (PPF Noise)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Power Noise Suite for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/WASasquatch/WAS_Extras\": [\n                                    [\n                                        \"BLVAEEncode\",\n                                        \"CLIPTextEncodeList\",\n                                        \"CLIPTextEncodeSequence2\",\n                                        \"ConditioningBlend\",\n                                        \"DebugInput\",\n                                        \"KSamplerSeq\",\n                                        \"KSamplerSeq2\",\n                                        \"VAEEncodeForInpaint (WAS)\",\n                                        \"VividSharpen\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"WAS_Extras\"\n                                    }\n                                ],\n                                \"https://github.com/WASasquatch/was-node-suite-comfyui\": [\n                                    [\n                                        \"BLIP Analyze Image\",\n                                        \"BLIP Model Loader\",\n                                        \"Blend Latents\",\n                                        \"Boolean To Text\",\n                                        \"Bounded Image Blend\",\n                                        \"Bounded Image Blend with Mask\",\n                                        \"Bounded Image Crop\",\n                                        \"Bounded Image Crop with Mask\",\n                                        \"Bus Node\",\n                                        \"CLIP Input Switch\",\n                                        \"CLIP Vision Input Switch\",\n                                        \"CLIPSeg Batch Masking\",\n                                        \"CLIPSeg Masking\",\n                                        \"CLIPSeg Model Loader\",\n                                        \"CLIPTextEncode (BlenderNeko Advanced + NSP)\",\n                                        \"CLIPTextEncode (NSP)\",\n                                        \"Cache Node\",\n                                        \"Checkpoint Loader\",\n                                        \"Checkpoint Loader (Simple)\",\n                                        \"Conditioning Input Switch\",\n                                        \"Constant Number\",\n                                        \"Control Net Model Input Switch\",\n                                        \"Convert Masks to Images\",\n                                        \"Create Grid Image\",\n                                        \"Create Grid Image from Batch\",\n                                        \"Create Morph Image\",\n                                        \"Create Morph Image from Path\",\n                                        \"Create Video from Path\",\n                                        \"Debug Number to Console\",\n                                        \"Dictionary to Console\",\n                                        \"Diffusers Hub Model Down-Loader\",\n                                        \"Diffusers Model Loader\",\n                                        \"Export API\",\n                                        \"Image Analyze\",\n                                        \"Image Aspect Ratio\",\n                                        \"Image Batch\",\n                                        \"Image Blank\",\n                                        \"Image Blend\",\n                                        \"Image Blend by Mask\",\n                                        \"Image Blending Mode\",\n                                        \"Image Bloom Filter\",\n                                        \"Image Bounds\",\n                                        \"Image Bounds to Console\",\n                                        \"Image Canny Filter\",\n                                        \"Image Chromatic Aberration\",\n                                        \"Image Color Palette\",\n                                        \"Image Crop Face\",\n                                        \"Image Crop Location\",\n                                        \"Image Crop Square Location\",\n                                        \"Image Displacement Warp\",\n                                        \"Image Dragan Photography Filter\",\n                                        \"Image Edge Detection Filter\",\n                                        \"Image Film Grain\",\n                                        \"Image Filter Adjustments\",\n                                        \"Image Flip\",\n                                        \"Image Generate Gradient\",\n                                        \"Image Gradient Map\",\n                                        \"Image High Pass Filter\",\n                                        \"Image History Loader\",\n                                        \"Image Input Switch\",\n                                        \"Image Levels Adjustment\",\n                                        \"Image Load\",\n                                        \"Image Lucy Sharpen\",\n                                        \"Image Median Filter\",\n                                        \"Image Mix RGB Channels\",\n                                        \"Image Monitor Effects Filter\",\n                                        \"Image Nova Filter\",\n                                        \"Image Padding\",\n                                        \"Image Paste Crop\",\n                                        \"Image Paste Crop by Location\",\n                                        \"Image Paste Face\",\n                                        \"Image Perlin Noise\",\n                                        \"Image Perlin Power Fractal\",\n                                        \"Image Pixelate\",\n                                        \"Image Power Noise\",\n                                        \"Image Rembg (Remove Background)\",\n                                        \"Image Remove Background (Alpha)\",\n                                        \"Image Remove Color\",\n                                        \"Image Resize\",\n                                        \"Image Rotate\",\n                                        \"Image Rotate Hue\",\n                                        \"Image SSAO (Ambient Occlusion)\",\n                                        \"Image SSDO (Direct Occlusion)\",\n                                        \"Image Save\",\n                                        \"Image Seamless Texture\",\n                                        \"Image Select Channel\",\n                                        \"Image Select Color\",\n                                        \"Image Shadows and Highlights\",\n                                        \"Image Size to Number\",\n                                        \"Image Stitch\",\n                                        \"Image Style Filter\",\n                                        \"Image Threshold\",\n                                        \"Image Tiled\",\n                                        \"Image Transpose\",\n                                        \"Image Voronoi Noise Filter\",\n                                        \"Image fDOF Filter\",\n                                        \"Image to Latent Mask\",\n                                        \"Image to Noise\",\n                                        \"Image to Seed\",\n                                        \"Images to Linear\",\n                                        \"Images to RGB\",\n                                        \"Inset Image Bounds\",\n                                        \"Integer place counter\",\n                                        \"KSampler (WAS)\",\n                                        \"KSampler Cycle\",\n                                        \"Latent Batch\",\n                                        \"Latent Input Switch\",\n                                        \"Latent Noise Injection\",\n                                        \"Latent Size to Number\",\n                                        \"Latent Upscale by Factor (WAS)\",\n                                        \"Load Cache\",\n                                        \"Load Image Batch\",\n                                        \"Load Lora\",\n                                        \"Load Text File\",\n                                        \"Logic Boolean\",\n                                        \"Logic Boolean Primitive\",\n                                        \"Logic Comparison AND\",\n                                        \"Logic Comparison OR\",\n                                        \"Logic Comparison XOR\",\n                                        \"Logic NOT\",\n                                        \"Lora Input Switch\",\n                                        \"Lora Loader\",\n                                        \"Mask Arbitrary Region\",\n                                        \"Mask Batch\",\n                                        \"Mask Batch to Mask\",\n                                        \"Mask Ceiling Region\",\n                                        \"Mask Crop Dominant Region\",\n                                        \"Mask Crop Minority Region\",\n                                        \"Mask Crop Region\",\n                                        \"Mask Dilate Region\",\n                                        \"Mask Dominant Region\",\n                                        \"Mask Erode Region\",\n                                        \"Mask Fill Holes\",\n                                        \"Mask Floor Region\",\n                                        \"Mask Gaussian Region\",\n                                        \"Mask Invert\",\n                                        \"Mask Minority Region\",\n                                        \"Mask Paste Region\",\n                                        \"Mask Smooth Region\",\n                                        \"Mask Threshold Region\",\n                                        \"Masks Add\",\n                                        \"Masks Combine Batch\",\n                                        \"Masks Combine Regions\",\n                                        \"Masks Subtract\",\n                                        \"MiDaS Depth Approximation\",\n                                        \"MiDaS Mask Image\",\n                                        \"MiDaS Model Loader\",\n                                        \"Model Input Switch\",\n                                        \"Number Counter\",\n                                        \"Number Input Condition\",\n                                        \"Number Input Switch\",\n                                        \"Number Multiple Of\",\n                                        \"Number Operation\",\n                                        \"Number PI\",\n                                        \"Number to Float\",\n                                        \"Number to Int\",\n                                        \"Number to Seed\",\n                                        \"Number to String\",\n                                        \"Number to Text\",\n                                        \"Prompt Multiple Styles Selector\",\n                                        \"Prompt Styles Selector\",\n                                        \"Random Number\",\n                                        \"SAM Image Mask\",\n                                        \"SAM Model Loader\",\n                                        \"SAM Parameters\",\n                                        \"SAM Parameters Combine\",\n                                        \"Samples Passthrough (Stat System)\",\n                                        \"Save Text File\",\n                                        \"Seed\",\n                                        \"String to Text\",\n                                        \"Tensor Batch to Image\",\n                                        \"Text Add Token by Input\",\n                                        \"Text Add Tokens\",\n                                        \"Text Compare\",\n                                        \"Text Concatenate\",\n                                        \"Text Contains\",\n                                        \"Text Dictionary Convert\",\n                                        \"Text Dictionary Get\",\n                                        \"Text Dictionary Keys\",\n                                        \"Text Dictionary New\",\n                                        \"Text Dictionary To Text\",\n                                        \"Text Dictionary Update\",\n                                        \"Text File History Loader\",\n                                        \"Text Find and Replace\",\n                                        \"Text Find and Replace Input\",\n                                        \"Text Find and Replace by Dictionary\",\n                                        \"Text Input Switch\",\n                                        \"Text List\",\n                                        \"Text List Concatenate\",\n                                        \"Text List to Text\",\n                                        \"Text Load Line From File\",\n                                        \"Text Multiline\",\n                                        \"Text Parse A1111 Embeddings\",\n                                        \"Text Parse Noodle Soup Prompts\",\n                                        \"Text Parse Tokens\",\n                                        \"Text Random Line\",\n                                        \"Text Random Prompt\",\n                                        \"Text Shuffle\",\n                                        \"Text String\",\n                                        \"Text String Truncate\",\n                                        \"Text to Conditioning\",\n                                        \"Text to Console\",\n                                        \"Text to Number\",\n                                        \"Text to String\",\n                                        \"True Random.org Number Generator\",\n                                        \"Upscale Model Loader\",\n                                        \"Upscale Model Switch\",\n                                        \"VAE Input Switch\",\n                                        \"Video Dump Frames\",\n                                        \"Write to GIF\",\n                                        \"Write to Video\",\n                                        \"unCLIP Checkpoint Loader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"WAS Node Suite\"\n                                    }\n                                ],\n                                \"https://github.com/WebDev9000/WebDev9000-Nodes\": [\n                                    [\n                                        \"IgnoreBraces\",\n                                        \"SettingsSwitch\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"WebDev9000-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/YMC-GitHub/ymc-node-suite-comfyui\": [\n                                    [\n                                        \"canvas-util-cal-size\",\n                                        \"conditioning-util-input-switch\",\n                                        \"cutoff-region-util\",\n                                        \"hks-util-cal-denoise-step\",\n                                        \"img-util-get-image-size\",\n                                        \"img-util-switch-input-image\",\n                                        \"io-image-save\",\n                                        \"io-text-save\",\n                                        \"io-util-file-list-get\",\n                                        \"io-util-file-list-get-text\",\n                                        \"number-util-random-num\",\n                                        \"pipe-util-to-basic-pipe\",\n                                        \"region-util-get-by-center-and-size\",\n                                        \"region-util-get-by-lt\",\n                                        \"region-util-get-crop-location-from-center-size-text\",\n                                        \"region-util-get-pad-out-location-by-size\",\n                                        \"text-preset-colors\",\n                                        \"text-util-join-text\",\n                                        \"text-util-loop-text\",\n                                        \"text-util-path-list\",\n                                        \"text-util-prompt-add-prompt\",\n                                        \"text-util-prompt-adv-dup\",\n                                        \"text-util-prompt-adv-search\",\n                                        \"text-util-prompt-del\",\n                                        \"text-util-prompt-dup\",\n                                        \"text-util-prompt-join\",\n                                        \"text-util-prompt-search\",\n                                        \"text-util-prompt-shuffle\",\n                                        \"text-util-prompt-std\",\n                                        \"text-util-prompt-unweight\",\n                                        \"text-util-random-text\",\n                                        \"text-util-search-text\",\n                                        \"text-util-show-text\",\n                                        \"text-util-switch-text\",\n                                        \"xyz-util-txt-to-int\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ymc-node-suite-comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/YOUR-WORST-TACO/ComfyUI-TacoNodes\": [\n                                    [\n                                        \"Example\",\n                                        \"TacoAnimatedLoader\",\n                                        \"TacoGifMaker\",\n                                        \"TacoImg2ImgAnimatedLoader\",\n                                        \"TacoImg2ImgAnimatedProcessor\",\n                                        \"TacoLatent\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-TacoNodes\"\n                                    }\n                                ],\n                                \"https://github.com/YinBailiang/MergeBlockWeighted_fo_ComfyUI\": [\n                                    [\n                                        \"MergeBlockWeighted\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"MergeBlockWeighted_fo_ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/ZHO-ZHO-ZHO/ComfyUI-ArtGallery\": [\n                                    [\n                                        \"ArtGallery_Zho\",\n                                        \"ArtistsImage_Zho\",\n                                        \"CamerasImage_Zho\",\n                                        \"FilmsImage_Zho\",\n                                        \"MovementsImage_Zho\",\n                                        \"StylesImage_Zho\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-ArtGallery\"\n                                    }\n                                ],\n                                \"https://github.com/ZHO-ZHO-ZHO/ComfyUI-Gemini\": [\n                                    [\n                                        \"ConcatText_Zho\",\n                                        \"DisplayText_Zho\",\n                                        \"Gemini_API_Chat_Zho\",\n                                        \"Gemini_API_S_Chat_Zho\",\n                                        \"Gemini_API_S_Vsion_ImgURL_Zho\",\n                                        \"Gemini_API_S_Zho\",\n                                        \"Gemini_API_Vsion_ImgURL_Zho\",\n                                        \"Gemini_API_Zho\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Gemini\"\n                                    }\n                                ],\n                                \"https://github.com/ZHO-ZHO-ZHO/ComfyUI-InstantID\": [\n                                    [\n                                        \"IDBaseModelLoader_fromhub\",\n                                        \"IDBaseModelLoader_local\",\n                                        \"IDControlNetLoader\",\n                                        \"IDGenerationNode\",\n                                        \"ID_Prompt_Styler\",\n                                        \"InsightFaceLoader_Zho\",\n                                        \"Ipadapter_instantidLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-InstantID\"\n                                    }\n                                ],\n                                \"https://github.com/ZHO-ZHO-ZHO/ComfyUI-PhotoMaker-ZHO\": [\n                                    [\n                                        \"BaseModel_Loader_fromhub\",\n                                        \"BaseModel_Loader_local\",\n                                        \"LoRALoader\",\n                                        \"NEW_PhotoMaker_Generation\",\n                                        \"PhotoMakerAdapter_Loader_fromhub\",\n                                        \"PhotoMakerAdapter_Loader_local\",\n                                        \"PhotoMaker_Generation\",\n                                        \"Prompt_Styler\",\n                                        \"Ref_Image_Preprocessing\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI PhotoMaker (ZHO)\"\n                                    }\n                                ],\n                                \"https://github.com/ZHO-ZHO-ZHO/ComfyUI-Q-Align\": [\n                                    [\n                                        \"QAlign_Zho\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Q-Align\"\n                                    }\n                                ],\n                                \"https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen-VL-API\": [\n                                    [\n                                        \"QWenVL_API_S_Multi_Zho\",\n                                        \"QWenVL_API_S_Zho\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Qwen-VL-API\"\n                                    }\n                                ],\n                                \"https://github.com/ZHO-ZHO-ZHO/ComfyUI-SVD-ZHO\": [\n                                    [\n                                        \"SVD_Aspect_Ratio_Zho\",\n                                        \"SVD_Steps_MotionStrength_Seed_Zho\",\n                                        \"SVD_Styler_Zho\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-SVD-ZHO (WIP)\"\n                                    }\n                                ],\n                                \"https://github.com/ZHO-ZHO-ZHO/ComfyUI-SegMoE\": [\n                                    [\n                                        \"SMoE_Generation_Zho\",\n                                        \"SMoE_ModelLoader_Zho\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI SegMoE\"\n                                    }\n                                ],\n                                \"https://github.com/ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite\": [\n                                    [\n                                        \"AlphaChanelAddByMask\",\n                                        \"ImageCompositeBy_BG_Zho\",\n                                        \"ImageCompositeBy_Zho\",\n                                        \"ImageComposite_BG_Zho\",\n                                        \"ImageComposite_Zho\",\n                                        \"RGB_Image_Zho\",\n                                        \"Text_Image_Frame_Zho\",\n                                        \"Text_Image_Multiline_Zho\",\n                                        \"Text_Image_Zho\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Text_Image-Composite [WIP]\"\n                                    }\n                                ],\n                                \"https://github.com/ZHO-ZHO-ZHO/comfyui-portrait-master-zh-cn\": [\n                                    [\n                                        \"PortraitMaster_\\u4e2d\\u6587\\u7248\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-portrait-master-zh-cn\"\n                                    }\n                                ],\n                                \"https://github.com/ZaneA/ComfyUI-ImageReward\": [\n                                    [\n                                        \"ImageRewardLoader\",\n                                        \"ImageRewardScore\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ImageReward\"\n                                    }\n                                ],\n                                \"https://github.com/Zuellni/ComfyUI-ExLlama\": [\n                                    [\n                                        \"ZuellniExLlamaGenerator\",\n                                        \"ZuellniExLlamaLoader\",\n                                        \"ZuellniTextPreview\",\n                                        \"ZuellniTextReplace\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-ExLlama\"\n                                    }\n                                ],\n                                \"https://github.com/Zuellni/ComfyUI-PickScore-Nodes\": [\n                                    [\n                                        \"ZuellniPickScoreImageProcessor\",\n                                        \"ZuellniPickScoreLoader\",\n                                        \"ZuellniPickScoreSelector\",\n                                        \"ZuellniPickScoreTextProcessor\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI PickScore Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/a1lazydog/ComfyUI-AudioScheduler\": [\n                                    [\n                                        \"AmplitudeToGraph\",\n                                        \"AmplitudeToNumber\",\n                                        \"AudioToAmplitudeGraph\",\n                                        \"AudioToFFTs\",\n                                        \"BatchAmplitudeSchedule\",\n                                        \"ClipAmplitude\",\n                                        \"GateNormalizedAmplitude\",\n                                        \"LoadAudio\",\n                                        \"NormalizeAmplitude\",\n                                        \"NormalizedAmplitudeDrivenString\",\n                                        \"NormalizedAmplitudeToGraph\",\n                                        \"NormalizedAmplitudeToNumber\",\n                                        \"TransientAmplitudeBasic\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-AudioScheduler\"\n                                    }\n                                ],\n                                \"https://github.com/abdozmantar/ComfyUI-InstaSwap\": [\n                                    [\n                                        \"InstaSwapFaceSwap\",\n                                        \"InstaSwapLoadFaceModel\",\n                                        \"InstaSwapSaveFaceModel\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"InstaSwap Face Swap Node for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/abyz22/image_control\": [\n                                    [\n                                        \"abyz22_Convertpipe\",\n                                        \"abyz22_Editpipe\",\n                                        \"abyz22_FirstNonNull\",\n                                        \"abyz22_FromBasicPipe_v2\",\n                                        \"abyz22_Frompipe\",\n                                        \"abyz22_ImpactWildcardEncode\",\n                                        \"abyz22_ImpactWildcardEncode_GetPrompt\",\n                                        \"abyz22_Ksampler\",\n                                        \"abyz22_Padding Image\",\n                                        \"abyz22_SaveImage\",\n                                        \"abyz22_SetQueue\",\n                                        \"abyz22_ToBasicPipe\",\n                                        \"abyz22_Topipe\",\n                                        \"abyz22_blend_onecolor\",\n                                        \"abyz22_blendimages\",\n                                        \"abyz22_bypass\",\n                                        \"abyz22_drawmask\",\n                                        \"abyz22_lamaInpaint\",\n                                        \"abyz22_lamaPreprocessor\",\n                                        \"abyz22_makecircles\",\n                                        \"abyz22_setimageinfo\",\n                                        \"abyz22_smallhead\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"image_control\"\n                                    }\n                                ],\n                                \"https://github.com/adbrasi/ComfyUI-TrashNodes-DownloadHuggingface\": [\n                                    [\n                                        \"DownloadLinkChecker\",\n                                        \"ShowFileNames\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-TrashNodes-DownloadHuggingface\"\n                                    }\n                                ],\n                                \"https://github.com/adieyal/comfyui-dynamicprompts\": [\n                                    [\n                                        \"DPCombinatorialGenerator\",\n                                        \"DPFeelingLucky\",\n                                        \"DPJinja\",\n                                        \"DPMagicPrompt\",\n                                        \"DPOutput\",\n                                        \"DPRandomGenerator\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"DynamicPrompts Custom Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/adriflex/ComfyUI_Blender_Texdiff\": [\n                                    [\n                                        \"ViewportColor\",\n                                        \"ViewportDepth\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_Blender_Texdiff\"\n                                    }\n                                ],\n                                \"https://github.com/aegis72/aegisflow_utility_nodes\": [\n                                    [\n                                        \"Add Text To Image\",\n                                        \"Aegisflow CLIP Pass\",\n                                        \"Aegisflow Conditioning Pass\",\n                                        \"Aegisflow Image Pass\",\n                                        \"Aegisflow Latent Pass\",\n                                        \"Aegisflow Mask Pass\",\n                                        \"Aegisflow Model Pass\",\n                                        \"Aegisflow Pos/Neg Pass\",\n                                        \"Aegisflow SDXL Tuple Pass\",\n                                        \"Aegisflow VAE Pass\",\n                                        \"Aegisflow controlnet preprocessor bus\",\n                                        \"Apply Instagram Filter\",\n                                        \"Brightness_Contrast_Ally\",\n                                        \"Flatten Colors\",\n                                        \"Gaussian Blur_Ally\",\n                                        \"GlitchThis Effect\",\n                                        \"Hue Rotation\",\n                                        \"Image Flip_ally\",\n                                        \"Placeholder Tuple\",\n                                        \"Swap Color Mode\",\n                                        \"aegisflow Multi_Pass\",\n                                        \"aegisflow Multi_Pass XL\",\n                                        \"af_pipe_in_15\",\n                                        \"af_pipe_in_xl\",\n                                        \"af_pipe_out_15\",\n                                        \"af_pipe_out_xl\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"AegisFlow Utility Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/aegis72/comfyui-styles-all\": [\n                                    [\n                                        \"menus\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-styles-all\"\n                                    }\n                                ],\n                                \"https://github.com/ai-liam/comfyui_liam_util\": [\n                                    [\n                                        \"LiamLoadImage\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"LiamUtil\"\n                                    }\n                                ],\n                                \"https://github.com/aianimation55/ComfyUI-FatLabels\": [\n                                    [\n                                        \"FatLabels\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfy UI FatLabels\"\n                                    }\n                                ],\n                                \"https://github.com/alexopus/ComfyUI-Image-Saver\": [\n                                    [\n                                        \"Cfg Literal (Image Saver)\",\n                                        \"Checkpoint Loader with Name (Image Saver)\",\n                                        \"Float Literal (Image Saver)\",\n                                        \"Image Saver\",\n                                        \"Int Literal (Image Saver)\",\n                                        \"Sampler Selector (Image Saver)\",\n                                        \"Scheduler Selector (Image Saver)\",\n                                        \"Seed Generator (Image Saver)\",\n                                        \"String Literal (Image Saver)\",\n                                        \"Width/Height Literal (Image Saver)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Image Saver\"\n                                    }\n                                ],\n                                \"https://github.com/alpertunga-bile/prompt-generator-comfyui\": [\n                                    [\n                                        \"Prompt Generator\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"prompt-generator\"\n                                    }\n                                ],\n                                \"https://github.com/alsritter/asymmetric-tiling-comfyui\": [\n                                    [\n                                        \"Asymmetric_Tiling_KSampler\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"asymmetric-tiling-comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/alt-key-project/comfyui-dream-project\": [\n                                    [\n                                        \"Analyze Palette [Dream]\",\n                                        \"Beat Curve [Dream]\",\n                                        \"Big Float Switch [Dream]\",\n                                        \"Big Image Switch [Dream]\",\n                                        \"Big Int Switch [Dream]\",\n                                        \"Big Latent Switch [Dream]\",\n                                        \"Big Palette Switch [Dream]\",\n                                        \"Big Text Switch [Dream]\",\n                                        \"Boolean To Float [Dream]\",\n                                        \"Boolean To Int [Dream]\",\n                                        \"Build Prompt [Dream]\",\n                                        \"CSV Curve [Dream]\",\n                                        \"CSV Generator [Dream]\",\n                                        \"Calculation [Dream]\",\n                                        \"Common Frame Dimensions [Dream]\",\n                                        \"Compare Palettes [Dream]\",\n                                        \"FFMPEG Video Encoder [Dream]\",\n                                        \"File Count [Dream]\",\n                                        \"Finalize Prompt [Dream]\",\n                                        \"Float Input [Dream]\",\n                                        \"Float to Log Entry [Dream]\",\n                                        \"Frame Count Calculator [Dream]\",\n                                        \"Frame Counter (Directory) [Dream]\",\n                                        \"Frame Counter (Simple) [Dream]\",\n                                        \"Frame Counter Info [Dream]\",\n                                        \"Frame Counter Offset [Dream]\",\n                                        \"Frame Counter Time Offset [Dream]\",\n                                        \"Image Brightness Adjustment [Dream]\",\n                                        \"Image Color Shift [Dream]\",\n                                        \"Image Contrast Adjustment [Dream]\",\n                                        \"Image Motion [Dream]\",\n                                        \"Image Sequence Blend [Dream]\",\n                                        \"Image Sequence Loader [Dream]\",\n                                        \"Image Sequence Saver [Dream]\",\n                                        \"Image Sequence Tweening [Dream]\",\n                                        \"Int Input [Dream]\",\n                                        \"Int to Log Entry [Dream]\",\n                                        \"Laboratory [Dream]\",\n                                        \"Linear Curve [Dream]\",\n                                        \"Log Entry Joiner [Dream]\",\n                                        \"Log File [Dream]\",\n                                        \"Noise from Area Palettes [Dream]\",\n                                        \"Noise from Palette [Dream]\",\n                                        \"Palette Color Align [Dream]\",\n                                        \"Palette Color Shift [Dream]\",\n                                        \"Sample Image Area as Palette [Dream]\",\n                                        \"Sample Image as Palette [Dream]\",\n                                        \"Saw Curve [Dream]\",\n                                        \"Sine Curve [Dream]\",\n                                        \"Smooth Event Curve [Dream]\",\n                                        \"String Input [Dream]\",\n                                        \"String Tokenizer [Dream]\",\n                                        \"String to Log Entry [Dream]\",\n                                        \"Text Input [Dream]\",\n                                        \"Triangle Curve [Dream]\",\n                                        \"Triangle Event Curve [Dream]\",\n                                        \"WAV Curve [Dream]\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Dream Project Animation Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/alt-key-project/comfyui-dream-video-batches\": [\n                                    [\n                                        \"Blended Transition [DVB]\",\n                                        \"Calculation [DVB]\",\n                                        \"Create Frame Set [DVB]\",\n                                        \"Divide [DVB]\",\n                                        \"Fade From Black [DVB]\",\n                                        \"Fade To Black [DVB]\",\n                                        \"Float Input [DVB]\",\n                                        \"For Each Done [DVB]\",\n                                        \"For Each Filename [DVB]\",\n                                        \"Frame Set Append [DVB]\",\n                                        \"Frame Set Frame Dimensions Scaled [DVB]\",\n                                        \"Frame Set Index Offset [DVB]\",\n                                        \"Frame Set Merger [DVB]\",\n                                        \"Frame Set Reindex [DVB]\",\n                                        \"Frame Set Repeat [DVB]\",\n                                        \"Frame Set Reverse [DVB]\",\n                                        \"Frame Set Split Beginning [DVB]\",\n                                        \"Frame Set Split End [DVB]\",\n                                        \"Frame Set Splitter [DVB]\",\n                                        \"Generate Inbetween Frames [DVB]\",\n                                        \"Int Input [DVB]\",\n                                        \"Linear Camera Pan [DVB]\",\n                                        \"Linear Camera Roll [DVB]\",\n                                        \"Linear Camera Zoom [DVB]\",\n                                        \"Load Image From Path [DVB]\",\n                                        \"Multiply [DVB]\",\n                                        \"Sine Camera Pan [DVB]\",\n                                        \"Sine Camera Roll [DVB]\",\n                                        \"Sine Camera Zoom [DVB]\",\n                                        \"String Input [DVB]\",\n                                        \"Text Input [DVB]\",\n                                        \"Trace Memory Allocation [DVB]\",\n                                        \"Unwrap Frame Set [DVB]\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Dream Video Batches\"\n                                    }\n                                ],\n                                \"https://github.com/an90ray/ComfyUI_RErouter_CustomNodes\": [\n                                    [\n                                        \"CLIPTextEncode (RE)\",\n                                        \"CLIPTextEncodeSDXL (RE)\",\n                                        \"CLIPTextEncodeSDXLRefiner (RE)\",\n                                        \"Int (RE)\",\n                                        \"RErouter <=\",\n                                        \"RErouter =>\",\n                                        \"String (RE)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_RErouter_CustomNodes\"\n                                    }\n                                ],\n                                \"https://github.com/andersxa/comfyui-PromptAttention\": [\n                                    [\n                                        \"CLIPAttentionMaskEncode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"CLIP Directional Prompt Attention\"\n                                    }\n                                ],\n                                \"https://github.com/antrobot1234/antrobots-comfyUI-nodepack\": [\n                                    [\n                                        \"composite\",\n                                        \"crop\",\n                                        \"paste\",\n                                        \"preview_mask\",\n                                        \"scale\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"antrobots ComfyUI Nodepack\"\n                                    }\n                                ],\n                                \"https://github.com/asagi4/ComfyUI-CADS\": [\n                                    [\n                                        \"CADS\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-CADS\"\n                                    }\n                                ],\n                                \"https://github.com/asagi4/comfyui-prompt-control\": [\n                                    [\n                                        \"EditableCLIPEncode\",\n                                        \"FilterSchedule\",\n                                        \"LoRAScheduler\",\n                                        \"PCApplySettings\",\n                                        \"PCPromptFromSchedule\",\n                                        \"PCScheduleSettings\",\n                                        \"PCSplitSampling\",\n                                        \"PromptControlSimple\",\n                                        \"PromptToSchedule\",\n                                        \"ScheduleToCond\",\n                                        \"ScheduleToModel\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI prompt control\"\n                                    }\n                                ],\n                                \"https://github.com/asagi4/comfyui-utility-nodes\": [\n                                    [\n                                        \"MUForceCacheClear\",\n                                        \"MUJinjaRender\",\n                                        \"MUSimpleWildcard\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"asagi4/comfyui-utility-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/aszc-dev/ComfyUI-CoreMLSuite\": [\n                                    [\n                                        \"Core ML Converter\",\n                                        \"Core ML LCM Converter\",\n                                        \"Core ML LoRA Loader\",\n                                        \"CoreMLModelAdapter\",\n                                        \"CoreMLSampler\",\n                                        \"CoreMLSamplerAdvanced\",\n                                        \"CoreMLUNetLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Core ML Suite for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/avatechai/avatar-graph-comfyui\": [\n                                    [\n                                        \"ApplyMeshTransformAsShapeKey\",\n                                        \"B_ENUM\",\n                                        \"B_VECTOR3\",\n                                        \"B_VECTOR4\",\n                                        \"Combine Points\",\n                                        \"CreateShapeFlow\",\n                                        \"ExportBlendshapes\",\n                                        \"ExportGLTF\",\n                                        \"Extract Boundary Points\",\n                                        \"Image Alpha Mask Merge\",\n                                        \"ImageBridge\",\n                                        \"LoadImageFromRequest\",\n                                        \"LoadImageWithAlpha\",\n                                        \"LoadValueFromRequest\",\n                                        \"SAM MultiLayer\",\n                                        \"Save Image With Workflow\"\n                                    ],\n                                    {\n                                        \"author\": \"Avatech Limited\",\n                                        \"description\": \"Include nodes for sam + bpy operation, that allows workflow creations for generative 2d character rig.\",\n                                        \"nickname\": \"Avatar Graph\",\n                                        \"title\": \"Avatar Graph\",\n                                        \"title_aux\": \"avatar-graph-comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/azure-dragon-ai/ComfyUI-ClipScore-Nodes\": [\n                                    [\n                                        \"HaojihuiClipScoreFakeImageProcessor\",\n                                        \"HaojihuiClipScoreImageProcessor\",\n                                        \"HaojihuiClipScoreImageScore\",\n                                        \"HaojihuiClipScoreLoader\",\n                                        \"HaojihuiClipScoreRealImageProcessor\",\n                                        \"HaojihuiClipScoreTextProcessor\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-ClipScore-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/badjeff/comfyui_lora_tag_loader\": [\n                                    [\n                                        \"LoraTagLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"LoRA Tag Loader for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/banodoco/steerable-motion\": [\n                                    [\n                                        \"BatchCreativeInterpolation\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Steerable Motion\"\n                                    }\n                                ],\n                                \"https://github.com/bash-j/mikey_nodes\": [\n                                    [\n                                        \"AddMetaData\",\n                                        \"Batch Crop Image\",\n                                        \"Batch Crop Resize Inplace\",\n                                        \"Batch Load Images\",\n                                        \"Batch Resize Image for SDXL\",\n                                        \"Checkpoint Loader Simple Mikey\",\n                                        \"CinematicLook\",\n                                        \"Empty Latent Ratio Custom SDXL\",\n                                        \"Empty Latent Ratio Select SDXL\",\n                                        \"EvalFloats\",\n                                        \"FaceFixerOpenCV\",\n                                        \"FileNamePrefix\",\n                                        \"FileNamePrefixDateDirFirst\",\n                                        \"Float to String\",\n                                        \"HaldCLUT\",\n                                        \"Image Caption\",\n                                        \"ImageBorder\",\n                                        \"ImageOverlay\",\n                                        \"ImagePaste\",\n                                        \"Int to String\",\n                                        \"LMStudioPrompt\",\n                                        \"Load Image Based on Number\",\n                                        \"LoraSyntaxProcessor\",\n                                        \"Mikey Sampler\",\n                                        \"Mikey Sampler Base Only\",\n                                        \"Mikey Sampler Base Only Advanced\",\n                                        \"Mikey Sampler Tiled\",\n                                        \"Mikey Sampler Tiled Base Only\",\n                                        \"MikeySamplerTiledAdvanced\",\n                                        \"MikeySamplerTiledAdvancedBaseOnly\",\n                                        \"OobaPrompt\",\n                                        \"PresetRatioSelector\",\n                                        \"Prompt With SDXL\",\n                                        \"Prompt With Style\",\n                                        \"Prompt With Style V2\",\n                                        \"Prompt With Style V3\",\n                                        \"Range Float\",\n                                        \"Range Integer\",\n                                        \"Ratio Advanced\",\n                                        \"Resize Image for SDXL\",\n                                        \"Save Image If True\",\n                                        \"Save Image With Prompt Data\",\n                                        \"Save Images Mikey\",\n                                        \"Save Images No Display\",\n                                        \"SaveMetaData\",\n                                        \"SearchAndReplace\",\n                                        \"Seed String\",\n                                        \"Style Conditioner\",\n                                        \"Style Conditioner Base Only\",\n                                        \"Text2InputOr3rdOption\",\n                                        \"TextCombinations\",\n                                        \"TextCombinations3\",\n                                        \"TextConcat\",\n                                        \"TextPreserve\",\n                                        \"Upscale Tile Calculator\",\n                                        \"Wildcard Processor\",\n                                        \"WildcardAndLoraSyntaxProcessor\",\n                                        \"WildcardOobaPrompt\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Mikey Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/bedovyy/ComfyUI_NAIDGenerator\": [\n                                    [\n                                        \"GenerateNAID\",\n                                        \"Img2ImgOptionNAID\",\n                                        \"InpaintingOptionNAID\",\n                                        \"MaskImageToNAID\",\n                                        \"ModelOptionNAID\",\n                                        \"PromptToNAID\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_NAIDGenerator\"\n                                    }\n                                ],\n                                \"https://github.com/biegert/ComfyUI-CLIPSeg/raw/main/custom_nodes/clipseg.py\": [\n                                    [\n                                        \"CLIPSeg\",\n                                        \"CombineSegMasks\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"CLIPSeg\"\n                                    }\n                                ],\n                                \"https://github.com/bilal-arikan/ComfyUI_TextAssets\": [\n                                    [\n                                        \"LoadTextAsset\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_TextAssets\"\n                                    }\n                                ],\n                                \"https://github.com/blepping/ComfyUI-bleh\": [\n                                    [\n                                        \"BlehDeepShrink\",\n                                        \"BlehDiscardPenultimateSigma\",\n                                        \"BlehHyperTile\",\n                                        \"BlehInsaneChainSampler\",\n                                        \"BlehModelPatchConditional\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-bleh\"\n                                    }\n                                ],\n                                \"https://github.com/bmad4ever/comfyui_ab_samplercustom\": [\n                                    [\n                                        \"AB SamplerCustom (experimental)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui_ab_sampler\"\n                                    }\n                                ],\n                                \"https://github.com/bmad4ever/comfyui_bmad_nodes\": [\n                                    [\n                                        \"AdaptiveThresholding\",\n                                        \"Add String To Many\",\n                                        \"AddAlpha\",\n                                        \"AdjustRect\",\n                                        \"AnyToAny\",\n                                        \"BoundingRect (contours)\",\n                                        \"BuildColorRangeAdvanced (hsv)\",\n                                        \"BuildColorRangeHSV (hsv)\",\n                                        \"CLAHE\",\n                                        \"CLIPEncodeMultiple\",\n                                        \"CLIPEncodeMultipleAdvanced\",\n                                        \"ChameleonMask\",\n                                        \"CheckpointLoader (dirty)\",\n                                        \"CheckpointLoaderSimple (dirty)\",\n                                        \"Color (RGB)\",\n                                        \"Color (hexadecimal)\",\n                                        \"Color Clip\",\n                                        \"Color Clip (advanced)\",\n                                        \"Color Clip ADE20k\",\n                                        \"ColorDictionary\",\n                                        \"ColorDictionary (custom)\",\n                                        \"Conditioning (combine multiple)\",\n                                        \"Conditioning (combine selective)\",\n                                        \"Conditioning Grid (cond)\",\n                                        \"Conditioning Grid (string)\",\n                                        \"Conditioning Grid (string) Advanced\",\n                                        \"Contour To Mask\",\n                                        \"Contours\",\n                                        \"ControlNetHadamard\",\n                                        \"ControlNetHadamard (manual)\",\n                                        \"ConvertImg\",\n                                        \"CopyMakeBorder\",\n                                        \"CreateRequestMetadata\",\n                                        \"DistanceTransform\",\n                                        \"Draw Contour(s)\",\n                                        \"EqualizeHistogram\",\n                                        \"ExtendColorList\",\n                                        \"ExtendCondList\",\n                                        \"ExtendFloatList\",\n                                        \"ExtendImageList\",\n                                        \"ExtendIntList\",\n                                        \"ExtendLatentList\",\n                                        \"ExtendMaskList\",\n                                        \"ExtendModelList\",\n                                        \"ExtendStringList\",\n                                        \"FadeMaskEdges\",\n                                        \"Filter Contour\",\n                                        \"FindComplementaryColor\",\n                                        \"FindThreshold\",\n                                        \"FlatLatentsIntoSingleGrid\",\n                                        \"Framed Mask Grab Cut\",\n                                        \"Framed Mask Grab Cut 2\",\n                                        \"FromListGet1Color\",\n                                        \"FromListGet1Cond\",\n                                        \"FromListGet1Float\",\n                                        \"FromListGet1Image\",\n                                        \"FromListGet1Int\",\n                                        \"FromListGet1Latent\",\n                                        \"FromListGet1Mask\",\n                                        \"FromListGet1Model\",\n                                        \"FromListGet1String\",\n                                        \"FromListGetColors\",\n                                        \"FromListGetConds\",\n                                        \"FromListGetFloats\",\n                                        \"FromListGetImages\",\n                                        \"FromListGetInts\",\n                                        \"FromListGetLatents\",\n                                        \"FromListGetMasks\",\n                                        \"FromListGetModels\",\n                                        \"FromListGetStrings\",\n                                        \"Get Contour from list\",\n                                        \"Get Models\",\n                                        \"Get Prompt\",\n                                        \"HypernetworkLoader (dirty)\",\n                                        \"ImageBatchToList\",\n                                        \"InRange (hsv)\",\n                                        \"Inpaint\",\n                                        \"Input/String to Int Array\",\n                                        \"KMeansColor\",\n                                        \"Load 64 Encoded Image\",\n                                        \"LoraLoader (dirty)\",\n                                        \"MaskGrid N KSamplers Advanced\",\n                                        \"MaskOuterBlur\",\n                                        \"Merge Latent Batch Gridwise\",\n                                        \"MonoMerge\",\n                                        \"MorphologicOperation\",\n                                        \"MorphologicSkeletoning\",\n                                        \"NaiveAutoKMeansColor\",\n                                        \"OtsuThreshold\",\n                                        \"RGB to HSV\",\n                                        \"Rect Grab Cut\",\n                                        \"Remap\",\n                                        \"RemapBarrelDistortion\",\n                                        \"RemapFromInsideParabolas\",\n                                        \"RemapFromQuadrilateral (homography)\",\n                                        \"RemapInsideParabolas\",\n                                        \"RemapInsideParabolasAdvanced\",\n                                        \"RemapPinch\",\n                                        \"RemapReverseBarrelDistortion\",\n                                        \"RemapStretch\",\n                                        \"RemapToInnerCylinder\",\n                                        \"RemapToOuterCylinder\",\n                                        \"RemapToQuadrilateral\",\n                                        \"RemapWarpPolar\",\n                                        \"Repeat Into Grid (image)\",\n                                        \"Repeat Into Grid (latent)\",\n                                        \"RequestInputs\",\n                                        \"SampleColorHSV\",\n                                        \"Save Image (api)\",\n                                        \"SeamlessClone\",\n                                        \"SeamlessClone (simple)\",\n                                        \"SetRequestStateToComplete\",\n                                        \"String\",\n                                        \"String to Float\",\n                                        \"String to Integer\",\n                                        \"ToColorList\",\n                                        \"ToCondList\",\n                                        \"ToFloatList\",\n                                        \"ToImageList\",\n                                        \"ToIntList\",\n                                        \"ToLatentList\",\n                                        \"ToMaskList\",\n                                        \"ToModelList\",\n                                        \"ToStringList\",\n                                        \"UnGridify (image)\",\n                                        \"VAEEncodeBatch\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Bmad Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/bmad4ever/comfyui_lists_cartesian_product\": [\n                                    [\n                                        \"AnyListCartesianProduct\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Lists Cartesian Product\"\n                                    }\n                                ],\n                                \"https://github.com/bradsec/ComfyUI_ResolutionSelector\": [\n                                    [\n                                        \"ResolutionSelector\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ResolutionSelector for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/braintacles/braintacles-comfyui-nodes\": [\n                                    [\n                                        \"CLIPTextEncodeSDXL-Multi-IO\",\n                                        \"CLIPTextEncodeSDXL-Pipe\",\n                                        \"Empty Latent Image from Aspect-Ratio\",\n                                        \"Random Find and Replace\",\n                                        \"VAE Decode Pipe\",\n                                        \"VAE Decode Tiled Pipe\",\n                                        \"VAE Encode Pipe\",\n                                        \"VAE Encode Tiled Pipe\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"braintacles-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/brianfitzgerald/style_aligned_comfy\": [\n                                    [\n                                        \"StyleAlignedBatchAlign\",\n                                        \"StyleAlignedReferenceSampler\",\n                                        \"StyleAlignedSampleReferenceLatents\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"StyleAligned for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/bronkula/comfyui-fitsize\": [\n                                    [\n                                        \"FS: Crop Image Into Even Pieces\",\n                                        \"FS: Fit Image And Resize\",\n                                        \"FS: Fit Size From Image\",\n                                        \"FS: Fit Size From Int\",\n                                        \"FS: Image Region To Mask\",\n                                        \"FS: Load Image And Resize To Fit\",\n                                        \"FS: Pick Image From Batch\",\n                                        \"FS: Pick Image From Batches\",\n                                        \"FS: Pick Image From List\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-fitsize\"\n                                    }\n                                ],\n                                \"https://github.com/bruefire/ComfyUI-SeqImageLoader\": [\n                                    [\n                                        \"VFrame Loader With Mask Editor\",\n                                        \"Video Loader With Mask Editor\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Sequential Image Loader\"\n                                    }\n                                ],\n                                \"https://github.com/budihartono/comfyui_otonx_nodes\": [\n                                    [\n                                        \"OTX Integer Multiple Inputs 4\",\n                                        \"OTX Integer Multiple Inputs 5\",\n                                        \"OTX Integer Multiple Inputs 6\",\n                                        \"OTX KSampler Feeder\",\n                                        \"OTX Versatile Multiple Inputs 4\",\n                                        \"OTX Versatile Multiple Inputs 5\",\n                                        \"OTX Versatile Multiple Inputs 6\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Otonx's Custom Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/bvhari/ComfyUI_ImageProcessing\": [\n                                    [\n                                        \"BilateralFilter\",\n                                        \"Brightness\",\n                                        \"Gamma\",\n                                        \"Hue\",\n                                        \"Saturation\",\n                                        \"SigmoidCorrection\",\n                                        \"UnsharpMask\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ImageProcessing\"\n                                    }\n                                ],\n                                \"https://github.com/bvhari/ComfyUI_LatentToRGB\": [\n                                    [\n                                        \"LatentToRGB\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"LatentToRGB\"\n                                    }\n                                ],\n                                \"https://github.com/bvhari/ComfyUI_PerpWeight\": [\n                                    [\n                                        \"CLIPTextEncodePerpWeight\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_PerpWeight\"\n                                    }\n                                ],\n                                \"https://github.com/catscandrive/comfyui-imagesubfolders/raw/main/loadImageWithSubfolders.py\": [\n                                    [\n                                        \"LoadImagewithSubfolders\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Image loader with subfolders\"\n                                    }\n                                ],\n                                \"https://github.com/ccvv804/ComfyUI-DiffusersStableCascade-LowVRAM\": [\n                                    [\n                                        \"DiffusersStableCascade\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI StableCascade using diffusers for Low VRAM\"\n                                    }\n                                ],\n                                \"https://github.com/celsojr2013/comfyui_simpletools/raw/main/google_translator.py\": [\n                                    [\n                                        \"GoogleTranslator\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI SimpleTools Suit\"\n                                    }\n                                ],\n                                \"https://github.com/ceruleandeep/ComfyUI-LLaVA-Captioner\": [\n                                    [\n                                        \"LlavaCaptioner\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI LLaVA Captioner\"\n                                    }\n                                ],\n                                \"https://github.com/chaojie/ComfyUI-DragNUWA\": [\n                                    [\n                                        \"BrushMotion\",\n                                        \"CompositeMotionBrush\",\n                                        \"CompositeMotionBrushWithoutModel\",\n                                        \"DragNUWA Run\",\n                                        \"DragNUWA Run MotionBrush\",\n                                        \"Get First Image\",\n                                        \"Get Last Image\",\n                                        \"InstantCameraMotionBrush\",\n                                        \"InstantObjectMotionBrush\",\n                                        \"Load CheckPoint DragNUWA\",\n                                        \"Load MotionBrush From Optical Flow\",\n                                        \"Load MotionBrush From Optical Flow Directory\",\n                                        \"Load MotionBrush From Optical Flow Without Model\",\n                                        \"Load MotionBrush From Tracking Points\",\n                                        \"Load MotionBrush From Tracking Points Without Model\",\n                                        \"Load Pose KeyPoints\",\n                                        \"Loop\",\n                                        \"LoopEnd_IMAGE\",\n                                        \"LoopStart_IMAGE\",\n                                        \"Split Tracking Points\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-DragNUWA\"\n                                    }\n                                ],\n                                \"https://github.com/chaojie/ComfyUI-DynamiCrafter\": [\n                                    [\n                                        \"DynamiCrafter Simple\",\n                                        \"DynamiCrafterLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-DynamiCrafter\"\n                                    }\n                                ],\n                                \"https://github.com/chaojie/ComfyUI-I2VGEN-XL\": [\n                                    [\n                                        \"I2VGEN-XL Simple\",\n                                        \"Modelscope Pipeline Loader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-I2VGEN-XL\"\n                                    }\n                                ],\n                                \"https://github.com/chaojie/ComfyUI-LightGlue\": [\n                                    [\n                                        \"LightGlue Loader\",\n                                        \"LightGlue Simple\",\n                                        \"LightGlue Simple Multi\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-LightGlue\"\n                                    }\n                                ],\n                                \"https://github.com/chaojie/ComfyUI-Moore-AnimateAnyone\": [\n                                    [\n                                        \"Moore-AnimateAnyone Denoising Unet\",\n                                        \"Moore-AnimateAnyone Image Encoder\",\n                                        \"Moore-AnimateAnyone Pipeline Loader\",\n                                        \"Moore-AnimateAnyone Pose Guider\",\n                                        \"Moore-AnimateAnyone Reference Unet\",\n                                        \"Moore-AnimateAnyone Simple\",\n                                        \"Moore-AnimateAnyone VAE\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Moore-AnimateAnyone\"\n                                    }\n                                ],\n                                \"https://github.com/chaojie/ComfyUI-Motion-Vector-Extractor\": [\n                                    [\n                                        \"Motion Vector Extractor\",\n                                        \"VideoCombineThenPath\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Motion-Vector-Extractor\"\n                                    }\n                                ],\n                                \"https://github.com/chaojie/ComfyUI-MotionCtrl\": [\n                                    [\n                                        \"Load Motion Camera Preset\",\n                                        \"Load Motion Traj Preset\",\n                                        \"Load Motionctrl Checkpoint\",\n                                        \"Motionctrl Cond\",\n                                        \"Motionctrl Sample\",\n                                        \"Motionctrl Sample Simple\",\n                                        \"Select Image Indices\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-MotionCtrl\"\n                                    }\n                                ],\n                                \"https://github.com/chaojie/ComfyUI-MotionCtrl-SVD\": [\n                                    [\n                                        \"Load Motionctrl-SVD Camera Preset\",\n                                        \"Load Motionctrl-SVD Checkpoint\",\n                                        \"Motionctrl-SVD Sample Simple\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-MotionCtrl-SVD\"\n                                    }\n                                ],\n                                \"https://github.com/chaojie/ComfyUI-Panda3d\": [\n                                    [\n                                        \"Panda3dAmbientLight\",\n                                        \"Panda3dAttachNewNode\",\n                                        \"Panda3dBase\",\n                                        \"Panda3dDirectionalLight\",\n                                        \"Panda3dLoadDepthModel\",\n                                        \"Panda3dLoadModel\",\n                                        \"Panda3dLoadTexture\",\n                                        \"Panda3dModelMerge\",\n                                        \"Panda3dTest\",\n                                        \"Panda3dTextureMerge\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Panda3d\"\n                                    }\n                                ],\n                                \"https://github.com/chaojie/ComfyUI-Pymunk\": [\n                                    [\n                                        \"PygameRun\",\n                                        \"PygameSurface\",\n                                        \"PymunkDynamicBox\",\n                                        \"PymunkDynamicCircle\",\n                                        \"PymunkRun\",\n                                        \"PymunkShapeMerge\",\n                                        \"PymunkSpace\",\n                                        \"PymunkStaticLine\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Pymunk\"\n                                    }\n                                ],\n                                \"https://github.com/chaojie/ComfyUI-RAFT\": [\n                                    [\n                                        \"Load MotionBrush\",\n                                        \"RAFT Run\",\n                                        \"Save MotionBrush\",\n                                        \"VizMotionBrush\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-RAFT\"\n                                    }\n                                ],\n                                \"https://github.com/chflame163/ComfyUI_LayerStyle\": [\n                                    [\n                                        \"LayerColor: Brightness & Contrast\",\n                                        \"LayerColor: ColorAdapter\",\n                                        \"LayerColor: Exposure\",\n                                        \"LayerColor: Gamma\",\n                                        \"LayerColor: HSV\",\n                                        \"LayerColor: LAB\",\n                                        \"LayerColor: LUT Apply\",\n                                        \"LayerColor: RGB\",\n                                        \"LayerColor: YUV\",\n                                        \"LayerFilter: ChannelShake\",\n                                        \"LayerFilter: ColorMap\",\n                                        \"LayerFilter: GaussianBlur\",\n                                        \"LayerFilter: MotionBlur\",\n                                        \"LayerFilter: Sharp & Soft\",\n                                        \"LayerFilter: SkinBeauty\",\n                                        \"LayerFilter: SoftLight\",\n                                        \"LayerFilter: WaterColor\",\n                                        \"LayerMask: CreateGradientMask\",\n                                        \"LayerMask: MaskBoxDetect\",\n                                        \"LayerMask: MaskByDifferent\",\n                                        \"LayerMask: MaskEdgeShrink\",\n                                        \"LayerMask: MaskEdgeUltraDetail\",\n                                        \"LayerMask: MaskGradient\",\n                                        \"LayerMask: MaskGrow\",\n                                        \"LayerMask: MaskInvert\",\n                                        \"LayerMask: MaskMotionBlur\",\n                                        \"LayerMask: MaskPreview\",\n                                        \"LayerMask: MaskStroke\",\n                                        \"LayerMask: PixelSpread\",\n                                        \"LayerMask: RemBgUltra\",\n                                        \"LayerMask: SegmentAnythingUltra\",\n                                        \"LayerStyle: ColorOverlay\",\n                                        \"LayerStyle: DropShadow\",\n                                        \"LayerStyle: GradientOverlay\",\n                                        \"LayerStyle: InnerGlow\",\n                                        \"LayerStyle: InnerShadow\",\n                                        \"LayerStyle: OuterGlow\",\n                                        \"LayerStyle: Stroke\",\n                                        \"LayerUtility: ColorImage\",\n                                        \"LayerUtility: ColorPicker\",\n                                        \"LayerUtility: CropByMask\",\n                                        \"LayerUtility: ExtendCanvas\",\n                                        \"LayerUtility: GetColorTone\",\n                                        \"LayerUtility: GetImageSize\",\n                                        \"LayerUtility: GradientImage\",\n                                        \"LayerUtility: ImageBlend\",\n                                        \"LayerUtility: ImageBlendAdvance\",\n                                        \"LayerUtility: ImageChannelMerge\",\n                                        \"LayerUtility: ImageChannelSplit\",\n                                        \"LayerUtility: ImageMaskScaleAs\",\n                                        \"LayerUtility: ImageOpacity\",\n                                        \"LayerUtility: ImageScaleRestore\",\n                                        \"LayerUtility: ImageShift\",\n                                        \"LayerUtility: LayerImageTransform\",\n                                        \"LayerUtility: LayerMaskTransform\",\n                                        \"LayerUtility: PrintInfo\",\n                                        \"LayerUtility: RestoreCropBox\",\n                                        \"LayerUtility: TextImage\",\n                                        \"LayerUtility: XY to Percent\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Layer Style\"\n                                    }\n                                ],\n                                \"https://github.com/chflame163/ComfyUI_MSSpeech_TTS\": [\n                                    [\n                                        \"Input Trigger\",\n                                        \"MicrosoftSpeech_TTS\",\n                                        \"Play Sound\",\n                                        \"Play Sound (loop)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_MSSpeech_TTS\"\n                                    }\n                                ],\n                                \"https://github.com/chflame163/ComfyUI_WordCloud\": [\n                                    [\n                                        \"ComfyWordCloud\",\n                                        \"LoadTextFile\",\n                                        \"RGB_Picker\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_WordCloud\"\n                                    }\n                                ],\n                                \"https://github.com/chibiace/ComfyUI-Chibi-Nodes\": [\n                                    [\n                                        \"ConditionText\",\n                                        \"ConditionTextMulti\",\n                                        \"ImageAddText\",\n                                        \"ImageSimpleResize\",\n                                        \"ImageSizeInfo\",\n                                        \"ImageTool\",\n                                        \"Int2String\",\n                                        \"LoadEmbedding\",\n                                        \"LoadImageExtended\",\n                                        \"Loader\",\n                                        \"Prompts\",\n                                        \"RandomResolutionLatent\",\n                                        \"SaveImages\",\n                                        \"SeedGenerator\",\n                                        \"SimpleSampler\",\n                                        \"TextSplit\",\n                                        \"Textbox\",\n                                        \"Wildcards\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Chibi-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/chrisgoringe/cg-image-picker\": [\n                                    [\n                                        \"Preview Chooser\",\n                                        \"Preview Chooser Fabric\"\n                                    ],\n                                    {\n                                        \"author\": \"chrisgoringe\",\n                                        \"description\": \"Custom nodes that preview images and pause the workflow to allow the user to select one or more to progress\",\n                                        \"nickname\": \"Image Chooser\",\n                                        \"title\": \"Image Chooser\",\n                                        \"title_aux\": \"Image chooser\"\n                                    }\n                                ],\n                                \"https://github.com/chrisgoringe/cg-noise\": [\n                                    [\n                                        \"Hijack\",\n                                        \"KSampler Advanced with Variations\",\n                                        \"KSampler with Variations\",\n                                        \"UnHijack\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Variation seeds\"\n                                    }\n                                ],\n                                \"https://github.com/chrisgoringe/cg-use-everywhere\": [\n                                    [\n                                        \"Seed Everywhere\"\n                                    ],\n                                    {\n                                        \"nodename_pattern\": \"(^(Prompts|Anything) Everywhere|Simple String)\",\n                                        \"title_aux\": \"Use Everywhere (UE Nodes)\"\n                                    }\n                                ],\n                                \"https://github.com/city96/ComfyUI_ColorMod\": [\n                                    [\n                                        \"ColorModEdges\",\n                                        \"ColorModPivot\",\n                                        \"LoadImageHighPrec\",\n                                        \"PreviewImageHighPrec\",\n                                        \"SaveImageHighPrec\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_ColorMod\"\n                                    }\n                                ],\n                                \"https://github.com/city96/ComfyUI_DiT\": [\n                                    [\n                                        \"DiTCheckpointLoader\",\n                                        \"DiTCheckpointLoaderSimple\",\n                                        \"DiTLabelCombine\",\n                                        \"DiTLabelSelect\",\n                                        \"DiTSampler\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_DiT [WIP]\"\n                                    }\n                                ],\n                                \"https://github.com/city96/ComfyUI_ExtraModels\": [\n                                    [\n                                        \"DiTCondLabelEmpty\",\n                                        \"DiTCondLabelSelect\",\n                                        \"DitCheckpointLoader\",\n                                        \"ExtraVAELoader\",\n                                        \"PixArtCheckpointLoader\",\n                                        \"PixArtDPMSampler\",\n                                        \"PixArtLoraLoader\",\n                                        \"PixArtResolutionSelect\",\n                                        \"PixArtT5TextEncode\",\n                                        \"T5TextEncode\",\n                                        \"T5v11Loader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Extra Models for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/city96/ComfyUI_NetDist\": [\n                                    [\n                                        \"CombineImageBatch\",\n                                        \"FetchRemote\",\n                                        \"LoadCurrentWorkflowJSON\",\n                                        \"LoadDiskWorkflowJSON\",\n                                        \"LoadImageUrl\",\n                                        \"LoadLatentNumpy\",\n                                        \"LoadLatentUrl\",\n                                        \"RemoteChainEnd\",\n                                        \"RemoteChainStart\",\n                                        \"RemoteQueueSimple\",\n                                        \"RemoteQueueWorker\",\n                                        \"SaveDiskWorkflowJSON\",\n                                        \"SaveImageUrl\",\n                                        \"SaveLatentNumpy\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_NetDist\"\n                                    }\n                                ],\n                                \"https://github.com/city96/SD-Advanced-Noise\": [\n                                    [\n                                        \"LatentGaussianNoise\",\n                                        \"MathEncode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SD-Advanced-Noise\"\n                                    }\n                                ],\n                                \"https://github.com/city96/SD-Latent-Interposer\": [\n                                    [\n                                        \"LatentInterposer\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Latent-Interposer\"\n                                    }\n                                ],\n                                \"https://github.com/city96/SD-Latent-Upscaler\": [\n                                    [\n                                        \"LatentUpscaler\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SD-Latent-Upscaler\"\n                                    }\n                                ],\n                                \"https://github.com/civitai/comfy-nodes\": [\n                                    [\n                                        \"CivitAI_Checkpoint_Loader\",\n                                        \"CivitAI_Lora_Loader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfy-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/comfyanonymous/ComfyUI\": [\n                                    [\n                                        \"BasicScheduler\",\n                                        \"CLIPLoader\",\n                                        \"CLIPMergeSimple\",\n                                        \"CLIPSave\",\n                                        \"CLIPSetLastLayer\",\n                                        \"CLIPTextEncode\",\n                                        \"CLIPTextEncodeControlnet\",\n                                        \"CLIPTextEncodeSDXL\",\n                                        \"CLIPTextEncodeSDXLRefiner\",\n                                        \"CLIPVisionEncode\",\n                                        \"CLIPVisionLoader\",\n                                        \"Canny\",\n                                        \"CheckpointLoader\",\n                                        \"CheckpointLoaderSimple\",\n                                        \"CheckpointSave\",\n                                        \"ConditioningAverage\",\n                                        \"ConditioningCombine\",\n                                        \"ConditioningConcat\",\n                                        \"ConditioningSetArea\",\n                                        \"ConditioningSetAreaPercentage\",\n                                        \"ConditioningSetAreaStrength\",\n                                        \"ConditioningSetMask\",\n                                        \"ConditioningSetTimestepRange\",\n                                        \"ConditioningZeroOut\",\n                                        \"ControlNetApply\",\n                                        \"ControlNetApplyAdvanced\",\n                                        \"ControlNetLoader\",\n                                        \"CropMask\",\n                                        \"DiffControlNetLoader\",\n                                        \"DiffusersLoader\",\n                                        \"DualCLIPLoader\",\n                                        \"EmptyImage\",\n                                        \"EmptyLatentImage\",\n                                        \"ExponentialScheduler\",\n                                        \"FeatherMask\",\n                                        \"FlipSigmas\",\n                                        \"FreeU\",\n                                        \"FreeU_V2\",\n                                        \"GLIGENLoader\",\n                                        \"GLIGENTextBoxApply\",\n                                        \"GrowMask\",\n                                        \"HyperTile\",\n                                        \"HypernetworkLoader\",\n                                        \"ImageBatch\",\n                                        \"ImageBlend\",\n                                        \"ImageBlur\",\n                                        \"ImageColorToMask\",\n                                        \"ImageCompositeMasked\",\n                                        \"ImageCrop\",\n                                        \"ImageFromBatch\",\n                                        \"ImageInvert\",\n                                        \"ImageOnlyCheckpointLoader\",\n                                        \"ImageOnlyCheckpointSave\",\n                                        \"ImagePadForOutpaint\",\n                                        \"ImageQuantize\",\n                                        \"ImageScale\",\n                                        \"ImageScaleBy\",\n                                        \"ImageScaleToTotalPixels\",\n                                        \"ImageSharpen\",\n                                        \"ImageToMask\",\n                                        \"ImageUpscaleWithModel\",\n                                        \"InpaintModelConditioning\",\n                                        \"InvertMask\",\n                                        \"JoinImageWithAlpha\",\n                                        \"KSampler\",\n                                        \"KSamplerAdvanced\",\n                                        \"KSamplerSelect\",\n                                        \"KarrasScheduler\",\n                                        \"LatentAdd\",\n                                        \"LatentBatch\",\n                                        \"LatentBatchSeedBehavior\",\n                                        \"LatentBlend\",\n                                        \"LatentComposite\",\n                                        \"LatentCompositeMasked\",\n                                        \"LatentCrop\",\n                                        \"LatentFlip\",\n                                        \"LatentFromBatch\",\n                                        \"LatentInterpolate\",\n                                        \"LatentMultiply\",\n                                        \"LatentRotate\",\n                                        \"LatentSubtract\",\n                                        \"LatentUpscale\",\n                                        \"LatentUpscaleBy\",\n                                        \"LoadImage\",\n                                        \"LoadImageMask\",\n                                        \"LoadLatent\",\n                                        \"LoraLoader\",\n                                        \"LoraLoaderModelOnly\",\n                                        \"MaskComposite\",\n                                        \"MaskToImage\",\n                                        \"ModelMergeAdd\",\n                                        \"ModelMergeBlocks\",\n                                        \"ModelMergeSimple\",\n                                        \"ModelMergeSubtract\",\n                                        \"ModelSamplingContinuousEDM\",\n                                        \"ModelSamplingDiscrete\",\n                                        \"PatchModelAddDownscale\",\n                                        \"PerpNeg\",\n                                        \"PhotoMakerEncode\",\n                                        \"PhotoMakerLoader\",\n                                        \"PolyexponentialScheduler\",\n                                        \"PorterDuffImageComposite\",\n                                        \"PreviewImage\",\n                                        \"RebatchImages\",\n                                        \"RebatchLatents\",\n                                        \"RepeatImageBatch\",\n                                        \"RepeatLatentBatch\",\n                                        \"RescaleCFG\",\n                                        \"SDTurboScheduler\",\n                                        \"SD_4XUpscale_Conditioning\",\n                                        \"SVD_img2vid_Conditioning\",\n                                        \"SamplerCustom\",\n                                        \"SamplerDPMPP_2M_SDE\",\n                                        \"SamplerDPMPP_SDE\",\n                                        \"SaveAnimatedPNG\",\n                                        \"SaveAnimatedWEBP\",\n                                        \"SaveImage\",\n                                        \"SaveLatent\",\n                                        \"SelfAttentionGuidance\",\n                                        \"SetLatentNoiseMask\",\n                                        \"SolidMask\",\n                                        \"SplitImageWithAlpha\",\n                                        \"SplitSigmas\",\n                                        \"StableCascade_EmptyLatentImage\",\n                                        \"StableCascade_StageB_Conditioning\",\n                                        \"StableZero123_Conditioning\",\n                                        \"StableZero123_Conditioning_Batched\",\n                                        \"StyleModelApply\",\n                                        \"StyleModelLoader\",\n                                        \"TomePatchModel\",\n                                        \"UNETLoader\",\n                                        \"UpscaleModelLoader\",\n                                        \"VAEDecode\",\n                                        \"VAEDecodeTiled\",\n                                        \"VAEEncode\",\n                                        \"VAEEncodeForInpaint\",\n                                        \"VAEEncodeTiled\",\n                                        \"VAELoader\",\n                                        \"VAESave\",\n                                        \"VPScheduler\",\n                                        \"VideoLinearCFGGuidance\",\n                                        \"unCLIPCheckpointLoader\",\n                                        \"unCLIPConditioning\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/comfyanonymous/ComfyUI_experiments\": [\n                                    [\n                                        \"ModelMergeBlockNumber\",\n                                        \"ModelMergeSDXL\",\n                                        \"ModelMergeSDXLDetailedTransformers\",\n                                        \"ModelMergeSDXLTransformers\",\n                                        \"ModelSamplerTonemapNoiseTest\",\n                                        \"ReferenceOnlySimple\",\n                                        \"RescaleClassifierFreeGuidanceTest\",\n                                        \"TonemapNoiseWithRescaleCFG\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_experiments\"\n                                    }\n                                ],\n                                \"https://github.com/concarne000/ConCarneNode\": [\n                                    [\n                                        \"BingImageGrabber\",\n                                        \"Zephyr\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ConCarneNode\"\n                                    }\n                                ],\n                                \"https://github.com/coreyryanhanson/ComfyQR\": [\n                                    [\n                                        \"comfy-qr-by-image-size\",\n                                        \"comfy-qr-by-module-size\",\n                                        \"comfy-qr-by-module-split\",\n                                        \"comfy-qr-mask_errors\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyQR\"\n                                    }\n                                ],\n                                \"https://github.com/coreyryanhanson/ComfyQR-scanning-nodes\": [\n                                    [\n                                        \"comfy-qr-read\",\n                                        \"comfy-qr-validate\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyQR-scanning-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/cubiq/ComfyUI_IPAdapter_plus\": [\n                                    [\n                                        \"IPAdapterApply\",\n                                        \"IPAdapterApplyEncoded\",\n                                        \"IPAdapterApplyFaceID\",\n                                        \"IPAdapterBatchEmbeds\",\n                                        \"IPAdapterEncoder\",\n                                        \"IPAdapterLoadEmbeds\",\n                                        \"IPAdapterModelLoader\",\n                                        \"IPAdapterSaveEmbeds\",\n                                        \"IPAdapterTilesMasked\",\n                                        \"InsightFaceLoader\",\n                                        \"PrepImageForClipVision\",\n                                        \"PrepImageForInsightFace\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_IPAdapter_plus\"\n                                    }\n                                ],\n                                \"https://github.com/cubiq/ComfyUI_InstantID\": [\n                                    [\n                                        \"ApplyInstantID\",\n                                        \"FaceKeypointsPreprocessor\",\n                                        \"InstantIDFaceAnalysis\",\n                                        \"InstantIDModelLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI InstantID (Native Support)\"\n                                    }\n                                ],\n                                \"https://github.com/cubiq/ComfyUI_SimpleMath\": [\n                                    [\n                                        \"SimpleMath\",\n                                        \"SimpleMathDebug\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Simple Math\"\n                                    }\n                                ],\n                                \"https://github.com/cubiq/ComfyUI_essentials\": [\n                                    [\n                                        \"BatchCount+\",\n                                        \"CLIPTextEncodeSDXL+\",\n                                        \"ConsoleDebug+\",\n                                        \"DebugTensorShape+\",\n                                        \"DrawText+\",\n                                        \"ExtractKeyframes+\",\n                                        \"GetImageSize+\",\n                                        \"ImageApplyLUT+\",\n                                        \"ImageCASharpening+\",\n                                        \"ImageCompositeFromMaskBatch+\",\n                                        \"ImageCrop+\",\n                                        \"ImageDesaturate+\",\n                                        \"ImageEnhanceDifference+\",\n                                        \"ImageExpandBatch+\",\n                                        \"ImageFlip+\",\n                                        \"ImageFromBatch+\",\n                                        \"ImagePosterize+\",\n                                        \"ImageRemoveBackground+\",\n                                        \"ImageResize+\",\n                                        \"ImageSeamCarving+\",\n                                        \"KSamplerVariationsStochastic+\",\n                                        \"KSamplerVariationsWithNoise+\",\n                                        \"MaskBatch+\",\n                                        \"MaskBlur+\",\n                                        \"MaskExpandBatch+\",\n                                        \"MaskFlip+\",\n                                        \"MaskFromBatch+\",\n                                        \"MaskFromColor+\",\n                                        \"MaskPreview+\",\n                                        \"ModelCompile+\",\n                                        \"RemBGSession+\",\n                                        \"SDXLEmptyLatentSizePicker+\",\n                                        \"SimpleMath+\",\n                                        \"TransitionMask+\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Essentials\"\n                                    }\n                                ],\n                                \"https://github.com/dagthomas/comfyui_dagthomas\": [\n                                    [\n                                        \"CSL\",\n                                        \"CSVPromptGenerator\",\n                                        \"PromptGenerator\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SDXL Auto Prompter\"\n                                    }\n                                ],\n                                \"https://github.com/daniel-lewis-ab/ComfyUI-Llama\": [\n                                    [\n                                        \"Call LLM Advanced\",\n                                        \"Call LLM Basic\",\n                                        \"LLM_Create_Completion Advanced\",\n                                        \"LLM_Detokenize\",\n                                        \"LLM_Embed\",\n                                        \"LLM_Eval\",\n                                        \"LLM_Load_State\",\n                                        \"LLM_Reset\",\n                                        \"LLM_Sample\",\n                                        \"LLM_Save_State\",\n                                        \"LLM_Token_BOS\",\n                                        \"LLM_Token_EOS\",\n                                        \"LLM_Tokenize\",\n                                        \"Load LLM Model Advanced\",\n                                        \"Load LLM Model Basic\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Llama\"\n                                    }\n                                ],\n                                \"https://github.com/daniel-lewis-ab/ComfyUI-TTS\": [\n                                    [\n                                        \"Load_Piper_Model\",\n                                        \"Piper_Speak_Text\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-TTS\"\n                                    }\n                                ],\n                                \"https://github.com/darkpixel/darkprompts\": [\n                                    [\n                                        \"DarkCombine\",\n                                        \"DarkFaceIndexShuffle\",\n                                        \"DarkLoRALoader\",\n                                        \"DarkPrompt\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"DarkPrompts\"\n                                    }\n                                ],\n                                \"https://github.com/davask/ComfyUI-MarasIT-Nodes\": [\n                                    [\n                                        \"MarasitBusNode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"MarasIT Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/dave-palt/comfyui_DSP_imagehelpers\": [\n                                    [\n                                        \"dsp-imagehelpers-concat\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui_DSP_imagehelpers\"\n                                    }\n                                ],\n                                \"https://github.com/dawangraoming/ComfyUI_ksampler_gpu/raw/main/ksampler_gpu.py\": [\n                                    [\n                                        \"KSamplerAdvancedGPU\",\n                                        \"KSamplerGPU\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"KSampler GPU\"\n                                    }\n                                ],\n                                \"https://github.com/daxthin/DZ-FaceDetailer\": [\n                                    [\n                                        \"DZ_Face_Detailer\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"DZ-FaceDetailer\"\n                                    }\n                                ],\n                                \"https://github.com/deroberon/StableZero123-comfyui\": [\n                                    [\n                                        \"SDZero ImageSplit\",\n                                        \"Stablezero123\",\n                                        \"Stablezero123WithDepth\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"StableZero123-comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/deroberon/demofusion-comfyui\": [\n                                    [\n                                        \"Batch Unsampler\",\n                                        \"Demofusion\",\n                                        \"Demofusion From Single File\",\n                                        \"Iterative Mixing KSampler\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"demofusion-comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/dfl/comfyui-clip-with-break\": [\n                                    [\n                                        \"AdvancedCLIPTextEncodeWithBreak\",\n                                        \"CLIPTextEncodeWithBreak\"\n                                    ],\n                                    {\n                                        \"author\": \"dfl\",\n                                        \"description\": \"CLIP text encoder that does BREAK prompting like A1111\",\n                                        \"nickname\": \"CLIP with BREAK\",\n                                        \"title\": \"CLIP with BREAK syntax\",\n                                        \"title_aux\": \"comfyui-clip-with-break\"\n                                    }\n                                ],\n                                \"https://github.com/digitaljohn/comfyui-propost\": [\n                                    [\n                                        \"ProPostApplyLUT\",\n                                        \"ProPostDepthMapBlur\",\n                                        \"ProPostFilmGrain\",\n                                        \"ProPostRadialBlur\",\n                                        \"ProPostVignette\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-ProPost\"\n                                    }\n                                ],\n                                \"https://github.com/dimtoneff/ComfyUI-PixelArt-Detector\": [\n                                    [\n                                        \"PixelArtAddDitherPattern\",\n                                        \"PixelArtDetectorConverter\",\n                                        \"PixelArtDetectorSave\",\n                                        \"PixelArtDetectorToImage\",\n                                        \"PixelArtLoadPalettes\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI PixelArt Detector\"\n                                    }\n                                ],\n                                \"https://github.com/diontimmer/ComfyUI-Vextra-Nodes\": [\n                                    [\n                                        \"Add Text To Image\",\n                                        \"Apply Instagram Filter\",\n                                        \"Create Solid Color\",\n                                        \"Flatten Colors\",\n                                        \"Generate Noise Image\",\n                                        \"GlitchThis Effect\",\n                                        \"Hue Rotation\",\n                                        \"Load Picture Index\",\n                                        \"Pixel Sort\",\n                                        \"Play Sound At Execution\",\n                                        \"Prettify Prompt Using distilgpt2\",\n                                        \"Swap Color Mode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Vextra-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/djbielejeski/a-person-mask-generator\": [\n                                    [\n                                        \"APersonMaskGenerator\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"a-person-mask-generator\"\n                                    }\n                                ],\n                                \"https://github.com/dmarx/ComfyUI-AudioReactive\": [\n                                    [\n                                        \"OpAbs\",\n                                        \"OpBandpass\",\n                                        \"OpClamp\",\n                                        \"OpHarmonic\",\n                                        \"OpModulo\",\n                                        \"OpNormalize\",\n                                        \"OpNovelty\",\n                                        \"OpPercussive\",\n                                        \"OpPow\",\n                                        \"OpPow2\",\n                                        \"OpPredominant_pulse\",\n                                        \"OpQuantize\",\n                                        \"OpRms\",\n                                        \"OpSmoosh\",\n                                        \"OpSmooth\",\n                                        \"OpSqrt\",\n                                        \"OpStretch\",\n                                        \"OpSustain\",\n                                        \"OpThreshold\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-AudioReactive\"\n                                    }\n                                ],\n                                \"https://github.com/dmarx/ComfyUI-Keyframed\": [\n                                    [\n                                        \"Example\",\n                                        \"KfAddCurveToPGroup\",\n                                        \"KfAddCurveToPGroupx10\",\n                                        \"KfApplyCurveToCond\",\n                                        \"KfConditioningAdd\",\n                                        \"KfConditioningAddx10\",\n                                        \"KfCurveConstant\",\n                                        \"KfCurveDraw\",\n                                        \"KfCurveFromString\",\n                                        \"KfCurveFromYAML\",\n                                        \"KfCurveInverse\",\n                                        \"KfCurveToAcnLatentKeyframe\",\n                                        \"KfCurvesAdd\",\n                                        \"KfCurvesAddx10\",\n                                        \"KfCurvesDivide\",\n                                        \"KfCurvesMultiply\",\n                                        \"KfCurvesMultiplyx10\",\n                                        \"KfCurvesSubtract\",\n                                        \"KfDebug_Clip\",\n                                        \"KfDebug_Cond\",\n                                        \"KfDebug_Curve\",\n                                        \"KfDebug_Float\",\n                                        \"KfDebug_Image\",\n                                        \"KfDebug_Int\",\n                                        \"KfDebug_Latent\",\n                                        \"KfDebug_Model\",\n                                        \"KfDebug_Passthrough\",\n                                        \"KfDebug_Segs\",\n                                        \"KfDebug_String\",\n                                        \"KfDebug_Vae\",\n                                        \"KfDrawSchedule\",\n                                        \"KfEvaluateCurveAtT\",\n                                        \"KfGetCurveFromPGroup\",\n                                        \"KfGetScheduleConditionAtTime\",\n                                        \"KfGetScheduleConditionSlice\",\n                                        \"KfKeyframedCondition\",\n                                        \"KfKeyframedConditionWithText\",\n                                        \"KfPGroupCurveAdd\",\n                                        \"KfPGroupCurveMultiply\",\n                                        \"KfPGroupDraw\",\n                                        \"KfPGroupProd\",\n                                        \"KfPGroupSum\",\n                                        \"KfSetCurveLabel\",\n                                        \"KfSetKeyframe\",\n                                        \"KfSinusoidalAdjustAmplitude\",\n                                        \"KfSinusoidalAdjustFrequency\",\n                                        \"KfSinusoidalAdjustPhase\",\n                                        \"KfSinusoidalAdjustWavelength\",\n                                        \"KfSinusoidalEntangledZeroOneFromFrequencyx2\",\n                                        \"KfSinusoidalEntangledZeroOneFromFrequencyx3\",\n                                        \"KfSinusoidalEntangledZeroOneFromFrequencyx4\",\n                                        \"KfSinusoidalEntangledZeroOneFromFrequencyx5\",\n                                        \"KfSinusoidalEntangledZeroOneFromFrequencyx6\",\n                                        \"KfSinusoidalEntangledZeroOneFromFrequencyx7\",\n                                        \"KfSinusoidalEntangledZeroOneFromFrequencyx8\",\n                                        \"KfSinusoidalEntangledZeroOneFromFrequencyx9\",\n                                        \"KfSinusoidalEntangledZeroOneFromWavelengthx2\",\n                                        \"KfSinusoidalEntangledZeroOneFromWavelengthx3\",\n                                        \"KfSinusoidalEntangledZeroOneFromWavelengthx4\",\n                                        \"KfSinusoidalEntangledZeroOneFromWavelengthx5\",\n                                        \"KfSinusoidalEntangledZeroOneFromWavelengthx6\",\n                                        \"KfSinusoidalEntangledZeroOneFromWavelengthx7\",\n                                        \"KfSinusoidalEntangledZeroOneFromWavelengthx8\",\n                                        \"KfSinusoidalEntangledZeroOneFromWavelengthx9\",\n                                        \"KfSinusoidalGetAmplitude\",\n                                        \"KfSinusoidalGetFrequency\",\n                                        \"KfSinusoidalGetPhase\",\n                                        \"KfSinusoidalGetWavelength\",\n                                        \"KfSinusoidalWithFrequency\",\n                                        \"KfSinusoidalWithWavelength\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Keyframed\"\n                                    }\n                                ],\n                                \"https://github.com/drago87/ComfyUI_Dragos_Nodes\": [\n                                    [\n                                        \"file_padding\",\n                                        \"image_info\",\n                                        \"lora_loader\",\n                                        \"vae_loader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_Dragos_Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/drustan-hawk/primitive-types\": [\n                                    [\n                                        \"float\",\n                                        \"int\",\n                                        \"string\",\n                                        \"string_multiline\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"primitive-types\"\n                                    }\n                                ],\n                                \"https://github.com/ealkanat/comfyui_easy_padding\": [\n                                    [\n                                        \"comfyui-easy-padding\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Easy Padding\"\n                                    }\n                                ],\n                                \"https://github.com/edenartlab/eden_comfy_pipelines\": [\n                                    [\n                                        \"CLIP_Interrogator\",\n                                        \"Eden_Bool\",\n                                        \"Eden_Compare\",\n                                        \"Eden_DebugPrint\",\n                                        \"Eden_Float\",\n                                        \"Eden_Int\",\n                                        \"Eden_String\",\n                                        \"Filepicker\",\n                                        \"IMG_blender\",\n                                        \"IMG_padder\",\n                                        \"IMG_scaler\",\n                                        \"IMG_unpadder\",\n                                        \"If ANY execute A else B\",\n                                        \"LatentTypeConversion\",\n                                        \"SaveImageAdvanced\",\n                                        \"VAEDecode_to_folder\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"eden_comfy_pipelines\"\n                                    }\n                                ],\n                                \"https://github.com/evanspearman/ComfyMath\": [\n                                    [\n                                        \"CM_BoolBinaryOperation\",\n                                        \"CM_BoolToInt\",\n                                        \"CM_BoolUnaryOperation\",\n                                        \"CM_BreakoutVec2\",\n                                        \"CM_BreakoutVec3\",\n                                        \"CM_BreakoutVec4\",\n                                        \"CM_ComposeVec2\",\n                                        \"CM_ComposeVec3\",\n                                        \"CM_ComposeVec4\",\n                                        \"CM_FloatBinaryCondition\",\n                                        \"CM_FloatBinaryOperation\",\n                                        \"CM_FloatToInt\",\n                                        \"CM_FloatToNumber\",\n                                        \"CM_FloatUnaryCondition\",\n                                        \"CM_FloatUnaryOperation\",\n                                        \"CM_IntBinaryCondition\",\n                                        \"CM_IntBinaryOperation\",\n                                        \"CM_IntToBool\",\n                                        \"CM_IntToFloat\",\n                                        \"CM_IntToNumber\",\n                                        \"CM_IntUnaryCondition\",\n                                        \"CM_IntUnaryOperation\",\n                                        \"CM_NearestSDXLResolution\",\n                                        \"CM_NumberBinaryCondition\",\n                                        \"CM_NumberBinaryOperation\",\n                                        \"CM_NumberToFloat\",\n                                        \"CM_NumberToInt\",\n                                        \"CM_NumberUnaryCondition\",\n                                        \"CM_NumberUnaryOperation\",\n                                        \"CM_SDXLResolution\",\n                                        \"CM_Vec2BinaryCondition\",\n                                        \"CM_Vec2BinaryOperation\",\n                                        \"CM_Vec2ScalarOperation\",\n                                        \"CM_Vec2ToScalarBinaryOperation\",\n                                        \"CM_Vec2ToScalarUnaryOperation\",\n                                        \"CM_Vec2UnaryCondition\",\n                                        \"CM_Vec2UnaryOperation\",\n                                        \"CM_Vec3BinaryCondition\",\n                                        \"CM_Vec3BinaryOperation\",\n                                        \"CM_Vec3ScalarOperation\",\n                                        \"CM_Vec3ToScalarBinaryOperation\",\n                                        \"CM_Vec3ToScalarUnaryOperation\",\n                                        \"CM_Vec3UnaryCondition\",\n                                        \"CM_Vec3UnaryOperation\",\n                                        \"CM_Vec4BinaryCondition\",\n                                        \"CM_Vec4BinaryOperation\",\n                                        \"CM_Vec4ScalarOperation\",\n                                        \"CM_Vec4ToScalarBinaryOperation\",\n                                        \"CM_Vec4ToScalarUnaryOperation\",\n                                        \"CM_Vec4UnaryCondition\",\n                                        \"CM_Vec4UnaryOperation\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyMath\"\n                                    }\n                                ],\n                                \"https://github.com/fearnworks/ComfyUI_FearnworksNodes/raw/main/fw_nodes.py\": [\n                                    [\n                                        \"Count Files in Directory (FW)\",\n                                        \"Count Tokens (FW)\",\n                                        \"Token Count Ranker(FW)\",\n                                        \"Trim To Tokens (FW)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Fearnworks Custom Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/fexli/fexli-util-node-comfyui\": [\n                                    [\n                                        \"FEBCPrompt\",\n                                        \"FEBatchGenStringBCDocker\",\n                                        \"FEColor2Image\",\n                                        \"FEColorOut\",\n                                        \"FEDataInsertor\",\n                                        \"FEDataPacker\",\n                                        \"FEDataUnpacker\",\n                                        \"FEDeepClone\",\n                                        \"FEDictPacker\",\n                                        \"FEDictUnpacker\",\n                                        \"FEEncLoraLoader\",\n                                        \"FEExtraInfoAdd\",\n                                        \"FEGenStringBCDocker\",\n                                        \"FEGenStringGPT\",\n                                        \"FEImageNoiseGenerate\",\n                                        \"FEImagePadForOutpaint\",\n                                        \"FEImagePadForOutpaintByImage\",\n                                        \"FEOperatorIf\",\n                                        \"FEPythonStrOp\",\n                                        \"FERandomLoraSelect\",\n                                        \"FERandomPrompt\",\n                                        \"FERandomizedColor2Image\",\n                                        \"FERandomizedColorOut\",\n                                        \"FERerouteWithName\",\n                                        \"FESaveEncryptImage\",\n                                        \"FETextCombine\",\n                                        \"FETextInput\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"fexli-util-node-comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/filipemeneses/comfy_pixelization\": [\n                                    [\n                                        \"Pixelization\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Pixelization\"\n                                    }\n                                ],\n                                \"https://github.com/filliptm/ComfyUI_Fill-Nodes\": [\n                                    [\n                                        \"FL_ImageCaptionSaver\",\n                                        \"FL_ImageRandomizer\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_Fill-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/fitCorder/fcSuite/raw/main/fcSuite.py\": [\n                                    [\n                                        \"fcFloat\",\n                                        \"fcFloatMatic\",\n                                        \"fcHex\",\n                                        \"fcInteger\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"fcSuite\"\n                                    }\n                                ],\n                                \"https://github.com/florestefano1975/comfyui-portrait-master\": [\n                                    [\n                                        \"PortraitMaster\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-portrait-master\"\n                                    }\n                                ],\n                                \"https://github.com/florestefano1975/comfyui-prompt-composer\": [\n                                    [\n                                        \"PromptComposerCustomLists\",\n                                        \"PromptComposerEffect\",\n                                        \"PromptComposerGrouping\",\n                                        \"PromptComposerMerge\",\n                                        \"PromptComposerStyler\",\n                                        \"PromptComposerTextSingle\",\n                                        \"promptComposerTextMultiple\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-prompt-composer\"\n                                    }\n                                ],\n                                \"https://github.com/flowtyone/ComfyUI-Flowty-LDSR\": [\n                                    [\n                                        \"LDSRModelLoader\",\n                                        \"LDSRUpscale\",\n                                        \"LDSRUpscaler\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Flowty-LDSR\"\n                                    }\n                                ],\n                                \"https://github.com/flyingshutter/As_ComfyUI_CustomNodes\": [\n                                    [\n                                        \"BatchIndex_AS\",\n                                        \"CropImage_AS\",\n                                        \"ImageMixMasked_As\",\n                                        \"ImageToMask_AS\",\n                                        \"Increment_AS\",\n                                        \"Int2Any_AS\",\n                                        \"LatentAdd_AS\",\n                                        \"LatentMixMasked_As\",\n                                        \"LatentMix_AS\",\n                                        \"LatentToImages_AS\",\n                                        \"LoadLatent_AS\",\n                                        \"MapRange_AS\",\n                                        \"MaskToImage_AS\",\n                                        \"Math_AS\",\n                                        \"NoiseImage_AS\",\n                                        \"Number2Float_AS\",\n                                        \"Number2Int_AS\",\n                                        \"Number_AS\",\n                                        \"SaveLatent_AS\",\n                                        \"TextToImage_AS\",\n                                        \"TextWildcardList_AS\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"As_ComfyUI_CustomNodes\"\n                                    }\n                                ],\n                                \"https://github.com/foxtrot-roger/comfyui-rf-nodes\": [\n                                    [\n                                        \"LogBool\",\n                                        \"LogFloat\",\n                                        \"LogInt\",\n                                        \"LogNumber\",\n                                        \"LogString\",\n                                        \"LogVec2\",\n                                        \"LogVec3\",\n                                        \"RF_AtIndexString\",\n                                        \"RF_BoolToString\",\n                                        \"RF_FloatToString\",\n                                        \"RF_IntToString\",\n                                        \"RF_JsonStyleLoader\",\n                                        \"RF_MergeLines\",\n                                        \"RF_NumberToString\",\n                                        \"RF_OptionsString\",\n                                        \"RF_RangeFloat\",\n                                        \"RF_RangeInt\",\n                                        \"RF_RangeNumber\",\n                                        \"RF_SavePromptInfo\",\n                                        \"RF_SplitLines\",\n                                        \"RF_TextConcatenate\",\n                                        \"RF_TextInput\",\n                                        \"RF_TextReplace\",\n                                        \"RF_Timestamp\",\n                                        \"RF_ToString\",\n                                        \"RF_Vec2ToString\",\n                                        \"RF_Vec3ToString\",\n                                        \"TextLine\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"RF Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/gemell1/ComfyUI_GMIC\": [\n                                    [\n                                        \"GmicCliWrapper\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_GMIC\"\n                                    }\n                                ],\n                                \"https://github.com/giriss/comfy-image-saver\": [\n                                    [\n                                        \"Cfg Literal\",\n                                        \"Checkpoint Selector\",\n                                        \"Int Literal\",\n                                        \"Sampler Selector\",\n                                        \"Save Image w/Metadata\",\n                                        \"Scheduler Selector\",\n                                        \"Seed Generator\",\n                                        \"String Literal\",\n                                        \"Width/Height Literal\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Save Image with Generation Metadata\"\n                                    }\n                                ],\n                                \"https://github.com/glibsonoran/Plush-for-ComfyUI\": [\n                                    [\n                                        \"DalleImage\",\n                                        \"Enhancer\",\n                                        \"ImgTextSwitch\",\n                                        \"Plush-Exif Wrangler\",\n                                        \"mulTextSwitch\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Plush-for-ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/glifxyz/ComfyUI-GlifNodes\": [\n                                    [\n                                        \"GlifConsistencyDecoder\",\n                                        \"GlifPatchConsistencyDecoderTiled\",\n                                        \"SDXLAspectRatio\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-GlifNodes\"\n                                    }\n                                ],\n                                \"https://github.com/glowcone/comfyui-base64-to-image\": [\n                                    [\n                                        \"LoadImageFromBase64\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Load Image From Base64 URI\"\n                                    }\n                                ],\n                                \"https://github.com/godspede/ComfyUI_Substring\": [\n                                    [\n                                        \"SubstringTheory\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Substring\"\n                                    }\n                                ],\n                                \"https://github.com/gokayfem/ComfyUI_VLM_nodes\": [\n                                    [\n                                        \"Joytag\",\n                                        \"JsonToText\",\n                                        \"KeywordExtraction\",\n                                        \"LLMLoader\",\n                                        \"LLMPromptGenerator\",\n                                        \"LLMSampler\",\n                                        \"LLava Loader Simple\",\n                                        \"LLavaPromptGenerator\",\n                                        \"LLavaSamplerAdvanced\",\n                                        \"LLavaSamplerSimple\",\n                                        \"LlavaClipLoader\",\n                                        \"MoonDream\",\n                                        \"PromptGenerateAPI\",\n                                        \"SimpleText\",\n                                        \"Suggester\",\n                                        \"ViewText\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"VLM_nodes\"\n                                    }\n                                ],\n                                \"https://github.com/guoyk93/yk-node-suite-comfyui\": [\n                                    [\n                                        \"YKImagePadForOutpaint\",\n                                        \"YKMaskToImage\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"y.k.'s ComfyUI node suite\"\n                                    }\n                                ],\n                                \"https://github.com/hhhzzyang/Comfyui_Lama\": [\n                                    [\n                                        \"LamaApply\",\n                                        \"LamaModelLoader\",\n                                        \"YamlConfigLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfyui-Lama\"\n                                    }\n                                ],\n                                \"https://github.com/hinablue/ComfyUI_3dPoseEditor\": [\n                                    [\n                                        \"Hina.PoseEditor3D\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI 3D Pose Editor\"\n                                    }\n                                ],\n                                \"https://github.com/hustille/ComfyUI_Fooocus_KSampler\": [\n                                    [\n                                        \"KSampler With Refiner (Fooocus)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_Fooocus_KSampler\"\n                                    }\n                                ],\n                                \"https://github.com/hustille/ComfyUI_hus_utils\": [\n                                    [\n                                        \"3way Prompt Styler\",\n                                        \"Batch State\",\n                                        \"Date Time Format\",\n                                        \"Debug Extra\",\n                                        \"Fetch widget value\",\n                                        \"Text Hash\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"hus' utils for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/hylarucoder/ComfyUI-Eagle-PNGInfo\": [\n                                    [\n                                        \"EagleImageNode\",\n                                        \"SDXLPromptStyler\",\n                                        \"SDXLPromptStylerAdvanced\",\n                                        \"SDXLResolutionPresets\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Eagle PNGInfo\"\n                                    }\n                                ],\n                                \"https://github.com/idrirap/ComfyUI-Lora-Auto-Trigger-Words\": [\n                                    [\n                                        \"FusionText\",\n                                        \"LoraListNames\",\n                                        \"LoraLoaderAdvanced\",\n                                        \"LoraLoaderStackedAdvanced\",\n                                        \"LoraLoaderStackedVanilla\",\n                                        \"LoraLoaderVanilla\",\n                                        \"LoraTagsOnly\",\n                                        \"Randomizer\",\n                                        \"TagsFormater\",\n                                        \"TagsSelector\",\n                                        \"TextInputBasic\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Lora-Auto-Trigger-Words\"\n                                    }\n                                ],\n                                \"https://github.com/imb101/ComfyUI-FaceSwap\": [\n                                    [\n                                        \"FaceSwapNode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"FaceSwap\"\n                                    }\n                                ],\n                                \"https://github.com/jags111/ComfyUI_Jags_Audiotools\": [\n                                    [\n                                        \"BatchJoinAudio\",\n                                        \"BatchToList\",\n                                        \"BitCrushAudioFX\",\n                                        \"BulkVariation\",\n                                        \"ChorusAudioFX\",\n                                        \"ClippingAudioFX\",\n                                        \"CompressorAudioFX\",\n                                        \"ConcatAudioList\",\n                                        \"ConvolutionAudioFX\",\n                                        \"CutAudio\",\n                                        \"DelayAudioFX\",\n                                        \"DistortionAudioFX\",\n                                        \"DuplicateAudio\",\n                                        \"GainAudioFX\",\n                                        \"GenerateAudioSample\",\n                                        \"GenerateAudioWave\",\n                                        \"GetAudioFromFolderIndex\",\n                                        \"GetSingle\",\n                                        \"GetStringByIndex\",\n                                        \"HighShelfFilter\",\n                                        \"HighpassFilter\",\n                                        \"ImageToSpectral\",\n                                        \"InvertAudioFX\",\n                                        \"JoinAudio\",\n                                        \"LadderFilter\",\n                                        \"LimiterAudioFX\",\n                                        \"ListToBatch\",\n                                        \"LoadAudioDir\",\n                                        \"LoadAudioFile\",\n                                        \"LoadAudioModel (DD)\",\n                                        \"LoadVST3\",\n                                        \"LowShelfFilter\",\n                                        \"LowpassFilter\",\n                                        \"MP3CompressorAudioFX\",\n                                        \"MixAudioTensors\",\n                                        \"NoiseGateAudioFX\",\n                                        \"OTTAudioFX\",\n                                        \"PeakFilter\",\n                                        \"PhaserEffectAudioFX\",\n                                        \"PitchShiftAudioFX\",\n                                        \"PlotSpectrogram\",\n                                        \"PreviewAudioFile\",\n                                        \"PreviewAudioTensor\",\n                                        \"ResampleAudio\",\n                                        \"ReverbAudioFX\",\n                                        \"ReverseAudio\",\n                                        \"SaveAudioTensor\",\n                                        \"SequenceVariation\",\n                                        \"SliceAudio\",\n                                        \"SoundPlayer\",\n                                        \"StretchAudio\",\n                                        \"samplerate\"\n                                    ],\n                                    {\n                                        \"author\": \"jags111\",\n                                        \"description\": \"This extension offers various audio generation tools\",\n                                        \"nickname\": \"Audiotools\",\n                                        \"title\": \"Jags_Audiotools\",\n                                        \"title_aux\": \"ComfyUI_Jags_Audiotools\"\n                                    }\n                                ],\n                                \"https://github.com/jags111/ComfyUI_Jags_VectorMagic\": [\n                                    [\n                                        \"CircularVAEDecode\",\n                                        \"JagsCLIPSeg\",\n                                        \"JagsClipseg\",\n                                        \"JagsCombineMasks\",\n                                        \"SVG\",\n                                        \"YoloSEGdetectionNode\",\n                                        \"YoloSegNode\",\n                                        \"color_drop\",\n                                        \"my unique name\",\n                                        \"xy_Tiling_KSampler\"\n                                    ],\n                                    {\n                                        \"author\": \"jags111\",\n                                        \"description\": \"This extension offers various vector manipulation and  generation tools\",\n                                        \"nickname\": \"Jags_VectorMagic\",\n                                        \"title\": \"Jags_VectorMagic\",\n                                        \"title_aux\": \"ComfyUI_Jags_VectorMagic\"\n                                    }\n                                ],\n                                \"https://github.com/jags111/efficiency-nodes-comfyui\": [\n                                    [\n                                        \"AnimateDiff Script\",\n                                        \"Apply ControlNet Stack\",\n                                        \"Control Net Stacker\",\n                                        \"Eff. Loader SDXL\",\n                                        \"Efficient Loader\",\n                                        \"HighRes-Fix Script\",\n                                        \"Image Overlay\",\n                                        \"Join XY Inputs of Same Type\",\n                                        \"KSampler (Efficient)\",\n                                        \"KSampler Adv. (Efficient)\",\n                                        \"KSampler SDXL (Eff.)\",\n                                        \"LatentUpscaler\",\n                                        \"LoRA Stack to String converter\",\n                                        \"LoRA Stacker\",\n                                        \"Manual XY Entry Info\",\n                                        \"NNLatentUpscale\",\n                                        \"Noise Control Script\",\n                                        \"Pack SDXL Tuple\",\n                                        \"Tiled Upscaler Script\",\n                                        \"Unpack SDXL Tuple\",\n                                        \"XY Input: Add/Return Noise\",\n                                        \"XY Input: Aesthetic Score\",\n                                        \"XY Input: CFG Scale\",\n                                        \"XY Input: Checkpoint\",\n                                        \"XY Input: Clip Skip\",\n                                        \"XY Input: Control Net\",\n                                        \"XY Input: Control Net Plot\",\n                                        \"XY Input: Denoise\",\n                                        \"XY Input: LoRA\",\n                                        \"XY Input: LoRA Plot\",\n                                        \"XY Input: LoRA Stacks\",\n                                        \"XY Input: Manual XY Entry\",\n                                        \"XY Input: Prompt S/R\",\n                                        \"XY Input: Refiner On/Off\",\n                                        \"XY Input: Sampler/Scheduler\",\n                                        \"XY Input: Seeds++ Batch\",\n                                        \"XY Input: Steps\",\n                                        \"XY Input: VAE\",\n                                        \"XY Plot\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Efficiency Nodes for ComfyUI Version 2.0+\"\n                                    }\n                                ],\n                                \"https://github.com/jamal-alkharrat/ComfyUI_rotate_image\": [\n                                    [\n                                        \"RotateImage\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_rotate_image\"\n                                    }\n                                ],\n                                \"https://github.com/jamesWalker55/comfyui-various\": [\n                                    [],\n                                    {\n                                        \"nodename_pattern\": \"^JW\",\n                                        \"title_aux\": \"Various ComfyUI Nodes by Type\"\n                                    }\n                                ],\n                                \"https://github.com/jesenzhang/ComfyUI_StreamDiffusion\": [\n                                    [\n                                        \"StreamDiffusion_Loader\",\n                                        \"StreamDiffusion_Sampler\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_StreamDiffusion\"\n                                    }\n                                ],\n                                \"https://github.com/jitcoder/lora-info\": [\n                                    [\n                                        \"ImageFromURL\",\n                                        \"LoraInfo\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"LoraInfo\"\n                                    }\n                                ],\n                                \"https://github.com/jjkramhoeft/ComfyUI-Jjk-Nodes\": [\n                                    [\n                                        \"JjkConcat\",\n                                        \"JjkShowText\",\n                                        \"JjkText\",\n                                        \"SDXLRecommendedImageSize\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Jjk-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/jojkaart/ComfyUI-sampler-lcm-alternative\": [\n                                    [\n                                        \"LCMScheduler\",\n                                        \"SamplerLCMAlternative\",\n                                        \"SamplerLCMCycle\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-sampler-lcm-alternative\"\n                                    }\n                                ],\n                                \"https://github.com/jordoh/ComfyUI-Deepface\": [\n                                    [\n                                        \"DeepfaceExtractFaces\",\n                                        \"DeepfaceVerify\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Deepface\"\n                                    }\n                                ],\n                                \"https://github.com/jtrue/ComfyUI-JaRue\": [\n                                    [\n                                        \"Text2Image_jru\",\n                                        \"YouTube2Prompt_jru\"\n                                    ],\n                                    {\n                                        \"nodename_pattern\": \"_jru$\",\n                                        \"title_aux\": \"ComfyUI-JaRue\"\n                                    }\n                                ],\n                                \"https://github.com/ka-puna/comfyui-yanc\": [\n                                    [\n                                        \"YANC.ConcatStrings\",\n                                        \"YANC.FormatDatetimeString\",\n                                        \"YANC.GetWidgetValueString\",\n                                        \"YANC.IntegerCaster\",\n                                        \"YANC.MultilineString\",\n                                        \"YANC.TruncateString\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-yanc\"\n                                    }\n                                ],\n                                \"https://github.com/kadirnar/ComfyUI-Transformers\": [\n                                    [\n                                        \"DepthEstimationPipeline\",\n                                        \"ImageClassificationPipeline\",\n                                        \"ImageSegmentationPipeline\",\n                                        \"ObjectDetectionPipeline\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Transformers\"\n                                    }\n                                ],\n                                \"https://github.com/kenjiqq/qq-nodes-comfyui\": [\n                                    [\n                                        \"Any List\",\n                                        \"Axis Pack\",\n                                        \"Axis Unpack\",\n                                        \"Image Accumulator End\",\n                                        \"Image Accumulator Start\",\n                                        \"Load Lines From Text File\",\n                                        \"Slice List\",\n                                        \"Text Splitter\",\n                                        \"XY Grid Helper\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"qq-nodes-comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/kft334/Knodes\": [\n                                    [\n                                        \"Image(s) To Websocket (Base64)\",\n                                        \"ImageOutput\",\n                                        \"Load Image (Base64)\",\n                                        \"Load Images (Base64)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Knodes\"\n                                    }\n                                ],\n                                \"https://github.com/kijai/ComfyUI-CCSR\": [\n                                    [\n                                        \"CCSR_Model_Select\",\n                                        \"CCSR_Upscale\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-CCSR\"\n                                    }\n                                ],\n                                \"https://github.com/kijai/ComfyUI-DDColor\": [\n                                    [\n                                        \"DDColor_Colorize\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-DDColor\"\n                                    }\n                                ],\n                                \"https://github.com/kijai/ComfyUI-DiffusersStableCascade\": [\n                                    [\n                                        \"DiffusersStableCascade\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI StableCascade using diffusers\"\n                                    }\n                                ],\n                                \"https://github.com/kijai/ComfyUI-KJNodes\": [\n                                    [\n                                        \"AddLabel\",\n                                        \"BatchCLIPSeg\",\n                                        \"BatchCropFromMask\",\n                                        \"BatchCropFromMaskAdvanced\",\n                                        \"BatchUncrop\",\n                                        \"BatchUncropAdvanced\",\n                                        \"BboxToInt\",\n                                        \"ColorMatch\",\n                                        \"ColorToMask\",\n                                        \"CondPassThrough\",\n                                        \"ConditioningMultiCombine\",\n                                        \"ConditioningSetMaskAndCombine\",\n                                        \"ConditioningSetMaskAndCombine3\",\n                                        \"ConditioningSetMaskAndCombine4\",\n                                        \"ConditioningSetMaskAndCombine5\",\n                                        \"CreateAudioMask\",\n                                        \"CreateFadeMask\",\n                                        \"CreateFadeMaskAdvanced\",\n                                        \"CreateFluidMask\",\n                                        \"CreateGradientMask\",\n                                        \"CreateMagicMask\",\n                                        \"CreateShapeMask\",\n                                        \"CreateTextMask\",\n                                        \"CreateVoronoiMask\",\n                                        \"CrossFadeImages\",\n                                        \"DummyLatentOut\",\n                                        \"EmptyLatentImagePresets\",\n                                        \"FilterZeroMasksAndCorrespondingImages\",\n                                        \"FlipSigmasAdjusted\",\n                                        \"FloatConstant\",\n                                        \"GLIGENTextBoxApplyBatch\",\n                                        \"GenerateNoise\",\n                                        \"GetImageRangeFromBatch\",\n                                        \"GetImagesFromBatchIndexed\",\n                                        \"GetLatentsFromBatchIndexed\",\n                                        \"GrowMaskWithBlur\",\n                                        \"INTConstant\",\n                                        \"ImageBatchRepeatInterleaving\",\n                                        \"ImageBatchTestPattern\",\n                                        \"ImageConcanate\",\n                                        \"ImageGrabPIL\",\n                                        \"ImageGridComposite2x2\",\n                                        \"ImageGridComposite3x3\",\n                                        \"ImageTransformByNormalizedAmplitude\",\n                                        \"ImageUpscaleWithModelBatched\",\n                                        \"InjectNoiseToLatent\",\n                                        \"InsertImageBatchByIndexes\",\n                                        \"NormalizeLatent\",\n                                        \"NormalizedAmplitudeToMask\",\n                                        \"OffsetMask\",\n                                        \"OffsetMaskByNormalizedAmplitude\",\n                                        \"ReferenceOnlySimple3\",\n                                        \"ReplaceImagesInBatch\",\n                                        \"ResizeMask\",\n                                        \"ReverseImageBatch\",\n                                        \"RoundMask\",\n                                        \"SaveImageWithAlpha\",\n                                        \"ScaleBatchPromptSchedule\",\n                                        \"SomethingToString\",\n                                        \"SoundReactive\",\n                                        \"SplitBboxes\",\n                                        \"StableZero123_BatchSchedule\",\n                                        \"StringConstant\",\n                                        \"VRAM_Debug\",\n                                        \"WidgetToString\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"KJNodes for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/kijai/ComfyUI-Marigold\": [\n                                    [\n                                        \"ColorizeDepthmap\",\n                                        \"MarigoldDepthEstimation\",\n                                        \"RemapDepth\",\n                                        \"SaveImageOpenEXR\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Marigold depth estimation in ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/kijai/ComfyUI-SVD\": [\n                                    [\n                                        \"SVDimg2vid\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-SVD\"\n                                    }\n                                ],\n                                \"https://github.com/kinfolk0117/ComfyUI_GradientDeepShrink\": [\n                                    [\n                                        \"GradientPatchModelAddDownscale\",\n                                        \"GradientPatchModelAddDownscaleAdvanced\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_GradientDeepShrink\"\n                                    }\n                                ],\n                                \"https://github.com/kinfolk0117/ComfyUI_Pilgram\": [\n                                    [\n                                        \"Pilgram\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_Pilgram\"\n                                    }\n                                ],\n                                \"https://github.com/kinfolk0117/ComfyUI_SimpleTiles\": [\n                                    [\n                                        \"DynamicTileMerge\",\n                                        \"DynamicTileSplit\",\n                                        \"TileCalc\",\n                                        \"TileMerge\",\n                                        \"TileSplit\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SimpleTiles\"\n                                    }\n                                ],\n                                \"https://github.com/kinfolk0117/ComfyUI_TiledIPAdapter\": [\n                                    [\n                                        \"TiledIPAdapter\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"TiledIPAdapter\"\n                                    }\n                                ],\n                                \"https://github.com/knuknX/ComfyUI-Image-Tools\": [\n                                    [\n                                        \"BatchImagePathLoader\",\n                                        \"ImageBgRemoveProcessor\",\n                                        \"ImageCheveretoUploader\",\n                                        \"ImageStandardResizeProcessor\",\n                                        \"JSONMessageNotifyTool\",\n                                        \"PreviewJSONNode\",\n                                        \"SingleImagePathLoader\",\n                                        \"SingleImageUrlLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Image-Tools\"\n                                    }\n                                ],\n                                \"https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI\": [\n                                    [\n                                        \"LLLiteLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ControlNet-LLLite-ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/komojini/ComfyUI_SDXL_DreamBooth_LoRA_CustomNodes\": [\n                                    [\n                                        \"S3 Bucket LoRA\",\n                                        \"S3Bucket_Load_LoRA\",\n                                        \"XL DreamBooth LoRA\",\n                                        \"XLDB_LoRA\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_SDXL_DreamBooth_LoRA_CustomNodes\"\n                                    }\n                                ],\n                                \"https://github.com/komojini/komojini-comfyui-nodes\": [\n                                    [\n                                        \"BatchCreativeInterpolationNodeDynamicSettings\",\n                                        \"CachedGetter\",\n                                        \"DragNUWAImageCanvas\",\n                                        \"FlowBuilder\",\n                                        \"FlowBuilder (adv)\",\n                                        \"FlowBuilder (advanced)\",\n                                        \"FlowBuilder (advanced) Setter\",\n                                        \"FlowBuilderSetter\",\n                                        \"FlowBuilderSetter (adv)\",\n                                        \"Getter\",\n                                        \"ImageCropByRatio\",\n                                        \"ImageCropByRatioAndResize\",\n                                        \"ImageGetter\",\n                                        \"ImageMerger\",\n                                        \"ImagesCropByRatioAndResizeBatch\",\n                                        \"KSamplerAdvancedCacheable\",\n                                        \"KSamplerCacheable\",\n                                        \"Setter\",\n                                        \"UltimateVideoLoader\",\n                                        \"UltimateVideoLoader (simple)\",\n                                        \"YouTubeVideoLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"komojini-comfyui-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/kwaroran/abg-comfyui\": [\n                                    [\n                                        \"Remove Image Background (abg)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"abg-comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/laksjdjf/LCMSampler-ComfyUI\": [\n                                    [\n                                        \"SamplerLCM\",\n                                        \"TAESDLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"LCMSampler-ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/laksjdjf/LoRA-Merger-ComfyUI\": [\n                                    [\n                                        \"LoraLoaderFromWeight\",\n                                        \"LoraLoaderWeightOnly\",\n                                        \"LoraMerge\",\n                                        \"LoraSave\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"LoRA-Merger-ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/laksjdjf/attention-couple-ComfyUI\": [\n                                    [\n                                        \"Attention couple\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"attention-couple-ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/laksjdjf/cd-tuner_negpip-ComfyUI\": [\n                                    [\n                                        \"CDTuner\",\n                                        \"Negapip\",\n                                        \"Negpip\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"cd-tuner_negpip-ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/laksjdjf/pfg-ComfyUI\": [\n                                    [\n                                        \"PFG\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"pfg-ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/lilly1987/ComfyUI_node_Lilly\": [\n                                    [\n                                        \"CheckpointLoaderSimpleText\",\n                                        \"LoraLoaderText\",\n                                        \"LoraLoaderTextRandom\",\n                                        \"Random_Sampler\",\n                                        \"VAELoaderDecode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"simple wildcard for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/lldacing/comfyui-easyapi-nodes\": [\n                                    [\n                                        \"Base64ToImage\",\n                                        \"Base64ToMask\",\n                                        \"ImageToBase64\",\n                                        \"ImageToBase64Advanced\",\n                                        \"LoadImageFromURL\",\n                                        \"LoadImageToBase64\",\n                                        \"LoadMaskFromURL\",\n                                        \"MaskImageToBase64\",\n                                        \"MaskToBase64\",\n                                        \"MaskToBase64Image\",\n                                        \"SamAutoMaskSEGS\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-easyapi-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/longgui0318/comfyui-mask-util\": [\n                                    [\n                                        \"Mask Region Info\",\n                                        \"Mask Selection Of Masks\",\n                                        \"Split Masks\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-mask-util\"\n                                    }\n                                ],\n                                \"https://github.com/lordgasmic/ComfyUI-Wildcards/raw/master/wildcards.py\": [\n                                    [\n                                        \"CLIPTextEncodeWithWildcards\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Wildcards\"\n                                    }\n                                ],\n                                \"https://github.com/lrzjason/ComfyUIJasonNode/raw/main/SDXLMixSampler.py\": [\n                                    [\n                                        \"SDXLMixSampler\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUIJasonNode\"\n                                    }\n                                ],\n                                \"https://github.com/ltdrdata/ComfyUI-Impact-Pack\": [\n                                    [\n                                        \"AddMask\",\n                                        \"BasicPipeToDetailerPipe\",\n                                        \"BasicPipeToDetailerPipeSDXL\",\n                                        \"BboxDetectorCombined\",\n                                        \"BboxDetectorCombined_v2\",\n                                        \"BboxDetectorForEach\",\n                                        \"BboxDetectorSEGS\",\n                                        \"BitwiseAndMask\",\n                                        \"BitwiseAndMaskForEach\",\n                                        \"CLIPSegDetectorProvider\",\n                                        \"CfgScheduleHookProvider\",\n                                        \"CombineRegionalPrompts\",\n                                        \"CoreMLDetailerHookProvider\",\n                                        \"DenoiseScheduleHookProvider\",\n                                        \"DenoiseSchedulerDetailerHookProvider\",\n                                        \"DetailerForEach\",\n                                        \"DetailerForEachDebug\",\n                                        \"DetailerForEachDebugPipe\",\n                                        \"DetailerForEachPipe\",\n                                        \"DetailerForEachPipeForAnimateDiff\",\n                                        \"DetailerHookCombine\",\n                                        \"DetailerPipeToBasicPipe\",\n                                        \"EditBasicPipe\",\n                                        \"EditDetailerPipe\",\n                                        \"EditDetailerPipeSDXL\",\n                                        \"EmptySegs\",\n                                        \"FaceDetailer\",\n                                        \"FaceDetailerPipe\",\n                                        \"FromBasicPipe\",\n                                        \"FromBasicPipe_v2\",\n                                        \"FromDetailerPipe\",\n                                        \"FromDetailerPipeSDXL\",\n                                        \"FromDetailerPipe_v2\",\n                                        \"ImageListToImageBatch\",\n                                        \"ImageMaskSwitch\",\n                                        \"ImageReceiver\",\n                                        \"ImageSender\",\n                                        \"ImpactAssembleSEGS\",\n                                        \"ImpactCombineConditionings\",\n                                        \"ImpactCompare\",\n                                        \"ImpactConcatConditionings\",\n                                        \"ImpactConditionalBranch\",\n                                        \"ImpactConditionalBranchSelMode\",\n                                        \"ImpactConditionalStopIteration\",\n                                        \"ImpactControlBridge\",\n                                        \"ImpactControlNetApplyAdvancedSEGS\",\n                                        \"ImpactControlNetApplySEGS\",\n                                        \"ImpactControlNetClearSEGS\",\n                                        \"ImpactConvertDataType\",\n                                        \"ImpactDecomposeSEGS\",\n                                        \"ImpactDilateMask\",\n                                        \"ImpactDilateMaskInSEGS\",\n                                        \"ImpactDilate_Mask_SEG_ELT\",\n                                        \"ImpactDummyInput\",\n                                        \"ImpactEdit_SEG_ELT\",\n                                        \"ImpactFloat\",\n                                        \"ImpactFrom_SEG_ELT\",\n                                        \"ImpactGaussianBlurMask\",\n                                        \"ImpactGaussianBlurMaskInSEGS\",\n                                        \"ImpactHFTransformersClassifierProvider\",\n                                        \"ImpactIfNone\",\n                                        \"ImpactImageBatchToImageList\",\n                                        \"ImpactImageInfo\",\n                                        \"ImpactInt\",\n                                        \"ImpactInversedSwitch\",\n                                        \"ImpactIsNotEmptySEGS\",\n                                        \"ImpactKSamplerAdvancedBasicPipe\",\n                                        \"ImpactKSamplerBasicPipe\",\n                                        \"ImpactLatentInfo\",\n                                        \"ImpactLogger\",\n                                        \"ImpactLogicalOperators\",\n                                        \"ImpactMakeImageBatch\",\n                                        \"ImpactMakeImageList\",\n                                        \"ImpactMakeTileSEGS\",\n                                        \"ImpactMinMax\",\n                                        \"ImpactNeg\",\n                                        \"ImpactNodeSetMuteState\",\n                                        \"ImpactQueueTrigger\",\n                                        \"ImpactQueueTriggerCountdown\",\n                                        \"ImpactRemoteBoolean\",\n                                        \"ImpactRemoteInt\",\n                                        \"ImpactSEGSClassify\",\n                                        \"ImpactSEGSConcat\",\n                                        \"ImpactSEGSLabelFilter\",\n                                        \"ImpactSEGSOrderedFilter\",\n                                        \"ImpactSEGSPicker\",\n                                        \"ImpactSEGSRangeFilter\",\n                                        \"ImpactSEGSToMaskBatch\",\n                                        \"ImpactSEGSToMaskList\",\n                                        \"ImpactScaleBy_BBOX_SEG_ELT\",\n                                        \"ImpactSegsAndMask\",\n                                        \"ImpactSegsAndMaskForEach\",\n                                        \"ImpactSetWidgetValue\",\n                                        \"ImpactSimpleDetectorSEGS\",\n                                        \"ImpactSimpleDetectorSEGSPipe\",\n                                        \"ImpactSimpleDetectorSEGS_for_AD\",\n                                        \"ImpactSleep\",\n                                        \"ImpactStringSelector\",\n                                        \"ImpactSwitch\",\n                                        \"ImpactValueReceiver\",\n                                        \"ImpactValueSender\",\n                                        \"ImpactWildcardEncode\",\n                                        \"ImpactWildcardProcessor\",\n                                        \"IterativeImageUpscale\",\n                                        \"IterativeLatentUpscale\",\n                                        \"KSamplerAdvancedProvider\",\n                                        \"KSamplerProvider\",\n                                        \"LatentPixelScale\",\n                                        \"LatentReceiver\",\n                                        \"LatentSender\",\n                                        \"LatentSwitch\",\n                                        \"MMDetDetectorProvider\",\n                                        \"MMDetLoader\",\n                                        \"MaskDetailerPipe\",\n                                        \"MaskListToMaskBatch\",\n                                        \"MaskPainter\",\n                                        \"MaskToSEGS\",\n                                        \"MaskToSEGS_for_AnimateDiff\",\n                                        \"MasksToMaskList\",\n                                        \"MediaPipeFaceMeshToSEGS\",\n                                        \"NoiseInjectionDetailerHookProvider\",\n                                        \"NoiseInjectionHookProvider\",\n                                        \"ONNXDetectorProvider\",\n                                        \"ONNXDetectorSEGS\",\n                                        \"PixelKSampleHookCombine\",\n                                        \"PixelKSampleUpscalerProvider\",\n                                        \"PixelKSampleUpscalerProviderPipe\",\n                                        \"PixelTiledKSampleUpscalerProvider\",\n                                        \"PixelTiledKSampleUpscalerProviderPipe\",\n                                        \"PreviewBridge\",\n                                        \"PreviewBridgeLatent\",\n                                        \"PreviewDetailerHookProvider\",\n                                        \"ReencodeLatent\",\n                                        \"ReencodeLatentPipe\",\n                                        \"RegionalPrompt\",\n                                        \"RegionalSampler\",\n                                        \"RegionalSamplerAdvanced\",\n                                        \"RemoveImageFromSEGS\",\n                                        \"RemoveNoiseMask\",\n                                        \"SAMDetectorCombined\",\n                                        \"SAMDetectorSegmented\",\n                                        \"SAMLoader\",\n                                        \"SEGSDetailer\",\n                                        \"SEGSDetailerForAnimateDiff\",\n                                        \"SEGSLabelFilterDetailerHookProvider\",\n                                        \"SEGSOrderedFilterDetailerHookProvider\",\n                                        \"SEGSPaste\",\n                                        \"SEGSPreview\",\n                                        \"SEGSPreviewCNet\",\n                                        \"SEGSRangeFilterDetailerHookProvider\",\n                                        \"SEGSSwitch\",\n                                        \"SEGSToImageList\",\n                                        \"SegmDetectorCombined\",\n                                        \"SegmDetectorCombined_v2\",\n                                        \"SegmDetectorForEach\",\n                                        \"SegmDetectorSEGS\",\n                                        \"Segs  Mask\",\n                                        \"Segs  Mask ForEach\",\n                                        \"SegsMaskCombine\",\n                                        \"SegsToCombinedMask\",\n                                        \"SetDefaultImageForSEGS\",\n                                        \"StepsScheduleHookProvider\",\n                                        \"SubtractMask\",\n                                        \"SubtractMaskForEach\",\n                                        \"TiledKSamplerProvider\",\n                                        \"ToBasicPipe\",\n                                        \"ToBinaryMask\",\n                                        \"ToDetailerPipe\",\n                                        \"ToDetailerPipeSDXL\",\n                                        \"TwoAdvancedSamplersForMask\",\n                                        \"TwoSamplersForMask\",\n                                        \"TwoSamplersForMaskUpscalerProvider\",\n                                        \"TwoSamplersForMaskUpscalerProviderPipe\",\n                                        \"UltralyticsDetectorProvider\",\n                                        \"UnsamplerDetailerHookProvider\",\n                                        \"UnsamplerHookProvider\"\n                                    ],\n                                    {\n                                        \"author\": \"Dr.Lt.Data\",\n                                        \"description\": \"This extension offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler.\",\n                                        \"nickname\": \"Impact Pack\",\n                                        \"title\": \"Impact Pack\",\n                                        \"title_aux\": \"ComfyUI Impact Pack\"\n                                    }\n                                ],\n                                \"https://github.com/ltdrdata/ComfyUI-Inspire-Pack\": [\n                                    [\n                                        \"AnimeLineArt_Preprocessor_Provider_for_SEGS //Inspire\",\n                                        \"ApplyRegionalIPAdapters //Inspire\",\n                                        \"BindImageListPromptList //Inspire\",\n                                        \"CLIPTextEncodeWithWeight //Inspire\",\n                                        \"CacheBackendData //Inspire\",\n                                        \"CacheBackendDataList //Inspire\",\n                                        \"CacheBackendDataNumberKey //Inspire\",\n                                        \"CacheBackendDataNumberKeyList //Inspire\",\n                                        \"Canny_Preprocessor_Provider_for_SEGS //Inspire\",\n                                        \"ChangeImageBatchSize //Inspire\",\n                                        \"CheckpointLoaderSimpleShared //Inspire\",\n                                        \"Color_Preprocessor_Provider_for_SEGS //Inspire\",\n                                        \"ConcatConditioningsWithMultiplier //Inspire\",\n                                        \"DWPreprocessor_Provider_for_SEGS //Inspire\",\n                                        \"FakeScribblePreprocessor_Provider_for_SEGS //Inspire\",\n                                        \"FloatRange //Inspire\",\n                                        \"FromIPAdapterPipe //Inspire\",\n                                        \"GlobalSampler //Inspire\",\n                                        \"GlobalSeed //Inspire\",\n                                        \"HEDPreprocessor_Provider_for_SEGS //Inspire\",\n                                        \"HyperTile //Inspire\",\n                                        \"IPAdapterModelHelper //Inspire\",\n                                        \"ImageBatchSplitter //Inspire\",\n                                        \"InpaintPreprocessor_Provider_for_SEGS //Inspire\",\n                                        \"KSampler //Inspire\",\n                                        \"KSamplerAdvanced //Inspire\",\n                                        \"KSamplerAdvancedPipe //Inspire\",\n                                        \"KSamplerAdvancedProgress //Inspire\",\n                                        \"KSamplerPipe //Inspire\",\n                                        \"KSamplerProgress //Inspire\",\n                                        \"LatentBatchSplitter //Inspire\",\n                                        \"LeRes_DepthMap_Preprocessor_Provider_for_SEGS //Inspire\",\n                                        \"LineArt_Preprocessor_Provider_for_SEGS //Inspire\",\n                                        \"ListCounter //Inspire\",\n                                        \"LoadImage //Inspire\",\n                                        \"LoadImageListFromDir //Inspire\",\n                                        \"LoadImagesFromDir //Inspire\",\n                                        \"LoadPromptsFromDir //Inspire\",\n                                        \"LoadPromptsFromFile //Inspire\",\n                                        \"LoadSinglePromptFromFile //Inspire\",\n                                        \"LoraBlockInfo //Inspire\",\n                                        \"LoraLoaderBlockWeight //Inspire\",\n                                        \"MakeBasicPipe //Inspire\",\n                                        \"Manga2Anime_LineArt_Preprocessor_Provider_for_SEGS //Inspire\",\n                                        \"MediaPipeFaceMeshDetectorProvider //Inspire\",\n                                        \"MediaPipe_FaceMesh_Preprocessor_Provider_for_SEGS //Inspire\",\n                                        \"MeshGraphormerDepthMapPreprocessorProvider_for_SEGS //Inspire\",\n                                        \"MiDaS_DepthMap_Preprocessor_Provider_for_SEGS //Inspire\",\n                                        \"OpenPose_Preprocessor_Provider_for_SEGS //Inspire\",\n                                        \"PromptBuilder //Inspire\",\n                                        \"PromptExtractor //Inspire\",\n                                        \"RandomGeneratorForList //Inspire\",\n                                        \"RegionalConditioningColorMask //Inspire\",\n                                        \"RegionalConditioningSimple //Inspire\",\n                                        \"RegionalIPAdapterColorMask //Inspire\",\n                                        \"RegionalIPAdapterEncodedColorMask //Inspire\",\n                                        \"RegionalIPAdapterEncodedMask //Inspire\",\n                                        \"RegionalIPAdapterMask //Inspire\",\n                                        \"RegionalPromptColorMask //Inspire\",\n                                        \"RegionalPromptSimple //Inspire\",\n                                        \"RegionalSeedExplorerColorMask //Inspire\",\n                                        \"RegionalSeedExplorerMask //Inspire\",\n                                        \"RemoveBackendData //Inspire\",\n                                        \"RemoveBackendDataNumberKey //Inspire\",\n                                        \"RemoveControlNet //Inspire\",\n                                        \"RemoveControlNetFromRegionalPrompts //Inspire\",\n                                        \"RetrieveBackendData //Inspire\",\n                                        \"RetrieveBackendDataNumberKey //Inspire\",\n                                        \"SeedExplorer //Inspire\",\n                                        \"ShowCachedInfo //Inspire\",\n                                        \"TilePreprocessor_Provider_for_SEGS //Inspire\",\n                                        \"ToIPAdapterPipe //Inspire\",\n                                        \"UnzipPrompt //Inspire\",\n                                        \"WildcardEncode //Inspire\",\n                                        \"XY Input: Lora Block Weight //Inspire\",\n                                        \"ZipPrompt //Inspire\",\n                                        \"Zoe_DepthMap_Preprocessor_Provider_for_SEGS //Inspire\"\n                                    ],\n                                    {\n                                        \"author\": \"Dr.Lt.Data\",\n                                        \"description\": \"This extension provides various nodes to support Lora Block Weight and the Impact Pack.\",\n                                        \"nickname\": \"Inspire Pack\",\n                                        \"nodename_pattern\": \"Inspire$\",\n                                        \"title\": \"Inspire Pack\",\n                                        \"title_aux\": \"ComfyUI Inspire Pack\"\n                                    }\n                                ],\n                                \"https://github.com/m-sokes/ComfyUI-Sokes-Nodes\": [\n                                    [\n                                        \"Custom Date Format | sokes \\ud83e\\uddac\",\n                                        \"Latent Switch x9 | sokes \\ud83e\\uddac\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Sokes Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/m957ymj75urz/ComfyUI-Custom-Nodes/raw/main/clip-text-encode-split/clip_text_encode_split.py\": [\n                                    [\n                                        \"RawText\",\n                                        \"RawTextCombine\",\n                                        \"RawTextEncode\",\n                                        \"RawTextReplace\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"m957ymj75urz/ComfyUI-Custom-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/mape/ComfyUI-mape-Helpers\": [\n                                    [\n                                        \"mape Variable\"\n                                    ],\n                                    {\n                                        \"author\": \"mape\",\n                                        \"description\": \"Various QoL improvements like prompt tweaking, variable assignment, image preview, fuzzy search, error reporting, organizing and node navigation.\",\n                                        \"nickname\": \"\\ud83d\\udfe1 mape's helpers\",\n                                        \"title\": \"mape's helpers\",\n                                        \"title_aux\": \"mape's ComfyUI Helpers\"\n                                    }\n                                ],\n                                \"https://github.com/marhensa/sdxl-recommended-res-calc\": [\n                                    [\n                                        \"RecommendedResCalc\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Recommended Resolution Calculator\"\n                                    }\n                                ],\n                                \"https://github.com/martijnat/comfyui-previewlatent\": [\n                                    [\n                                        \"PreviewLatent\",\n                                        \"PreviewLatentAdvanced\",\n                                        \"PreviewLatentXL\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-previewlatent\"\n                                    }\n                                ],\n                                \"https://github.com/massao000/ComfyUI_aspect_ratios\": [\n                                    [\n                                        \"Aspect Ratios Node\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_aspect_ratios\"\n                                    }\n                                ],\n                                \"https://github.com/matan1905/ComfyUI-Serving-Toolkit\": [\n                                    [\n                                        \"DiscordServing\",\n                                        \"ServingInputNumber\",\n                                        \"ServingInputText\",\n                                        \"ServingOutput\",\n                                        \"WebSocketServing\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Serving toolkit\"\n                                    }\n                                ],\n                                \"https://github.com/mav-rik/facerestore_cf\": [\n                                    [\n                                        \"CropFace\",\n                                        \"FaceRestoreCFWithModel\",\n                                        \"FaceRestoreModelLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Facerestore CF (Code Former)\"\n                                    }\n                                ],\n                                \"https://github.com/mbrostami/ComfyUI-HF\": [\n                                    [\n                                        \"GPT2Node\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-HF\"\n                                    }\n                                ],\n                                \"https://github.com/mcmonkeyprojects/sd-dynamic-thresholding\": [\n                                    [\n                                        \"DynamicThresholdingFull\",\n                                        \"DynamicThresholdingSimple\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Stable Diffusion Dynamic Thresholding (CFG Scale Fix)\"\n                                    }\n                                ],\n                                \"https://github.com/meap158/ComfyUI-Background-Replacement\": [\n                                    [\n                                        \"BackgroundReplacement\",\n                                        \"ImageComposite\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Background-Replacement\"\n                                    }\n                                ],\n                                \"https://github.com/meap158/ComfyUI-GPU-temperature-protection\": [\n                                    [\n                                        \"GPUTemperatureProtection\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"GPU temperature protection\"\n                                    }\n                                ],\n                                \"https://github.com/meap158/ComfyUI-Prompt-Expansion\": [\n                                    [\n                                        \"PromptExpansion\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Prompt-Expansion\"\n                                    }\n                                ],\n                                \"https://github.com/melMass/comfy_mtb\": [\n                                    [\n                                        \"Animation Builder (mtb)\",\n                                        \"Any To String (mtb)\",\n                                        \"Batch Float (mtb)\",\n                                        \"Batch Float Assemble (mtb)\",\n                                        \"Batch Float Fill (mtb)\",\n                                        \"Batch Make (mtb)\",\n                                        \"Batch Merge (mtb)\",\n                                        \"Batch Shake (mtb)\",\n                                        \"Batch Shape (mtb)\",\n                                        \"Batch Transform (mtb)\",\n                                        \"Bbox (mtb)\",\n                                        \"Bbox From Mask (mtb)\",\n                                        \"Blur (mtb)\",\n                                        \"Color Correct (mtb)\",\n                                        \"Colored Image (mtb)\",\n                                        \"Concat Images (mtb)\",\n                                        \"Crop (mtb)\",\n                                        \"Debug (mtb)\",\n                                        \"Deep Bump (mtb)\",\n                                        \"Export With Ffmpeg (mtb)\",\n                                        \"Face Swap (mtb)\",\n                                        \"Film Interpolation (mtb)\",\n                                        \"Fit Number (mtb)\",\n                                        \"Float To Number (mtb)\",\n                                        \"Get Batch From History (mtb)\",\n                                        \"Image Compare (mtb)\",\n                                        \"Image Premultiply (mtb)\",\n                                        \"Image Remove Background Rembg (mtb)\",\n                                        \"Image Resize Factor (mtb)\",\n                                        \"Image Tile Offset (mtb)\",\n                                        \"Int To Bool (mtb)\",\n                                        \"Int To Number (mtb)\",\n                                        \"Interpolate Clip Sequential (mtb)\",\n                                        \"Latent Lerp (mtb)\",\n                                        \"Load Face Analysis Model (mtb)\",\n                                        \"Load Face Enhance Model (mtb)\",\n                                        \"Load Face Swap Model (mtb)\",\n                                        \"Load Film Model (mtb)\",\n                                        \"Load Image From Url (mtb)\",\n                                        \"Load Image Sequence (mtb)\",\n                                        \"Mask To Image (mtb)\",\n                                        \"Math Expression (mtb)\",\n                                        \"Model Patch Seamless (mtb)\",\n                                        \"Pick From Batch (mtb)\",\n                                        \"Qr Code (mtb)\",\n                                        \"Restore Face (mtb)\",\n                                        \"Save Gif (mtb)\",\n                                        \"Save Image Grid (mtb)\",\n                                        \"Save Image Sequence (mtb)\",\n                                        \"Save Tensors (mtb)\",\n                                        \"Sharpen (mtb)\",\n                                        \"Smart Step (mtb)\",\n                                        \"Stack Images (mtb)\",\n                                        \"String Replace (mtb)\",\n                                        \"Styles Loader (mtb)\",\n                                        \"Text To Image (mtb)\",\n                                        \"Transform Image (mtb)\",\n                                        \"Uncrop (mtb)\",\n                                        \"Unsplash Image (mtb)\",\n                                        \"Vae Decode (mtb)\"\n                                    ],\n                                    {\n                                        \"nodename_pattern\": \"\\\\(mtb\\\\)$\",\n                                        \"title_aux\": \"MTB Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/mihaiiancu/ComfyUI_Inpaint\": [\n                                    [\n                                        \"InpaintMediapipe\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"mihaiiancu/Inpaint\"\n                                    }\n                                ],\n                                \"https://github.com/mikkel/ComfyUI-text-overlay\": [\n                                    [\n                                        \"Image Text Overlay\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI - Text Overlay Plugin\"\n                                    }\n                                ],\n                                \"https://github.com/mikkel/comfyui-mask-boundingbox\": [\n                                    [\n                                        \"Mask Bounding Box\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI - Mask Bounding Box\"\n                                    }\n                                ],\n                                \"https://github.com/mlinmg/ComfyUI-LaMA-Preprocessor\": [\n                                    [\n                                        \"LaMaPreprocessor\",\n                                        \"lamaPreprocessor\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"LaMa Preprocessor [WIP]\"\n                                    }\n                                ],\n                                \"https://github.com/modusCell/ComfyUI-dimension-node-modusCell\": [\n                                    [\n                                        \"DimensionProviderFree modusCell\",\n                                        \"DimensionProviderRatio modusCell\",\n                                        \"String Concat modusCell\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Preset Dimensions\"\n                                    }\n                                ],\n                                \"https://github.com/mpiquero7164/ComfyUI-SaveImgPrompt\": [\n                                    [\n                                        \"Save IMG Prompt\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SaveImgPrompt\"\n                                    }\n                                ],\n                                \"https://github.com/nagolinc/ComfyUI_FastVAEDecorder_SDXL\": [\n                                    [\n                                        \"FastLatentToImage\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_FastVAEDecorder_SDXL\"\n                                    }\n                                ],\n                                \"https://github.com/natto-maki/ComfyUI-NegiTools\": [\n                                    [\n                                        \"NegiTools_CompositeImages\",\n                                        \"NegiTools_DepthEstimationByMarigold\",\n                                        \"NegiTools_DetectFaceRotationForInpainting\",\n                                        \"NegiTools_ImageProperties\",\n                                        \"NegiTools_LatentProperties\",\n                                        \"NegiTools_NoiseImageGenerator\",\n                                        \"NegiTools_OpenAiDalle3\",\n                                        \"NegiTools_OpenAiGpt\",\n                                        \"NegiTools_OpenAiGpt4v\",\n                                        \"NegiTools_OpenAiTranslate\",\n                                        \"NegiTools_OpenPoseToPointList\",\n                                        \"NegiTools_PointListToMask\",\n                                        \"NegiTools_RandomImageLoader\",\n                                        \"NegiTools_SaveImageToDirectory\",\n                                        \"NegiTools_SeedGenerator\",\n                                        \"NegiTools_StereoImageGenerator\",\n                                        \"NegiTools_StringFunction\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-NegiTools\"\n                                    }\n                                ],\n                                \"https://github.com/nicolai256/comfyUI_Nodes_nicolai256/raw/main/yugioh-presets.py\": [\n                                    [\n                                        \"yugioh_Presets\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyUI_Nodes_nicolai256\"\n                                    }\n                                ],\n                                \"https://github.com/ningxiaoxiao/comfyui-NDI\": [\n                                    [\n                                        \"NDI_LoadImage\",\n                                        \"NDI_SendImage\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-NDI\"\n                                    }\n                                ],\n                                \"https://github.com/nkchocoai/ComfyUI-PromptUtilities\": [\n                                    [\n                                        \"PromptUtilitiesConstString\",\n                                        \"PromptUtilitiesConstStringMultiLine\",\n                                        \"PromptUtilitiesFormatString\",\n                                        \"PromptUtilitiesJoinStringList\",\n                                        \"PromptUtilitiesLoadPreset\",\n                                        \"PromptUtilitiesLoadPresetAdvanced\",\n                                        \"PromptUtilitiesRandomPreset\",\n                                        \"PromptUtilitiesRandomPresetAdvanced\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-PromptUtilities\"\n                                    }\n                                ],\n                                \"https://github.com/nkchocoai/ComfyUI-SizeFromPresets\": [\n                                    [\n                                        \"EmptyLatentImageFromPresetsSD15\",\n                                        \"EmptyLatentImageFromPresetsSDXL\",\n                                        \"RandomEmptyLatentImageFromPresetsSD15\",\n                                        \"RandomEmptyLatentImageFromPresetsSDXL\",\n                                        \"RandomSizeFromPresetsSD15\",\n                                        \"RandomSizeFromPresetsSDXL\",\n                                        \"SizeFromPresetsSD15\",\n                                        \"SizeFromPresetsSDXL\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-SizeFromPresets\"\n                                    }\n                                ],\n                                \"https://github.com/nkchocoai/ComfyUI-TextOnSegs\": [\n                                    [\n                                        \"CalcMaxFontSize\",\n                                        \"ExtractDominantColor\",\n                                        \"GetComplementaryColor\",\n                                        \"SegsToRegion\",\n                                        \"TextOnSegsFloodFill\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-TextOnSegs\"\n                                    }\n                                ],\n                                \"https://github.com/noembryo/ComfyUI-noEmbryo\": [\n                                    [\n                                        \"PromptTermList1\",\n                                        \"PromptTermList2\",\n                                        \"PromptTermList3\",\n                                        \"PromptTermList4\",\n                                        \"PromptTermList5\",\n                                        \"PromptTermList6\"\n                                    ],\n                                    {\n                                        \"author\": \"noEmbryo\",\n                                        \"description\": \"Some useful nodes for ComfyUI\",\n                                        \"nickname\": \"noEmbryo\",\n                                        \"title\": \"noEmbryo nodes for ComfyUI\",\n                                        \"title_aux\": \"noEmbryo nodes\"\n                                    }\n                                ],\n                                \"https://github.com/nosiu/comfyui-instantId-faceswap\": [\n                                    [\n                                        \"FaceEmbed\",\n                                        \"FaceSwapGenerationInpaint\",\n                                        \"FaceSwapSetupPipeline\",\n                                        \"LCMLora\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI InstantID Faceswapper\"\n                                    }\n                                ],\n                                \"https://github.com/noxinias/ComfyUI_NoxinNodes\": [\n                                    [\n                                        \"NoxinChime\",\n                                        \"NoxinPromptLoad\",\n                                        \"NoxinPromptSave\",\n                                        \"NoxinScaledResolution\",\n                                        \"NoxinSimpleMath\",\n                                        \"NoxinSplitPrompt\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_NoxinNodes\"\n                                    }\n                                ],\n                                \"https://github.com/ntc-ai/ComfyUI-DARE-LoRA-Merge\": [\n                                    [\n                                        \"Apply LoRA\",\n                                        \"DARE Merge LoRA Stack\",\n                                        \"Save LoRA\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI - Apply LoRA Stacker with DARE\"\n                                    }\n                                ],\n                                \"https://github.com/ntdviet/comfyui-ext/raw/main/custom_nodes/gcLatentTunnel/gcLatentTunnel.py\": [\n                                    [\n                                        \"gcLatentTunnel\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ntdviet/comfyui-ext\"\n                                    }\n                                ],\n                                \"https://github.com/omar92/ComfyUI-QualityOfLifeSuit_Omar92\": [\n                                    [\n                                        \"CLIPStringEncode _O\",\n                                        \"Chat completion _O\",\n                                        \"ChatGPT Simple _O\",\n                                        \"ChatGPT _O\",\n                                        \"ChatGPT compact _O\",\n                                        \"Chat_Completion _O\",\n                                        \"Chat_Message _O\",\n                                        \"Chat_Message_fromString _O\",\n                                        \"Concat Text _O\",\n                                        \"ConcatRandomNSP_O\",\n                                        \"Debug String _O\",\n                                        \"Debug Text _O\",\n                                        \"Debug Text route _O\",\n                                        \"Edit_image _O\",\n                                        \"Equation1param _O\",\n                                        \"Equation2params _O\",\n                                        \"GetImage_(Width&Height) _O\",\n                                        \"GetLatent_(Width&Height) _O\",\n                                        \"ImageScaleFactor _O\",\n                                        \"ImageScaleFactorSimple _O\",\n                                        \"LatentUpscaleFactor _O\",\n                                        \"LatentUpscaleFactorSimple _O\",\n                                        \"LatentUpscaleMultiply\",\n                                        \"Note _O\",\n                                        \"RandomNSP _O\",\n                                        \"Replace Text _O\",\n                                        \"String _O\",\n                                        \"Text _O\",\n                                        \"Text2Image _O\",\n                                        \"Trim Text _O\",\n                                        \"VAEDecodeParallel _O\",\n                                        \"combine_chat_messages _O\",\n                                        \"compine_chat_messages _O\",\n                                        \"concat Strings _O\",\n                                        \"create image _O\",\n                                        \"create_image _O\",\n                                        \"debug Completeion _O\",\n                                        \"debug messages_O\",\n                                        \"float _O\",\n                                        \"floatToInt _O\",\n                                        \"floatToText _O\",\n                                        \"int _O\",\n                                        \"intToFloat _O\",\n                                        \"load_openAI _O\",\n                                        \"replace String _O\",\n                                        \"replace String advanced _O\",\n                                        \"saveTextToFile _O\",\n                                        \"seed _O\",\n                                        \"selectLatentFromBatch _O\",\n                                        \"string2Image _O\",\n                                        \"trim String _O\",\n                                        \"variation_image _O\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Quality of life Suit:V2\"\n                                    }\n                                ],\n                                \"https://github.com/ostris/ostris_nodes_comfyui\": [\n                                    [\n                                        \"LLM Pipe Loader - Ostris\",\n                                        \"LLM Prompt Upsampling - Ostris\",\n                                        \"One Seed - Ostris\",\n                                        \"Text Box - Ostris\"\n                                    ],\n                                    {\n                                        \"nodename_pattern\": \"- Ostris$\",\n                                        \"title_aux\": \"Ostris Nodes ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/ownimage/ComfyUI-ownimage\": [\n                                    [\n                                        \"Caching Image Loader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-ownimage\"\n                                    }\n                                ],\n                                \"https://github.com/oyvindg/ComfyUI-TrollSuite\": [\n                                    [\n                                        \"BinaryImageMask\",\n                                        \"ImagePadding\",\n                                        \"LoadLastImage\",\n                                        \"RandomMask\",\n                                        \"TransparentImage\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-TrollSuite\"\n                                    }\n                                ],\n                                \"https://github.com/palant/extended-saveimage-comfyui\": [\n                                    [\n                                        \"SaveImageExtended\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Extended Save Image for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/palant/image-resize-comfyui\": [\n                                    [\n                                        \"ImageResize\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Image Resize for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/pants007/comfy-pants\": [\n                                    [\n                                        \"CLIPTextEncodeAIO\",\n                                        \"Image Make Square\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"pants\"\n                                    }\n                                ],\n                                \"https://github.com/paulo-coronado/comfy_clip_blip_node\": [\n                                    [\n                                        \"CLIPTextEncodeBLIP\",\n                                        \"CLIPTextEncodeBLIP-2\",\n                                        \"Example\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfy_clip_blip_node\"\n                                    }\n                                ],\n                                \"https://github.com/picturesonpictures/comfy_PoP\": [\n                                    [\n                                        \"AdaptiveCannyDetector_PoP\",\n                                        \"AnyAspectRatio\",\n                                        \"ConditioningMultiplier_PoP\",\n                                        \"ConditioningNormalizer_PoP\",\n                                        \"DallE3_PoP\",\n                                        \"LoadImageResizer_PoP\",\n                                        \"LoraStackLoader10_PoP\",\n                                        \"LoraStackLoader_PoP\",\n                                        \"VAEDecoderPoP\",\n                                        \"VAEEncoderPoP\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfy_PoP\"\n                                    }\n                                ],\n                                \"https://github.com/pkpkTech/ComfyUI-SaveAVIF\": [\n                                    [\n                                        \"SaveAvif\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-SaveAVIF\"\n                                    }\n                                ],\n                                \"https://github.com/pkpkTech/ComfyUI-TemporaryLoader\": [\n                                    [\n                                        \"LoadTempCheckpoint\",\n                                        \"LoadTempLoRA\",\n                                        \"LoadTempMultiLoRA\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-TemporaryLoader\"\n                                    }\n                                ],\n                                \"https://github.com/pythongosssss/ComfyUI-Custom-Scripts\": [\n                                    [\n                                        \"CheckpointLoader|pysssss\",\n                                        \"ConstrainImageforVideo|pysssss\",\n                                        \"ConstrainImage|pysssss\",\n                                        \"LoadText|pysssss\",\n                                        \"LoraLoader|pysssss\",\n                                        \"MathExpression|pysssss\",\n                                        \"MultiPrimitive|pysssss\",\n                                        \"PlaySound|pysssss\",\n                                        \"Repeater|pysssss\",\n                                        \"ReroutePrimitive|pysssss\",\n                                        \"SaveText|pysssss\",\n                                        \"ShowText|pysssss\",\n                                        \"StringFunction|pysssss\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"pythongosssss/ComfyUI-Custom-Scripts\"\n                                    }\n                                ],\n                                \"https://github.com/pythongosssss/ComfyUI-WD14-Tagger\": [\n                                    [\n                                        \"WD14Tagger|pysssss\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI WD 1.4 Tagger\"\n                                    }\n                                ],\n                                \"https://github.com/ramyma/A8R8_ComfyUI_nodes\": [\n                                    [\n                                        \"Base64ImageInput\",\n                                        \"Base64ImageOutput\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"A8R8 ComfyUI Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/rcfcu2000/zhihuige-nodes-comfyui\": [\n                                    [\n                                        \"Combine ZHGMasks\",\n                                        \"Cover ZHGMasks\",\n                                        \"From ZHG pip\",\n                                        \"GroundingDinoModelLoader (zhihuige)\",\n                                        \"GroundingDinoPIPESegment (zhihuige)\",\n                                        \"GroundingDinoSAMSegment (zhihuige)\",\n                                        \"InvertMask (zhihuige)\",\n                                        \"SAMModelLoader (zhihuige)\",\n                                        \"To ZHG pip\",\n                                        \"ZHG FaceIndex\",\n                                        \"ZHG GetMaskArea\",\n                                        \"ZHG Image Levels\",\n                                        \"ZHG SaveImage\",\n                                        \"ZHG SmoothEdge\",\n                                        \"ZHG UltimateSDUpscale\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"zhihuige-nodes-comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/rcsaquino/comfyui-custom-nodes\": [\n                                    [\n                                        \"BackgroundRemover | rcsaquino\",\n                                        \"VAELoader | rcsaquino\",\n                                        \"VAEProcessor | rcsaquino\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"rcsaquino/comfyui-custom-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/receyuki/comfyui-prompt-reader-node\": [\n                                    [\n                                        \"SDBatchLoader\",\n                                        \"SDLoraLoader\",\n                                        \"SDLoraSelector\",\n                                        \"SDParameterExtractor\",\n                                        \"SDParameterGenerator\",\n                                        \"SDPromptMerger\",\n                                        \"SDPromptReader\",\n                                        \"SDPromptSaver\",\n                                        \"SDTypeConverter\"\n                                    ],\n                                    {\n                                        \"author\": \"receyuki\",\n                                        \"description\": \"ComfyUI node version of the SD Prompt Reader\",\n                                        \"nickname\": \"SD Prompt Reader\",\n                                        \"title\": \"SD Prompt Reader\",\n                                        \"title_aux\": \"comfyui-prompt-reader-node\"\n                                    }\n                                ],\n                                \"https://github.com/redhottensors/ComfyUI-Prediction\": [\n                                    [\n                                        \"SamplerCustomPrediction\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Prediction\"\n                                    }\n                                ],\n                                \"https://github.com/rgthree/rgthree-comfy\": [\n                                    [],\n                                    {\n                                        \"author\": \"rgthree\",\n                                        \"description\": \"A bunch of nodes I created that I also find useful.\",\n                                        \"nickname\": \"rgthree\",\n                                        \"nodename_pattern\": \" \\\\(rgthree\\\\)$\",\n                                        \"title\": \"Comfy Nodes\",\n                                        \"title_aux\": \"rgthree's ComfyUI Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/richinsley/Comfy-LFO\": [\n                                    [\n                                        \"LFO_Pulse\",\n                                        \"LFO_Sawtooth\",\n                                        \"LFO_Sine\",\n                                        \"LFO_Square\",\n                                        \"LFO_Triangle\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfy-LFO\"\n                                    }\n                                ],\n                                \"https://github.com/ricklove/comfyui-ricklove\": [\n                                    [\n                                        \"RL_Crop_Resize\",\n                                        \"RL_Crop_Resize_Batch\",\n                                        \"RL_Depth16\",\n                                        \"RL_Finetune_Analyze\",\n                                        \"RL_Finetune_Analyze_Batch\",\n                                        \"RL_Finetune_Variable\",\n                                        \"RL_Image_Shadow\",\n                                        \"RL_Image_Threshold_Channels\",\n                                        \"RL_Internet_Search\",\n                                        \"RL_LoadImageSequence\",\n                                        \"RL_Optical_Flow_Dip\",\n                                        \"RL_SaveImageSequence\",\n                                        \"RL_Uncrop\",\n                                        \"RL_Warp_Image\",\n                                        \"RL_Zoe_Depth_Map_Preprocessor\",\n                                        \"RL_Zoe_Depth_Map_Preprocessor_Raw_Infer\",\n                                        \"RL_Zoe_Depth_Map_Preprocessor_Raw_Process\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-ricklove\"\n                                    }\n                                ],\n                                \"https://github.com/rklaffehn/rk-comfy-nodes\": [\n                                    [\n                                        \"RK_CivitAIAddHashes\",\n                                        \"RK_CivitAIMetaChecker\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"rk-comfy-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/romeobuilderotti/ComfyUI-PNG-Metadata\": [\n                                    [\n                                        \"SetMetadataAll\",\n                                        \"SetMetadataString\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI PNG Metadata\"\n                                    }\n                                ],\n                                \"https://github.com/rui40000/RUI-Nodes\": [\n                                    [\n                                        \"ABCondition\",\n                                        \"CharacterCount\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"RUI-Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/s1dlx/comfy_meh/raw/main/meh.py\": [\n                                    [\n                                        \"MergingExecutionHelper\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfy_meh\"\n                                    }\n                                ],\n                                \"https://github.com/seanlynch/comfyui-optical-flow\": [\n                                    [\n                                        \"Apply optical flow\",\n                                        \"Compute optical flow\",\n                                        \"Visualize optical flow\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Optical Flow\"\n                                    }\n                                ],\n                                \"https://github.com/seanlynch/srl-nodes\": [\n                                    [\n                                        \"SRL Conditional Interrrupt\",\n                                        \"SRL Eval\",\n                                        \"SRL Filter Image List\",\n                                        \"SRL Format String\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SRL's nodes\"\n                                    }\n                                ],\n                                \"https://github.com/sergekatzmann/ComfyUI_Nimbus-Pack\": [\n                                    [\n                                        \"ImageResizeAndCropNode\",\n                                        \"ImageSquareAdapterNode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_Nimbus-Pack\"\n                                    }\n                                ],\n                                \"https://github.com/shadowcz007/comfyui-consistency-decoder\": [\n                                    [\n                                        \"VAEDecodeConsistencyDecoder\",\n                                        \"VAELoaderConsistencyDecoder\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Consistency Decoder\"\n                                    }\n                                ],\n                                \"https://github.com/shadowcz007/comfyui-mixlab-nodes\": [\n                                    [\n                                        \"3DImage\",\n                                        \"AppInfo\",\n                                        \"AreaToMask\",\n                                        \"CenterImage\",\n                                        \"CharacterInText\",\n                                        \"ChatGPTOpenAI\",\n                                        \"CkptNames_\",\n                                        \"Color\",\n                                        \"DynamicDelayProcessor\",\n                                        \"EmbeddingPrompt\",\n                                        \"EnhanceImage\",\n                                        \"FaceToMask\",\n                                        \"FeatheredMask\",\n                                        \"FloatSlider\",\n                                        \"FloatingVideo\",\n                                        \"Font\",\n                                        \"GamePal\",\n                                        \"GetImageSize_\",\n                                        \"GradientImage\",\n                                        \"GridOutput\",\n                                        \"ImageColorTransfer\",\n                                        \"ImageCropByAlpha\",\n                                        \"IntNumber\",\n                                        \"JoinWithDelimiter\",\n                                        \"LaMaInpainting\",\n                                        \"LimitNumber\",\n                                        \"LoadImagesFromPath\",\n                                        \"LoadImagesFromURL\",\n                                        \"LoraNames_\",\n                                        \"MergeLayers\",\n                                        \"MirroredImage\",\n                                        \"MultiplicationNode\",\n                                        \"NewLayer\",\n                                        \"NoiseImage\",\n                                        \"OutlineMask\",\n                                        \"PromptImage\",\n                                        \"PromptSimplification\",\n                                        \"PromptSlide\",\n                                        \"RandomPrompt\",\n                                        \"ResizeImageMixlab\",\n                                        \"SamplerNames_\",\n                                        \"SaveImageToLocal\",\n                                        \"ScreenShare\",\n                                        \"Seed_\",\n                                        \"ShowLayer\",\n                                        \"ShowTextForGPT\",\n                                        \"SmoothMask\",\n                                        \"SpeechRecognition\",\n                                        \"SpeechSynthesis\",\n                                        \"SplitImage\",\n                                        \"SplitLongMask\",\n                                        \"SvgImage\",\n                                        \"SwitchByIndex\",\n                                        \"TESTNODE_\",\n                                        \"TESTNODE_TOKEN\",\n                                        \"TextImage\",\n                                        \"TextInput_\",\n                                        \"TextToNumber\",\n                                        \"TransparentImage\",\n                                        \"VAEDecodeConsistencyDecoder\",\n                                        \"VAELoaderConsistencyDecoder\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-mixlab-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/shadowcz007/comfyui-ultralytics-yolo\": [\n                                    [\n                                        \"DetectByLabel\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-ultralytics-yolo\"\n                                    }\n                                ],\n                                \"https://github.com/shiimizu/ComfyUI-PhotoMaker-Plus\": [\n                                    [\n                                        \"PhotoMakerEncodePlus\",\n                                        \"PhotoMakerStyles\",\n                                        \"PrepImagesForClipVisionFromPath\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI PhotoMaker Plus\"\n                                    }\n                                ],\n                                \"https://github.com/shiimizu/ComfyUI-TiledDiffusion\": [\n                                    [\n                                        \"NoiseInversion\",\n                                        \"TiledDiffusion\",\n                                        \"VAEDecodeTiled_TiledDiffusion\",\n                                        \"VAEEncodeTiled_TiledDiffusion\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Tiled Diffusion & VAE for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/shiimizu/ComfyUI_smZNodes\": [\n                                    [\n                                        \"smZ CLIPTextEncode\",\n                                        \"smZ Settings\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"smZNodes\"\n                                    }\n                                ],\n                                \"https://github.com/shingo1228/ComfyUI-SDXL-EmptyLatentImage\": [\n                                    [\n                                        \"SDXL Empty Latent Image\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-SDXL-EmptyLatentImage\"\n                                    }\n                                ],\n                                \"https://github.com/shingo1228/ComfyUI-send-eagle-slim\": [\n                                    [\n                                        \"Send Eagle with text\",\n                                        \"Send Webp Image to Eagle\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-send-Eagle(slim)\"\n                                    }\n                                ],\n                                \"https://github.com/shockz0rz/ComfyUI_InterpolateEverything\": [\n                                    [\n                                        \"OpenposePreprocessorInterpolate\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"InterpolateEverything\"\n                                    }\n                                ],\n                                \"https://github.com/shockz0rz/comfy-easy-grids\": [\n                                    [\n                                        \"FloatToText\",\n                                        \"GridFloatList\",\n                                        \"GridFloats\",\n                                        \"GridIntList\",\n                                        \"GridInts\",\n                                        \"GridLoras\",\n                                        \"GridStringList\",\n                                        \"GridStrings\",\n                                        \"ImageGridCommander\",\n                                        \"IntToText\",\n                                        \"SaveImageGrid\",\n                                        \"TextConcatenator\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfy-easy-grids\"\n                                    }\n                                ],\n                                \"https://github.com/siliconflow/onediff_comfy_nodes\": [\n                                    [\n                                        \"CompareModel\",\n                                        \"ControlNetGraphLoader\",\n                                        \"ControlNetGraphSaver\",\n                                        \"ControlNetSpeedup\",\n                                        \"ModelGraphLoader\",\n                                        \"ModelGraphSaver\",\n                                        \"ModelSpeedup\",\n                                        \"ModuleDeepCacheSpeedup\",\n                                        \"OneDiffCheckpointLoaderSimple\",\n                                        \"SVDSpeedup\",\n                                        \"ShowImageDiff\",\n                                        \"VaeGraphLoader\",\n                                        \"VaeGraphSaver\",\n                                        \"VaeSpeedup\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"OneDiff Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/sipherxyz/comfyui-art-venture\": [\n                                    [\n                                        \"AV_CheckpointMerge\",\n                                        \"AV_CheckpointModelsToParametersPipe\",\n                                        \"AV_CheckpointSave\",\n                                        \"AV_ControlNetEfficientLoader\",\n                                        \"AV_ControlNetEfficientLoaderAdvanced\",\n                                        \"AV_ControlNetEfficientStacker\",\n                                        \"AV_ControlNetEfficientStackerSimple\",\n                                        \"AV_ControlNetLoader\",\n                                        \"AV_ControlNetPreprocessor\",\n                                        \"AV_LoraListLoader\",\n                                        \"AV_LoraListStacker\",\n                                        \"AV_LoraLoader\",\n                                        \"AV_ParametersPipeToCheckpointModels\",\n                                        \"AV_ParametersPipeToPrompts\",\n                                        \"AV_PromptsToParametersPipe\",\n                                        \"AV_SAMLoader\",\n                                        \"AV_VAELoader\",\n                                        \"AspectRatioSelector\",\n                                        \"BLIPCaption\",\n                                        \"BLIPLoader\",\n                                        \"BooleanPrimitive\",\n                                        \"ColorBlend\",\n                                        \"ColorCorrect\",\n                                        \"DeepDanbooruCaption\",\n                                        \"DependenciesEdit\",\n                                        \"Fooocus_KSampler\",\n                                        \"Fooocus_KSamplerAdvanced\",\n                                        \"GetBoolFromJson\",\n                                        \"GetFloatFromJson\",\n                                        \"GetIntFromJson\",\n                                        \"GetObjectFromJson\",\n                                        \"GetSAMEmbedding\",\n                                        \"GetTextFromJson\",\n                                        \"ISNetLoader\",\n                                        \"ISNetSegment\",\n                                        \"ImageAlphaComposite\",\n                                        \"ImageApplyChannel\",\n                                        \"ImageExtractChannel\",\n                                        \"ImageGaussianBlur\",\n                                        \"ImageMuxer\",\n                                        \"ImageRepeat\",\n                                        \"ImageScaleDown\",\n                                        \"ImageScaleDownBy\",\n                                        \"ImageScaleDownToSize\",\n                                        \"ImageScaleToMegapixels\",\n                                        \"LaMaInpaint\",\n                                        \"LoadImageAsMaskFromUrl\",\n                                        \"LoadImageFromUrl\",\n                                        \"LoadJsonFromUrl\",\n                                        \"MergeModels\",\n                                        \"NumberScaler\",\n                                        \"OverlayInpaintedImage\",\n                                        \"OverlayInpaintedLatent\",\n                                        \"PrepareImageAndMaskForInpaint\",\n                                        \"QRCodeGenerator\",\n                                        \"RandomFloat\",\n                                        \"RandomInt\",\n                                        \"SAMEmbeddingToImage\",\n                                        \"SDXLAspectRatioSelector\",\n                                        \"SDXLPromptStyler\",\n                                        \"SeedSelector\",\n                                        \"StringToInt\",\n                                        \"StringToNumber\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-art-venture\"\n                                    }\n                                ],\n                                \"https://github.com/skfoo/ComfyUI-Coziness\": [\n                                    [\n                                        \"LoraTextExtractor-b1f83aa2\",\n                                        \"MultiLoraLoader-70bf3d77\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Coziness\"\n                                    }\n                                ],\n                                \"https://github.com/smagnetize/kb-comfyui-nodes\": [\n                                    [\n                                        \"SingleImageDataUrlLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"kb-comfyui-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/space-nuko/ComfyUI-Disco-Diffusion\": [\n                                    [\n                                        \"DiscoDiffusion_DiscoDiffusion\",\n                                        \"DiscoDiffusion_DiscoDiffusionExtraSettings\",\n                                        \"DiscoDiffusion_GuidedDiffusionLoader\",\n                                        \"DiscoDiffusion_OpenAICLIPLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Disco Diffusion\"\n                                    }\n                                ],\n                                \"https://github.com/space-nuko/ComfyUI-OpenPose-Editor\": [\n                                    [\n                                        \"Nui.OpenPoseEditor\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"OpenPose Editor\"\n                                    }\n                                ],\n                                \"https://github.com/space-nuko/nui-suite\": [\n                                    [\n                                        \"Nui.DynamicPromptsTextGen\",\n                                        \"Nui.FeelingLuckyTextGen\",\n                                        \"Nui.OutputString\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"nui suite\"\n                                    }\n                                ],\n                                \"https://github.com/spacepxl/ComfyUI-HQ-Image-Save\": [\n                                    [\n                                        \"LoadEXR\",\n                                        \"LoadLatentEXR\",\n                                        \"SaveEXR\",\n                                        \"SaveLatentEXR\",\n                                        \"SaveTiff\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-HQ-Image-Save\"\n                                    }\n                                ],\n                                \"https://github.com/spacepxl/ComfyUI-Image-Filters\": [\n                                    [\n                                        \"AdainImage\",\n                                        \"AdainLatent\",\n                                        \"AlphaClean\",\n                                        \"AlphaMatte\",\n                                        \"BatchAverageImage\",\n                                        \"BatchAverageUnJittered\",\n                                        \"BatchNormalizeImage\",\n                                        \"BatchNormalizeLatent\",\n                                        \"BlurImageFast\",\n                                        \"BlurMaskFast\",\n                                        \"ClampOutliers\",\n                                        \"ConvertNormals\",\n                                        \"DifferenceChecker\",\n                                        \"DilateErodeMask\",\n                                        \"EnhanceDetail\",\n                                        \"ExposureAdjust\",\n                                        \"GuidedFilterAlpha\",\n                                        \"ImageConstant\",\n                                        \"ImageConstantHSV\",\n                                        \"JitterImage\",\n                                        \"Keyer\",\n                                        \"LatentStats\",\n                                        \"NormalMapSimple\",\n                                        \"OffsetLatentImage\",\n                                        \"RemapRange\",\n                                        \"Tonemap\",\n                                        \"UnJitterImage\",\n                                        \"UnTonemap\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Image-Filters\"\n                                    }\n                                ],\n                                \"https://github.com/spacepxl/ComfyUI-RAVE\": [\n                                    [\n                                        \"ConditioningDebug\",\n                                        \"ImageGridCompose\",\n                                        \"ImageGridDecompose\",\n                                        \"KSamplerRAVE\",\n                                        \"LatentGridCompose\",\n                                        \"LatentGridDecompose\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-RAVE\"\n                                    }\n                                ],\n                                \"https://github.com/spinagon/ComfyUI-seam-carving\": [\n                                    [\n                                        \"SeamCarving\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-seam-carving\"\n                                    }\n                                ],\n                                \"https://github.com/spinagon/ComfyUI-seamless-tiling\": [\n                                    [\n                                        \"CircularVAEDecode\",\n                                        \"MakeCircularVAE\",\n                                        \"OffsetImage\",\n                                        \"SeamlessTile\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Seamless tiling Node for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/spro/comfyui-mirror\": [\n                                    [\n                                        \"LatentMirror\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Latent Mirror node for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/ssitu/ComfyUI_UltimateSDUpscale\": [\n                                    [\n                                        \"UltimateSDUpscale\",\n                                        \"UltimateSDUpscaleNoUpscale\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"UltimateSDUpscale\"\n                                    }\n                                ],\n                                \"https://github.com/ssitu/ComfyUI_fabric\": [\n                                    [\n                                        \"FABRICPatchModel\",\n                                        \"FABRICPatchModelAdv\",\n                                        \"KSamplerAdvFABRICAdv\",\n                                        \"KSamplerFABRIC\",\n                                        \"KSamplerFABRICAdv\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI fabric\"\n                                    }\n                                ],\n                                \"https://github.com/ssitu/ComfyUI_restart_sampling\": [\n                                    [\n                                        \"KRestartSampler\",\n                                        \"KRestartSamplerAdv\",\n                                        \"KRestartSamplerSimple\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Restart Sampling\"\n                                    }\n                                ],\n                                \"https://github.com/ssitu/ComfyUI_roop\": [\n                                    [\n                                        \"RoopImproved\",\n                                        \"roop\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI roop\"\n                                    }\n                                ],\n                                \"https://github.com/storyicon/comfyui_segment_anything\": [\n                                    [\n                                        \"GroundingDinoModelLoader (segment anything)\",\n                                        \"GroundingDinoSAMSegment (segment anything)\",\n                                        \"InvertMask (segment anything)\",\n                                        \"IsMaskEmpty\",\n                                        \"SAMModelLoader (segment anything)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"segment anything\"\n                                    }\n                                ],\n                                \"https://github.com/strimmlarn/ComfyUI_Strimmlarns_aesthetic_score\": [\n                                    [\n                                        \"AesthetlcScoreSorter\",\n                                        \"CalculateAestheticScore\",\n                                        \"LoadAesteticModel\",\n                                        \"ScoreToNumber\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_Strimmlarns_aesthetic_score\"\n                                    }\n                                ],\n                                \"https://github.com/styler00dollar/ComfyUI-deepcache\": [\n                                    [\n                                        \"DeepCache\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-deepcache\"\n                                    }\n                                ],\n                                \"https://github.com/styler00dollar/ComfyUI-sudo-latent-upscale\": [\n                                    [\n                                        \"SudoLatentUpscale\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-sudo-latent-upscale\"\n                                    }\n                                ],\n                                \"https://github.com/syllebra/bilbox-comfyui\": [\n                                    [\n                                        \"BilboXLut\",\n                                        \"BilboXPhotoPrompt\",\n                                        \"BilboXVignette\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"BilboX's ComfyUI Custom Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/sylym/comfy_vid2vid\": [\n                                    [\n                                        \"CheckpointLoaderSimpleSequence\",\n                                        \"DdimInversionSequence\",\n                                        \"KSamplerSequence\",\n                                        \"LoadImageMaskSequence\",\n                                        \"LoadImageSequence\",\n                                        \"LoraLoaderSequence\",\n                                        \"SetLatentNoiseSequence\",\n                                        \"TrainUnetSequence\",\n                                        \"VAEEncodeForInpaintSequence\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Vid2vid\"\n                                    }\n                                ],\n                                \"https://github.com/szhublox/ambw_comfyui\": [\n                                    [\n                                        \"Auto Merge Block Weighted\",\n                                        \"CLIPMergeSimple\",\n                                        \"CheckpointSave\",\n                                        \"ModelMergeBlocks\",\n                                        \"ModelMergeSimple\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Auto-MBW\"\n                                    }\n                                ],\n                                \"https://github.com/taabata/Comfy_Syrian_Falcon_Nodes/raw/main/SyrianFalconNodes.py\": [\n                                    [\n                                        \"CompositeImage\",\n                                        \"KSamplerAlternate\",\n                                        \"KSamplerPromptEdit\",\n                                        \"KSamplerPromptEditAndAlternate\",\n                                        \"LoopBack\",\n                                        \"QRGenerate\",\n                                        \"WordAsImage\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Syrian Falcon Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/taabata/LCM_Inpaint-Outpaint_Comfy\": [\n                                    [\n                                        \"ComfyNodesToSaveCanvas\",\n                                        \"FloatNumber\",\n                                        \"FreeU_LCM\",\n                                        \"ImageOutputToComfyNodes\",\n                                        \"ImageShuffle\",\n                                        \"ImageSwitch\",\n                                        \"LCMGenerate\",\n                                        \"LCMGenerate_ReferenceOnly\",\n                                        \"LCMGenerate_SDTurbo\",\n                                        \"LCMGenerate_img2img\",\n                                        \"LCMGenerate_img2img_IPAdapter\",\n                                        \"LCMGenerate_img2img_controlnet\",\n                                        \"LCMGenerate_inpaintv2\",\n                                        \"LCMGenerate_inpaintv3\",\n                                        \"LCMLoader\",\n                                        \"LCMLoader_RefInpaint\",\n                                        \"LCMLoader_ReferenceOnly\",\n                                        \"LCMLoader_SDTurbo\",\n                                        \"LCMLoader_controlnet\",\n                                        \"LCMLoader_controlnet_inpaint\",\n                                        \"LCMLoader_img2img\",\n                                        \"LCMLoraLoader_inpaint\",\n                                        \"LCMLoraLoader_ipadapter\",\n                                        \"LCMLora_inpaint\",\n                                        \"LCMLora_ipadapter\",\n                                        \"LCMT2IAdapter\",\n                                        \"LCM_IPAdapter\",\n                                        \"LCM_IPAdapter_inpaint\",\n                                        \"LCM_outpaint_prep\",\n                                        \"LoadImageNode_LCM\",\n                                        \"Loader_SegmindVega\",\n                                        \"OutpaintCanvasTool\",\n                                        \"SaveImage_Canvas\",\n                                        \"SaveImage_LCM\",\n                                        \"SaveImage_Puzzle\",\n                                        \"SaveImage_PuzzleV2\",\n                                        \"SegmindVega\",\n                                        \"SettingsSwitch\",\n                                        \"stitch\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"LCM_Inpaint-Outpaint_Comfy\"\n                                    }\n                                ],\n                                \"https://github.com/talesofai/comfyui-browser\": [\n                                    [\n                                        \"DifyTextGenerator //Browser\",\n                                        \"LoadImageByUrl //Browser\",\n                                        \"SelectInputs //Browser\",\n                                        \"UploadToRemote //Browser\",\n                                        \"XyzPlot //Browser\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Browser\"\n                                    }\n                                ],\n                                \"https://github.com/theUpsider/ComfyUI-Logic\": [\n                                    [\n                                        \"Bool\",\n                                        \"Compare\",\n                                        \"DebugPrint\",\n                                        \"Float\",\n                                        \"If ANY execute A else B\",\n                                        \"Int\",\n                                        \"String\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Logic\"\n                                    }\n                                ],\n                                \"https://github.com/theUpsider/ComfyUI-Styles_CSV_Loader\": [\n                                    [\n                                        \"Load Styles CSV\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Styles CSV Loader Extension for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/thecooltechguy/ComfyUI-MagicAnimate\": [\n                                    [\n                                        \"MagicAnimate\",\n                                        \"MagicAnimateModelLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-MagicAnimate\"\n                                    }\n                                ],\n                                \"https://github.com/thecooltechguy/ComfyUI-Stable-Video-Diffusion\": [\n                                    [\n                                        \"SVDDecoder\",\n                                        \"SVDModelLoader\",\n                                        \"SVDSampler\",\n                                        \"SVDSimpleImg2Vid\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Stable Video Diffusion\"\n                                    }\n                                ],\n                                \"https://github.com/thedyze/save-image-extended-comfyui\": [\n                                    [\n                                        \"SaveImageExtended\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Save Image Extended for ComfyUI\"\n                                    }\n                                ],\n                                \"https://github.com/tocubed/ComfyUI-AudioReactor\": [\n                                    [\n                                        \"AudioFrameTransformBeats\",\n                                        \"AudioFrameTransformShadertoy\",\n                                        \"AudioLoadPath\",\n                                        \"Shadertoy\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-AudioReactor\"\n                                    }\n                                ],\n                                \"https://github.com/toyxyz/ComfyUI_toyxyz_test_nodes\": [\n                                    [\n                                        \"CaptureWebcam\",\n                                        \"LatentDelay\",\n                                        \"LoadWebcamImage\",\n                                        \"SaveImagetoPath\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_toyxyz_test_nodes\"\n                                    }\n                                ],\n                                \"https://github.com/trojblue/trNodes\": [\n                                    [\n                                        \"JpgConvertNode\",\n                                        \"trColorCorrection\",\n                                        \"trLayering\",\n                                        \"trRouter\",\n                                        \"trRouterLonger\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"trNodes\"\n                                    }\n                                ],\n                                \"https://github.com/trumanwong/ComfyUI-NSFW-Detection\": [\n                                    [\n                                        \"NSFWDetection\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-NSFW-Detection\"\n                                    }\n                                ],\n                                \"https://github.com/ttulttul/ComfyUI-Iterative-Mixer\": [\n                                    [\n                                        \"Batch Unsampler\",\n                                        \"Iterative Mixing KSampler\",\n                                        \"Iterative Mixing KSampler Advanced\",\n                                        \"IterativeMixingSampler\",\n                                        \"IterativeMixingScheduler\",\n                                        \"IterativeMixingSchedulerAdvanced\",\n                                        \"Latent Batch Comparison Plot\",\n                                        \"Latent Batch Statistics Plot\",\n                                        \"MixingMaskGenerator\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Iterative Mixing Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/ttulttul/ComfyUI-Tensor-Operations\": [\n                                    [\n                                        \"Image Match Normalize\",\n                                        \"Latent Match Normalize\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Tensor-Operations\"\n                                    }\n                                ],\n                                \"https://github.com/tudal/Hakkun-ComfyUI-nodes/raw/main/hakkun_nodes.py\": [\n                                    [\n                                        \"Any Converter\",\n                                        \"Calculate Upscale\",\n                                        \"Image Resize To Height\",\n                                        \"Image Resize To Width\",\n                                        \"Image size to string\",\n                                        \"Load Random Image\",\n                                        \"Load Text\",\n                                        \"Multi Text Merge\",\n                                        \"Prompt Parser\",\n                                        \"Random Line\",\n                                        \"Random Line 4\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Hakkun-ComfyUI-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/tusharbhutt/Endless-Nodes\": [\n                                    [\n                                        \"ESS Aesthetic Scoring\",\n                                        \"ESS Aesthetic Scoring Auto\",\n                                        \"ESS Combo Parameterizer\",\n                                        \"ESS Combo Parameterizer & Prompts\",\n                                        \"ESS Eight Input Random\",\n                                        \"ESS Eight Input Text Switch\",\n                                        \"ESS Float to Integer\",\n                                        \"ESS Float to Number\",\n                                        \"ESS Float to String\",\n                                        \"ESS Float to X\",\n                                        \"ESS Global Envoy\",\n                                        \"ESS Image Reward\",\n                                        \"ESS Image Reward Auto\",\n                                        \"ESS Image Saver with JSON\",\n                                        \"ESS Integer to Float\",\n                                        \"ESS Integer to Number\",\n                                        \"ESS Integer to String\",\n                                        \"ESS Integer to X\",\n                                        \"ESS Number to Float\",\n                                        \"ESS Number to Integer\",\n                                        \"ESS Number to String\",\n                                        \"ESS Number to X\",\n                                        \"ESS Parameterizer\",\n                                        \"ESS Parameterizer & Prompts\",\n                                        \"ESS Six Float Output\",\n                                        \"ESS Six Input Random\",\n                                        \"ESS Six Input Text Switch\",\n                                        \"ESS Six Integer IO Switch\",\n                                        \"ESS Six Integer IO Widget\",\n                                        \"ESS String to Float\",\n                                        \"ESS String to Integer\",\n                                        \"ESS String to Num\",\n                                        \"ESS String to X\",\n                                        \"\\u267e\\ufe0f\\ud83c\\udf0a\\u2728 Image Saver with JSON\"\n                                    ],\n                                    {\n                                        \"author\": \"BiffMunky\",\n                                        \"description\": \"A small set of nodes I created for various numerical and text inputs.  Features image saver with ability to have JSON saved to separate folder, parameter collection nodes, two aesthetic scoring models, switches for text and numbers, and conversion of string to numeric and vice versa.\",\n                                        \"nickname\": \"\\u267e\\ufe0f\\ud83c\\udf0a\\u2728\",\n                                        \"title\": \"Endless \\ufe0f\\ud83c\\udf0a\\u2728 Nodes\",\n                                        \"title_aux\": \"Endless \\ufe0f\\ud83c\\udf0a\\u2728 Nodes\"\n                                    }\n                                ],\n                                \"https://github.com/twri/sdxl_prompt_styler\": [\n                                    [\n                                        \"SDXLPromptStyler\",\n                                        \"SDXLPromptStylerAdvanced\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SDXL Prompt Styler\"\n                                    }\n                                ],\n                                \"https://github.com/uarefans/ComfyUI-Fans\": [\n                                    [\n                                        \"Fans Prompt Styler Negative\",\n                                        \"Fans Prompt Styler Positive\",\n                                        \"Fans Styler\",\n                                        \"Fans Text Concatenate\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Fans\"\n                                    }\n                                ],\n                                \"https://github.com/vanillacode314/SimpleWildcardsComfyUI\": [\n                                    [\n                                        \"SimpleConcat\",\n                                        \"SimpleWildcard\"\n                                    ],\n                                    {\n                                        \"author\": \"VanillaCode314\",\n                                        \"description\": \"A simple wildcard node for ComfyUI. Can also be used a style prompt node.\",\n                                        \"nickname\": \"Simple Wildcard\",\n                                        \"title\": \"Simple Wildcard\",\n                                        \"title_aux\": \"Simple Wildcard\"\n                                    }\n                                ],\n                                \"https://github.com/vienteck/ComfyUI-Chat-GPT-Integration\": [\n                                    [\n                                        \"ChatGptPrompt\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Chat-GPT-Integration\"\n                                    }\n                                ],\n                                \"https://github.com/violet-chen/comfyui-psd2png\": [\n                                    [\n                                        \"Psd2Png\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui-psd2png\"\n                                    }\n                                ],\n                                \"https://github.com/wallish77/wlsh_nodes\": [\n                                    [\n                                        \"Alternating KSampler (WLSH)\",\n                                        \"Build Filename String (WLSH)\",\n                                        \"CLIP +/- w/Text Unified (WLSH)\",\n                                        \"CLIP Positive-Negative (WLSH)\",\n                                        \"CLIP Positive-Negative XL (WLSH)\",\n                                        \"CLIP Positive-Negative XL w/Text (WLSH)\",\n                                        \"CLIP Positive-Negative w/Text (WLSH)\",\n                                        \"Checkpoint Loader w/Name (WLSH)\",\n                                        \"Empty Latent by Pixels (WLSH)\",\n                                        \"Empty Latent by Ratio (WLSH)\",\n                                        \"Empty Latent by Size (WLSH)\",\n                                        \"Generate Border Mask (WLSH)\",\n                                        \"Grayscale Image (WLSH)\",\n                                        \"Image Load with Metadata (WLSH)\",\n                                        \"Image Save with Prompt (WLSH)\",\n                                        \"Image Save with Prompt File (WLSH)\",\n                                        \"Image Save with Prompt/Info (WLSH)\",\n                                        \"Image Save with Prompt/Info File (WLSH)\",\n                                        \"Image Scale By Factor (WLSH)\",\n                                        \"Image Scale by Shortside (WLSH)\",\n                                        \"KSamplerAdvanced (WLSH)\",\n                                        \"Multiply Integer (WLSH)\",\n                                        \"Outpaint to Image (WLSH)\",\n                                        \"Prompt Weight (WLSH)\",\n                                        \"Quick Resolution Multiply (WLSH)\",\n                                        \"Resolutions by Ratio (WLSH)\",\n                                        \"SDXL Quick Empty Latent (WLSH)\",\n                                        \"SDXL Quick Image Scale (WLSH)\",\n                                        \"SDXL Resolutions (WLSH)\",\n                                        \"SDXL Steps (WLSH)\",\n                                        \"Save Positive Prompt(WLSH)\",\n                                        \"Save Prompt (WLSH)\",\n                                        \"Save Prompt/Info (WLSH)\",\n                                        \"Seed and Int (WLSH)\",\n                                        \"Seed to Number (WLSH)\",\n                                        \"Simple Pattern Replace (WLSH)\",\n                                        \"Simple String Combine (WLSH)\",\n                                        \"Time String (WLSH)\",\n                                        \"Upscale by Factor with Model (WLSH)\",\n                                        \"VAE Encode for Inpaint w/Padding (WLSH)\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"wlsh_nodes\"\n                                    }\n                                ],\n                                \"https://github.com/whatbirdisthat/cyberdolphin\": [\n                                    [\n                                        \"\\ud83d\\udc2c Gradio ChatInterface\",\n                                        \"\\ud83d\\udc2c OpenAI Advanced\",\n                                        \"\\ud83d\\udc2c OpenAI Compatible\",\n                                        \"\\ud83d\\udc2c OpenAI DALL\\u00b7E\",\n                                        \"\\ud83d\\udc2c OpenAI Simple\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"cyberdolphin\"\n                                    }\n                                ],\n                                \"https://github.com/whmc76/ComfyUI-Openpose-Editor-Plus\": [\n                                    [\n                                        \"CDL.OpenPoseEditorPlus\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Openpose-Editor-Plus\"\n                                    }\n                                ],\n                                \"https://github.com/wmatson/easy-comfy-nodes\": [\n                                    [\n                                        \"EZAssocDictNode\",\n                                        \"EZAssocImgNode\",\n                                        \"EZAssocStrNode\",\n                                        \"EZEmptyDictNode\",\n                                        \"EZHttpPostNode\",\n                                        \"EZLoadImgBatchFromUrlsNode\",\n                                        \"EZLoadImgFromUrlNode\",\n                                        \"EZRemoveImgBackground\",\n                                        \"EZS3Uploader\",\n                                        \"EZVideoCombiner\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"easy-comfy-nodes\"\n                                    }\n                                ],\n                                \"https://github.com/wolfden/ComfyUi_PromptStylers\": [\n                                    [\n                                        \"SDXLPromptStylerAll\",\n                                        \"SDXLPromptStylerHorror\",\n                                        \"SDXLPromptStylerMisc\",\n                                        \"SDXLPromptStylerbyArtist\",\n                                        \"SDXLPromptStylerbyCamera\",\n                                        \"SDXLPromptStylerbyComposition\",\n                                        \"SDXLPromptStylerbyCyberpunkSurrealism\",\n                                        \"SDXLPromptStylerbyDepth\",\n                                        \"SDXLPromptStylerbyEnvironment\",\n                                        \"SDXLPromptStylerbyFantasySetting\",\n                                        \"SDXLPromptStylerbyFilter\",\n                                        \"SDXLPromptStylerbyFocus\",\n                                        \"SDXLPromptStylerbyImpressionism\",\n                                        \"SDXLPromptStylerbyLighting\",\n                                        \"SDXLPromptStylerbyMileHigh\",\n                                        \"SDXLPromptStylerbyMood\",\n                                        \"SDXLPromptStylerbyMythicalCreature\",\n                                        \"SDXLPromptStylerbyOriginal\",\n                                        \"SDXLPromptStylerbyQuantumRealism\",\n                                        \"SDXLPromptStylerbySteamPunkRealism\",\n                                        \"SDXLPromptStylerbySubject\",\n                                        \"SDXLPromptStylerbySurrealism\",\n                                        \"SDXLPromptStylerbyTheme\",\n                                        \"SDXLPromptStylerbyTimeofDay\",\n                                        \"SDXLPromptStylerbyWyvern\",\n                                        \"SDXLPromptbyCelticArt\",\n                                        \"SDXLPromptbyContemporaryNordicArt\",\n                                        \"SDXLPromptbyFashionArt\",\n                                        \"SDXLPromptbyGothicRevival\",\n                                        \"SDXLPromptbyIrishFolkArt\",\n                                        \"SDXLPromptbyRomanticNationalismArt\",\n                                        \"SDXLPromptbySportsArt\",\n                                        \"SDXLPromptbyStreetArt\",\n                                        \"SDXLPromptbyVikingArt\",\n                                        \"SDXLPromptbyWildlifeArt\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SDXL Prompt Styler (customized version by wolfden)\"\n                                    }\n                                ],\n                                \"https://github.com/wolfden/ComfyUi_String_Function_Tree\": [\n                                    [\n                                        \"StringFunction\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUi_String_Function_Tree\"\n                                    }\n                                ],\n                                \"https://github.com/wsippel/comfyui_ws/raw/main/sdxl_utility.py\": [\n                                    [\n                                        \"SDXLResolutionPresets\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SDXLResolutionPresets\"\n                                    }\n                                ],\n                                \"https://github.com/wutipong/ComfyUI-TextUtils\": [\n                                    [\n                                        \"Text Utils - Join N-Elements of String List\",\n                                        \"Text Utils - Join String List\",\n                                        \"Text Utils - Join Strings\",\n                                        \"Text Utils - Split String to List\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-TextUtils\"\n                                    }\n                                ],\n                                \"https://github.com/wwwins/ComfyUI-Simple-Aspect-Ratio\": [\n                                    [\n                                        \"SimpleAspectRatio\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Simple-Aspect-Ratio\"\n                                    }\n                                ],\n                                \"https://github.com/xXAdonesXx/NodeGPT\": [\n                                    [\n                                        \"AppendAgent\",\n                                        \"Assistant\",\n                                        \"Chat\",\n                                        \"ChatGPT\",\n                                        \"CombineInput\",\n                                        \"Conditioning\",\n                                        \"CostumeAgent_1\",\n                                        \"CostumeAgent_2\",\n                                        \"CostumeMaster_1\",\n                                        \"Critic\",\n                                        \"DisplayString\",\n                                        \"DisplayTextAsImage\",\n                                        \"EVAL\",\n                                        \"Engineer\",\n                                        \"Executor\",\n                                        \"GroupChat\",\n                                        \"Image_generation_Conditioning\",\n                                        \"LM_Studio\",\n                                        \"LoadAPIconfig\",\n                                        \"LoadTXT\",\n                                        \"MemGPT\",\n                                        \"Memory_Excel\",\n                                        \"Model_1\",\n                                        \"Ollama\",\n                                        \"Output2String\",\n                                        \"Planner\",\n                                        \"Scientist\",\n                                        \"TextCombine\",\n                                        \"TextGeneration\",\n                                        \"TextGenerator\",\n                                        \"TextInput\",\n                                        \"TextOutput\",\n                                        \"UserProxy\",\n                                        \"llama-cpp\",\n                                        \"llava\",\n                                        \"oobaboogaOpenAI\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"NodeGPT\"\n                                    }\n                                ],\n                                \"https://github.com/xiaoxiaodesha/hd_node\": [\n                                    [\n                                        \"Combine HDMasks\",\n                                        \"Cover HDMasks\",\n                                        \"HD FaceIndex\",\n                                        \"HD GetMaskArea\",\n                                        \"HD Image Levels\",\n                                        \"HD SmoothEdge\",\n                                        \"HD UltimateSDUpscale\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"hd-nodes-comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/yffyhk/comfyui_auto_danbooru\": [\n                                    [\n                                        \"GetDanbooru\",\n                                        \"TagEncode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui_auto_danbooru\"\n                                    }\n                                ],\n                                \"https://github.com/yolain/ComfyUI-Easy-Use\": [\n                                    [\n                                        \"dynamicThresholdingFull\",\n                                        \"easy LLLiteLoader\",\n                                        \"easy XYInputs: CFG Scale\",\n                                        \"easy XYInputs: Checkpoint\",\n                                        \"easy XYInputs: ControlNet\",\n                                        \"easy XYInputs: Denoise\",\n                                        \"easy XYInputs: Lora\",\n                                        \"easy XYInputs: ModelMergeBlocks\",\n                                        \"easy XYInputs: NegativeCond\",\n                                        \"easy XYInputs: NegativeCondList\",\n                                        \"easy XYInputs: PositiveCond\",\n                                        \"easy XYInputs: PositiveCondList\",\n                                        \"easy XYInputs: PromptSR\",\n                                        \"easy XYInputs: Sampler/Scheduler\",\n                                        \"easy XYInputs: Seeds++ Batch\",\n                                        \"easy XYInputs: Steps\",\n                                        \"easy XYPlot\",\n                                        \"easy XYPlotAdvanced\",\n                                        \"easy a1111Loader\",\n                                        \"easy boolean\",\n                                        \"easy cleanGpuUsed\",\n                                        \"easy comfyLoader\",\n                                        \"easy compare\",\n                                        \"easy controlnetLoader\",\n                                        \"easy controlnetLoaderADV\",\n                                        \"easy convertAnything\",\n                                        \"easy detailerFix\",\n                                        \"easy float\",\n                                        \"easy fooocusInpaintLoader\",\n                                        \"easy fullLoader\",\n                                        \"easy fullkSampler\",\n                                        \"easy globalSeed\",\n                                        \"easy hiresFix\",\n                                        \"easy if\",\n                                        \"easy imageInsetCrop\",\n                                        \"easy imagePixelPerfect\",\n                                        \"easy imageRemoveBG\",\n                                        \"easy imageSave\",\n                                        \"easy imageScaleDown\",\n                                        \"easy imageScaleDownBy\",\n                                        \"easy imageScaleDownToSize\",\n                                        \"easy imageSize\",\n                                        \"easy imageSizeByLongerSide\",\n                                        \"easy imageSizeBySide\",\n                                        \"easy imageSwitch\",\n                                        \"easy imageToMask\",\n                                        \"easy int\",\n                                        \"easy isSDXL\",\n                                        \"easy joinImageBatch\",\n                                        \"easy kSampler\",\n                                        \"easy kSamplerDownscaleUnet\",\n                                        \"easy kSamplerInpainting\",\n                                        \"easy kSamplerSDTurbo\",\n                                        \"easy kSamplerTiled\",\n                                        \"easy latentCompositeMaskedWithCond\",\n                                        \"easy latentNoisy\",\n                                        \"easy loraStack\",\n                                        \"easy negative\",\n                                        \"easy pipeIn\",\n                                        \"easy pipeOut\",\n                                        \"easy pipeToBasicPipe\",\n                                        \"easy portraitMaster\",\n                                        \"easy poseEditor\",\n                                        \"easy positive\",\n                                        \"easy preDetailerFix\",\n                                        \"easy preSampling\",\n                                        \"easy preSamplingAdvanced\",\n                                        \"easy preSamplingDynamicCFG\",\n                                        \"easy preSamplingSdTurbo\",\n                                        \"easy promptList\",\n                                        \"easy rangeFloat\",\n                                        \"easy rangeInt\",\n                                        \"easy samLoaderPipe\",\n                                        \"easy seed\",\n                                        \"easy showAnything\",\n                                        \"easy showLoaderSettingsNames\",\n                                        \"easy showSpentTime\",\n                                        \"easy string\",\n                                        \"easy stylesSelector\",\n                                        \"easy svdLoader\",\n                                        \"easy ultralyticsDetectorPipe\",\n                                        \"easy unSampler\",\n                                        \"easy wildcards\",\n                                        \"easy xyAny\",\n                                        \"easy zero123Loader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Easy Use\"\n                                    }\n                                ],\n                                \"https://github.com/yolanother/DTAIComfyImageSubmit\": [\n                                    [\n                                        \"DTSimpleSubmitImage\",\n                                        \"DTSubmitImage\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfy AI DoubTech.ai Image Sumission Node\"\n                                    }\n                                ],\n                                \"https://github.com/yolanother/DTAIComfyLoaders\": [\n                                    [\n                                        \"DTCLIPLoader\",\n                                        \"DTCLIPVisionLoader\",\n                                        \"DTCheckpointLoader\",\n                                        \"DTCheckpointLoaderSimple\",\n                                        \"DTControlNetLoader\",\n                                        \"DTDiffControlNetLoader\",\n                                        \"DTDiffusersLoader\",\n                                        \"DTGLIGENLoader\",\n                                        \"DTLoadImage\",\n                                        \"DTLoadImageMask\",\n                                        \"DTLoadLatent\",\n                                        \"DTLoraLoader\",\n                                        \"DTLorasLoader\",\n                                        \"DTStyleModelLoader\",\n                                        \"DTUpscaleModelLoader\",\n                                        \"DTVAELoader\",\n                                        \"DTunCLIPCheckpointLoader\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfy UI Online Loaders\"\n                                    }\n                                ],\n                                \"https://github.com/yolanother/DTAIComfyPromptAgent\": [\n                                    [\n                                        \"DTPromptAgent\",\n                                        \"DTPromptAgentString\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfy UI Prompt Agent\"\n                                    }\n                                ],\n                                \"https://github.com/yolanother/DTAIComfyQRCodes\": [\n                                    [\n                                        \"QRCode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfy UI QR Codes\"\n                                    }\n                                ],\n                                \"https://github.com/yolanother/DTAIComfyVariables\": [\n                                    [\n                                        \"DTCLIPTextEncode\",\n                                        \"DTSingleLineStringVariable\",\n                                        \"DTSingleLineStringVariableNoClip\",\n                                        \"FloatVariable\",\n                                        \"IntVariable\",\n                                        \"StringFormat\",\n                                        \"StringFormatSingleLine\",\n                                        \"StringVariable\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Variables for Comfy UI\"\n                                    }\n                                ],\n                                \"https://github.com/yolanother/DTAIImageToTextNode\": [\n                                    [\n                                        \"DTAIImageToTextNode\",\n                                        \"DTAIImageUrlToTextNode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Image to Text Node\"\n                                    }\n                                ],\n                                \"https://github.com/youyegit/tdxh_node_comfyui\": [\n                                    [\n                                        \"TdxhBoolNumber\",\n                                        \"TdxhClipVison\",\n                                        \"TdxhControlNetApply\",\n                                        \"TdxhControlNetProcessor\",\n                                        \"TdxhFloatInput\",\n                                        \"TdxhImageToSize\",\n                                        \"TdxhImageToSizeAdvanced\",\n                                        \"TdxhImg2ImgLatent\",\n                                        \"TdxhIntInput\",\n                                        \"TdxhLoraLoader\",\n                                        \"TdxhOnOrOff\",\n                                        \"TdxhReference\",\n                                        \"TdxhStringInput\",\n                                        \"TdxhStringInputTranslator\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"tdxh_node_comfyui\"\n                                    }\n                                ],\n                                \"https://github.com/yuvraj108c/ComfyUI-Pronodes\": [\n                                    [\n                                        \"LoadYoutubeVideoNode\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI-Pronodes\"\n                                    }\n                                ],\n                                \"https://github.com/yuvraj108c/ComfyUI-Whisper\": [\n                                    [\n                                        \"Add Subtitles To Background\",\n                                        \"Add Subtitles To Frames\",\n                                        \"Apply Whisper\",\n                                        \"Resize Cropped Subtitles\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Whisper\"\n                                    }\n                                ],\n                                \"https://github.com/zcfrank1st/Comfyui-Toolbox\": [\n                                    [\n                                        \"PreviewJson\",\n                                        \"PreviewVideo\",\n                                        \"SaveJson\",\n                                        \"TestJsonPreview\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfyui-Toolbox\"\n                                    }\n                                ],\n                                \"https://github.com/zcfrank1st/Comfyui-Yolov8\": [\n                                    [\n                                        \"Yolov8Detection\",\n                                        \"Yolov8Segmentation\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI Yolov8\"\n                                    }\n                                ],\n                                \"https://github.com/zcfrank1st/comfyui_visual_anagrams\": [\n                                    [\n                                        \"VisualAnagramsAnimate\",\n                                        \"VisualAnagramsSample\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"comfyui_visual_anagram\"\n                                    }\n                                ],\n                                \"https://github.com/zer0TF/cute-comfy\": [\n                                    [\n                                        \"Cute.Placeholder\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Cute Comfy\"\n                                    }\n                                ],\n                                \"https://github.com/zfkun/ComfyUI_zfkun\": [\n                                    [\n                                        \"ZFLoadImagePath\",\n                                        \"ZFPreviewText\",\n                                        \"ZFPreviewTextMultiline\",\n                                        \"ZFShareScreen\",\n                                        \"ZFTextTranslation\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI_zfkun\"\n                                    }\n                                ],\n                                \"https://github.com/zhongpei/ComfyUI-InstructIR\": [\n                                    [\n                                        \"InstructIRProcess\",\n                                        \"LoadInstructIRModel\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"ComfyUI for InstructIR\"\n                                    }\n                                ],\n                                \"https://github.com/zhongpei/Comfyui_image2prompt\": [\n                                    [\n                                        \"Image2Text\",\n                                        \"LoadImage2TextModel\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"Comfyui_image2prompt\"\n                                    }\n                                ],\n                                \"https://github.com/zhuanqianfish/ComfyUI-EasyNode\": [\n                                    [\n                                        \"EasyCaptureNode\",\n                                        \"EasyVideoOutputNode\",\n                                        \"SendImageWebSocket\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"EasyCaptureNode for ComfyUI\"\n                                    }\n                                ],\n                                \"https://raw.githubusercontent.com/throttlekitty/SDXLCustomAspectRatio/main/SDXLAspectRatio.py\": [\n                                    [\n                                        \"SDXLAspectRatio\"\n                                    ],\n                                    {\n                                        \"title_aux\": \"SDXLCustomAspectRatio\"\n                                    }\n                                ]\n                            }\n                            \"\"\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Configs/ApiOptions.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Configs;\n\n/// <summary>\n/// Configuration options for API services\n/// </summary>\npublic record ApiOptions\n{\n    /// <summary>\n    /// Base URL for Lykos Authentication API\n    /// </summary>\n    public Uri LykosAuthApiBaseUrl { get; init; } = new(\"https://auth.lykos.ai\");\n\n    /// <summary>\n    /// Base URL for Lykos Analytics API\n    /// </summary>\n    public Uri LykosAnalyticsApiBaseUrl { get; init; } = new(\"https://analytics.lykos.ai\");\n\n    /// <summary>\n    /// Base URL for Lykos Account API\n    /// </summary>\n    public Uri LykosAccountApiBaseUrl { get; init; } = new(\"https://account.lykos.ai/\");\n\n    /// <summary>\n    /// Base URL for PromptGen API\n    /// </summary>\n    public Uri LykosPromptGenApiBaseUrl { get; init; } = new(\"https://promptgen.lykos.ai/api\");\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Configs/DebugOptions.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Configs;\n\npublic class DebugOptions\n{\n    /// <summary>\n    /// Sets up LiteDB to use a temporary database file on each run\n    /// </summary>\n    public bool TempDatabase { get; set; }\n    \n    /// <summary>\n    /// Always show the one-click install page on launch\n    /// </summary>\n    public bool ShowOneClickInstall { get; set; }\n    \n    /// <summary>\n    /// Override the default update manifest url\n    /// (https://cdn.lykos.ai/update.json)\n    /// </summary>\n    public string? UpdateManifestUrl { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/ConnectedModelInfo.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class ConnectedModelInfo : IEquatable<ConnectedModelInfo>\n{\n    [JsonIgnore]\n    public const string FileExtension = \".cm-info.json\";\n\n    public int? ModelId { get; set; }\n    public string ModelName { get; set; }\n    public string ModelDescription { get; set; }\n    public bool Nsfw { get; set; }\n    public string[] Tags { get; set; }\n    public CivitModelType ModelType { get; set; }\n    public int? VersionId { get; set; }\n    public string VersionName { get; set; }\n    public string? VersionDescription { get; set; }\n    public string? BaseModel { get; set; }\n    public CivitFileMetadata? FileMetadata { get; set; }\n    public DateTimeOffset ImportedAt { get; set; }\n    public CivitFileHashes Hashes { get; set; }\n    public string[]? TrainedWords { get; set; }\n    public CivitModelStats? Stats { get; set; }\n\n    // User settings\n    public string? UserTitle { get; set; }\n    public string? ThumbnailImageUrl { get; set; }\n    public InferenceDefaults? InferenceDefaults { get; set; }\n\n    public ConnectedModelSource? Source { get; set; } = ConnectedModelSource.Civitai;\n\n    public ConnectedModelInfo() { }\n\n    public ConnectedModelInfo(\n        CivitModel civitModel,\n        CivitModelVersion civitModelVersion,\n        CivitFile civitFile,\n        DateTimeOffset importedAt\n    )\n    {\n        ModelId = civitModel.Id;\n        ModelName = civitModel.Name;\n        ModelDescription = civitModel.Description ?? string.Empty;\n        Nsfw = civitModel.Nsfw;\n        Tags = civitModel.Tags;\n        ModelType = civitModel.Type;\n        VersionId = civitModelVersion.Id;\n        VersionName = civitModelVersion.Name;\n        VersionDescription = civitModelVersion.Description;\n        ImportedAt = importedAt;\n        BaseModel = civitModelVersion.BaseModel;\n        FileMetadata = civitFile.Metadata;\n        Hashes = civitFile.Hashes;\n        TrainedWords = civitModelVersion.TrainedWords;\n        Stats = civitModel.Stats;\n        Source = ConnectedModelSource.Civitai;\n    }\n\n    public ConnectedModelInfo(\n        CivitModel civitModel,\n        CivitModelVersion civitModelVersion,\n        CivitFile civitFile,\n        DateTimeOffset importedAt,\n        InferenceDefaults? inferenceDefaults\n    )\n    {\n        ModelId = civitModel.Id;\n        ModelName = civitModel.Name;\n        ModelDescription = civitModel.Description ?? string.Empty;\n        Nsfw = civitModel.Nsfw;\n        Tags = civitModel.Tags;\n        ModelType = civitModel.Type;\n        VersionId = civitModelVersion.Id;\n        VersionName = civitModelVersion.Name;\n        VersionDescription = civitModelVersion.Description;\n        ImportedAt = importedAt;\n        BaseModel = civitModelVersion.BaseModel;\n        FileMetadata = civitFile.Metadata;\n        Hashes = civitFile.Hashes;\n        TrainedWords = civitModelVersion.TrainedWords;\n        Stats = civitModel.Stats;\n        Source = ConnectedModelSource.Civitai;\n        InferenceDefaults = inferenceDefaults;\n    }\n\n    public ConnectedModelInfo(\n        OpenModelDbKeyedModel model,\n        OpenModelDbResource resource,\n        DateTimeOffset importedAt\n    )\n    {\n        ModelName = model.Id;\n        ModelDescription = model.Description ?? string.Empty;\n        VersionName = model.Name ?? string.Empty;\n        Tags = model.Tags?.ToArray() ?? [];\n        ImportedAt = importedAt;\n        Hashes = new CivitFileHashes { SHA256 = resource.Sha256 };\n        ThumbnailImageUrl = resource.Urls?.FirstOrDefault();\n        ModelType = CivitModelType.Upscaler;\n        Source = ConnectedModelSource.OpenModelDb;\n    }\n\n    public static ConnectedModelInfo? FromJson(string json)\n    {\n        return JsonSerializer.Deserialize<ConnectedModelInfo>(\n            json,\n            new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }\n        );\n    }\n\n    /// <summary>\n    /// Saves the model info to a json file in the specified directory.\n    /// Overwrites existing files.\n    /// </summary>\n    /// <param name=\"directoryPath\">Path of directory to save file</param>\n    /// <param name=\"modelFileName\">Model file name without extensions</param>\n    public async Task SaveJsonToDirectory(string directoryPath, string modelFileName)\n    {\n        var name = modelFileName + FileExtension;\n        var json = JsonSerializer.Serialize(this);\n        await File.WriteAllTextAsync(Path.Combine(directoryPath, name), json);\n    }\n\n    [JsonIgnore]\n    public string TrainedWordsString => TrainedWords != null ? string.Join(\", \", TrainedWords) : string.Empty;\n\n    public bool Equals(ConnectedModelInfo? other)\n    {\n        if (other is null)\n            return false;\n        if (ReferenceEquals(this, other))\n            return true;\n        return Comparer.Equals(this, other);\n    }\n\n    public override bool Equals(object? obj)\n    {\n        if (obj is null)\n            return false;\n        if (ReferenceEquals(this, obj))\n            return true;\n        if (obj.GetType() != GetType())\n            return false;\n        return Equals((ConnectedModelInfo)obj);\n    }\n\n    public override int GetHashCode()\n    {\n        return Comparer.GetHashCode(this);\n    }\n\n    public static bool operator ==(ConnectedModelInfo? left, ConnectedModelInfo? right)\n    {\n        return Equals(left, right);\n    }\n\n    public static bool operator !=(ConnectedModelInfo? left, ConnectedModelInfo? right)\n    {\n        return !Equals(left, right);\n    }\n\n    private sealed class ConnectedModelInfoEqualityComparer : IEqualityComparer<ConnectedModelInfo>\n    {\n        public bool Equals(ConnectedModelInfo? x, ConnectedModelInfo? y)\n        {\n            if (ReferenceEquals(x, y))\n                return true;\n            if (x is null)\n                return false;\n            if (y is null)\n                return false;\n            if (x.GetType() != y.GetType())\n                return false;\n\n            return x.ModelId == y.ModelId\n                && x.ModelName == y.ModelName\n                && x.ModelDescription == y.ModelDescription\n                && x.Nsfw == y.Nsfw\n                && x.Tags?.SequenceEqual(y.Tags ?? []) is null or true\n                && x.ModelType == y.ModelType\n                && x.VersionId == y.VersionId\n                && x.VersionName == y.VersionName\n                && x.VersionDescription == y.VersionDescription\n                && x.BaseModel == y.BaseModel\n                && x.FileMetadata == y.FileMetadata\n                && x.ImportedAt.Equals(y.ImportedAt)\n                && x.Hashes == y.Hashes\n                && x.TrainedWords?.SequenceEqual(y.TrainedWords ?? []) is null or true\n                && x.Stats == y.Stats\n                && x.UserTitle == y.UserTitle\n                && x.ThumbnailImageUrl == y.ThumbnailImageUrl\n                && x.InferenceDefaults == y.InferenceDefaults\n                && x.Source == y.Source;\n        }\n\n        public int GetHashCode(ConnectedModelInfo obj)\n        {\n            var hashCode = new HashCode();\n            hashCode.Add(obj.ModelId);\n            hashCode.Add(obj.ModelName);\n            hashCode.Add(obj.ModelDescription);\n            hashCode.Add(obj.Nsfw);\n            hashCode.Add(obj.Tags);\n            hashCode.Add((int)obj.ModelType);\n            hashCode.Add(obj.VersionId);\n            hashCode.Add(obj.VersionName);\n            hashCode.Add(obj.VersionDescription);\n            hashCode.Add(obj.BaseModel);\n            hashCode.Add(obj.FileMetadata);\n            hashCode.Add(obj.ImportedAt);\n            hashCode.Add(obj.Hashes);\n            hashCode.Add(obj.TrainedWords);\n            hashCode.Add(obj.Stats);\n            hashCode.Add(obj.UserTitle);\n            hashCode.Add(obj.ThumbnailImageUrl);\n            hashCode.Add(obj.InferenceDefaults);\n            hashCode.Add(obj.Source);\n            return hashCode.ToHashCode();\n        }\n    }\n\n    public static IEqualityComparer<ConnectedModelInfo> Comparer { get; } =\n        new ConnectedModelInfoEqualityComparer();\n}\n\n[JsonSourceGenerationOptions(\n    GenerationMode = JsonSourceGenerationMode.Default,\n    WriteIndented = true,\n    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull\n)]\n[JsonSerializable(typeof(ConnectedModelInfo))]\ninternal partial class ConnectedModelInfoSerializerContext : JsonSerializerContext;\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/ConnectedModelSource.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic enum ConnectedModelSource\n{\n    Civitai,\n    OpenModelDb,\n    Other\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/CustomVersion.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic class CustomVersion : IComparable<CustomVersion>\n{\n    public int Major { get; set; }\n    public int Minor { get; set; }\n    public int Patch { get; set; }\n    public string? PreRelease { get; set; }\n\n    public CustomVersion() { }\n\n    public CustomVersion(string versionString)\n    {\n        var parts = versionString.Split(new[] { '-', '.' }, StringSplitOptions.None);\n        Major = int.Parse(parts[0]);\n        Minor = int.Parse(parts[1]);\n        Patch = int.Parse(parts[2]);\n        PreRelease = parts.Length > 3 ? string.Join(\".\", parts.Skip(3)) : null;\n    }\n\n    public int CompareTo(CustomVersion? other)\n    {\n        var result = Major.CompareTo(other?.Major);\n        if (result != 0)\n            return result;\n\n        result = Minor.CompareTo(other?.Minor);\n        if (result != 0)\n            return result;\n\n        result = Patch.CompareTo(other?.Patch);\n        if (result != 0)\n            return result;\n\n        switch (PreRelease)\n        {\n            case null when other?.PreRelease == null:\n                return 0;\n            case null:\n                return 1;\n        }\n\n        if (other?.PreRelease == null)\n            return -1;\n\n        return string.Compare(PreRelease, other.PreRelease, StringComparison.Ordinal);\n    }\n\n    public static bool operator <(CustomVersion v1, CustomVersion v2)\n    {\n        return v1.CompareTo(v2) < 0;\n    }\n\n    public static bool operator >(CustomVersion v1, CustomVersion v2)\n    {\n        return v1.CompareTo(v2) > 0;\n    }\n\n    public static bool operator <=(CustomVersion v1, CustomVersion v2)\n    {\n        return v1.CompareTo(v2) <= 0;\n    }\n\n    public static bool operator >=(CustomVersion v1, CustomVersion v2)\n    {\n        return v1.CompareTo(v2) >= 0;\n    }\n\n    public override string ToString()\n    {\n        return $\"{Major}.{Minor}.{Patch}\" + (PreRelease != null ? $\"-{PreRelease}\" : string.Empty);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Database/CivitBaseModelTypeCacheEntry.cs",
    "content": "namespace StabilityMatrix.Core.Models.Database;\n\npublic class CivitBaseModelTypeCacheEntry\n{\n    public required string Id { get; set; }\n    public List<string> ModelTypes { get; set; } = [];\n    public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Database/GitCommit.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Database;\n\npublic class GitCommit\n{\n    public string? Sha { get; set; }\n\n    [JsonIgnore]\n    public string ShortSha => string.IsNullOrWhiteSpace(Sha) ? string.Empty : Sha[..7];\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Database/GithubCacheEntry.cs",
    "content": "﻿using LiteDB;\nusing Octokit;\n\nnamespace StabilityMatrix.Core.Models.Database;\n\npublic class GithubCacheEntry\n{\n    [BsonId]\n    public string CacheKey { get; set; }\n    public DateTimeOffset LastUpdated { get; set; } = DateTimeOffset.UtcNow;\n\n    public IEnumerable<Release> AllReleases { get; set; } = new List<Release>();\n    public IEnumerable<Branch> Branches { get; set; } = new List<Branch>();\n    public IEnumerable<GitCommit> Commits { get; set; } = new List<GitCommit>();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Database/InferenceProjectEntry.cs",
    "content": "﻿using System.Text.Json.Nodes;\nusing LiteDB;\n\nnamespace StabilityMatrix.Core.Models.Database;\n\npublic record InferenceProjectEntry\n{\n    [BsonId]\n    public required Guid Id { get; init; }\n\n    /// <summary>\n    /// Full path to the project file (.smproj)\n    /// </summary>\n    public required string FilePath { get; init; }\n\n    /// <summary>\n    /// Whether the project is open in the editor\n    /// </summary>\n    public bool IsOpen { get; set; }\n\n    /// <summary>\n    /// Whether the project is selected in the editor\n    /// </summary>\n    public bool IsSelected { get; set; }\n\n    /// <summary>\n    /// Current index of the tab\n    /// </summary>\n    public int CurrentTabIndex { get; set; } = -1;\n\n    /// <summary>\n    /// The current dock layout state\n    /// </summary>\n    public JsonObject? DockLayout { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Database/LocalImageFile.cs",
    "content": "﻿using System.Text.Json;\nusing DynamicData.Tests;\nusing MetadataExtractor.Formats.Exif;\nusing SkiaSharp;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing JsonSerializer = System.Text.Json.JsonSerializer;\nusing Size = System.Drawing.Size;\n\nnamespace StabilityMatrix.Core.Models.Database;\n\n/// <summary>\n/// Represents a locally indexed image file.\n/// </summary>\npublic record LocalImageFile\n{\n    public required string AbsolutePath { get; init; }\n\n    /// <summary>\n    /// Type of the model file.\n    /// </summary>\n    public LocalImageFileType ImageType { get; init; }\n\n    /// <summary>\n    /// Creation time of the file.\n    /// </summary>\n    public DateTimeOffset CreatedAt { get; init; }\n\n    /// <summary>\n    /// Last modified time of the file.\n    /// </summary>\n    public DateTimeOffset LastModifiedAt { get; init; }\n\n    /// <summary>\n    /// Generation parameters metadata of the file.\n    /// </summary>\n    public GenerationParameters? GenerationParameters { get; init; }\n\n    /// <summary>\n    /// Dimensions of the image\n    /// </summary>\n    public Size? ImageSize { get; init; }\n\n    /// <summary>\n    /// File name of the relative path.\n    /// </summary>\n    public string FileName => Path.GetFileName(AbsolutePath);\n\n    /// <summary>\n    /// File name of the relative path without extension.\n    /// </summary>\n    public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(AbsolutePath);\n\n    public (\n        string? Parameters,\n        string? ParametersJson,\n        string? SMProject,\n        string? ComfyNodes,\n        string? CivitParameters\n    ) ReadMetadata()\n    {\n        if (AbsolutePath.EndsWith(\"webp\"))\n        {\n            var paramsJson = ImageMetadata.ReadTextChunkFromWebp(\n                AbsolutePath,\n                ExifDirectoryBase.TagImageDescription\n            );\n            var smProj = ImageMetadata.ReadTextChunkFromWebp(AbsolutePath, ExifDirectoryBase.TagSoftware);\n\n            return (null, paramsJson, smProj, null, null);\n        }\n\n        using var stream = new FileStream(AbsolutePath, FileMode.Open, FileAccess.Read, FileShare.Read);\n        using var reader = new BinaryReader(stream);\n\n        var parameters = ImageMetadata.ReadTextChunk(reader, \"parameters\");\n        var parametersJson = ImageMetadata.ReadTextChunk(reader, \"parameters-json\");\n        var smProject = ImageMetadata.ReadTextChunk(reader, \"smproj\");\n        var comfyNodes = ImageMetadata.ReadTextChunk(reader, \"prompt\");\n        var civitParameters = ImageMetadata.ReadTextChunk(reader, \"user_comment\");\n\n        return (\n            string.IsNullOrEmpty(parameters) ? null : parameters,\n            string.IsNullOrEmpty(parametersJson) ? null : parametersJson,\n            string.IsNullOrEmpty(smProject) ? null : smProject,\n            string.IsNullOrEmpty(comfyNodes) ? null : comfyNodes,\n            string.IsNullOrEmpty(civitParameters) ? null : civitParameters\n        );\n    }\n\n    public static LocalImageFile FromPath(FilePath filePath)\n    {\n        // TODO: Support other types\n        const LocalImageFileType imageType = LocalImageFileType.Inference | LocalImageFileType.TextToImage;\n\n        if (filePath.Extension.Equals(\".webp\", StringComparison.OrdinalIgnoreCase))\n        {\n            var paramsJson = ImageMetadata.ReadTextChunkFromWebp(\n                filePath,\n                ExifDirectoryBase.TagImageDescription\n            );\n\n            GenerationParameters? parameters = null;\n            try\n            {\n                parameters = string.IsNullOrWhiteSpace(paramsJson)\n                    ? null\n                    : JsonSerializer.Deserialize<GenerationParameters>(paramsJson);\n            }\n            catch (JsonException)\n            {\n                // just don't load params I guess, no logger here <_<\n            }\n\n            filePath.Info.Refresh();\n\n            return new LocalImageFile\n            {\n                AbsolutePath = filePath,\n                ImageType = imageType,\n                CreatedAt = filePath.Info.CreationTimeUtc,\n                LastModifiedAt = filePath.Info.LastWriteTimeUtc,\n                GenerationParameters = parameters,\n                ImageSize = new Size(parameters?.Width ?? 0, parameters?.Height ?? 0),\n            };\n        }\n\n        if (filePath.Extension.Equals(\".png\", StringComparison.OrdinalIgnoreCase))\n        {\n            // Get metadata\n            using var stream = filePath.Info.OpenRead();\n            using var reader = new BinaryReader(stream);\n\n            var imageSize = ImageMetadata.GetImageSize(reader);\n\n            var metadata = ImageMetadata.ReadTextChunk(reader, \"parameters-json\");\n\n            GenerationParameters? genParams;\n\n            if (!string.IsNullOrWhiteSpace(metadata))\n            {\n                genParams = JsonSerializer.Deserialize<GenerationParameters>(metadata);\n            }\n            else\n            {\n                metadata = ImageMetadata.ReadTextChunk(reader, \"parameters\");\n                if (string.IsNullOrWhiteSpace(metadata)) // if still empty, try civitai metadata (user_comment)\n                {\n                    metadata = ImageMetadata.ReadTextChunk(reader, \"user_comment\");\n                }\n                GenerationParameters.TryParse(metadata, out genParams);\n            }\n\n            filePath.Info.Refresh();\n\n            return new LocalImageFile\n            {\n                AbsolutePath = filePath,\n                ImageType = imageType,\n                CreatedAt = filePath.Info.CreationTimeUtc,\n                LastModifiedAt = filePath.Info.LastWriteTimeUtc,\n                GenerationParameters = genParams,\n                ImageSize = imageSize,\n            };\n        }\n\n        using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);\n        using var ms = new SKManagedStream(fs);\n        var codec = SKCodec.Create(ms);\n\n        return new LocalImageFile\n        {\n            AbsolutePath = filePath,\n            ImageType = imageType,\n            CreatedAt = filePath.Info.CreationTimeUtc,\n            LastModifiedAt = filePath.Info.LastWriteTimeUtc,\n            ImageSize = new Size { Height = codec.Info.Height, Width = codec.Info.Width },\n        };\n    }\n\n    public static readonly HashSet<string> SupportedImageExtensions =\n    [\n        \".png\",\n        \".jpg\",\n        \".jpeg\",\n        \".gif\",\n        \".webp\",\n    ];\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Database/LocalImageFileType.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Database;\n\n[Flags]\npublic enum LocalImageFileType : ulong\n{\n    // Source\n    Automatic = 1 << 1,\n    Comfy = 1 << 2,\n    Inference = 1 << 3,\n\n    // Generation Type\n    TextToImage = 1 << 10,\n    ImageToImage = 1 << 11\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Database/LocalModelFile.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing LiteDB;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Api;\n\nnamespace StabilityMatrix.Core.Models.Database;\n\n/// <summary>\n/// Represents a locally indexed model file.\n/// </summary>\npublic record LocalModelFile\n{\n    private sealed class RelativePathConnectedModelInfoEqualityComparer : IEqualityComparer<LocalModelFile>\n    {\n        public bool Equals(LocalModelFile? x, LocalModelFile? y)\n        {\n            if (ReferenceEquals(x, y))\n                return true;\n            if (ReferenceEquals(x, null))\n                return false;\n            if (ReferenceEquals(y, null))\n                return false;\n            if (x.GetType() != y.GetType())\n                return false;\n            return x.RelativePath == y.RelativePath\n                && Equals(x.ConnectedModelInfo, y.ConnectedModelInfo)\n                && x.HasUpdate == y.HasUpdate\n                && x.HasEarlyAccessUpdateOnly == y.HasEarlyAccessUpdateOnly;\n        }\n\n        public int GetHashCode(LocalModelFile obj)\n        {\n            return HashCode.Combine(\n                obj.RelativePath,\n                obj.ConnectedModelInfo,\n                obj.HasUpdate,\n                obj.HasEarlyAccessUpdateOnly\n            );\n        }\n    }\n\n    public static IEqualityComparer<LocalModelFile> RelativePathConnectedModelInfoComparer { get; } =\n        new RelativePathConnectedModelInfoEqualityComparer();\n\n    public virtual bool Equals(LocalModelFile? other)\n    {\n        if (ReferenceEquals(null, other))\n            return false;\n        if (ReferenceEquals(this, other))\n            return true;\n        return RelativePath == other.RelativePath\n            && ConnectedModelInfo == other.ConnectedModelInfo\n            && HasUpdate == other.HasUpdate\n            && HasEarlyAccessUpdateOnly == other.HasEarlyAccessUpdateOnly;\n    }\n\n    public override int GetHashCode()\n    {\n        return HashCode.Combine(RelativePath, ConnectedModelInfo, HasUpdate, HasEarlyAccessUpdateOnly);\n    }\n\n    /// <summary>\n    /// Relative path to the file from the root model directory.\n    /// </summary>\n    [BsonId]\n    public required string RelativePath { get; init; }\n\n    /// <summary>\n    /// Type of the model file.\n    /// </summary>\n    public required SharedFolderType SharedFolderType { get; set; }\n\n    /// <summary>\n    /// Optional connected model information.\n    /// </summary>\n    public ConnectedModelInfo? ConnectedModelInfo { get; set; }\n\n    /// <summary>\n    /// Optional preview image relative path.\n    /// </summary>\n    public string? PreviewImageRelativePath { get; set; }\n\n    /// <summary>\n    /// Optional preview image full path. Takes priority over <see cref=\"PreviewImageRelativePath\"/>.\n    /// </summary>\n    public string? PreviewImageFullPath { get; set; }\n\n    /// <summary>\n    /// Optional full path to the model's configuration (.yaml) file.\n    /// </summary>\n    public string? ConfigFullPath { get; set; }\n\n    /// <summary>\n    /// Whether or not an update is available for this model\n    /// </summary>\n    public bool HasUpdate { get; set; }\n\n    /// <summary>\n    /// Whether updates are limited to Early Access versions only.\n    /// </summary>\n    public bool HasEarlyAccessUpdateOnly { get; set; }\n\n    /// <summary>\n    /// Last time this model was checked for an update\n    /// </summary>\n    public DateTimeOffset LastUpdateCheck { get; set; }\n\n    /// <summary>\n    /// The latest CivitModel info\n    /// </summary>\n    [BsonRef(\"CivitModels\")]\n    public CivitModel? LatestModelInfo { get; set; }\n\n    /// <summary>\n    /// File name of the relative path.\n    /// </summary>\n    [BsonIgnore]\n    public string FileName => Path.GetFileName(RelativePath);\n\n    /// <summary>\n    /// File name of the relative path without extension.\n    /// </summary>\n    [BsonIgnore]\n    public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(RelativePath);\n\n    /// <summary>\n    /// Relative file path from the shared folder type model directory.\n    /// </summary>\n    [BsonIgnore]\n    public string RelativePathFromSharedFolder =>\n        Path.GetRelativePath(SharedFolderType.GetStringValue(), RelativePath);\n\n    /// <summary>\n    /// Blake3 hash of the file.\n    /// </summary>\n    public string? HashBlake3 => ConnectedModelInfo?.Hashes?.BLAKE3;\n\n    [BsonIgnore]\n    public bool IsSafetensorFile => Path.GetExtension(RelativePath) == \".safetensors\";\n\n    public string? HashSha256 => ConnectedModelInfo?.Hashes.SHA256;\n\n    [BsonIgnore]\n    public string? PreviewImageFullPathGlobal =>\n        PreviewImageFullPath ?? GetPreviewImageFullPath(GlobalConfig.ModelsDir);\n\n    [BsonIgnore]\n    public Uri? PreviewImageUriGlobal =>\n        PreviewImageFullPathGlobal == null ? null : new Uri(PreviewImageFullPathGlobal);\n\n    [BsonIgnore]\n    public string DisplayModelName => ConnectedModelInfo?.ModelName ?? FileNameWithoutExtension;\n\n    [BsonIgnore]\n    public string DisplayModelVersion => ConnectedModelInfo?.VersionName ?? string.Empty;\n\n    [BsonIgnore]\n    public string DisplayModelFileName => FileName;\n\n    [BsonIgnore]\n    public string DisplayConfigFileName => Path.GetFileName(ConfigFullPath) ?? string.Empty;\n\n    [BsonIgnore]\n    [MemberNotNullWhen(true, nameof(ConnectedModelInfo))]\n    public bool HasConnectedModel => ConnectedModelInfo != null;\n\n    [BsonIgnore]\n    [MemberNotNullWhen(true, nameof(ConnectedModelInfo))]\n    public bool HasCivitMetadata =>\n        HasConnectedModel\n        && ConnectedModelInfo.ModelId != null\n        && ConnectedModelInfo.Source == ConnectedModelSource.Civitai;\n\n    [BsonIgnore]\n    [MemberNotNullWhen(true, nameof(ConnectedModelInfo))]\n    public bool HasOpenModelDbMetadata =>\n        HasConnectedModel && ConnectedModelInfo.Source == ConnectedModelSource.OpenModelDb;\n\n    public string GetFullPath(string rootModelDirectory)\n    {\n        return Path.Combine(rootModelDirectory, RelativePath);\n    }\n\n    public string? GetPreviewImageFullPath(string rootModelDirectory)\n    {\n        if (PreviewImageFullPath != null)\n            return PreviewImageFullPath;\n\n        return PreviewImageRelativePath == null\n            ? null\n            : Path.Combine(rootModelDirectory, PreviewImageRelativePath);\n    }\n\n    public string GetConnectedModelInfoFullPath(string rootModelDirectory)\n    {\n        var modelNameNoExt = Path.GetFileNameWithoutExtension(RelativePath);\n        var modelParentDir = Path.GetDirectoryName(GetFullPath(rootModelDirectory)) ?? \"\";\n        return Path.Combine(modelParentDir, $\"{modelNameNoExt}.cm-info.json\");\n    }\n\n    public IEnumerable<string> GetDeleteFullPaths(string rootModelDirectory)\n    {\n        if (GetFullPath(rootModelDirectory) is { } filePath && File.Exists(filePath))\n        {\n            yield return filePath;\n        }\n\n        if (\n            HasConnectedModel\n            && GetConnectedModelInfoFullPath(rootModelDirectory) is { } cmInfoPath\n            && File.Exists(cmInfoPath)\n        )\n        {\n            yield return cmInfoPath;\n        }\n\n        var previewImagePath = GetPreviewImageFullPath(rootModelDirectory);\n        if (File.Exists(previewImagePath))\n        {\n            yield return previewImagePath;\n        }\n    }\n\n    public static readonly HashSet<string> SupportedCheckpointExtensions =\n    [\n        \".safetensors\",\n        \".pt\",\n        \".ckpt\",\n        \".pth\",\n        \".bin\",\n        \".sft\",\n        \".gguf\",\n    ];\n    public static readonly HashSet<string> SupportedImageExtensions = [\".png\", \".jpg\", \".jpeg\", \".webp\"];\n    public static readonly HashSet<string> SupportedMetadataExtensions = [\".json\"];\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Database/LocalModelFolder.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Database;\n\npublic class LocalModelFolder\n{\n    public required string RelativePath { get; set; }\n\n    public Dictionary<string, LocalModelFile> Files { get; set; } = [];\n\n    public Dictionary<string, LocalModelFolder> Folders { get; set; } = [];\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Database/PyPiCacheEntry.cs",
    "content": "﻿using LiteDB;\n\nnamespace StabilityMatrix.Core.Models.Database;\n\npublic class PyPiCacheEntry\n{\n    [BsonId]\n    public required string CacheKey { get; set; }\n    public required List<CustomVersion> Versions { get; set; }\n    public DateTimeOffset LastUpdated { get; set; } = DateTimeOffset.UtcNow;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/DimensionStringComparer.cs",
    "content": "﻿using System.Collections;\nusing System.Text.RegularExpressions;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic partial class DimensionStringComparer : IComparer, IComparer<string>\n{\n    public static readonly DimensionStringComparer Instance = new();\n\n    /// <summary>\n    /// Compares two dimension strings (like \"1024 x 768\") by the first numeric value.\n    /// </summary>\n    /// <param name=\"x\">First dimension string to compare</param>\n    /// <param name=\"y\">Second dimension string to compare</param>\n    /// <returns>\n    /// A negative value if x comes before y;\n    /// zero if x equals y;\n    /// a positive value if x comes after y\n    /// </returns>\n    public int Compare(object? x, object? y)\n    {\n        // Handle null cases\n        if (x == null && y == null)\n            return 0;\n        if (x == null)\n            return -1;\n        if (y == null)\n            return 1;\n\n        if (x is not string xStr || y is not string yStr)\n            throw new ArgumentException(\"Both arguments must be strings.\");\n\n        // Extract the first number from each string\n        var firstX = ExtractFirstNumber(xStr);\n        var firstY = ExtractFirstNumber(yStr);\n\n        // Compare the first numbers\n        return firstX.CompareTo(firstY);\n    }\n\n    public int Compare(string? x, string? y)\n    {\n        // Handle null cases\n        if (x == null && y == null)\n            return 0;\n        if (x == null)\n            return -1;\n        if (y == null)\n            return 1;\n\n        // Extract the first number from each string\n        var firstX = ExtractFirstNumber(x);\n        var firstY = ExtractFirstNumber(y);\n\n        // Compare the first numbers\n        return firstX.CompareTo(firstY);\n    }\n\n    /// <summary>\n    /// Extracts the first numeric value from a dimension string.\n    /// </summary>\n    /// <param name=\"dimensionString\">String in format like \"1024 x 768\"</param>\n    /// <returns>The first numeric value or 0 if no number is found</returns>\n    private static int ExtractFirstNumber(string dimensionString)\n    {\n        // Use regex to find the first number in the string\n        var match = NumberRegex().Match(dimensionString);\n\n        if (match.Success && int.TryParse(match.Value, out var result))\n        {\n            return result;\n        }\n\n        return 0; // Return 0 if no number found\n    }\n\n    [GeneratedRegex(@\"\\d+\")]\n    private static partial Regex NumberRegex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/DownloadPackageVersionOptions.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic class DownloadPackageVersionOptions\n{\n    public string? BranchName { get; set; }\n    public string? CommitHash { get; set; }\n    public string? VersionTag { get; set; }\n    public bool IsLatest { get; set; }\n    public bool IsPrerelease { get; set; }\n\n    public string GetReadableVersionString() =>\n        !string.IsNullOrWhiteSpace(VersionTag) ? VersionTag : $\"{BranchName}@{CommitHash?[..7]}\";\n\n    public string ReadableVersionString => GetReadableVersionString();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/EnvVarKeyPair.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic class EnvVarKeyPair\n{\n    public string Key { get; set; }\n    public string Value { get; set; }\n\n    public EnvVarKeyPair(string key = \"\", string value = \"\")\n    {\n        Key = key;\n        Value = value;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/ExtraPackageCommand.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic class ExtraPackageCommand\n{\n    public required string CommandName { get; set; }\n    public required Func<InstalledPackage, Task> Command { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/FDS/ComfyUiSelfStartSettings.cs",
    "content": "﻿// The MIT License (MIT)\n//\n// Copyright (c) 2024 Stability AI\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//     of this software and associated documentation files (the \"Software\"), to deal\n//     in the Software without restriction, including without limitation the rights\n//     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n//     copies of the Software, and to permit persons to whom the Software is\n//     furnished to do so, subject to the following conditions:\n//\n//     The above copyright notice and this permission notice shall be included in all\n//     copies or substantial portions of the Software.\n//\n//     THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n//     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n//     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n//     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n//     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n//     SOFTWARE.\n\n// from https://github.com/Stability-AI/StableSwarmUI/blob/master/src/BuiltinExtensions/ComfyUIBackend/ComfyUISelfStartBackend.cs\n\n\nusing FreneticUtilities.FreneticDataSyntax;\n\nnamespace StabilityMatrix.Core.Models.FDS;\n\npublic class ComfyUiSelfStartSettings : AutoConfiguration\n{\n    [ConfigComment(\n        \"The location of the 'main.py' file. Can be an absolute or relative path, but must end with 'main.py'.\\nIf you used the installer, this should be 'dlbackend/ComfyUI/main.py'.\"\n    )]\n    public string StartScript = \"\";\n\n    [ConfigComment(\"Any arguments to include in the launch script.\")]\n    public string ExtraArgs = \"\";\n\n    [ConfigComment(\n        \"If unchecked, the system will automatically add some relevant arguments to the comfy launch. If checked, automatic args (other than port) won't be added.\"\n    )]\n    public bool DisableInternalArgs = false;\n\n    [ConfigComment(\"If checked, will automatically keep the comfy backend up to date when launching.\")]\n    public bool AutoUpdate = true;\n\n    [ConfigComment(\n        \"If checked, tells Comfy to generate image previews. If unchecked, previews will not be generated, and images won't show up until they're done.\"\n    )]\n    public bool EnablePreviews = true;\n\n    [ConfigComment(\"Which GPU to use, if multiple are available.\")]\n    public int GPU_ID = 0;\n\n    [ConfigComment(\"How many extra requests may queue up on this backend while one is processing.\")]\n    public int OverQueue = 1;\n\n    [ConfigComment(\n        \"Whether the Comfy backend should automatically update nodes within Swarm's managed nodes folder.\\nYou can update every launch, never update automatically, or force-update (bypasses some common git issues).\"\n    )]\n    public string UpdateManagedNodes = \"true\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/FDS/StableSwarmSettings.cs",
    "content": "﻿// The MIT License (MIT)\n//\n// Copyright (c) 2024 Stability AI\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//     of this software and associated documentation files (the \"Software\"), to deal\n//     in the Software without restriction, including without limitation the rights\n//     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n//     copies of the Software, and to permit persons to whom the Software is\n//     furnished to do so, subject to the following conditions:\n//\n//     The above copyright notice and this permission notice shall be included in all\n//     copies or substantial portions of the Software.\n//\n//     THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n//     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n//     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n//     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n//     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n//     SOFTWARE.\n\n// from https://raw.githubusercontent.com/Stability-AI/StableSwarmUI/master/src/Core/Settings.cs\n\n\nusing FreneticUtilities.FreneticDataSyntax;\n\nnamespace StabilityMatrix.Core.Models.FDS;\n\n/// <summary>Central default settings list.</summary>\npublic class StableSwarmSettings : AutoConfiguration\n{\n    [ConfigComment(\"Settings related to file paths.\")]\n    public PathsData Paths = new();\n\n    [ConfigComment(\"Settings related to networking and the webserver.\")]\n    public NetworkData Network = new();\n\n    [ConfigComment(\"Restrictions to apply to default users.\")]\n    public UserRestriction DefaultUserRestriction = new();\n\n    [ConfigComment(\n        \"Default settings for users (unless the user modifies them, if so permitted).\\n(NOTE: Usually, don't edit this. Go to the 'User' tab to edit your User-Settings).\"\n    )]\n    public User DefaultUser = new();\n\n    [ConfigComment(\"Settings related to backends.\")]\n    public BackendData Backends = new();\n\n    [ConfigComment(\n        \"If this is set to 'true', hides the installer page. If 'false', the installer page will be shown.\"\n    )]\n    public bool IsInstalled = false;\n\n    [ConfigComment(\n        \"Ratelimit, in milliseconds, between Nvidia GPU status queries. Default is 1000 ms (1 second).\"\n    )]\n    public long NvidiaQueryRateLimitMS = 1000;\n\n    [ConfigComment(\n        \"How to launch the UI. If 'none', just quietly launch.\\nIf 'web', launch your web-browser to the page.\\nIf 'webinstall', launch web-browser to the install page.\\nIf 'electron', launch the UI in an electron window (NOT YET IMPLEMENTED).\"\n    )]\n    [ManualSettingsOptions(Impl = null, Vals = new string[] { \"none\", \"web\", \"webinstall\", \"electron\" })]\n    public string LaunchMode = \"webinstall\";\n\n    [ConfigComment(\"The minimum tier of logs that should be visible in the console.\\nDefault is 'info'.\")]\n    public string LogLevel = \"Info\";\n\n    [ConfigComment(\"Settings related to the User Interface.\")]\n    public UIData UI = new();\n\n    [ConfigComment(\"Settings related to webhooks.\")]\n    public WebHooksData WebHooks = new();\n\n    /// <summary>Settings related to backends.</summary>\n    public class BackendData : AutoConfiguration\n    {\n        [ConfigComment(\"How many times to retry initializing a backend before giving up. Default is 3.\")]\n        public int MaxBackendInitAttempts = 3;\n\n        [ConfigComment(\n            \"Safety check, the maximum duration all requests can be waiting for a backend before the system declares a backend handling failure.\"\n        )]\n        public int MaxTimeoutMinutes = 20;\n\n        [ConfigComment(\n            \"The maximum duration an individual request can be waiting on a backend to be available before giving up.\\n\"\n                + \"Not to be confused with 'MaxTimeoutMinutes' which requires backends be unresponsive for that duration, this duration includes requests that are merely waiting because other requests are queued.\"\n                + \"\\nDefaults to 60 * 24 * 7 = 1 week (ultra-long max queue duration).\"\n        )]\n        public int PerRequestTimeoutMinutes = 60 * 24 * 7;\n\n        [ConfigComment(\n            \"The maximum number of pending requests to continue forcing orderly processing of.\\nOver this limit, requests may start going out of order.\"\n        )]\n        public int MaxRequestsForcedOrder = 20;\n\n        [ConfigComment(\n            \"How many minutes to wait after the last generation before automatically freeing up VRAM (to prevent issues with other programs).\\nThis has the downside of a small added bit of time to load back onto VRAM at next usage.\\nUse a decimal number to free after seconds.\\nDefaults to 10 minutes.\"\n        )]\n        public double ClearVRAMAfterMinutes = 10;\n\n        [ConfigComment(\n            \"How many minutes to wait after the last generation before automatically freeing up system RAM (to prevent issues with other programs).\\nThis has the downside of causing models to fully load from data drive at next usage.\\nUse a decimal number to free after seconds.\\nDefaults to 60 minutes (one hour).\"\n        )]\n        public double ClearSystemRAMAfterMinutes = 60;\n    }\n\n    /// <summary>Settings related to networking and the webserver.</summary>\n    public class NetworkData : AutoConfiguration\n    {\n        [ConfigComment(\n            \"What web host address to use. `localhost` means your PC only.\"\n                + \"\\nLinux users may use `0.0.0.0` to mean accessible to anyone that can connect to your PC (ie LAN users, or the public if your firewall is open).\"\n                + \"\\nWindows users may use `*` for that, though it may require additional Windows firewall configuration.\"\n                + \"\\nAdvanced server users may wish to manually specify a host bind address here.\"\n        )]\n        public string Host = \"localhost\";\n\n        [ConfigComment(\"What web port to use. Default is '7801'.\")]\n        public int Port = 7801;\n\n        [ConfigComment(\n            \"If true, if the port is already in use, the server will try to find another port to use instead.\\nIf false, the server will fail to start if the port is already in use.\"\n        )]\n        public bool PortCanChange = true;\n\n        [ConfigComment(\n            \"Backends are automatically assigned unique ports. This value selects which port number to start the assignment from.\\nDefault is '7820'.\"\n        )]\n        public int BackendStartingPort = 7820;\n    }\n\n    /// <summary>Settings related to file paths.</summary>\n    public class PathsData : AutoConfiguration\n    {\n        [ConfigComment(\n            \"Root path for model files. Use a full-formed path (starting with '/' or a Windows drive like 'C:') to use an absolute path.\\nDefaults to 'Models'.\"\n        )]\n        public string ModelRoot = \"Models\";\n\n        [ConfigComment(\n            \"The model folder to use within 'ModelRoot'.\\nDefaults to 'Stable-Diffusion'.\\nAbsolute paths work too.\"\n        )]\n        public string SDModelFolder = \"Stable-Diffusion\";\n\n        [ConfigComment(\n            \"The LoRA (or related adapter type) model folder to use within 'ModelRoot'.\\nDefaults to 'Lora'.\\nAbsolute paths work too.\"\n        )]\n        public string SDLoraFolder = \"Lora\";\n\n        [ConfigComment(\n            \"The VAE (autoencoder) model folder to use within 'ModelRoot'.\\nDefaults to 'VAE'.\\nAbsolute paths work too.\"\n        )]\n        public string SDVAEFolder = \"VAE\";\n\n        [ConfigComment(\n            \"The Embedding (eg textual inversion) model folder to use within 'ModelRoot'.\\nDefaults to 'Embeddings'.\\nAbsolute paths work too.\"\n        )]\n        public string SDEmbeddingFolder = \"Embeddings\";\n\n        [ConfigComment(\n            \"The ControlNets model folder to use within 'ModelRoot'.\\nDefaults to 'controlnet'.\\nAbsolute paths work too.\"\n        )]\n        public string SDControlNetsFolder = \"controlnet\";\n\n        [ConfigComment(\n            \"The CLIP Vision model folder to use within 'ModelRoot'.\\nDefaults to 'clip_vision'.\\nAbsolute paths work too.\"\n        )]\n        public string SDClipVisionFolder = \"clip_vision\";\n\n        [ConfigComment(\"Root path for data (user configs, etc).\\nDefaults to 'Data'\")]\n        public string DataPath = \"Data\";\n\n        [ConfigComment(\"Root path for output files (images, etc).\\nDefaults to 'Output'\")]\n        public string OutputPath = \"Output\";\n\n        [ConfigComment(\"The folder for wildcard (.txt) files, under Data.\\nDefaults to 'Wildcards'\")]\n        public string WildcardsFolder = \"Wildcards\";\n\n        [ConfigComment(\n            \"When true, output paths always have the username as a folder.\\nWhen false, this will be skipped.\\nKeep this on in multi-user environments.\"\n        )]\n        public bool AppendUserNameToOutputPath = true;\n    }\n\n    /// <summary>Settings to control restrictions on users.</summary>\n    public class UserRestriction : AutoConfiguration\n    {\n        [ConfigComment(\"How many directories deep a user's custom OutPath can be.\\nDefault is 5.\")]\n        public int MaxOutPathDepth = 5;\n\n        [ConfigComment(\"Which user-settings the user is allowed to modify.\\nDefault is all of them.\")]\n        public List<string> AllowedSettings = new() { \"*\" };\n\n        [ConfigComment(\n            \"If true, the user is treated as a full admin.\\nThis includes the ability to modify these settings.\"\n        )]\n        public bool Admin = false;\n\n        [ConfigComment(\"If true, user may load models.\\nIf false, they may only use already-loaded models.\")]\n        public bool CanChangeModels = true;\n\n        [ConfigComment(\n            \"What models are allowed, as a path regex.\\nDirectory-separator is always '/'. Can be '.*' for all, 'MyFolder/.*' for only within that folder, etc.\\nDefault is all.\"\n        )]\n        public string AllowedModels = \".*\";\n\n        [ConfigComment(\"Generic permission flags. '*' means all.\\nDefault is all.\")]\n        public List<string> PermissionFlags = new() { \"*\" };\n\n        [ConfigComment(\"How many images can try to be generating at the same time on this user.\")]\n        public int MaxT2ISimultaneous = 32;\n    }\n\n    /// <summary>Settings per-user.</summary>\n    public class User : AutoConfiguration\n    {\n        public class OutPath : AutoConfiguration\n        {\n            [ConfigComment(\n                \"Builder for output file paths. Can use auto-filling placeholders like '[model]' for the model name, '[prompt]' for a snippet of prompt text, etc.\\n\"\n                    + \"Full details in the docs: https://github.com/Stability-AI/StableSwarmUI/blob/master/docs/User%20Settings.md#path-format\"\n            )]\n            public string Format = \"raw/[year]-[month]-[day]/[hour][minute]-[prompt]-[model]-[seed]\";\n\n            [ConfigComment(\"How long any one part can be.\\nDefault is 40 characters.\")]\n            public int MaxLenPerPart = 40;\n        }\n\n        [ConfigComment(\"Settings related to output path building.\")]\n        public OutPath OutPathBuilder = new();\n\n        public class FileFormatData : AutoConfiguration\n        {\n            [ConfigComment(\"What format to save images in.\\nDefault is '.jpg' (at 100% quality).\")]\n            public string ImageFormat = \"JPG\";\n\n            [ConfigComment(\"Whether to store metadata into saved images.\\nDefaults enabled.\")]\n            public bool SaveMetadata = true;\n\n            [ConfigComment(\n                \"If set to non-0, adds DPI metadata to saved images.\\n'72' is a good value for compatibility with some external software.\"\n            )]\n            public int DPI = 0;\n\n            [ConfigComment(\n                \"If set to true, a '.txt' file will be saved alongside images with the image metadata easily viewable.\\nThis can work even if saving in the image is disabled. Defaults disabled.\"\n            )]\n            public bool SaveTextFileMetadata = false;\n        }\n\n        [ConfigComment(\"Settings related to saved file format.\")]\n        public FileFormatData FileFormat = new();\n\n        [ConfigComment(\"Whether your files save to server data drive or not.\")]\n        public bool SaveFiles = true;\n\n        [ConfigComment(\"If true, folders will be discard from starred image paths.\")]\n        public bool StarNoFolders = false;\n\n        [ConfigComment(\"What theme to use. Default is 'dark_dreams'.\")]\n        public string Theme = \"dark_dreams\";\n\n        [ConfigComment(\n            \"If enabled, batch size will be reset to 1 when parameters are loaded.\\nThis can prevent accidents that might thrash your GPU or cause compatibility issues, especially for example when importing a comfy workflow.\\nYou can still set the batch size at will in the GUI.\"\n        )]\n        public bool ResetBatchSizeToOne = false;\n\n        public enum HintFormatOptions\n        {\n            BUTTON,\n            HOVER,\n            NONE\n        }\n\n        [ConfigComment(\"The format for parameter hints to display as.\\nDefault is 'BUTTON'.\")]\n        [SettingsOptions(Impl = typeof(SettingsOptionsAttribute.ForEnum<HintFormatOptions>))]\n        public string HintFormat = \"BUTTON\";\n\n        public class VAEsData : AutoConfiguration\n        {\n            [ConfigComment(\n                \"What VAE to use with SDXL models by default. Use 'None' to use the one in the model.\"\n            )]\n            [ManualSettingsOptions(Impl = null, Vals = new string[] { \"None\" })]\n            public string DefaultSDXLVAE = \"None\";\n\n            [ConfigComment(\n                \"What VAE to use with SDv1 models by default. Use 'None' to use the one in the model.\"\n            )]\n            [ManualSettingsOptions(Impl = null, Vals = new string[] { \"None\" })]\n            public string DefaultSDv1VAE = \"None\";\n        }\n\n        [ConfigComment(\"Options to override default VAEs with.\")]\n        public VAEsData VAEs = new();\n\n        [ConfigComment(\n            \"When generating live previews, this is how many simultaneous generation requests can be waiting at one time.\"\n        )]\n        public int MaxSimulPreviews = 1;\n\n        [ConfigComment(\n            \"Set to a number above 1 to allow generations of multiple images to automatically generate square mini-grids when they're done.\"\n        )]\n        public int MaxImagesInMiniGrid = 1;\n\n        [ConfigComment(\"How many images the history view should stop trying to load after.\")]\n        public int MaxImagesInHistory = 1000;\n\n        [ConfigComment(\n            \"If true, the Image History view will cache small preview thumbnails of images.\\nThis should make things run faster. You can turn it off if you don't want that.\"\n        )]\n        public bool ImageHistoryUsePreviews = true;\n\n        [ConfigComment(\n            \"Delay, in seconds, betweeen Generate Forever updates.\\nIf the delay hits and a generation is still waiting, it will be skipped.\\nDefault is 0.1 seconds.\"\n        )]\n        public double GenerateForeverDelay = 0.1;\n\n        [ConfigComment(\"What language to display the UI in.\\nDefault is 'en' (English).\")]\n        public string Language = \"en\";\n    }\n\n    /// <summary>UI-related settings.</summary>\n    public class UIData : AutoConfiguration\n    {\n        [ConfigComment(\n            \"Optionally specify a (raw HTML) welcome message here. If specified, will override the automatic welcome messages.\"\n        )]\n        public string OverrideWelcomeMessage = \"\";\n    }\n\n    /// <summary>Webhook settings.</summary>\n    public class WebHooksData : AutoConfiguration\n    {\n        [ConfigComment(\n            \"Webhook to call (empty JSON POST) when queues are starting up from idle.\\nLeave empty to disable any webhook.\\nCall must return before the first generation starts.\"\n        )]\n        public string QueueStartWebhook = \"\";\n\n        [ConfigComment(\n            \"Webhook to call (empty JSON POST) when all queues are done and the server is going idle.\\nLeave empty to disable any webhook.\\nCall must return before queuing may restart.\"\n        )]\n        public string QueueEndWebhook = \"\";\n\n        [ConfigComment(\n            \"How long to wait (in seconds) after all queues are done before sending the queue end webhook.\\nThis is useful to prevent rapid start+end calls.\"\n        )]\n        public double QueueEndDelay = 1;\n    }\n}\n\n[AttributeUsage(AttributeTargets.Field)]\npublic class SettingsOptionsAttribute : Attribute\n{\n    public abstract class AbstractImpl\n    {\n        public abstract string[] GetOptions { get; }\n    }\n\n    public class ForEnum<T> : AbstractImpl\n        where T : Enum\n    {\n        public override string[] GetOptions => Enum.GetNames(typeof(T));\n    }\n\n    public Type Impl;\n\n    public virtual string[] Options => (Activator.CreateInstance(Impl) as AbstractImpl).GetOptions;\n}\n\n[AttributeUsage(AttributeTargets.Field)]\npublic class ManualSettingsOptionsAttribute : SettingsOptionsAttribute\n{\n    public string[] Vals;\n\n    public override string[] Options => Vals;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/FileInterfaces/DirectoryPath.cs",
    "content": "﻿using System.Collections;\nusing System.ComponentModel;\nusing System.Text.Json.Serialization;\nusing JetBrains.Annotations;\nusing StabilityMatrix.Core.Converters.Json;\nusing StabilityMatrix.Core.Helper;\n\nnamespace StabilityMatrix.Core.Models.FileInterfaces;\n\n[PublicAPI]\n[Localizable(false)]\n[JsonConverter(typeof(StringJsonConverter<DirectoryPath>))]\npublic class DirectoryPath : FileSystemPath, IPathObject, IEnumerable<FileSystemPath>\n{\n    private DirectoryInfo? info;\n\n    [JsonIgnore]\n    public DirectoryInfo Info => info ??= new DirectoryInfo(FullPath);\n\n    [JsonIgnore]\n    FileSystemInfo IPathObject.Info => Info;\n\n    [JsonIgnore]\n    public bool IsSymbolicLink\n    {\n        get\n        {\n            Info.Refresh();\n            return Info.Exists && Info.Attributes.HasFlag(FileAttributes.ReparsePoint);\n        }\n    }\n\n    /// <summary>\n    /// Gets a value indicating whether the directory exists.\n    /// </summary>\n    [JsonIgnore]\n    public bool Exists => Info.Exists;\n\n    /// <inheritdoc/>\n    [JsonIgnore]\n    public string Name => Info.Name;\n\n    /// <summary>\n    /// Get the parent directory.\n    /// </summary>\n    [JsonIgnore]\n    public DirectoryPath? Parent => Info.Parent == null ? null : new DirectoryPath(Info.Parent);\n\n    public DirectoryPath([Localizable(false)] string path)\n        : base(path) { }\n\n    public DirectoryPath(FileSystemPath path)\n        : base(path) { }\n\n    public DirectoryPath(DirectoryInfo info)\n        : base(info.FullName)\n    {\n        // Additionally set the info field\n        this.info = info;\n    }\n\n    public DirectoryPath([Localizable(false)] params string[] paths)\n        : base(paths) { }\n\n    public DirectoryPath RelativeTo(DirectoryPath path)\n    {\n        return new DirectoryPath(Path.GetRelativePath(path.FullPath, FullPath));\n    }\n\n    /// <inheritdoc />\n    public long GetSize()\n    {\n        Info.Refresh();\n        return Info.EnumerateFiles(\"*\", EnumerationOptionConstants.AllDirectories).Sum(file => file.Length);\n    }\n\n    /// <summary>\n    /// Gets the size of the directory.\n    /// </summary>\n    /// <param name=\"includeSymbolicLinks\">\n    /// Whether to include files and subdirectories that are symbolic links / reparse points.\n    /// </param>\n    public long GetSize(bool includeSymbolicLinks)\n    {\n        if (includeSymbolicLinks)\n            return GetSize();\n\n        Info.Refresh();\n        var files = Info.GetFiles()\n            .Where(file => !file.Attributes.HasFlag(FileAttributes.ReparsePoint))\n            .Sum(file => file.Length);\n        var subDirs = Info.GetDirectories()\n            .Where(dir => !dir.Attributes.HasFlag(FileAttributes.ReparsePoint))\n            .Sum(\n                dir =>\n                    dir.EnumerateFiles(\"*\", EnumerationOptionConstants.AllDirectories)\n                        .Sum(file => file.Length)\n            );\n        return files + subDirs;\n    }\n\n    /// <summary>\n    /// Gets the size of the directory asynchronously.\n    /// </summary>\n    /// <param name=\"includeSymbolicLinks\">\n    /// Whether to include files and subdirectories that are symbolic links / reparse points.\n    /// </param>\n    public Task<long> GetSizeAsync(bool includeSymbolicLinks)\n    {\n        return Task.Run(() => GetSize(includeSymbolicLinks));\n    }\n\n    /// <summary>\n    /// Creates the directory.\n    /// </summary>\n    public void Create() => Directory.CreateDirectory(FullPath);\n\n    /// <summary>\n    /// Deletes the directory.\n    /// </summary>\n    public void Delete() => Info.Delete();\n\n    /// <summary> Deletes the directory asynchronously. </summary>\n    public Task DeleteAsync() => Task.Run(Delete);\n\n    /// <summary>\n    /// Deletes the directory.\n    /// </summary>\n    /// <param name=\"recursive\">Whether to delete subdirectories and files.</param>\n    public void Delete(bool recursive) => Info.Delete(recursive);\n\n    /// <summary>\n    /// Deletes the directory asynchronously.\n    /// </summary>\n    public Task DeleteAsync(bool recursive) => Task.Run(() => Delete(recursive));\n\n    void IPathObject.Delete() => Info.Delete(true);\n\n    Task IPathObject.DeleteAsync() => DeleteAsync(true);\n\n    private void ThrowIfNotExists()\n    {\n        if (!Exists)\n        {\n            throw new DirectoryNotFoundException($\"Directory not found: {FullPath}\");\n        }\n    }\n\n    public void CopyTo(DirectoryPath destinationDir, bool recursive = true)\n    {\n        ThrowIfNotExists();\n\n        // Cache directories before we start copying\n        var dirs = EnumerateDirectories().ToList();\n\n        destinationDir.Create();\n\n        // Get the files in the source directory and copy to the destination directory\n        foreach (var file in EnumerateFiles())\n        {\n            var targetFilePath = destinationDir.JoinFile(file.Name);\n            file.CopyTo(targetFilePath);\n        }\n\n        // If recursive and copying subdirectories, recursively call this method\n        if (recursive)\n        {\n            foreach (var subDir in dirs)\n            {\n                var targetDirectory = destinationDir.JoinDir(subDir.Name);\n                subDir.CopyTo(targetDirectory);\n            }\n        }\n    }\n\n    public async Task CopyToAsync(DirectoryPath destinationDir, bool recursive = true)\n    {\n        ThrowIfNotExists();\n\n        // Cache directories before we start copying\n        var dirs = EnumerateDirectories().ToList();\n\n        destinationDir.Create();\n\n        // Get the files in the source directory and copy to the destination directory\n        foreach (var file in EnumerateFiles())\n        {\n            var targetFilePath = destinationDir.JoinFile(file.Name);\n            await file.CopyToAsync(targetFilePath).ConfigureAwait(false);\n        }\n\n        // If recursive and copying subdirectories, recursively call this method\n        if (recursive)\n        {\n            foreach (var subDir in dirs)\n            {\n                var targetDirectory = destinationDir.JoinDir(subDir.Name);\n                await subDir.CopyToAsync(targetDirectory).ConfigureAwait(false);\n            }\n        }\n    }\n\n    /// <summary>\n    /// Move the directory to a destination path.\n    /// </summary>\n    public DirectoryPath MoveTo(DirectoryPath destinationDir)\n    {\n        Info.MoveTo(destinationDir.FullPath);\n        // Return the new path\n        return destinationDir;\n    }\n\n    /// <summary>\n    /// Move the file to a target path.\n    /// </summary>\n    public async Task<DirectoryPath> MoveToAsync(DirectoryPath destinationDir)\n    {\n        await Task.Run(() => Info.MoveTo(destinationDir.FullPath)).ConfigureAwait(false);\n        // Return the new path\n        return destinationDir;\n    }\n\n    /// <summary>\n    /// Move the directory to a destination path as a subfolder with the current name.\n    /// </summary>\n    public async Task<DirectoryPath> MoveToDirectoryAsync(DirectoryPath destinationParentDir)\n    {\n        await Task.Run(() => Info.MoveTo(destinationParentDir.JoinDir(Name))).ConfigureAwait(false);\n        // Return the new path\n        return destinationParentDir.JoinDir(this);\n    }\n\n    /// <summary>\n    /// Join with other paths to form a new directory path.\n    /// </summary>\n    public DirectoryPath JoinDir([Localizable(false)] params DirectoryPath[] paths) =>\n        new(Path.Combine(FullPath, Path.Combine(paths.Select(path => path.FullPath).ToArray())));\n\n    /// <summary>\n    /// Join with other paths to form a new file path.\n    /// </summary>\n    public FilePath JoinFile([Localizable(false)] params FilePath[] paths) =>\n        new(Path.Combine(FullPath, Path.Combine(paths.Select(path => path.FullPath).ToArray())));\n\n    /// <summary>\n    /// Returns an enumerable collection of files that matches\n    /// a specified search pattern and search subdirectory option.\n    /// </summary>\n    public IEnumerable<FilePath> EnumerateFiles(\n        string searchPattern = \"*\",\n        SearchOption searchOption = SearchOption.TopDirectoryOnly\n    ) => Info.EnumerateFiles(searchPattern, searchOption).Select(file => new FilePath(file));\n\n    /// <summary>\n    /// Returns an enumerable collection of files. Allows passing of <see cref=\"EnumerationOptions\"/>.\n    /// </summary>\n    public IEnumerable<FilePath> EnumerateFiles(\n        string searchPattern,\n        EnumerationOptions enumerationOptions\n    ) => Info.EnumerateFiles(searchPattern, enumerationOptions).Select(file => new FilePath(file));\n\n    /// <summary>\n    /// Returns an enumerable collection of directories. Allows passing of <see cref=\"EnumerationOptions\"/>.\n    /// </summary>\n    public IEnumerable<DirectoryPath> EnumerateDirectories(\n        string searchPattern,\n        EnumerationOptions enumerationOptions\n    ) =>\n        Info.EnumerateDirectories(searchPattern, enumerationOptions)\n            .Select(directory => new DirectoryPath(directory));\n\n    /// <summary>\n    /// Returns an enumerable collection of directories\n    /// </summary>\n    public IEnumerable<DirectoryPath> EnumerateDirectories(\n        string searchPattern = \"*\",\n        SearchOption searchOption = SearchOption.TopDirectoryOnly\n    ) =>\n        Info.EnumerateDirectories(searchPattern, searchOption)\n            .Select(directory => new DirectoryPath(directory));\n\n    /// <summary>\n    /// Return a new <see cref=\"DirectoryPath\"/> with the given file name.\n    /// </summary>\n    public DirectoryPath WithName(string directoryName)\n    {\n        if (Path.GetDirectoryName(FullPath) is { } directory && !string.IsNullOrWhiteSpace(directory))\n        {\n            return new DirectoryPath(directory, directoryName);\n        }\n\n        return new DirectoryPath(directoryName);\n    }\n\n    public override string ToString() => FullPath;\n\n    /// <inheritdoc />\n    public IEnumerator<FileSystemPath> GetEnumerator()\n    {\n        return Info.EnumerateFileSystemInfos(\"*\", EnumerationOptionConstants.TopLevelOnly)\n            .Select<FileSystemInfo, FileSystemPath>(\n                fsInfo =>\n                    fsInfo switch\n                    {\n                        FileInfo file => new FilePath(file),\n                        DirectoryInfo directory => new DirectoryPath(directory),\n                        _ => throw new InvalidOperationException(\"Unknown file system info type\")\n                    }\n            )\n            .GetEnumerator();\n    }\n\n    /// <inheritdoc />\n    IEnumerator IEnumerable.GetEnumerator()\n    {\n        return GetEnumerator();\n    }\n\n    // DirectoryPath + DirectoryPath = DirectoryPath\n    public static DirectoryPath operator +(DirectoryPath path, DirectoryPath other) =>\n        new(Path.Combine(path, other.FullPath));\n\n    // DirectoryPath + FilePath = FilePath\n    public static FilePath operator +(DirectoryPath path, FilePath other) =>\n        new(Path.Combine(path, other.FullPath));\n\n    // DirectoryPath + FileInfo = FilePath\n    public static FilePath operator +(DirectoryPath path, FileInfo other) =>\n        new(Path.Combine(path, other.FullName));\n\n    // DirectoryPath + string = string\n    public static string operator +(DirectoryPath path, [Localizable(false)] string other) =>\n        Path.Combine(path, other);\n\n    // Implicit conversions to and from string\n    public static implicit operator string(DirectoryPath path) => path.FullPath;\n\n    public static implicit operator DirectoryPath([Localizable(false)] string path) => new(path);\n\n    // Implicit conversions to and from DirectoryInfo\n    public static implicit operator DirectoryInfo(DirectoryPath path) => path.Info;\n\n    public static implicit operator DirectoryPath(DirectoryInfo path) => new(path);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/FileInterfaces/FilePath.Fluent.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.FileInterfaces;\n\npublic partial class FilePath\n{\n    /// <summary>\n    /// Return a new <see cref=\"FilePath\"/> with the given file name.\n    /// </summary>\n    public FilePath WithName(string fileName)\n    {\n        if (\n            Path.GetDirectoryName(FullPath) is { } directory\n            && !string.IsNullOrWhiteSpace(directory)\n        )\n        {\n            return new FilePath(directory, fileName);\n        }\n\n        return new FilePath(fileName);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/FileInterfaces/FilePath.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Text;\nusing System.Text.Json.Serialization;\nusing JetBrains.Annotations;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Core.Models.FileInterfaces;\n\n[PublicAPI]\n[Localizable(false)]\n[JsonConverter(typeof(StringJsonConverter<FilePath>))]\npublic partial class FilePath : FileSystemPath, IPathObject\n{\n    private FileInfo? _info;\n\n    [JsonIgnore]\n    public FileInfo Info => _info ??= new FileInfo(FullPath);\n\n    [JsonIgnore]\n    FileSystemInfo IPathObject.Info => Info;\n\n    [JsonIgnore]\n    public bool IsSymbolicLink\n    {\n        get\n        {\n            Info.Refresh();\n            return Info.Attributes.HasFlag(FileAttributes.ReparsePoint);\n        }\n    }\n\n    [JsonIgnore]\n    public bool Exists => Info.Exists;\n\n    [JsonIgnore]\n    public string Name => Info.Name;\n\n    [JsonIgnore]\n    public string NameWithoutExtension => Path.GetFileNameWithoutExtension(Info.Name);\n\n    /// <inheritdoc cref=\"FileInfo.Extension\"/>\n    [JsonIgnore]\n    public string Extension => Info.Extension;\n\n    /// <summary>\n    /// Get the directory of the file.\n    /// </summary>\n    [JsonIgnore]\n    public DirectoryPath? Directory\n    {\n        get\n        {\n            try\n            {\n                return Info.Directory == null ? null : new DirectoryPath(Info.Directory);\n            }\n            catch (DirectoryNotFoundException)\n            {\n                return null;\n            }\n        }\n    }\n\n    public FilePath([Localizable(false)] string path)\n        : base(path) { }\n\n    public FilePath(FileInfo fileInfo)\n        : base(fileInfo.FullName)\n    {\n        _info = fileInfo;\n    }\n\n    public FilePath(FileSystemPath path)\n        : base(path) { }\n\n    public FilePath([Localizable(false)] params string[] paths)\n        : base(paths) { }\n\n    public FilePath RelativeTo(DirectoryPath path)\n    {\n        return new FilePath(Path.GetRelativePath(path.FullPath, FullPath));\n    }\n\n    public long GetSize()\n    {\n        Info.Refresh();\n        return Info.Length;\n    }\n\n    public long GetSize(bool includeSymbolicLinks)\n    {\n        if (!includeSymbolicLinks && IsSymbolicLink)\n            return 0;\n        return GetSize();\n    }\n\n    public Task<long> GetSizeAsync(bool includeSymbolicLinks)\n    {\n        return Task.Run(() => GetSize(includeSymbolicLinks));\n    }\n\n    /// <summary> Creates an empty file. </summary>\n    public void Create() => File.Create(FullPath).Close();\n\n    /// <summary> Deletes the file </summary>\n    public void Delete() => File.Delete(FullPath);\n\n    /// <summary> Deletes the file asynchronously </summary>\n    public Task DeleteAsync(CancellationToken ct = default)\n    {\n        return Task.Run(() => File.Delete(FullPath), ct);\n    }\n\n    // Methods specific to files\n\n    /// <summary> Read text </summary>\n    public string ReadAllText() => File.ReadAllText(FullPath);\n\n    /// <summary> Read text asynchronously </summary>\n    public Task<string> ReadAllTextAsync(CancellationToken ct = default)\n    {\n        return File.ReadAllTextAsync(FullPath, ct);\n    }\n\n    /// <summary> Write text </summary>\n    public void WriteAllText(string text, Encoding? encoding = null) =>\n        File.WriteAllText(FullPath, text, encoding ?? new UTF8Encoding(false));\n\n    /// <summary> Write text asynchronously </summary>\n    public Task WriteAllTextAsync(string text, CancellationToken ct = default, Encoding? encoding = null)\n    {\n        return File.WriteAllTextAsync(FullPath, text, encoding ?? new UTF8Encoding(false), ct);\n    }\n\n    /// <summary> Read bytes </summary>\n    public byte[] ReadAllBytes() => File.ReadAllBytes(FullPath);\n\n    /// <summary> Read bytes asynchronously </summary>\n    public Task<byte[]> ReadAllBytesAsync(CancellationToken ct = default)\n    {\n        return File.ReadAllBytesAsync(FullPath, ct);\n    }\n\n    /// <summary> Write bytes </summary>\n    public void WriteAllBytes(byte[] bytes) => File.WriteAllBytes(FullPath, bytes);\n\n    /// <summary> Write bytes asynchronously </summary>\n    public Task WriteAllBytesAsync(byte[] bytes, CancellationToken ct = default)\n    {\n        return File.WriteAllBytesAsync(FullPath, bytes, ct);\n    }\n\n    /// <summary>\n    /// Rename the file.\n    /// </summary>\n    public FilePath Rename([Localizable(false)] string fileName)\n    {\n        if (Path.GetDirectoryName(FullPath) is { } directory && !string.IsNullOrWhiteSpace(directory))\n        {\n            var target = Path.Combine(directory, fileName);\n            Info.MoveTo(target, true);\n            return new FilePath(target);\n        }\n\n        throw new InvalidOperationException(\"Cannot rename a file path that is empty or has no directory\");\n    }\n\n    /// <summary>\n    /// Move the file to a directory.\n    /// </summary>\n    public FilePath MoveTo(FilePath destinationFile)\n    {\n        Info.MoveTo(destinationFile.FullPath, true);\n        // Return the new path\n        return destinationFile;\n    }\n\n    /// <summary>\n    /// Move the file to a directory.\n    /// </summary>\n    public async Task<FilePath> MoveToDirectoryAsync(DirectoryPath directory)\n    {\n        await Task.Run(() => Info.MoveTo(directory.JoinFile(Name), true)).ConfigureAwait(false);\n        // Return the new path\n        return directory.JoinFile(this);\n    }\n\n    /// <summary>\n    /// Move the file to a target path.\n    /// </summary>\n    public async Task<FilePath> MoveToAsync(FilePath destinationFile)\n    {\n        await Task.Run(() => Info.MoveTo(destinationFile.FullPath)).ConfigureAwait(false);\n        // Return the new path\n        return destinationFile;\n    }\n\n    /// <summary>\n    /// Move the file to a target path with auto increment if the file already exists.\n    /// </summary>\n    /// <returns>The new path, possibly with incremented file name</returns>\n    public async Task<FilePath> MoveToWithIncrementAsync(FilePath destinationFile, int maxTries = 100)\n    {\n        await Task.Yield();\n\n        var targetFile = destinationFile;\n\n        for (var i = 1; i < maxTries; i++)\n        {\n            if (!targetFile.Exists)\n            {\n                return await MoveToAsync(targetFile).ConfigureAwait(false);\n            }\n\n            targetFile = destinationFile.WithName(\n                destinationFile.NameWithoutExtension + $\" ({i})\" + destinationFile.Extension\n            );\n        }\n\n        throw new IOException($\"Could not move file to {destinationFile} because it already exists.\");\n    }\n\n    /// <summary>\n    /// Copy the file to a target path.\n    /// </summary>\n    public FilePath CopyTo(FilePath destinationFile, bool overwrite = false)\n    {\n        Info.CopyTo(destinationFile.FullPath, overwrite);\n        // Return the new path\n        return destinationFile;\n    }\n\n    /// <summary>\n    /// Copy the file to a target path asynchronously.\n    /// </summary>\n    public async Task<FilePath> CopyToAsync(FilePath destinationFile, bool overwrite = false)\n    {\n        await using var sourceStream = Info.OpenRead();\n        await using var destinationStream = destinationFile.Info.OpenWrite();\n\n        await sourceStream.CopyToAsync(destinationStream).ConfigureAwait(false);\n\n        // Return the new path\n        return destinationFile;\n    }\n\n    /// <summary>\n    /// Copy the file to a target path asynchronously with a specified the file share mode.\n    /// </summary>\n    public async Task<FilePath> CopyToAsync(\n        FilePath destinationFile,\n        FileShare sourceShare,\n        bool overwrite = false\n    )\n    {\n        await using var sourceStream = Info.Open(FileMode.Open, FileAccess.Read, sourceShare);\n        await using var destinationStream = destinationFile.Info.OpenWrite();\n\n        await sourceStream.CopyToAsync(destinationStream).ConfigureAwait(false);\n\n        // Return the new path\n        return destinationFile;\n    }\n\n    // Implicit conversions to and from string\n    public static implicit operator string(FilePath path) => path.FullPath;\n\n    public static implicit operator FilePath([Localizable(false)] string path) => new(path);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/FileInterfaces/FileSystemPath.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Diagnostics.CodeAnalysis;\nusing JetBrains.Annotations;\n\nnamespace StabilityMatrix.Core.Models.FileInterfaces;\n\n[PublicAPI]\n[Localizable(false)]\npublic class FileSystemPath : IEquatable<FileSystemPath>, IFormattable\n{\n    public string FullPath { get; }\n\n    protected FileSystemPath(string path)\n    {\n        FullPath = path;\n    }\n\n    protected FileSystemPath(FileSystemPath path)\n        : this(path.FullPath) { }\n\n    protected FileSystemPath(params string[] paths)\n        : this(Path.Combine(paths)) { }\n\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        return FullPath;\n    }\n\n    /// <inheritdoc />\n    string IFormattable.ToString(string? format, IFormatProvider? formatProvider)\n    {\n        return ToString(format, formatProvider);\n    }\n\n    /// <summary>\n    /// Overridable IFormattable.ToString method.\n    /// By default, returns <see cref=\"FullPath\"/>.\n    /// </summary>\n    protected virtual string ToString(string? format, IFormatProvider? formatProvider)\n    {\n        return FullPath;\n    }\n\n    public static bool operator ==(FileSystemPath? left, FileSystemPath? right)\n    {\n        return Equals(left, right);\n    }\n\n    public static bool operator !=(FileSystemPath? left, FileSystemPath? right)\n    {\n        return !Equals(left, right);\n    }\n\n    /// <inheritdoc />\n    public bool Equals(FileSystemPath? other)\n    {\n        if (ReferenceEquals(null, other))\n            return false;\n        if (ReferenceEquals(this, other))\n            return true;\n\n        return string.Equals(\n            GetNormalizedPath(FullPath),\n            GetNormalizedPath(other.FullPath),\n            OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal\n        );\n    }\n\n    /// <inheritdoc />\n    public override bool Equals(object? obj)\n    {\n        if (ReferenceEquals(null, obj))\n            return false;\n        if (ReferenceEquals(this, obj))\n            return true;\n        if (GetType() != obj.GetType())\n            return false;\n        return Equals((FileSystemPath)obj);\n    }\n\n    /// <summary>\n    /// Normalize a path to a consistent format for comparison.\n    /// </summary>\n    /// <param name=\"path\">Path to normalize.</param>\n    /// <returns>Normalized path.</returns>\n    [return: NotNullIfNotNull(nameof(path))]\n    private static string? GetNormalizedPath(string? path)\n    {\n        // Return null or empty paths as-is\n        if (string.IsNullOrEmpty(path))\n        {\n            return path;\n        }\n\n        if (Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uri))\n        {\n            if (uri.IsAbsoluteUri)\n            {\n                path = uri.LocalPath;\n            }\n        }\n\n        // Get full path if possible, ignore errors like invalid chars or too long\n        try\n        {\n            path = Path.GetFullPath(path);\n        }\n        catch (SystemException) { }\n\n        return path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);\n    }\n\n    /// <inheritdoc />\n    public override int GetHashCode()\n    {\n        return HashCode.Combine(GetType().GetHashCode(), FullPath.GetHashCode());\n    }\n\n    // Implicit conversions to and from string\n    public static implicit operator string(FileSystemPath path) => path.FullPath;\n\n    public static implicit operator FileSystemPath(string path) => new(path);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/FileInterfaces/IPathObject.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.FileInterfaces;\n\npublic interface IPathObject\n{\n    /// <summary> Full path of the file system object. </summary>\n    string FullPath { get; }\n\n    /// <summary> Info of the file system object. </summary>\n    FileSystemInfo Info { get; }\n\n    /// <summary> Name of the file system object. </summary>\n    string Name { get; }\n\n    /// <summary> Whether the file system object is a symbolic link or junction. </summary>\n    bool IsSymbolicLink { get; }\n\n    /// <summary> Gets the size of the file system object. </summary>\n    long GetSize();\n\n    /// <summary> Gets the size of the file system object asynchronously. </summary>\n    Task<long> GetSizeAsync() => Task.Run(GetSize);\n\n    /// <summary> Whether the file system object exists. </summary>\n    bool Exists { get; }\n\n    /// <summary> Deletes the file system object </summary>\n    void Delete();\n\n    /// <summary> Deletes the file system object asynchronously. </summary>\n    public Task DeleteAsync() => Task.Run(Delete);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/FileInterfaces/TempDirectoryPath.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.FileInterfaces;\n\npublic class TempDirectoryPath : DirectoryPath, IDisposable\n{\n    public TempDirectoryPath()\n        : base(Path.GetTempPath(), Path.GetRandomFileName())\n    {\n        Directory.CreateDirectory(FullPath);\n    }\n\n    public void Dispose()\n    {\n        ForceDeleteDirectory(FullPath);\n        GC.SuppressFinalize(this);\n    }\n\n    private static void ForceDeleteDirectory(string directoryPath)\n    {\n        if (!Directory.Exists(directoryPath))\n        {\n            return;\n        }\n\n        var files = Directory.GetFiles(directoryPath);\n        var directories = Directory.GetDirectories(directoryPath);\n\n        foreach (var file in files)\n        {\n            File.SetAttributes(file, FileAttributes.Normal);\n            File.Delete(file);\n        }\n\n        foreach (var dir in directories)\n        {\n            ForceDeleteDirectory(dir);\n        }\n\n        File.SetAttributes(directoryPath, FileAttributes.Normal);\n\n        Directory.Delete(directoryPath, false);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/FileSizeType.cs",
    "content": "﻿using System.Globalization;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class FileSizeType\n{\n    public double SizeInKB { get; private set; }\n\n    public string HumanReadableRepresentation { get; private set; }\n\n    public FileSizeType(double sizeInKB)\n    {\n        SizeInKB = sizeInKB;\n        HumanReadableRepresentation = ConvertToHumanReadable();\n    }\n\n    private string ConvertToHumanReadable()\n    {\n        var sizeUnits = new string[] { \"KB\", \"MB\", \"GB\", \"TB\" };\n        var size = SizeInKB;\n        var unitIndex = 0;\n\n        while (size >= 1024 && unitIndex < sizeUnits.Length - 1)\n        {\n            size /= 1024;\n            unitIndex++;\n        }\n\n        return string.Format(\"{0} {1}\", size.ToString(\"0.##\", CultureInfo.InvariantCulture), sizeUnits[unitIndex]);\n    }\n\n    public override string ToString()\n    {\n        return HumanReadableRepresentation;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/GenerationParameters.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Models.Api.Comfy;\n\nnamespace StabilityMatrix.Core.Models;\n\n[JsonSerializable(typeof(GenerationParameters))]\npublic record GenerationParameters\n{\n    public string? PositivePrompt { get; set; }\n    public string? NegativePrompt { get; set; }\n    public int Steps { get; set; }\n    public string? Sampler { get; set; }\n    public double CfgScale { get; set; }\n    public ulong Seed { get; set; }\n    public int Height { get; set; }\n    public int Width { get; set; }\n    public string? ModelHash { get; set; }\n    public string? ModelName { get; set; }\n    public int FrameCount { get; set; }\n    public int MotionBucketId { get; set; }\n    public int VideoQuality { get; set; }\n    public bool Lossless { get; set; }\n    public int Fps { get; set; }\n    public double OutputFps { get; set; }\n    public double MinCfg { get; set; }\n    public double AugmentationLevel { get; set; }\n    public string? VideoOutputMethod { get; set; }\n    public int? ModelVersionId { get; set; }\n    public List<int>? ExtraNetworkModelVersionIds { get; set; }\n\n    private static readonly JsonSerializerOptions JsonOptions = new()\n    {\n        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,\n    };\n\n    public static bool TryParse(\n        string? text,\n        [NotNullWhen(true)] out GenerationParameters? generationParameters\n    )\n    {\n        if (string.IsNullOrWhiteSpace(text))\n        {\n            generationParameters = null;\n            return false;\n        }\n\n        try\n        {\n            generationParameters = Parse(text);\n        }\n        catch (Exception)\n        {\n            generationParameters = null;\n            return false;\n        }\n\n        return true;\n    }\n\n    public static GenerationParameters Parse(string text)\n    {\n        var lines = text.Split('\\n');\n\n        if (lines.LastOrDefault() is not { } lastLine)\n        {\n            throw new ValidationException(\"Fields line not found\");\n        }\n\n        if (lastLine.StartsWith(\"Steps:\") != true)\n        {\n            lines = text.Split(\"\\r\\n\");\n            lastLine = lines.LastOrDefault() ?? string.Empty;\n\n            if (lastLine.StartsWith(\"Steps:\") != true)\n            {\n                throw new ValidationException(\"Unable to locate starting marker of last line\");\n            }\n        }\n\n        // Join lines before last line, split at 'Negative prompt: '\n        var joinedLines = string.Join(\"\\n\", lines[..^1]).Trim();\n\n        // Apparently there is no space after the colon if value is empty, so check and add space here\n        if (joinedLines.EndsWith(\"Negative prompt:\"))\n        {\n            joinedLines += ' ';\n        }\n\n        var splitFirstPart = joinedLines.Split(\"Negative prompt: \", 2);\n\n        var positivePrompt = splitFirstPart.ElementAtOrDefault(0)?.Trim();\n        var negativePrompt = splitFirstPart.ElementAtOrDefault(1)?.Trim();\n\n        // Parse last line\n        var lineFields = ParseLine(lastLine);\n\n        var generationParameters = new GenerationParameters\n        {\n            PositivePrompt = positivePrompt,\n            NegativePrompt = negativePrompt,\n            Steps = int.Parse(lineFields.GetValueOrDefault(\"Steps\", \"0\")),\n            Sampler = lineFields.GetValueOrDefault(\"Sampler\"),\n            CfgScale = double.Parse(lineFields.GetValueOrDefault(\"CFG scale\", \"0\")),\n            Seed = ulong.Parse(lineFields.GetValueOrDefault(\"Seed\", \"0\")),\n            ModelHash = lineFields.GetValueOrDefault(\"Model hash\"),\n            ModelName = lineFields.GetValueOrDefault(\"Model\"),\n        };\n\n        if (lineFields.ContainsKey(\"Civitai resources\"))\n        {\n            // [{\"type\":\"checkpoint\",\"modelVersionId\":290640,\"modelName\":\"Pony Diffusion V6 XL\",\"modelVersionName\":\"V6 (start with this one)\"},{\"type\":\"lora\",\"weight\":0.8,\"modelVersionId\":333590,\"modelName\":\"Not Artists Styles for Pony Diffusion V6 XL\",\"modelVersionName\":\"Anime 2\"}]\n            var civitaiResources = lineFields[\"Civitai resources\"];\n            if (!string.IsNullOrWhiteSpace(civitaiResources))\n            {\n                var resources = JsonSerializer.Deserialize<List<CivitaiResource>>(\n                    civitaiResources,\n                    JsonOptions\n                );\n                if (resources is not null)\n                {\n                    generationParameters.ModelName ??= resources\n                        .FirstOrDefault(x => x.Type == \"checkpoint\")\n                        ?.ModelName;\n                    generationParameters.ModelVersionId ??= resources\n                        .FirstOrDefault(x => x.Type == \"checkpoint\")\n                        ?.ModelVersionId;\n\n                    foreach (var lora in resources.Where(x => x.Type == \"lora\"))\n                    {\n                        generationParameters.ExtraNetworkModelVersionIds ??= [];\n                        generationParameters.ExtraNetworkModelVersionIds.Add(lora.ModelVersionId);\n                    }\n                }\n            }\n        }\n\n        if (lineFields.GetValueOrDefault(\"Size\") is { } size)\n        {\n            var split = size.Split('x', 2);\n            if (split.Length == 2)\n            {\n                generationParameters = generationParameters with\n                {\n                    Width = int.Parse(split[0]),\n                    Height = int.Parse(split[1]),\n                };\n            }\n        }\n\n        return generationParameters;\n    }\n\n    /// <summary>\n    /// Parse A1111 metadata fields in a single line where\n    /// fields are separated by commas and key-value pairs are separated by colons.\n    /// i.e. \"key1: value1, key2: value2\"\n    /// </summary>\n    internal static Dictionary<string, string> ParseLine(string line)\n    {\n        var dict = new Dictionary<string, string>();\n\n        var quoteStack = new Stack<char>();\n        // the Range for the key\n        Range? currentKeyRange = null;\n        // the start of the key or value\n        Index currentStart = 0;\n\n        for (var i = 0; i < line.Length; i++)\n        {\n            var c = line[i];\n\n            switch (c)\n            {\n                case '\"':\n                    // if we are in a \" quote, pop the stack\n                    if (quoteStack.Count > 0 && quoteStack.Peek() == '\"')\n                    {\n                        quoteStack.Pop();\n                    }\n                    else\n                    {\n                        // start of a new quoted section\n                        quoteStack.Push(c);\n                    }\n                    break;\n\n                case '[':\n                case '{':\n                case '(':\n                case '<':\n                    quoteStack.Push(c);\n                    break;\n\n                case ']':\n                    if (quoteStack.Count > 0 && quoteStack.Peek() == '[')\n                    {\n                        quoteStack.Pop();\n                    }\n                    break;\n                case '}':\n                    if (quoteStack.Count > 0 && quoteStack.Peek() == '{')\n                    {\n                        quoteStack.Pop();\n                    }\n                    break;\n                case ')':\n                    if (quoteStack.Count > 0 && quoteStack.Peek() == '(')\n                    {\n                        quoteStack.Pop();\n                    }\n                    break;\n                case '>':\n                    if (quoteStack.Count > 0 && quoteStack.Peek() == '<')\n                    {\n                        quoteStack.Pop();\n                    }\n                    break;\n\n                case ':':\n                    // : marks the end of the key\n\n                    // if we already have a key, ignore this colon as it is part of the value\n                    // if we are not in a quote, we have a key\n                    if (!currentKeyRange.HasValue && quoteStack.Count == 0)\n                    {\n                        currentKeyRange = new Range(currentStart, i);\n                        currentStart = i + 1;\n                    }\n                    break;\n\n                case ',':\n                    // , marks the end of a key-value pair\n                    // if we are not in a quote, we have a value\n                    if (quoteStack.Count != 0)\n                    {\n                        break;\n                    }\n\n                    if (!currentKeyRange.HasValue)\n                    {\n                        // unexpected comma, reset and start from current position\n                        currentStart = i + 1;\n                        break;\n                    }\n\n                    try\n                    {\n                        // extract the key and value\n                        var key = new string(line.AsSpan()[currentKeyRange!.Value].Trim());\n                        var value = new string(line.AsSpan()[currentStart..i].Trim());\n\n                        // check duplicates and prefer the first occurrence\n                        if (!string.IsNullOrWhiteSpace(key) && !dict.ContainsKey(key))\n                        {\n                            dict[key] = value;\n                        }\n                    }\n                    catch (Exception)\n                    {\n                        // ignore individual key-value pair errors\n                    }\n\n                    currentKeyRange = null;\n                    currentStart = i + 1;\n                    break;\n                default:\n                    break;\n            } // end of switch\n        } // end of for\n\n        // if we have a key-value pair at the end of the string\n        if (currentKeyRange.HasValue)\n        {\n            try\n            {\n                var key = new string(line.AsSpan()[currentKeyRange!.Value].Trim());\n                var value = new string(line.AsSpan()[currentStart..].Trim());\n\n                if (!string.IsNullOrWhiteSpace(key) && !dict.ContainsKey(key))\n                {\n                    dict[key] = value;\n                }\n            }\n            catch (Exception)\n            {\n                // ignore individual key-value pair errors\n            }\n        }\n\n        return dict;\n    }\n\n    /// <summary>\n    /// Converts current <see cref=\"Sampler\"/> string to <see cref=\"ComfySampler\"/> and <see cref=\"ComfyScheduler\"/>.\n    /// </summary>\n    /// <returns></returns>\n    public (ComfySampler sampler, ComfyScheduler scheduler)? GetComfySamplers()\n    {\n        if (Sampler is not { } source)\n            return null;\n\n        var scheduler = source switch\n        {\n            _ when source.Contains(\"Karras\") => ComfyScheduler.Karras,\n            _ when source.Contains(\"Exponential\") => ComfyScheduler.Exponential,\n            _ => ComfyScheduler.Normal,\n        };\n\n        var sampler = source switch\n        {\n            \"LMS\" => ComfySampler.LMS,\n            \"DDIM\" => ComfySampler.DDIM,\n            \"UniPC\" => ComfySampler.UniPC,\n            \"DPM fast\" => ComfySampler.DpmFast,\n            \"DPM adaptive\" => ComfySampler.DpmAdaptive,\n            \"Heun\" => ComfySampler.Heun,\n            _ when source.StartsWith(\"DPM2 a\") => ComfySampler.Dpm2Ancestral,\n            _ when source.StartsWith(\"DPM2\") => ComfySampler.Dpm2,\n            _ when source.StartsWith(\"DPM++ 2M SDE\") => ComfySampler.Dpmpp2MSde,\n            _ when source.StartsWith(\"DPM++ 2M\") => ComfySampler.Dpmpp2M,\n            _ when source.StartsWith(\"DPM++ 3M SDE\") => ComfySampler.Dpmpp3MSde,\n            _ when source.StartsWith(\"DPM++ 3M\") => ComfySampler.Dpmpp3M,\n            _ when source.StartsWith(\"DPM++ SDE\") => ComfySampler.DpmppSde,\n            _ when source.StartsWith(\"DPM++ 2S a\") => ComfySampler.Dpmpp2SAncestral,\n            _ => default,\n        };\n\n        return (sampler, scheduler);\n    }\n\n    /// <summary>\n    /// Return a sample parameters for UI preview\n    /// </summary>\n    public static GenerationParameters GetSample()\n    {\n        return new GenerationParameters\n        {\n            PositivePrompt = \"(cat:1.2), by artist, detailed, [shaded]\",\n            NegativePrompt = \"blurry, jpg artifacts\",\n            Steps = 30,\n            CfgScale = 7,\n            Width = 640,\n            Height = 896,\n            Seed = 124825529,\n            ModelName = \"ExampleMix7\",\n            ModelHash = \"b899d188a1ac7356bfb9399b2277d5b21712aa360f8f9514fba6fcce021baff7\",\n            Sampler = \"DPM++ 2M Karras\",\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/GitVersion.cs",
    "content": "using System.ComponentModel;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Text;\n\nnamespace StabilityMatrix.Core.Models;\n\n/// <summary>\n/// Union of either Tag or Branch + CommitSha.\n/// </summary>\n[Localizable(false)]\npublic record GitVersion : IFormattable, IUtf8SpanParsable<GitVersion>\n{\n    public string? Tag { get; init; }\n\n    public string? Branch { get; init; }\n\n    public string? CommitSha { get; init; }\n\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        return ToString(null, null);\n    }\n\n    /// <inheritdoc />\n    /// <remarks>\n    /// - The \"O\" format specifier can be used to format for round-trip serialization with full commit SHAs.\n    /// - The \"G\" format specifier uses abbreviated commit SHAs (first 7 characters).\n    /// \"O\" is used by default.\n    /// </remarks>\n    public string ToString(string? format, IFormatProvider? formatProvider)\n    {\n        switch (format)\n        {\n            case \"G\":\n            {\n                if (!string.IsNullOrEmpty(Tag))\n                {\n                    return Tag;\n                }\n\n                if (!string.IsNullOrEmpty(Branch) && !string.IsNullOrEmpty(CommitSha))\n                {\n                    return $\"{Branch}@{CommitSha[..7]}\";\n                }\n\n                if (!string.IsNullOrEmpty(Branch))\n                {\n                    return Branch;\n                }\n\n                return !string.IsNullOrEmpty(CommitSha) ? CommitSha[..7] : \"\";\n            }\n            case \"O\":\n            case null:\n            {\n                if (!string.IsNullOrEmpty(Tag))\n                {\n                    return Tag;\n                }\n\n                if (!string.IsNullOrEmpty(Branch) && !string.IsNullOrEmpty(CommitSha))\n                {\n                    return $\"{Branch}@{CommitSha}\";\n                }\n\n                if (!string.IsNullOrEmpty(Branch))\n                {\n                    return Branch;\n                }\n\n                return !string.IsNullOrEmpty(CommitSha) ? CommitSha : \"\";\n            }\n            default:\n                throw new FormatException($\"The {format} format specifier is not supported.\");\n        }\n    }\n\n    public static bool TryParse(\n        ReadOnlySpan<byte> utf8Text,\n        IFormatProvider? provider,\n        [MaybeNullWhen(false)] out GitVersion result\n    )\n    {\n        return TryParse(utf8Text, provider, out result, false);\n    }\n\n    private static bool TryParse(\n        ReadOnlySpan<byte> utf8Source,\n        IFormatProvider? provider,\n        [MaybeNullWhen(false)] out GitVersion result,\n        bool throwOnFailure\n    )\n    {\n        result = null;\n\n        try\n        {\n            var source = Encoding.UTF8.GetString(utf8Source);\n            if (string.IsNullOrEmpty(source))\n            {\n                return false;\n            }\n\n            if (source.Contains('@'))\n            {\n                var parts = source.Split('@');\n                if (parts.Length == 2)\n                {\n                    var branch = parts[0];\n                    var commitSha = parts[1];\n\n                    result = new GitVersion { Branch = branch, CommitSha = commitSha };\n                    return true;\n                }\n            }\n            else\n            {\n                result = new GitVersion { Tag = source };\n                return true;\n            }\n        }\n        catch\n        {\n            if (throwOnFailure)\n            {\n                throw;\n            }\n            return false;\n        }\n\n        return false;\n    }\n\n    public static GitVersion Parse(ReadOnlySpan<byte> utf8Source, IFormatProvider? provider)\n    {\n        if (TryParse(utf8Source, provider, out var result))\n        {\n            return result;\n        }\n\n        throw new FormatException(\"Invalid GitVersion format.\");\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/GlobalConfig.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Core.Models;\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic static class GlobalConfig\n{\n    private static DirectoryPath? libraryDir;\n    private static DirectoryPath? modelsDir;\n\n    /// <summary>\n    /// Absolute path to the library directory.\n    /// Needs to be set by SettingsManager.TryFindLibrary() before being accessed.\n    /// </summary>\n    /// <exception cref=\"Exception\"></exception>\n    public static DirectoryPath LibraryDir\n    {\n        get\n        {\n            if (libraryDir is null)\n            {\n                throw new NullReferenceException(\n                    \"GlobalConfig.LibraryDir was not set before being accessed.\"\n                );\n            }\n            return libraryDir;\n        }\n        set => libraryDir = value;\n    }\n\n    /// <summary>\n    /// Full path to the %APPDATA% directory.\n    /// Usually C:\\Users\\{username}\\AppData\\Roaming\n    /// </summary>\n    public static DirectoryPath AppDataDir { get; } =\n        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);\n\n    /// <summary>\n    /// Full path to the fixed home directory.\n    /// Currently %APPDATA%\\StabilityMatrix\n    ///</summary>\n    public static DirectoryPath HomeDir { get; set; } = AppDataDir.JoinDir(\"StabilityMatrix\");\n\n    public static DirectoryPath ModelsDir\n    {\n        get\n        {\n            if (modelsDir is null)\n            {\n                throw new NullReferenceException(\"GlobalConfig.ModelsDir was not set before being accessed.\");\n            }\n            return modelsDir;\n        }\n        set => modelsDir = value;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/GlobalEncryptedSerializer.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Runtime.InteropServices;\nusing System.Security;\nusing System.Security.Cryptography;\nusing System.Text.Json;\nusing DeviceId;\n\nnamespace StabilityMatrix.Core.Models;\n\n/// <summary>\n/// Encrypted MessagePack Serializer that uses a global key derived from the computer's SID.\n/// Header contains additional random entropy as a salt that is used in decryption.\n/// </summary>\npublic static class GlobalEncryptedSerializer\n{\n    internal static KeyInfo KeyInfoV1 => new(FormatVersion.V1, 32, 16, 300);\n\n    internal static KeyInfo KeyInfoV2 => new(FormatVersion.V2, 32, 16, 300);\n\n    private static byte[] HeaderPrefixV2 => [0x4C, 0x4B, 0x1F, 0x45, 0x5C, 0x02, 0x00];\n\n    internal readonly record struct KeyInfo(FormatVersion Version, int KeySize, int SaltSize, int Iterations);\n\n    internal enum FormatVersion : byte\n    {\n        /// <summary>\n        /// Version 1\n        /// Original format, no header.\n        /// File: [(16 bytes salt), (Encrypted json data)]\n        /// </summary>\n        V1 = 1,\n\n        /// <summary>\n        /// Version 2+\n        /// Header: [4C, 4B, 1F, 45, 5C, ??, 00] where ?? is the version byte.\n        /// File: [(Header), (SaltSize bytes salt), (Encrypted json data)]\n        /// </summary>\n        V2 = 2\n    }\n\n    [Serializable]\n    [StructLayout(LayoutKind.Sequential, Pack = 1)]\n    internal struct HeaderV2\n    {\n        private unsafe fixed byte Prefix[7];\n\n        public int KeySize;\n\n        public int SaltSize;\n\n        public int Iterations;\n\n        public HeaderV2()\n        {\n            unsafe\n            {\n                Prefix[0] = 0x4C;\n                Prefix[1] = 0x4B;\n                Prefix[2] = 0x1F;\n                Prefix[3] = 0x45;\n                Prefix[4] = 0x5C;\n                Prefix[5] = 0x02;\n                Prefix[6] = 0x00;\n            }\n        }\n    }\n\n    public static T Deserialize<T>(ReadOnlySpan<byte> data)\n    {\n        // Header prefix, use v2\n        if (data.StartsWith(HeaderPrefixV2))\n        {\n            var json = DeserializeToBytesV2(data);\n            return JsonSerializer.Deserialize<T>(json)\n                ?? throw new InvalidOperationException(\"Deserialize returned null\");\n        }\n        // No header, use v1\n        else\n        {\n            var json = DeserializeToBytesV1(data);\n            return JsonSerializer.Deserialize<T>(json)\n                ?? throw new InvalidOperationException(\"Deserialize returned null\");\n        }\n    }\n\n    public static byte[] Serialize<T>(T obj)\n    {\n        return Serialize(obj, KeyInfoV2);\n    }\n\n    internal static byte[] Serialize<T>(T obj, KeyInfo keyInfo)\n    {\n        switch (keyInfo.Version)\n        {\n            case <= FormatVersion.V1:\n            {\n                var json = JsonSerializer.SerializeToUtf8Bytes(obj);\n                return SerializeToBytesV1(json);\n            }\n            case >= FormatVersion.V2:\n            {\n                var json = JsonSerializer.SerializeToUtf8Bytes(obj);\n                return SerializeToBytesV2(json, keyInfo);\n            }\n        }\n    }\n\n    private static byte[] SerializeToBytesV1(byte[] data)\n    {\n        // Get encrypted bytes and salt\n        var password = GetComputerKeyPhrase(KeyInfoV1.Version);\n        var (encrypted, salt) = EncryptBytes(data, password, KeyInfoV1);\n\n        // Prepend salt to encrypted json\n        var fileData = salt.Concat(encrypted);\n\n        return fileData.ToArray();\n    }\n\n    private static byte[] SerializeToBytesV2(byte[] data, KeyInfo keyInfo)\n    {\n        // Create header\n        var headerSize = Marshal.SizeOf<HeaderV2>();\n        var header = new HeaderV2\n        {\n            KeySize = keyInfo.KeySize,\n            SaltSize = keyInfo.SaltSize,\n            Iterations = keyInfo.Iterations\n        };\n        var headerBytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref header, 1));\n        Debug.Assert(headerBytes.Length == headerSize);\n\n        // Get salt + encrypted json\n        var password = GetComputerKeyPhrase(keyInfo.Version);\n        var (encrypted, salt) = EncryptBytes(data, password, keyInfo);\n        Debug.Assert(salt.Length == keyInfo.SaltSize);\n\n        // Write result as [header, salt, encrypted]\n        var result = new byte[headerBytes.Length + salt.Length + encrypted.Length];\n\n        headerBytes.CopyTo(result.AsSpan(0, headerSize));\n        salt.CopyTo(result.AsSpan(headerSize, keyInfo.SaltSize));\n        encrypted.CopyTo(result.AsSpan(headerSize + keyInfo.SaltSize));\n\n        return result;\n    }\n\n    private static byte[] DeserializeToBytesV1(ReadOnlySpan<byte> data)\n    {\n        var keyInfo = KeyInfoV1;\n\n        // Get salt from start of file\n        var salt = data[..keyInfo.SaltSize].ToArray();\n\n        // Get encrypted json from rest of file\n        var encryptedJson = data[keyInfo.SaltSize..];\n\n        var password = GetComputerKeyPhrase(keyInfo.Version);\n        return DecryptBytes(encryptedJson, salt, password, keyInfo);\n    }\n\n    private static byte[] DeserializeToBytesV2(ReadOnlySpan<byte> data)\n    {\n        // Read header\n        var headerSize = Marshal.SizeOf<HeaderV2>();\n        var header = MemoryMarshal.Read<HeaderV2>(data[..Marshal.SizeOf<HeaderV2>()]);\n\n        // Read Salt\n        var salt = data[headerSize..(headerSize + header.SaltSize)].ToArray();\n\n        // Rest of data is encrypted json\n        var encryptedData = data[(headerSize + header.SaltSize)..];\n\n        var keyInfo = new KeyInfo(FormatVersion.V2, header.KeySize, header.SaltSize, header.Iterations);\n\n        var password = GetComputerKeyPhrase(keyInfo.Version);\n        return DecryptBytes(encryptedData, salt, password, keyInfo);\n    }\n\n    private static string? GetComputerSid(FormatVersion version)\n    {\n        return version switch\n        {\n            FormatVersion.V1\n                => new DeviceIdBuilder()\n                    .AddMachineName()\n                    .AddOsVersion()\n                    .OnWindows(\n                        windows =>\n                            windows.AddProcessorId().AddMotherboardSerialNumber().AddSystemDriveSerialNumber()\n                    )\n                    .OnLinux(linux => linux.AddMotherboardSerialNumber().AddSystemDriveSerialNumber())\n                    .OnMac(mac => mac.AddSystemDriveSerialNumber().AddPlatformSerialNumber())\n                    .ToString(),\n            // v2: Removed OsVersion since it's updated often on macOS\n            FormatVersion.V2\n                => new DeviceIdBuilder()\n                    .AddMachineName()\n                    .OnWindows(\n                        windows =>\n                            windows.AddProcessorId().AddMotherboardSerialNumber().AddSystemDriveSerialNumber()\n                    )\n                    .OnLinux(linux => linux.AddMotherboardSerialNumber().AddSystemDriveSerialNumber())\n                    .OnMac(mac => mac.AddSystemDriveSerialNumber().AddPlatformSerialNumber())\n                    .ToString(),\n            _ => throw new ArgumentOutOfRangeException(nameof(version))\n        };\n    }\n\n    private static SecureString GetComputerKeyPhrase(FormatVersion version)\n    {\n        var keySource = GetComputerSid(version);\n        // If no sid, use username as fallback\n        keySource ??= Environment.UserName;\n\n        // XOR with fixed constant\n        const string keyPhrase = \"StabilityMatrix\";\n        var result = new SecureString();\n\n        for (var i = 0; i < keySource.Length; i++)\n        {\n            result.AppendChar((char)(keySource[i] ^ keyPhrase[i % keyPhrase.Length]));\n        }\n\n        return result;\n    }\n\n    private static byte[] GenerateSalt(int length)\n    {\n        return RandomNumberGenerator.GetBytes(length);\n    }\n\n    private static byte[] DeriveKey(SecureString password, byte[] salt, int iterations, int keyLength)\n    {\n        var ptr = Marshal.SecureStringToBSTR(password);\n        try\n        {\n            var length = Marshal.ReadInt32(ptr, -4);\n            var passwordByteArray = new byte[length];\n            var handle = GCHandle.Alloc(passwordByteArray, GCHandleType.Pinned);\n            try\n            {\n                for (var i = 0; i < length; i++)\n                {\n                    passwordByteArray[i] = Marshal.ReadByte(ptr, i);\n                }\n\n                using var rfc2898 = new Rfc2898DeriveBytes(\n                    passwordByteArray,\n                    salt,\n                    iterations,\n                    HashAlgorithmName.SHA512\n                );\n                return rfc2898.GetBytes(keyLength);\n            }\n            finally\n            {\n                Array.Clear(passwordByteArray, 0, passwordByteArray.Length);\n                handle.Free();\n            }\n        }\n        finally\n        {\n            Marshal.ZeroFreeBSTR(ptr);\n        }\n    }\n\n    internal static (byte[] EncryptedData, byte[] Salt) EncryptBytes(\n        byte[] data,\n        SecureString password,\n        KeyInfo keyInfo\n    )\n    {\n        var salt = GenerateSalt(keyInfo.SaltSize);\n        var key = DeriveKey(password, salt, keyInfo.Iterations, keyInfo.KeySize);\n\n        using var aes = Aes.Create();\n        aes.Key = key;\n        aes.IV = salt;\n        aes.Padding = PaddingMode.PKCS7;\n        aes.Mode = CipherMode.CBC;\n\n        var transform = aes.CreateEncryptor();\n        return (transform.TransformFinalBlock(data, 0, data.Length), salt);\n    }\n\n    internal static byte[] DecryptBytes(\n        ReadOnlySpan<byte> encryptedData,\n        byte[] salt,\n        SecureString password,\n        KeyInfo keyInfo\n    )\n    {\n        var key = DeriveKey(password, salt, keyInfo.Iterations, keyInfo.KeySize);\n\n        using var aes = Aes.Create();\n        aes.Key = key;\n        aes.IV = salt;\n        aes.Padding = PaddingMode.PKCS7;\n        aes.Mode = CipherMode.CBC;\n\n        var transform = aes.CreateDecryptor();\n        return transform.TransformFinalBlock(encryptedData.ToArray(), 0, encryptedData.Length);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/HybridModelFile.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Database;\n\nnamespace StabilityMatrix.Core.Models;\n\n/// <summary>\n/// Model file union that may be remote or local.\n/// </summary>\n[Localizable(false)]\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\npublic record HybridModelFile : ISearchText, IDownloadableResource\n{\n    /// <summary>\n    /// Singleton instance of <see cref=\"HybridModelFile\"/> that represents use of a default model.\n    /// </summary>\n    public static HybridModelFile Default { get; } = FromRemote(\"@default\");\n\n    /// <summary>\n    /// Singleton instance of <see cref=\"HybridModelFile\"/> that represents no model.\n    /// </summary>\n    public static HybridModelFile None { get; } = FromRemote(\"@none\");\n\n    public string? RemoteName { get; init; }\n\n    public LocalModelFile? Local { get; init; }\n\n    /// <summary>\n    /// Downloadable model information.\n    /// </summary>\n    public RemoteResource? DownloadableResource { get; init; }\n\n    public HybridModelType Type { get; init; }\n\n    [MemberNotNullWhen(true, nameof(RemoteName))]\n    [JsonIgnore]\n    public bool IsRemote => RemoteName != null;\n\n    [MemberNotNullWhen(true, nameof(DownloadableResource))]\n    public bool IsDownloadable => DownloadableResource != null;\n\n    [JsonIgnore]\n    public string RelativePath =>\n        Type switch\n        {\n            HybridModelType.Local => Local!.RelativePathFromSharedFolder,\n            HybridModelType.Remote => RemoteName!,\n            HybridModelType.Downloadable => DownloadableResource!.Value.RelativeDirectory == null\n                ? DownloadableResource!.Value.FileName\n                : Path.Combine(\n                    DownloadableResource!.Value.RelativeDirectory,\n                    DownloadableResource!.Value.FileName\n                ),\n            HybridModelType.None => throw new InvalidOperationException(),\n            _ => throw new ArgumentOutOfRangeException(),\n        };\n\n    [JsonIgnore]\n    public string FileName => Path.GetFileName(RelativePath);\n\n    [JsonIgnore]\n    public string ShortDisplayName\n    {\n        get\n        {\n            if (IsNone)\n            {\n                return \"None\";\n            }\n\n            if (IsDefault)\n            {\n                return \"Default\";\n            }\n\n            if (ReferenceEquals(this, RemoteModels.ControlNetReferenceOnlyModel))\n            {\n                return \"Reference Only\";\n            }\n\n            var fileName = Path.GetFileNameWithoutExtension(RelativePath);\n\n            if (\n                !fileName.Equals(\"diffusion_pytorch_model\", StringComparison.OrdinalIgnoreCase)\n                && !fileName.Equals(\"pytorch_model\", StringComparison.OrdinalIgnoreCase)\n                && !fileName.Equals(\"ip_adapter\", StringComparison.OrdinalIgnoreCase)\n            )\n            {\n                return Path.GetFileNameWithoutExtension(RelativePath);\n            }\n\n            // show a friendlier name when models have the same name like ip_adapter or diffusion_pytorch_model\n            var directoryName = Path.GetDirectoryName(RelativePath);\n            if (directoryName is null)\n                return Path.GetFileNameWithoutExtension(RelativePath);\n\n            var lastIndex = directoryName.LastIndexOf(Path.DirectorySeparatorChar);\n            if (lastIndex < 0)\n                return $\"{fileName} ({directoryName})\";\n\n            var parentDirectoryName = directoryName.Substring(lastIndex + 1);\n            return $\"{fileName} ({parentDirectoryName})\";\n        }\n    }\n\n    [JsonIgnore]\n    public string SortKey =>\n        Local?.ConnectedModelInfo != null\n            ? $\"{Local.ConnectedModelInfo.ModelName}{Local.ConnectedModelInfo.VersionName}\"\n            : ShortDisplayName;\n\n    public static HybridModelFile FromLocal(LocalModelFile local)\n    {\n        return new HybridModelFile { Local = local, Type = HybridModelType.Local };\n    }\n\n    public static HybridModelFile FromRemote(string remoteName)\n    {\n        return new HybridModelFile { RemoteName = remoteName, Type = HybridModelType.Remote };\n    }\n\n    public static HybridModelFile FromDownloadable(RemoteResource resource)\n    {\n        return new HybridModelFile { DownloadableResource = resource, Type = HybridModelType.Downloadable };\n    }\n\n    public string GetId()\n    {\n        return $\"{RelativePath.NormalizePathSeparators()};{IsNone};{IsDefault}\";\n    }\n\n    /// <summary>\n    /// Special Comparer that compares Remote Name and Local RelativePath,\n    /// used for letting remote models not override local models with more metadata.\n    /// Pls do not use for other stuff.\n    /// </summary>\n    private sealed class RemoteNameLocalEqualityComparer : IEqualityComparer<HybridModelFile>\n    {\n        public bool Equals(HybridModelFile? x, HybridModelFile? y)\n        {\n            if (ReferenceEquals(x, y))\n                return true;\n            if (ReferenceEquals(x, null))\n                return false;\n            if (ReferenceEquals(y, null))\n                return false;\n            if (x.GetType() != y.GetType())\n                return false;\n\n            if (!Equals(x.RelativePath.NormalizePathSeparators(), y.RelativePath.NormalizePathSeparators()))\n                return false;\n\n            // This equality affects replacements of remote over local models\n            // We want local and remote models to be considered equal if they have the same relative path\n            // But 2 local models with the same path but different config paths should be considered different\n            return !(x.Type == y.Type && x.Local?.ConfigFullPath != y.Local?.ConfigFullPath);\n        }\n\n        public int GetHashCode(HybridModelFile obj)\n        {\n            return HashCode.Combine(obj.IsNone, obj.IsDefault, obj.RelativePath);\n        }\n    }\n\n    /// <summary>\n    /// Actual general purpose equality comparer.\n    /// Use this for general equality checks :)\n    /// </summary>\n    private sealed class EqualityComparer : IEqualityComparer<HybridModelFile>\n    {\n        public bool Equals(HybridModelFile? x, HybridModelFile? y)\n        {\n            if (ReferenceEquals(x, y))\n                return true;\n            if (ReferenceEquals(x, null))\n                return false;\n            if (ReferenceEquals(y, null))\n                return false;\n            if (x.GetType() != y.GetType())\n                return false;\n\n            if (!Equals(x.RelativePath.NormalizePathSeparators(), y.RelativePath.NormalizePathSeparators()))\n                return false;\n\n            return Equals(x.Type, y.Type)\n                && x.RemoteName == y.RemoteName\n                && x.Local?.ConfigFullPath == y.Local?.ConfigFullPath\n                && x.Local?.ConnectedModelInfo == y.Local?.ConnectedModelInfo;\n        }\n\n        public int GetHashCode(HybridModelFile obj)\n        {\n            return HashCode.Combine(\n                obj.IsNone,\n                obj.IsDefault,\n                obj.RelativePath,\n                obj.RemoteName,\n                obj.Local?.ConfigFullPath,\n                obj.Local?.ConnectedModelInfo\n            );\n        }\n    }\n\n    /// <summary>\n    /// Whether this instance is the default model.\n    /// </summary>\n    public bool IsDefault => ReferenceEquals(this, Default);\n\n    /// <summary>\n    /// Whether this instance is no model.\n    /// </summary>\n    public bool IsNone => ReferenceEquals(this, None);\n\n    /// <summary>\n    /// Actual general purpose equality comparer.\n    /// Use this for general equality checks :)\n    /// </summary>\n    public static IEqualityComparer<HybridModelFile> Comparer { get; } = new EqualityComparer();\n\n    /// <summary>\n    /// Special Comparer that compares Remote Name and Local RelativePath,\n    /// used for letting remote models not override local models with more metadata.\n    /// Pls do not use for other stuff.\n    /// </summary>\n    public static IEqualityComparer<HybridModelFile> RemoteLocalComparer { get; } =\n        new RemoteNameLocalEqualityComparer();\n\n    [JsonIgnore]\n    public string SearchText => SortKey;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/HybridModelType.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic enum HybridModelType\n{\n    None,\n    Local,\n    Remote,\n    Downloadable\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/IContextAction.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models;\n\n[JsonDerivedType(typeof(CivitPostDownloadContextAction), \"CivitPostDownload\")]\n[JsonDerivedType(typeof(ModelPostDownloadContextAction), \"ModelPostDownload\")]\npublic interface IContextAction\n{\n    object? Context { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/IDownloadableResource.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\n\nnamespace StabilityMatrix.Core.Models;\n\n/// <summary>\n/// Interface for items that may have a downloadable resource.\n/// </summary>\npublic interface IDownloadableResource\n{\n    /// <summary>\n    /// Downloadable resource information.\n    /// </summary>\n    RemoteResource? DownloadableResource { get; }\n\n    [MemberNotNullWhen(true, nameof(DownloadableResource))]\n    bool IsDownloadable => DownloadableResource is not null;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/IHandleNavigation.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic interface IHandleNavigation\n{\n    bool GoBack();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/ISearchText.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic interface ISearchText\n{\n    string SearchText { get; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/IndexCollection.cs",
    "content": "﻿using System.Reactive.Linq;\nusing DynamicData;\nusing DynamicData.Binding;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class IndexCollection<TObject, TKey>\n    where TKey : notnull\n{\n    private readonly IImageIndexService imageIndexService;\n\n    public string? RelativePath { get; set; }\n\n    public SourceCache<TObject, TKey> ItemsSource { get; }\n\n    /// <summary>\n    /// Observable Collection of indexed items\n    /// </summary>\n    public IObservableCollection<TObject> Items { get; } = new ObservableCollectionExtended<TObject>();\n\n    public IndexCollection(\n        IImageIndexService imageIndexService,\n        Func<TObject, TKey> keySelector,\n        Func<IObservable<IChangeSet<TObject, TKey>>, IObservable<IChangeSet<TObject, TKey>>>? transform = null\n    )\n    {\n        this.imageIndexService = imageIndexService;\n\n        ItemsSource = new SourceCache<TObject, TKey>(keySelector);\n\n        var source = ItemsSource.Connect().DeferUntilLoaded();\n\n        if (transform is not null)\n        {\n            source = transform(source);\n        }\n\n        source.Bind(Items).ObserveOn(SynchronizationContext.Current).Subscribe();\n    }\n\n    public void Add(TObject item)\n    {\n        ItemsSource.AddOrUpdate(item);\n    }\n\n    public void Remove(TObject item)\n    {\n        ItemsSource.Remove(item);\n    }\n\n    public void RemoveKey(TKey key)\n    {\n        ItemsSource.RemoveKey(key);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Inference/InferenceProjectType.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Inference;\n\npublic enum InferenceProjectType\n{\n    Unknown,\n    TextToImage,\n    ImageToImage,\n    Inpainting,\n    Upscale,\n    ImageToVideo,\n    FluxTextToImage,\n    WanTextToVideo,\n    WanImageToVideo,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Inference/LayerDiffuseMode.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace StabilityMatrix.Core.Models.Inference;\n\npublic enum LayerDiffuseMode\n{\n    /// <summary>\n    /// The layer diffuse mode is not set.\n    /// </summary>\n    [Display(Name = \"None\")]\n    None,\n\n    /// <summary>\n    /// Generate foreground only with transparency. SD1.5\n    /// </summary>\n    [Display(Name = \"(SD 1.5) Generate Foreground with Transparency\")]\n    GenerateForegroundWithTransparencySD15,\n\n    /// <summary>\n    /// Generate foreground only with transparency. SDXL\n    /// </summary>\n    [Display(Name = \"(SDXL) Generate Foreground with Transparency\")]\n    GenerateForegroundWithTransparencySDXL,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Inference/ModelLoader.cs",
    "content": "﻿using StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Core.Models.Inference;\n\npublic enum ModelLoader\n{\n    [StringValue(\"Default\")]\n    Default,\n\n    [StringValue(\"GGUF\")]\n    Gguf,\n\n    [StringValue(\"nf4\")]\n    Nf4,\n\n    [StringValue(\"UNet\")]\n    Unet\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Inference/ModuleApplyStepTemporaryArgs.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;\n\nnamespace StabilityMatrix.Core.Models.Inference;\n\npublic class ModuleApplyStepTemporaryArgs\n{\n    /// <summary>\n    /// Temporary Primary apply step, used by ControlNet ReferenceOnly which changes the latent.\n    /// </summary>\n    public PrimaryNodeConnection? Primary { get; set; }\n\n    public VAENodeConnection? PrimaryVAE { get; set; }\n\n    /// <summary>\n    /// Used by Reference-Only ControlNet to indicate that <see cref=\"Primary\"/> has been batched.\n    /// </summary>\n    public bool IsPrimaryTempBatched { get; set; }\n\n    /// <summary>\n    /// When <see cref=\"IsPrimaryTempBatched\"/> is true, this is the index of the temp batch to pick after sampling.\n    /// </summary>\n    public int PrimaryTempBatchPickIndex { get; set; }\n\n    public Dictionary<string, ModelConnections> Models { get; set; } =\n        new() { [\"Base\"] = new ModelConnections(\"Base\"), [\"Refiner\"] = new ModelConnections(\"Refiner\") };\n\n    public ModelConnections Base => Models[\"Base\"];\n    public ModelConnections Refiner => Models[\"Refiner\"];\n\n    public ConditioningConnections GetRefinerOrBaseConditioning()\n    {\n        return Refiner.Conditioning\n            ?? Base.Conditioning\n            ?? throw new NullReferenceException(\"No Refiner or Base Conditioning\");\n    }\n\n    public ModelNodeConnection GetRefinerOrBaseModel()\n    {\n        return Refiner.Model ?? Base.Model ?? throw new NullReferenceException(\"No Refiner or Base Model\");\n    }\n\n    public VAENodeConnection GetDefaultVAE()\n    {\n        return PrimaryVAE ?? Refiner.VAE ?? Base.VAE ?? throw new NullReferenceException(\"No VAE\");\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/InferenceDefaults.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Api.Comfy;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic record InferenceDefaults\n{\n    public ComfySampler? Sampler { get; set; }\n    public ComfyScheduler? Scheduler { get; set; }\n    public int Steps { get; set; }\n    public double CfgScale { get; set; }\n    public int Width { get; set; }\n    public int Height { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/InferenceRunCustomPromptEventArgs.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Api.Comfy.Nodes;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class InferenceQueueCustomPromptEventArgs : EventArgs\n{\n    public ComfyNodeBuilder Builder { get; } = new();\n\n    public NodeDictionary Nodes => Builder.Nodes;\n\n    public long? SeedOverride { get; init; }\n\n    public List<(string SourcePath, string DestinationRelativePath)> FilesToTransfer { get; init; } = [];\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/InstalledPackage.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Core.Models;\n\n/// <summary>\n/// Profile information for a user-installed package.\n/// </summary>\npublic class InstalledPackage : IJsonOnDeserialized\n{\n    // Unique ID for the installation\n    public Guid Id { get; set; }\n\n    // User defined name\n    public string? DisplayName { get; set; }\n\n    // Package name\n    public string? PackageName { get; set; }\n\n    // Package version\n    public InstalledPackageVersion? Version { get; set; }\n\n    /// <summary>\n    /// Relative path from the library root.\n    /// </summary>\n    public string? LibraryPath { get; set; }\n\n    /// <summary>\n    /// Full path to the package, using LibraryPath and GlobalConfig.LibraryDir.\n    /// </summary>\n    [JsonIgnore]\n    public string? FullPath =>\n        LibraryPath != null ? System.IO.Path.Combine(GlobalConfig.LibraryDir, LibraryPath) : null;\n\n    public string? LaunchCommand { get; set; }\n    public List<LaunchOption>? LaunchArgs { get; set; }\n    public DateTimeOffset? LastUpdateCheck { get; set; }\n    public bool UpdateAvailable { get; set; }\n    public bool DontCheckForUpdates { get; set; }\n\n    [JsonConverter(typeof(JsonStringEnumConverter<TorchIndex>))]\n    public TorchIndex? PreferredTorchIndex { get; set; }\n\n    [JsonConverter(typeof(JsonStringEnumConverter<TorchIndex>))]\n    [Obsolete(\"Use PreferredTorchIndex instead. (Kept for migration)\")]\n    public TorchIndex? PreferredTorchVersion { get; set; }\n\n    [JsonConverter(typeof(JsonStringEnumConverter<SharedFolderMethod>))]\n    public SharedFolderMethod? PreferredSharedFolderMethod { get; set; }\n\n    public bool UseSharedOutputFolder { get; set; }\n\n    public List<string>? ExtraExtensionManifestUrls { get; set; }\n\n    public List<PipPackageSpecifierOverride>? PipOverrides { get; set; }\n\n    public string PythonVersion { get; set; } = PyInstallationManager.Python_3_10_11.StringValue;\n\n    /// <summary>\n    /// Get the launch args host option value.\n    /// </summary>\n    public string? GetLaunchArgsHost()\n    {\n        var hostOption = LaunchArgs?.FirstOrDefault(x => x.Name.ToLowerInvariant() == \"--host\");\n        if (hostOption?.OptionValue != null)\n        {\n            return hostOption.OptionValue as string;\n        }\n        return hostOption?.DefaultValue as string;\n    }\n\n    /// <summary>\n    /// Get the launch args port option value.\n    /// </summary>\n    public string? GetLaunchArgsPort()\n    {\n        var portOption = LaunchArgs?.FirstOrDefault(x => x.Name.ToLowerInvariant() == \"--port\");\n        if (portOption?.OptionValue != null)\n        {\n            return portOption.OptionValue as string;\n        }\n        return portOption?.DefaultValue as string;\n    }\n\n    /// <summary>\n    /// Get the path as a relative sub-path of the relative path.\n    /// If not a sub-path, return null.\n    /// </summary>\n    public static string? GetSubPath(string relativeTo, string path)\n    {\n        var relativePath = System.IO.Path.GetRelativePath(relativeTo, path);\n        // GetRelativePath returns the path if it's not relative\n        if (relativePath == path)\n            return null;\n        // Further check if the path is a sub-path of the library\n        var isSubPath =\n            relativePath != \".\"\n            && relativePath != \"..\"\n            && !relativePath.StartsWith(\"..\" + System.IO.Path.DirectorySeparatorChar)\n            && !System.IO.Path.IsPathRooted(relativePath);\n        return isSubPath ? relativePath : null;\n    }\n\n    public static IEqualityComparer<InstalledPackage> Comparer { get; } =\n        new PropertyComparer<InstalledPackage>(p => p.Id);\n\n    protected bool Equals(InstalledPackage other)\n    {\n        return Id.Equals(other.Id);\n    }\n\n    public override bool Equals(object? obj)\n    {\n        if (ReferenceEquals(null, obj))\n            return false;\n        if (ReferenceEquals(this, obj))\n            return true;\n        return obj.GetType() == this.GetType() && Equals((InstalledPackage)obj);\n    }\n\n    public override int GetHashCode()\n    {\n        return Id.GetHashCode();\n    }\n\n    #region Migration / Obsolete\n\n    // Old type absolute path\n    [Obsolete(\"Use LibraryPath instead. (Kept for migration)\")]\n    public string? Path { get; set; }\n\n    // Old type versions\n    [Obsolete(\"Use Version instead. (Kept for migration)\")]\n    public string? PackageVersion { get; set; }\n\n    [Obsolete(\"Use Version instead. (Kept for migration)\")]\n    public string? InstalledBranch { get; set; }\n\n    [Obsolete(\"Use Version instead. (Kept for migration)\")]\n    public string? DisplayVersion { get; set; }\n\n    /// <summary>\n    /// Migrates the old Path to the new LibraryPath.\n    /// If libraryDirectory is null, GlobalConfig.LibraryDir is used.\n    /// </summary>\n    /// <returns>True if the path was migrated, false otherwise.</returns>\n    public bool TryPureMigratePath(string? libraryDirectory = null)\n    {\n#pragma warning disable CS0618\n        var oldPath = Path;\n#pragma warning restore CS0618\n        if (oldPath == null)\n            return false;\n\n        // Check if the path is a sub-path of the library\n        var library = libraryDirectory ?? GlobalConfig.LibraryDir;\n        var relativePath = GetSubPath(library, oldPath);\n\n        // If so we migrate without any IO operations\n        if (relativePath != null)\n        {\n            LibraryPath = relativePath;\n#pragma warning disable CS0618\n            Path = null;\n#pragma warning restore CS0618\n            return true;\n        }\n\n        return false;\n    }\n\n    /// <summary>\n    ///  Check if the old Path can be migrated to the new LibraryPath.\n    /// </summary>\n    /// <param name=\"libraryDirectory\"></param>\n    /// <returns></returns>\n    public bool CanPureMigratePath(string? libraryDirectory = null)\n    {\n#pragma warning disable CS0618\n        var oldPath = Path;\n#pragma warning restore CS0618\n        if (oldPath == null)\n            return false;\n\n        // Check if the path is a sub-path of the library\n        var library = libraryDirectory ?? GlobalConfig.LibraryDir;\n        var relativePath = GetSubPath(library, oldPath);\n        return relativePath != null;\n    }\n\n    /// <summary>\n    /// Migrate the old Path to the new LibraryPath.\n    /// If libraryDirectory is null, GlobalConfig.LibraryDir is used.\n    /// Will move the package directory to Library/Packages if not relative.\n    /// </summary>\n    public async Task MigratePath(string? libraryDirectory = null)\n    {\n#pragma warning disable CS0618\n        var oldPath = Path;\n#pragma warning restore CS0618\n        if (oldPath == null)\n            return;\n\n        var libDir = libraryDirectory ?? GlobalConfig.LibraryDir;\n        // if old package Path is same as new library, return\n        if (oldPath.Replace(DisplayName, \"\") == libDir)\n        {\n            // Update the paths\n#pragma warning disable CS0618\n            Path = null;\n#pragma warning restore CS0618\n            LibraryPath = System.IO.Path.Combine(\"Packages\", DisplayName);\n            return;\n        }\n\n        // Try using pure migration first\n        if (TryPureMigratePath(libraryDirectory))\n            return;\n\n        // If not, we need to move the package directory\n        var packageFolderName = new DirectoryInfo(oldPath).Name;\n\n        // Get the new Library/Packages path\n        var library = libraryDirectory ?? GlobalConfig.LibraryDir;\n        var newPackagesDir = System.IO.Path.Combine(library, \"Packages\");\n\n        // Get the new target path\n        var newPackagePath = System.IO.Path.Combine(newPackagesDir, packageFolderName);\n        // Ensure it is not already there, if so, add a suffix until it's not\n        var suffix = 2;\n        while (Directory.Exists(newPackagePath))\n        {\n            newPackagePath = System.IO.Path.Combine(newPackagesDir, $\"{packageFolderName}-{suffix}\");\n            suffix++;\n        }\n\n        // Move the package directory\n        await Task.Run(() => Utilities.CopyDirectory(oldPath, newPackagePath, true));\n\n        // Update the paths\n#pragma warning disable CS0618\n        Path = null;\n#pragma warning restore CS0618\n        LibraryPath = System.IO.Path.Combine(\"Packages\", packageFolderName);\n    }\n\n    public void OnDeserialized()\n    {\n#pragma warning disable CS0618 // Type or member is obsolete\n        // handle TorchIndex migration\n        PreferredTorchIndex ??= PreferredTorchVersion;\n\n        // Handle version migration\n        if (Version != null)\n            return;\n\n        if (string.IsNullOrWhiteSpace(InstalledBranch) && !string.IsNullOrWhiteSpace(PackageVersion))\n        {\n            // release mode\n            Version = new InstalledPackageVersion\n            {\n                InstalledReleaseVersion = PackageVersion,\n                IsPrerelease = false\n            };\n        }\n        else if (!string.IsNullOrWhiteSpace(PackageVersion))\n        {\n            Version = new InstalledPackageVersion\n            {\n                InstalledBranch = InstalledBranch,\n                InstalledCommitSha = PackageVersion,\n                IsPrerelease = false\n            };\n        }\n#pragma warning restore CS0618 // Type or member is obsolete\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/InstalledPackageVersion.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class InstalledPackageVersion\n{\n    public string? InstalledReleaseVersion { get; set; }\n    public string? InstalledBranch { get; set; }\n    public string? InstalledCommitSha { get; set; }\n    public bool IsPrerelease { get; set; }\n\n    [JsonIgnore]\n    public bool IsReleaseMode => string.IsNullOrWhiteSpace(InstalledBranch);\n\n    [JsonIgnore]\n    public string DisplayVersion =>\n        (\n            IsReleaseMode\n                ? InstalledReleaseVersion\n                : string.IsNullOrWhiteSpace(InstalledCommitSha)\n                    ? InstalledBranch\n                    : $\"{InstalledBranch}@{InstalledCommitSha[..7]}\"\n        ) ?? string.Empty;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/LaunchOption.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class LaunchOption\n{\n    public required string Name { get; init; }\n\n    public LaunchOptionType Type { get; init; } = LaunchOptionType.Bool;\n    \n    [JsonIgnore]\n    public object? DefaultValue { get; init; }\n    \n    [JsonIgnore]\n    public bool HasDefaultValue => DefaultValue != null;\n\n    [JsonConverter(typeof(LaunchOptionValueJsonConverter))]\n    public object? OptionValue { get; set; }\n\n    /// <summary>\n    /// Checks if the option has no user entered value,\n    /// or that the value is the same as the default value.\n    /// </summary>\n    /// <exception cref=\"ArgumentOutOfRangeException\"></exception>\n    public bool IsEmptyOrDefault()\n    {\n        return Type switch\n        {\n            LaunchOptionType.Bool => OptionValue == null,\n            LaunchOptionType.Int => OptionValue == null ||\n                                    (int?) OptionValue == (int?) DefaultValue,\n            LaunchOptionType.String => OptionValue == null ||\n                                       (string?) OptionValue == (string?) DefaultValue,\n            _ => throw new ArgumentOutOfRangeException()\n        };\n    }\n\n    /// <summary>\n    /// Parses a string value to the correct type for the option.\n    /// This returned object can be assigned to OptionValue.\n    /// </summary>\n    public static object? ParseValue(string? value, LaunchOptionType type)\n    {\n        return type switch\n        {\n            LaunchOptionType.Bool => bool.TryParse(value, out var boolValue) ? boolValue : null,\n            LaunchOptionType.Int => int.TryParse(value, out var intValue) ? intValue : null,\n            LaunchOptionType.String => value,\n            _ => throw new ArgumentException($\"Unknown option type {type}\")\n        };\n    }\n\n    /// <summary>\n    /// Convert the option value to a string that can be passed to a Process.\n    /// </summary>\n    /// <returns></returns>\n    /// <exception cref=\"ArgumentException\"></exception>\n    public string? ToArgString()\n    {\n        // Convert to string\n        switch (Type)\n        {\n            case LaunchOptionType.Bool:\n                return (bool?) OptionValue == true ? Name : null;\n            case LaunchOptionType.Int:\n                return (int?) OptionValue != null ? $\"{Name} {OptionValue}\" : null;\n            case LaunchOptionType.String:\n                var valueString = (string?) OptionValue;\n                // Special case empty string name to not do quoting (for custom launch args)\n                if (Name == \"\")\n                {\n                    return valueString;\n                }\n                return string.IsNullOrWhiteSpace(valueString) ? null : $\"{Name} {ProcessRunner.Quote(valueString)}\";\n            default:\n                throw new ArgumentOutOfRangeException();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/LaunchOptionCard.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Diagnostics;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic readonly record struct LaunchOptionCard\n{\n    public required string Title { get; init; }\n    public required LaunchOptionType Type { get; init; }\n    public required IReadOnlyList<LaunchOption> Options { get; init; }\n    public string? Description { get; init; }\n    \n    public static LaunchOptionCard FromDefinition(LaunchOptionDefinition definition)\n    {\n        return new LaunchOptionCard\n        {\n            Title = definition.Name,\n            Description = definition.Description,\n            Type = definition.Type,\n    \n            Options = definition.Options.Select(s =>\n            {\n                var option = new LaunchOption\n                {\n                    Name = s,\n                    Type = definition.Type,\n                    DefaultValue = definition.DefaultValue\n                };\n                return option;\n            }).ToImmutableArray()\n        };\n    }\n    \n    /// <summary>\n    /// Yield LaunchOptionCards given definitions and launch args to load\n    /// </summary>\n    /// <param name=\"definitions\"></param>\n    /// <param name=\"launchArgs\"></param>\n    /// <returns></returns>\n    /// <exception cref=\"InvalidOperationException\"></exception>\n    public static IEnumerable<LaunchOptionCard> FromDefinitions(\n        IEnumerable<LaunchOptionDefinition> definitions, \n        IEnumerable<LaunchOption> launchArgs)\n    {\n        // During card creation, store dict of options with initial values\n        var initialOptions = new Dictionary<string, object>();\n        \n        // To dictionary ignoring duplicates\n        var launchArgsDict = launchArgs\n            .ToLookup(launchArg => launchArg.Name)\n            .ToDictionary(\n                group => group.Key, \n                group => group.First()\n            );\n        \n        // Create cards\n        foreach (var definition in definitions)\n        {\n            // Check that non-bool types have exactly one option\n            if (definition.Type != LaunchOptionType.Bool && definition.Options.Count != 1)\n            {\n                throw new InvalidOperationException(\n                    $\"Definition: '{definition.Name}' has {definition.Options.Count} options,\" +\n                    $\" it must have exactly 1 option for non-bool types\");\n            }\n            // Store initial values\n            if (definition.InitialValue != null)\n            {\n                // For bool types, initial value can be string (single/multiple options) or bool (single option)\n                if (definition.Type == LaunchOptionType.Bool)\n                {\n                    // For single option, check bool\n                    if (definition.Options.Count == 1 && definition.InitialValue is bool boolValue)\n                    {\n                        initialOptions[definition.Options.First()] = boolValue;\n                    }\n                    else\n                    {\n                        // For single/multiple options (string only)\n                        var option = definition.Options.FirstOrDefault(opt => opt.Equals(definition.InitialValue));\n                        if (option == null)\n                        {\n                            throw new InvalidOperationException(\n                                $\"Definition '{definition.Name}' has InitialValue of '{definition.InitialValue}', but it was not found in options:\" +\n                                $\" '{string.Join(\",\", definition.Options)}'\");\n                        }\n                        initialOptions[option] = true;\n                    }\n                }\n                else\n                {\n                    // Otherwise store initial value for first option\n                    initialOptions[definition.Options.First()] = definition.InitialValue;\n                }\n            }\n            // Create the new card\n            var card = new LaunchOptionCard\n            {\n                Title = definition.Name,\n                Description = definition.Description,\n                Type = definition.Type,\n                Options = definition.Options.Select(s =>\n                {\n                    // Parse defaults and user loaded values here\n                    var userOption = launchArgsDict.GetValueOrDefault(s);\n                    var userValue = userOption?.OptionValue;\n                    // If no user value, check set initial value\n                    if (userValue is null)\n                    {\n                        var initialValue = initialOptions.GetValueOrDefault(s);\n                        userValue ??= initialValue;\n                        Debug.WriteLineIf(initialValue != null, \n                            $\"Using initial value {initialValue} for option {s}\");\n                    }\n                    \n                    var option = new LaunchOption\n                    {\n                        Name = s,\n                        Type = definition.Type,\n                        DefaultValue = definition.DefaultValue,\n                        OptionValue = userValue\n                    };\n                    return option;\n                }).ToImmutableArray()\n            };\n            \n            yield return card;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/LaunchOptionDefinition.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models;\n\n/// <summary>\n/// Defines a launch option for a BasePackage.\n/// </summary>\npublic record LaunchOptionDefinition\n{\n    /// <summary>\n    /// Name or title of the card.\n    /// </summary>\n    public required string Name { get; init; }\n\n    /// <summary>\n    /// Type of the option. \"bool\", \"int\", or \"string\"\n    /// - \"bool\" can supply 1 or more flags in the Options list (e.g. [\"--api\", \"--lowvram\"])\n    /// - \"int\" and \"string\" should supply a single flag in the Options list (e.g. [\"--width\"], [\"--api\"])\n    /// </summary>\n    public required LaunchOptionType Type { get; init; }\n    \n    /// <summary>\n    /// Optional description of the option.\n    /// </summary>\n    public string? Description { get; init; }\n    \n    /// <summary>\n    /// Server-side default for the option. (Ignored for launch and saving if value matches)\n    /// Use `InitialValue` to provide a default that is set as the user value and used for launch.\n    /// </summary>\n    public object? DefaultValue { get; init; }\n    \n    /// <summary>\n    /// Initial value for the option if no set value is available, set as the user value on save.\n    /// Use `DefaultValue` to provide a server-side default that is ignored for launch and saving.\n    /// </summary>\n    public object? InitialValue { get; init; }\n\n    /// <summary>\n    /// Minimum number of selected options (for validation)\n    /// </summary>\n    public int? MinSelectedOptions { get; init; }\n    \n    /// <summary>\n    /// Maximum number of selected options (for validation)\n    /// </summary>\n    public int? MaxSelectedOptions { get; init; }\n\n    /// <summary>\n    /// List of option flags like \"--api\", \"--lowvram\", etc.\n    /// </summary>\n    public List<string> Options { get; init; } = new();\n    \n    /// <summary>\n    /// Constant for the Extras launch option.\n    /// </summary>\n    [JsonIgnore]\n    public static LaunchOptionDefinition Extras => new()\n    {\n        Name = \"Extra Launch Arguments\",\n        Type = LaunchOptionType.String,\n        Options = new List<string> {\"\"}\n    };\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/LaunchOptionType.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models;\n\n[JsonConverter(typeof(JsonStringEnumConverter<LaunchOptionType>))]\npublic enum LaunchOptionType\n{\n    Bool,\n    String,\n    Int\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/LicenseInfo.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic class LicenseInfo\n{\n    public string PackageName { get; set; }\n    public string PackageVersion { get; set; }\n    public string PackageUrl { get; set; }\n    public string Copyright { get; set; }\n    public List<string> Authors { get; set; }\n    public string Description { get; set; }\n    public string LicenseUrl { get; set; }\n    public string LicenseType { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/LoadState.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic enum LoadState\n{\n    NotLoaded,\n    Loading,\n    Loaded,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/ModelPostDownloadContextAction.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class ModelPostDownloadContextAction : IContextAction\n{\n    /// <inheritdoc />\n    public object? Context { get; set; }\n\n    [SuppressMessage(\"Performance\", \"CA1822:Mark members as static\")]\n    public void Invoke(IModelIndexService modelIndexService)\n    {\n        // Request reindex\n        modelIndexService.BackgroundRefreshIndex();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/ObservableHashSet.cs",
    "content": "﻿using System.Collections.ObjectModel;\nusing System.Collections.Specialized;\nusing System.ComponentModel;\n\nnamespace StabilityMatrix.Core.Models;\n\n/// <summary>\n/// Represents a observable hash set of values.\n/// </summary>\n/// <typeparam name=\"T\">The type of elements in the hash set.</typeparam>\n/// <summary>\n/// An <see cref=\"ObservableCollection{T}\"/> that also implements <see cref=\"ISet{T}\"/> –\n/// perfect when you need *unique* items **and** change notifications\n/// (e.g. for XAML bindings or ReactiveUI/DynamicData pipelines).\n/// </summary>\npublic class ObservableHashSet<T> : ObservableCollection<T>, ISet<T>\n{\n    private readonly HashSet<T> set;\n\n    #region ░░░ Constructors ░░░\n\n    public ObservableHashSet()\n        : this((IEqualityComparer<T>?)null) { }\n\n    public ObservableHashSet(IEqualityComparer<T>? comparer)\n        : base()\n    {\n        set = new HashSet<T>(comparer ?? EqualityComparer<T>.Default);\n    }\n\n    public ObservableHashSet(IEnumerable<T> collection)\n        : this(collection, null) { }\n\n    public ObservableHashSet(IEnumerable<T> collection, IEqualityComparer<T>? comparer)\n        : this(comparer)\n    {\n        foreach (var item in collection)\n            Add(item); // guarantees uniqueness + raises events\n    }\n\n    #endregion\n\n    #region ░░░ Overrides that enforce set semantics ░░░\n\n    /// <summary>\n    /// Called by <see cref=\"Collection{T}.Add\"/> (and therefore by LINQ’s\n    /// <c>Add</c> extension, by <see cref=\"ICollection{T}.Add\"/>, etc.).\n    /// We only insert if the value was *not* already present in the set.\n    /// </summary>\n    protected override void InsertItem(int index, T item)\n    {\n        if (set.Add(item)) // true == unique\n            base.InsertItem(index, item); // fires events\n        // duplicate → ignore silently (same behaviour as HashSet<T>)\n    }\n\n    protected override void SetItem(int index, T item)\n    {\n        var existing = this[index];\n\n        // no-op if same reference/value\n        if (EqualityComparer<T>.Default.Equals(existing, item))\n            return;\n\n        // attempting to “replace” with a value already in the set → ignore\n        if (set.Contains(item))\n            return;\n\n        set.Remove(existing);\n        set.Add(item);\n\n        base.SetItem(index, item); // fires events\n    }\n\n    protected override void RemoveItem(int index)\n    {\n        set.Remove(this[index]);\n        base.RemoveItem(index); // fires events\n    }\n\n    protected override void ClearItems()\n    {\n        set.Clear();\n        base.ClearItems(); // fires events\n    }\n\n    #endregion\n\n    #region ░░░ ISet<T> explicit implementation ░░░\n\n    // Most operations delegate to the HashSet for the heavy lifting,\n    // THEN synchronise the ObservableCollection so that UI bindings\n    // get the right notifications.\n\n    bool ISet<T>.Add(T item) => !set.Contains(item) && AddAndReturnTrue(item);\n\n    private bool AddAndReturnTrue(T item)\n    {\n        base.Add(item);\n        return true;\n    }\n\n    void ISet<T>.ExceptWith(IEnumerable<T> other)\n    {\n        ArgumentNullException.ThrowIfNull(other);\n        foreach (var item in other)\n            _ = Remove(item); // Remove() already updates both collections\n    }\n\n    void ISet<T>.IntersectWith(IEnumerable<T> other)\n    {\n        ArgumentNullException.ThrowIfNull(other);\n        var keep = new HashSet<T>(other, set.Comparer);\n        for (var i = Count - 1; i >= 0; i--)\n            if (!keep.Contains(this[i]))\n                RemoveItem(i);\n    }\n\n    void ISet<T>.SymmetricExceptWith(IEnumerable<T> other)\n    {\n        ArgumentNullException.ThrowIfNull(other);\n        var toToggle = new HashSet<T>(other, set.Comparer);\n\n        foreach (var item in toToggle)\n            if (!Remove(item))\n                Add(item);\n    }\n\n    void ISet<T>.UnionWith(IEnumerable<T> other)\n    {\n        ArgumentNullException.ThrowIfNull(other);\n        foreach (var item in other)\n            _ = ((ISet<T>)this).Add(item); // uses our Add logic\n    }\n\n    // The pure-query methods just delegate to HashSet<T>:\n\n    bool ISet<T>.IsSubsetOf(IEnumerable<T> other) => set.IsSubsetOf(other);\n\n    bool ISet<T>.IsSupersetOf(IEnumerable<T> other) => set.IsSupersetOf(other);\n\n    bool ISet<T>.IsProperSubsetOf(IEnumerable<T> other) => set.IsProperSubsetOf(other);\n\n    bool ISet<T>.IsProperSupersetOf(IEnumerable<T> other) => set.IsProperSupersetOf(other);\n\n    bool ISet<T>.Overlaps(IEnumerable<T> other) => set.Overlaps(other);\n\n    public bool SetEquals(IEnumerable<T> other) => set.SetEquals(other);\n\n    #endregion\n\n    #region ░░░ Useful helpers ░░░\n\n    public new bool Contains(T item) => set.Contains(item);\n\n    /// <summary>\n    /// Returns a copy of the internal <see cref=\"HashSet{T}\"/>.\n    /// Handy when you need fast look-ups without exposing mutability.\n    /// </summary>\n    public HashSet<T> ToHashSet() => new(set, set.Comparer);\n\n    public void AddRange(IEnumerable<T> items)\n    {\n        ArgumentNullException.ThrowIfNull(items);\n\n        // 1.  Keep only values that are truly new for this set\n        var newItems = new List<T>();\n        foreach (var item in items)\n            if (set.Add(item)) // true == not yet present\n                newItems.Add(item);\n\n        if (newItems.Count == 0)\n            return; // nothing to do\n\n        CheckReentrancy(); // ObservableCollection helper\n\n        // 2.  Append to the internal Items list *without* raising events yet\n        int startIdx = Items.Count;\n        foreach (var item in newItems)\n            Items.Add(item);\n\n        // 3.  Fire a single consolidated notification\n        OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));\n\n        //   choose either a single \"Add\" with the batch…\n        OnCollectionChanged(\n            new NotifyCollectionChangedEventArgs(\n                NotifyCollectionChangedAction.Add,\n                newItems, // the items added\n                startIdx\n            )\n        ); // starting index\n\n        // …or, if you want absolute safety for all consumers,\n        // you could raise \"Reset\" instead:\n        // OnCollectionChanged(new NotifyCollectionChangedEventArgs(\n        //     NotifyCollectionChangedAction.Reset));\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/OrderedValue.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic readonly record struct OrderedValue<TValue>(int Order, TValue Value)\n    : IComparable<OrderedValue<TValue>>,\n        IComparable\n{\n    private sealed class OrderRelationalComparer : IComparer<OrderedValue<TValue>>\n    {\n        public int Compare(OrderedValue<TValue> x, OrderedValue<TValue> y)\n        {\n            return x.Order.CompareTo(y.Order);\n        }\n    }\n\n    public static IComparer<OrderedValue<TValue>> OrderComparer { get; } = new OrderRelationalComparer();\n\n    public int CompareTo(OrderedValue<TValue> other)\n    {\n        return Order.CompareTo(other.Order);\n    }\n\n    public int CompareTo(object? obj)\n    {\n        if (ReferenceEquals(null, obj))\n            return 1;\n\n        return obj is OrderedValue<TValue> other\n            ? CompareTo(other)\n            : throw new ArgumentException($\"Object must be of type {nameof(OrderedValue<TValue>)}\");\n    }\n\n    public static bool operator <(OrderedValue<TValue> left, OrderedValue<TValue> right)\n    {\n        return left.CompareTo(right) < 0;\n    }\n\n    public static bool operator >(OrderedValue<TValue> left, OrderedValue<TValue> right)\n    {\n        return left.CompareTo(right) > 0;\n    }\n\n    public static bool operator <=(OrderedValue<TValue> left, OrderedValue<TValue> right)\n    {\n        return left.CompareTo(right) <= 0;\n    }\n\n    public static bool operator >=(OrderedValue<TValue> left, OrderedValue<TValue> right)\n    {\n        return left.CompareTo(right) >= 0;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageDifficulty.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic enum PackageDifficulty\n{\n    ReallyRecommended = -1,\n    Recommended = 0,\n    InferenceCompatible = 1,\n    Simple = 2,\n    Advanced = 3,\n    Expert = 4,\n    Nightmare = 5,\n    UltraNightmare = 6,\n    Impossible = 999\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/ActionPackageStep.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\n/// <summary>\n/// A package step that wraps an async action, useful for ad-hoc operations\n/// that need to run within the PackageModificationRunner.\n/// </summary>\npublic class ActionPackageStep(\n    Func<IProgress<ProgressReport>, Task> action,\n    string progressTitle = \"Working...\"\n) : IPackageStep\n{\n    public string ProgressTitle => progressTitle;\n\n    public async Task ExecuteAsync(IProgress<ProgressReport>? progress)\n    {\n        await action(progress ?? new Progress<ProgressReport>()).ConfigureAwait(false);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/AddInstalledPackageStep.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class AddInstalledPackageStep : IPackageStep\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly InstalledPackage newInstalledPackage;\n\n    public AddInstalledPackageStep(\n        ISettingsManager settingsManager,\n        InstalledPackage newInstalledPackage\n    )\n    {\n        this.settingsManager = settingsManager;\n        this.newInstalledPackage = newInstalledPackage;\n    }\n\n    public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        if (!string.IsNullOrWhiteSpace(newInstalledPackage.DisplayName))\n        {\n            settingsManager.PackageInstallsInProgress.Remove(newInstalledPackage.DisplayName);\n        }\n\n        await using var transaction = settingsManager.BeginTransaction();\n        transaction.Settings.InstalledPackages.Add(newInstalledPackage);\n        transaction.Settings.ActiveInstalledPackageId = newInstalledPackage.Id;\n    }\n\n    public string ProgressTitle => $\"{newInstalledPackage.DisplayName} Installed\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/DownloadOpenArtWorkflowStep.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Nodes;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Models.Api.OpenArt;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class DownloadOpenArtWorkflowStep(\n    IOpenArtApi openArtApi,\n    OpenArtSearchResult workflow,\n    ISettingsManager settingsManager\n) : IPackageStep\n{\n    public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        var workflowData = await openArtApi\n            .DownloadWorkflowAsync(new OpenArtDownloadRequest { WorkflowId = workflow.Id })\n            .ConfigureAwait(false);\n\n        var workflowJson = JsonSerializer.SerializeToNode(workflow);\n\n        Directory.CreateDirectory(settingsManager.WorkflowDirectory);\n        var filePath = Path.Combine(settingsManager.WorkflowDirectory, $\"{workflowData.Filename}.json\");\n\n        var jsonObject = JsonNode.Parse(workflowData.Payload) as JsonObject;\n        jsonObject?.Add(\"sm_workflow_data\", workflowJson);\n\n        await File.WriteAllTextAsync(filePath, JsonSerializer.Serialize(jsonObject)).ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(1f, \"Downloaded OpenArt Workflow\"));\n    }\n\n    public string ProgressTitle => \"Downloading OpenArt Workflow\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/DownloadPackageVersionStep.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class DownloadPackageVersionStep(\n    BasePackage package,\n    string installPath,\n    DownloadPackageOptions options\n) : ICancellablePackageStep\n{\n    public Task ExecuteAsync(\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        return package.DownloadPackage(installPath, options, progress, cancellationToken);\n    }\n\n    public string ProgressTitle => \"Downloading package...\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/ICancellablePackageStep.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic interface ICancellablePackageStep : IPackageStep\n{\n    Task IPackageStep.ExecuteAsync(IProgress<ProgressReport>? progress)\n    {\n        return ExecuteAsync(progress, CancellationToken.None);\n    }\n\n    Task ExecuteAsync(\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/IPackageModificationRunner.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic interface IPackageModificationRunner\n{\n    Task ExecuteSteps(IEnumerable<IPackageStep> steps);\n\n    bool IsRunning { get; }\n\n    [MemberNotNullWhen(true, nameof(Exception))]\n    bool Failed { get; }\n\n    Exception? Exception { get; }\n\n    ProgressReport CurrentProgress { get; set; }\n\n    IPackageStep? CurrentStep { get; set; }\n\n    event EventHandler<ProgressReport>? ProgressChanged;\n\n    event EventHandler<IPackageModificationRunner>? Completed;\n\n    List<string> ConsoleOutput { get; }\n\n    Guid Id { get; }\n\n    bool ShowDialogOnStart { get; init; }\n\n    bool HideCloseButton { get; init; }\n\n    bool CloseWhenFinished { get; init; }\n\n    string? ModificationCompleteTitle { get; init; }\n\n    string ModificationCompleteMessage { get; init; }\n\n    string? ModificationFailedTitle { get; init; }\n\n    string? ModificationFailedMessage { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/ImportModelsStep.cs",
    "content": "﻿using StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class ImportModelsStep(\n    ModelFinder modelFinder,\n    IDownloadService downloadService,\n    IModelIndexService modelIndexService,\n    IEnumerable<string> files,\n    DirectoryPath destinationFolder,\n    bool isImportAsConnectedEnabled,\n    bool moveFiles = false\n) : IPackageStep\n{\n    public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        var copyPaths = files.ToDictionary(k => k, v => Path.Combine(destinationFolder, Path.GetFileName(v)));\n\n        // remove files that are already in the folder\n        foreach (var (source, destination) in copyPaths)\n        {\n            if (source == destination)\n            {\n                copyPaths.Remove(source);\n            }\n        }\n\n        if (copyPaths.Count == 0)\n        {\n            progress?.Report(new ProgressReport(1f, message: \"Import Complete\"));\n            return;\n        }\n\n        progress?.Report(new ProgressReport(0f, message: \"Importing...\"));\n\n        var lastMessage = string.Empty;\n        var transferProgress = new Progress<ProgressReport>(report =>\n        {\n            var message =\n                copyPaths.Count > 1\n                    ? $\"Importing {report.Title} ({report.Message})\"\n                    : $\"Importing {report.Title}\";\n            progress?.Report(\n                new ProgressReport(\n                    report.Progress ?? 0,\n                    message: message,\n                    printToConsole: message != lastMessage\n                )\n            );\n            lastMessage = message;\n        });\n\n        if (moveFiles)\n        {\n            var filesMoved = 0;\n            foreach (var (source, destination) in copyPaths)\n            {\n                try\n                {\n                    await FileTransfers.MoveFileAsync(source, destination).ConfigureAwait(false);\n                    filesMoved++;\n                }\n                catch (Exception)\n                {\n                    // ignored\n                }\n\n                progress?.Report(\n                    new ProgressReport(\n                        filesMoved,\n                        copyPaths.Count,\n                        Path.GetFileName(source),\n                        $\"{filesMoved}/{copyPaths.Count}\"\n                    )\n                );\n            }\n        }\n        else\n        {\n            await FileTransfers.CopyFiles(copyPaths, transferProgress).ConfigureAwait(false);\n        }\n\n        // Hash files and convert them to connected model if found\n        if (isImportAsConnectedEnabled)\n        {\n            var modelFilesCount = copyPaths.Count;\n            var modelFiles = copyPaths.Values.Select(path => new FilePath(path));\n\n            // Holds tasks for model queries after hash\n            var modelQueryTasks = new List<Task<bool>>();\n\n            foreach (var (i, modelFile) in modelFiles.Enumerate())\n            {\n                var hashProgress = new Progress<ProgressReport>(report =>\n                {\n                    var message =\n                        modelFilesCount > 1\n                            ? $\"Computing metadata for {modelFile.Name} ({i}/{modelFilesCount})\"\n                            : $\"Computing metadata for {modelFile.Name}\";\n\n                    progress?.Report(\n                        new ProgressReport(\n                            report.Progress ?? 0,\n                            message: message,\n                            printToConsole: message != lastMessage\n                        )\n                    );\n                    lastMessage = message;\n                });\n\n                var hashBlake3 = await FileHash.GetBlake3Async(modelFile, hashProgress).ConfigureAwait(false);\n\n                // Start a task to query the model in background\n                var queryTask = Task.Run(async () =>\n                {\n                    var result = await modelFinder.LocalFindModel(hashBlake3).ConfigureAwait(false);\n                    result ??= await modelFinder.RemoteFindModel(hashBlake3).ConfigureAwait(false);\n\n                    if (result is null)\n                        return false; // Not found\n\n                    var (model, version, file) = result.Value;\n\n                    // Save connected model info json\n                    var modelFileName = Path.GetFileNameWithoutExtension(modelFile.Info.Name);\n                    var modelInfo = new ConnectedModelInfo(model, version, file, DateTimeOffset.UtcNow);\n                    await modelInfo\n                        .SaveJsonToDirectory(destinationFolder, modelFileName)\n                        .ConfigureAwait(false);\n\n                    // If available, save thumbnail\n                    var image = version.Images?.FirstOrDefault(x => x.Type == \"image\");\n                    if (image != null)\n                    {\n                        var imageExt = Path.GetExtension(image.Url).TrimStart('.');\n                        if (imageExt is \"jpg\" or \"jpeg\" or \"png\")\n                        {\n                            var imageDownloadPath = Path.GetFullPath(\n                                Path.Combine(destinationFolder, $\"{modelFileName}.preview.{imageExt}\")\n                            );\n                            await downloadService\n                                .DownloadToFileAsync(image.Url, imageDownloadPath)\n                                .ConfigureAwait(false);\n                        }\n                    }\n\n                    return true;\n                });\n                modelQueryTasks.Add(queryTask);\n            }\n\n            // Set progress to indeterminate\n            progress?.Report(\n                new ProgressReport\n                {\n                    IsIndeterminate = true,\n                    Progress = -1f,\n                    Message = \"Checking connected model information\",\n                    PrintToConsole = true\n                }\n            );\n\n            // Wait for all model queries to finish\n            var modelQueryResults = await Task.WhenAll(modelQueryTasks).ConfigureAwait(false);\n\n            var successCount = modelQueryResults.Count(r => r);\n            var totalCount = modelQueryResults.Length;\n            var failCount = totalCount - successCount;\n\n            var progressText = successCount switch\n            {\n                0 when failCount > 0 => \"Import complete. No connected data found.\",\n                > 0 when failCount > 0\n                    => $\"Import complete. Found connected data for {successCount} of {totalCount} models.\",\n                1 when failCount == 0 => \"Import complete. Found connected data for 1 model.\",\n                _ => $\"Import complete. Found connected data for all {totalCount} models.\"\n            };\n\n            progress?.Report(new ProgressReport(1f, message: progressText));\n        }\n        else\n        {\n            progress?.Report(new ProgressReport(1f, message: \"Import Complete\"));\n        }\n\n        await modelIndexService.RefreshIndex().ConfigureAwait(false);\n    }\n\n    public string ProgressTitle => \"Importing Models\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/InstallExtensionStep.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class InstallExtensionStep(\n    IPackageExtensionManager extensionManager,\n    InstalledPackage installedPackage,\n    PackageExtension packageExtension,\n    PackageExtensionVersion? extensionVersion = null\n) : IPackageStep\n{\n    public Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        return extensionManager.InstallExtensionAsync(\n            packageExtension,\n            installedPackage,\n            extensionVersion,\n            progress\n        );\n    }\n\n    public string ProgressTitle => $\"Installing Extension {packageExtension.Title}\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/InstallNunchakuStep.cs",
    "content": "﻿using StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class InstallNunchakuStep(IPyInstallationManager pyInstallationManager) : IPackageStep\n{\n    public required InstalledPackage InstalledPackage { get; init; }\n    public required DirectoryPath WorkingDirectory { get; init; }\n    public required GpuInfo? PreferredGpu { get; init; }\n    public required IPackageExtensionManager ComfyExtensionManager { get; init; }\n    public IReadOnlyDictionary<string, string>? EnvironmentVariables { get; init; }\n\n    public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        if (Compat.IsMacOS || PreferredGpu?.ComputeCapabilityValue is null or < 7.5m)\n        {\n            throw new NotSupportedException(\n                \"Nunchaku is not supported on macOS or GPUs with compute capability < 7.5.\"\n            );\n        }\n\n        var venvDir = WorkingDirectory.JoinDir(\"venv\");\n        var pyVersion = PyVersion.Parse(InstalledPackage.PythonVersion);\n        if (pyVersion.StringValue == \"0.0.0\")\n        {\n            pyVersion = PyInstallationManager.Python_3_10_11;\n        }\n\n        var baseInstall = !string.IsNullOrWhiteSpace(InstalledPackage.PythonVersion)\n            ? new PyBaseInstall(\n                await pyInstallationManager.GetInstallationAsync(pyVersion).ConfigureAwait(false)\n            )\n            : PyBaseInstall.Default;\n\n        await using var venvRunner = baseInstall.CreateVenvRunner(\n            venvDir,\n            workingDirectory: WorkingDirectory,\n            environmentVariables: EnvironmentVariables\n        );\n\n        var torchInfo = await venvRunner.PipShow(\"torch\").ConfigureAwait(false);\n        var shortPythonVersionString = pyVersion.Minor switch\n        {\n            10 => \"cp310\",\n            11 => \"cp311\",\n            12 => \"cp312\",\n            13 => \"cp313\",\n            _ => throw new ArgumentOutOfRangeException(\"Invalid Python version\"),\n        };\n        var platform = Compat.IsWindows ? \"win_amd64\" : \"linux_x86_64\";\n\n        if (torchInfo is null)\n        {\n            throw new InvalidOperationException(\"Torch is not installed in the virtual environment.\");\n        }\n\n        var torchVersion = torchInfo.Version switch\n        {\n            var v when v.StartsWith(\"2.7\") => \"2.7\",\n            var v when v.StartsWith(\"2.8\") => \"2.8\",\n            var v when v.StartsWith(\"2.9\") => \"2.9\",\n            var v when v.StartsWith(\"2.10\") => \"2.10\",\n            _ => throw new InvalidOperationException(\n                \"No compatible torch version found in the virtual environment.\"\n            ),\n        };\n\n        var downloadUrl =\n            $\"https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.2/nunchaku-1.0.2+torch{torchVersion}-{shortPythonVersionString}-{shortPythonVersionString}-{platform}.whl\";\n        progress?.Report(\n            new ProgressReport(-1f, message: \"Installing Nunchaku backend\", isIndeterminate: true)\n        );\n\n        await venvRunner.PipInstall(downloadUrl, progress.AsProcessOutputHandler()).ConfigureAwait(false);\n\n        progress?.Report(\n            new ProgressReport(1f, message: \"Nunchaku backend installed successfully\", isIndeterminate: false)\n        );\n\n        var extensions = await ComfyExtensionManager\n            .GetManifestExtensionsAsync(ComfyExtensionManager.DefaultManifests)\n            .ConfigureAwait(false);\n        var nunchakuExtension = extensions.FirstOrDefault(e =>\n            e.Title.Equals(\"ComfyUI-nunchaku\", StringComparison.OrdinalIgnoreCase)\n        );\n\n        if (nunchakuExtension is null)\n            return;\n\n        var installedExtensions = await ComfyExtensionManager\n            .GetInstalledExtensionsLiteAsync(InstalledPackage)\n            .ConfigureAwait(false);\n        var installedNunchakuExtension = installedExtensions.FirstOrDefault(e =>\n            e.Title.Equals(\"ComfyUI-nunchaku\", StringComparison.OrdinalIgnoreCase)\n        );\n\n        if (installedNunchakuExtension is not null)\n        {\n            var installedNunchakuExtensionWithVersion = await ComfyExtensionManager\n                .GetInstalledExtensionInfoAsync(installedNunchakuExtension)\n                .ConfigureAwait(false);\n            installedNunchakuExtensionWithVersion = installedNunchakuExtensionWithVersion with\n            {\n                Definition = nunchakuExtension,\n            };\n\n            await ComfyExtensionManager\n                .UpdateExtensionAsync(installedNunchakuExtensionWithVersion, InstalledPackage, null, progress)\n                .ConfigureAwait(false);\n        }\n        else\n        {\n            await ComfyExtensionManager\n                .InstallExtensionAsync(nunchakuExtension, InstalledPackage, null, progress)\n                .ConfigureAwait(false);\n        }\n\n        progress?.Report(\n            new ProgressReport(\n                1f,\n                message: \"Nunchaku extension installed successfully.\",\n                isIndeterminate: false\n            )\n        );\n    }\n\n    public string ProgressTitle => \"Installing nunchaku...\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs",
    "content": "﻿using StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class InstallPackageStep(\n    BasePackage basePackage,\n    string installLocation,\n    InstalledPackage installedPackage,\n    InstallPackageOptions options\n) : ICancellablePackageStep\n{\n    public async Task ExecuteAsync(\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await basePackage\n            .InstallPackage(\n                installLocation,\n                installedPackage,\n                options,\n                progress,\n                progress.AsProcessOutputHandler(setMessageAsOutput: false),\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n    }\n\n    public string ProgressTitle => $\"Installing {basePackage.DisplayName}...\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/InstallSageAttentionStep.cs",
    "content": "﻿using StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class InstallSageAttentionStep(\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager\n) : IPackageStep\n{\n    private const string PythonLibsDownloadUrl = \"https://cdn.lykos.ai/python_libs_for_sage.zip\";\n\n    public required InstalledPackage InstalledPackage { get; init; }\n    public required DirectoryPath WorkingDirectory { get; init; }\n    public required bool IsBlackwellGpu { get; init; }\n    public IReadOnlyDictionary<string, string>? EnvironmentVariables { get; init; }\n\n    public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        if (!Compat.IsWindows)\n        {\n            throw new PlatformNotSupportedException(\n                \"This method of installing Triton and SageAttention is only supported on Windows\"\n            );\n        }\n\n        var venvDir = WorkingDirectory.JoinDir(\"venv\");\n        var pyVersion = PyVersion.Parse(InstalledPackage.PythonVersion);\n        if (pyVersion.StringValue == \"0.0.0\")\n        {\n            pyVersion = PyInstallationManager.Python_3_10_11;\n        }\n\n        var baseInstall = !string.IsNullOrWhiteSpace(InstalledPackage.PythonVersion)\n            ? new PyBaseInstall(\n                await pyInstallationManager.GetInstallationAsync(pyVersion).ConfigureAwait(false)\n            )\n            : PyBaseInstall.Default;\n\n        await using var venvRunner = baseInstall.CreateVenvRunner(\n            venvDir,\n            workingDirectory: WorkingDirectory,\n            environmentVariables: EnvironmentVariables\n        );\n\n        var torchInfo = await venvRunner.PipShow(\"torch\").ConfigureAwait(false);\n        var sageWheelUrl = string.Empty;\n\n        if (torchInfo != null)\n        {\n            // Extract base version (before +) and CUDA index\n            var versionString = torchInfo.Version;\n            var plusIndex = versionString.IndexOf('+');\n            var baseVersionString = plusIndex >= 0 ? versionString[..plusIndex] : versionString;\n            var cudaIndex = plusIndex >= 0 ? versionString[(plusIndex + 1)..] : string.Empty;\n\n            // Try to parse base version for comparison\n            if (Version.TryParse(baseVersionString, out var torchVersion))\n            {\n                var minVersion = new Version(2, 9, 0);\n\n                // New wheels work for torch >= 2.9.0 with cu128 or cu130\n                if (torchVersion >= minVersion)\n                {\n                    if (cudaIndex.Contains(\"cu128\"))\n                    {\n                        sageWheelUrl =\n                            \"https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows.post4/sageattention-2.2.0+cu128torch2.9.0andhigher.post4-cp39-abi3-win_amd64.whl\";\n                    }\n                    else if (cudaIndex.Contains(\"cu130\"))\n                    {\n                        sageWheelUrl =\n                            \"https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows.post4/sageattention-2.2.0+cu130torch2.9.0andhigher.post4-cp39-abi3-win_amd64.whl\";\n                    }\n                }\n                else\n                {\n                    // Fallback to old wheels for torch < 2.9.0\n                    var shortPythonVersionString = pyVersion.Minor switch\n                    {\n                        10 => \"cp310\",\n                        11 => \"cp311\",\n                        12 => \"cp312\",\n                        _ => throw new ArgumentOutOfRangeException(\"Invalid Python version\"),\n                    };\n\n                    if (versionString.Contains(\"2.5.1\") && versionString.Contains(\"cu124\"))\n                    {\n                        sageWheelUrl =\n                            \"https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows.post3/sageattention-2.2.0+cu124torch2.5.1.post3-cp39-abi3-win_amd64.whl\";\n                    }\n                    else if (versionString.Contains(\"2.6.0\") && versionString.Contains(\"cu126\"))\n                    {\n                        sageWheelUrl =\n                            \"https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows.post3/sageattention-2.2.0+cu126torch2.6.0.post3-cp39-abi3-win_amd64.whl\";\n                    }\n                    else if (versionString.Contains(\"2.7.0\") && versionString.Contains(\"cu128\"))\n                    {\n                        sageWheelUrl =\n                            $\"https://github.com/woct0rdho/SageAttention/releases/download/v2.1.1-windows/sageattention-2.1.1+cu128torch2.7.0-{shortPythonVersionString}-{shortPythonVersionString}-win_amd64.whl\";\n                    }\n                    else if (versionString.Contains(\"2.7.1\") && versionString.Contains(\"cu128\"))\n                    {\n                        sageWheelUrl =\n                            \"https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows.post3/sageattention-2.2.0+cu128torch2.7.1.post3-cp39-abi3-win_amd64.whl\";\n                    }\n                    else if (versionString.Contains(\"2.8.0\") && versionString.Contains(\"cu128\"))\n                    {\n                        sageWheelUrl =\n                            \"https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows.post3/sageattention-2.2.0+cu128torch2.8.0.post3-cp39-abi3-win_amd64.whl\";\n                    }\n                }\n            }\n        }\n\n        var pipArgs = new PipInstallArgs(\"triton-windows\");\n\n        if (!string.IsNullOrWhiteSpace(sageWheelUrl))\n        {\n            pipArgs = pipArgs.AddArg(sageWheelUrl);\n\n            progress?.Report(\n                new ProgressReport(-1f, message: \"Installing Triton & SageAttention\", isIndeterminate: true)\n            );\n\n            await venvRunner.PipInstall(pipArgs, progress.AsProcessOutputHandler()).ConfigureAwait(false);\n\n            return;\n        }\n\n        // no wheels, gotta build\n        if (!prerequisiteHelper.IsVcBuildToolsInstalled)\n        {\n            throw new MissingPrerequisiteException(\n                \"Visual Studio 2022 Build Tools\",\n                \"Could not find Visual Studio 2022 Build Tools. Please install them from the link below.\",\n                \"https://aka.ms/vs/17/release/vs_BuildTools.exe\"\n            );\n        }\n\n        var nvccPath = await Utilities.WhichAsync(\"nvcc\").ConfigureAwait(false);\n        if (string.IsNullOrWhiteSpace(nvccPath))\n        {\n            var cuda126ExpectedPath = new DirectoryPath(\n                @\"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v12.6\\bin\"\n            );\n            var cuda128ExpectedPath = new DirectoryPath(\n                @\"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v12.8\\bin\"\n            );\n\n            if (!cuda126ExpectedPath.Exists && !cuda128ExpectedPath.Exists)\n            {\n                throw new MissingPrerequisiteException(\n                    \"CUDA Toolkit\",\n                    \"Could not find CUDA Toolkit. Please install version 12.6 or newer from the link below.\",\n                    \"https://developer.nvidia.com/cuda-downloads?target_os=Windows&target_arch=x86_64\"\n                );\n            }\n\n            nvccPath = cuda128ExpectedPath.Exists\n                ? cuda128ExpectedPath.JoinFile(\"nvcc.exe\").ToString()\n                : cuda126ExpectedPath.JoinFile(\"nvcc.exe\").ToString();\n        }\n\n        venvRunner.UpdateEnvironmentVariables(env =>\n        {\n            var cudaBinPath = Path.GetDirectoryName(nvccPath)!;\n            var cudaHome = Path.GetDirectoryName(cudaBinPath)!;\n\n            env = env.TryGetValue(\"PATH\", out var pathValue)\n                ? env.SetItem(\"PATH\", $\"{cudaBinPath}{Path.PathSeparator}{pathValue}\")\n                : env.Add(\"PATH\", cudaBinPath);\n\n            if (!env.ContainsKey(\"CUDA_HOME\"))\n            {\n                env = env.Add(\"CUDA_HOME\", cudaHome);\n            }\n\n            return env;\n        });\n\n        progress?.Report(new ProgressReport(-1f, message: \"Installing Triton\", isIndeterminate: true));\n\n        await venvRunner.PipInstall(pipArgs, progress.AsProcessOutputHandler()).ConfigureAwait(false);\n\n        venvRunner.UpdateEnvironmentVariables(env => env.SetItem(\"SETUPTOOLS_USE_DISTUTILS\", \"setuptools\"));\n\n        progress?.Report(\n            new ProgressReport(-1f, message: \"Downloading Python libraries\", isIndeterminate: true)\n        );\n        await AddMissingLibsToVenv(WorkingDirectory, progress).ConfigureAwait(false);\n\n        var sageDir = WorkingDirectory.JoinDir(\"SageAttention\");\n\n        if (!sageDir.Exists)\n        {\n            progress?.Report(\n                new ProgressReport(-1f, message: \"Downloading SageAttention\", isIndeterminate: true)\n            );\n            await prerequisiteHelper\n                .RunGit(\n                    [\"clone\", \"https://github.com/thu-ml/SageAttention.git\", sageDir.ToString()],\n                    progress.AsProcessOutputHandler()\n                )\n                .ConfigureAwait(false);\n        }\n\n        progress?.Report(new ProgressReport(-1f, message: \"Installing SageAttention\", isIndeterminate: true));\n        await venvRunner\n            .PipInstall(\n                [WorkingDirectory.JoinDir(\"SageAttention\").ToString()],\n                progress.AsProcessOutputHandler()\n            )\n            .ConfigureAwait(false);\n    }\n\n    private async Task AddMissingLibsToVenv(\n        DirectoryPath installedPackagePath,\n        IProgress<ProgressReport>? progress = null\n    )\n    {\n        var venvLibsDir = installedPackagePath.JoinDir(\"venv\", \"libs\");\n        var venvIncludeDir = installedPackagePath.JoinDir(\"venv\", \"include\");\n        if (\n            venvLibsDir.Exists\n            && venvIncludeDir.Exists\n            && venvLibsDir.JoinFile(\"python3.lib\").Exists\n            && venvLibsDir.JoinFile(\"python310.lib\").Exists\n        )\n        {\n            return;\n        }\n\n        var downloadPath = installedPackagePath.JoinFile(\"python_libs_for_sage.zip\");\n        var venvDir = installedPackagePath.JoinDir(\"venv\");\n        await downloadService\n            .DownloadToFileAsync(PythonLibsDownloadUrl, downloadPath, progress)\n            .ConfigureAwait(false);\n\n        progress?.Report(\n            new ProgressReport(-1f, message: \"Extracting Python libraries\", isIndeterminate: true)\n        );\n        await ArchiveHelper.Extract7Z(downloadPath, venvDir, progress).ConfigureAwait(false);\n\n        var includeFolder = venvDir.JoinDir(\"include\");\n        var scriptsIncludeFolder = venvDir.JoinDir(\"Scripts\").JoinDir(\"include\");\n        await includeFolder.CopyToAsync(scriptsIncludeFolder).ConfigureAwait(false);\n\n        await downloadPath.DeleteAsync().ConfigureAwait(false);\n    }\n\n    public string ProgressTitle => \"Installing Triton and SageAttention\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class PackageModificationRunner : IPackageModificationRunner\n{\n    public async Task ExecuteSteps(IEnumerable<IPackageStep> steps)\n    {\n        IProgress<ProgressReport> progress = new Progress<ProgressReport>(report =>\n        {\n            CurrentProgress = report;\n            if (!string.IsNullOrWhiteSpace(report.Message) && report.PrintToConsole)\n            {\n                ConsoleOutput.Add(report.Message);\n            }\n\n            if (!string.IsNullOrWhiteSpace(report.Title) && report.PrintToConsole)\n            {\n                ConsoleOutput.Add(report.Title);\n            }\n\n            OnProgressChanged(report);\n        });\n\n        IsRunning = true;\n\n        try\n        {\n            foreach (var step in steps)\n            {\n                CurrentStep = step;\n                try\n                {\n                    await step.ExecuteAsync(progress).ConfigureAwait(false);\n                }\n                catch (Exception e)\n                {\n                    var failedMessage = string.IsNullOrWhiteSpace(ModificationFailedMessage)\n                        ? $\"Error: {e}\"\n                        : ModificationFailedMessage + $\" ({e})\";\n\n                    progress.Report(\n                        new ProgressReport(\n                            1f,\n                            title: ModificationFailedTitle,\n                            message: failedMessage,\n                            isIndeterminate: false\n                        )\n                    );\n\n                    Exception = e;\n                    Failed = true;\n                    return;\n                }\n            }\n\n            if (!Failed)\n            {\n                progress.Report(\n                    new ProgressReport(\n                        1f,\n                        title: ModificationCompleteTitle,\n                        message: ModificationCompleteMessage,\n                        isIndeterminate: false\n                    )\n                );\n            }\n        }\n        finally\n        {\n            IsRunning = false;\n            OnCompleted();\n        }\n    }\n\n    public bool HideCloseButton { get; init; }\n\n    public bool CloseWhenFinished { get; init; } = true;\n\n    public bool ShowDialogOnStart { get; init; }\n\n    public string? ModificationCompleteTitle { get; init; } = \"Install Complete\";\n\n    public required string ModificationCompleteMessage { get; init; }\n\n    public string? ModificationFailedTitle { get; init; } = \"Install Failed\";\n\n    public string? ModificationFailedMessage { get; init; }\n\n    public bool IsRunning { get; private set; }\n\n    [MemberNotNullWhen(true, nameof(Exception))]\n    public bool Failed { get; private set; }\n\n    public Exception? Exception { get; set; }\n\n    public ProgressReport CurrentProgress { get; set; }\n\n    public IPackageStep? CurrentStep { get; set; }\n\n    public List<string> ConsoleOutput { get; } = new();\n\n    public Guid Id { get; } = Guid.NewGuid();\n\n    public event EventHandler<ProgressReport>? ProgressChanged;\n\n    public event EventHandler<IPackageModificationRunner>? Completed;\n\n    protected virtual void OnProgressChanged(ProgressReport e) => ProgressChanged?.Invoke(this, e);\n\n    protected virtual void OnCompleted() => Completed?.Invoke(this, this);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/PackageStep.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic interface IPackageStep\n{\n    Task ExecuteAsync(IProgress<ProgressReport>? progress = null);\n    string ProgressTitle { get; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/PipStep.cs",
    "content": "﻿using StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class PipStep : IPackageStep\n{\n    public required ProcessArgs Args { get; init; }\n    public required DirectoryPath VenvDirectory { get; init; }\n\n    public DirectoryPath? WorkingDirectory { get; init; }\n\n    public IReadOnlyDictionary<string, string>? EnvironmentVariables { get; init; }\n\n    public PyBaseInstall? BaseInstall { get; set; }\n\n    /// <inheritdoc />\n    public string ProgressTitle =>\n        Args switch\n        {\n            _ when Args.Contains(\"install\") => \"Installing Pip Packages\",\n            _ when Args.Contains(\"uninstall\") => \"Uninstalling Pip Packages\",\n            _ when Args.Contains(\"-U\") || Args.Contains(\"--upgrade\") => \"Updating Pip Packages\",\n            _ => \"Running Pip\"\n        };\n\n    /// <inheritdoc />\n    public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        BaseInstall ??= PyBaseInstall.Default;\n        await using var venvRunner = BaseInstall.CreateVenvRunner(\n            VenvDirectory,\n            workingDirectory: WorkingDirectory,\n            environmentVariables: EnvironmentVariables\n        );\n\n        if (BaseInstall.UsesUv && Args.Contains(\"install\"))\n        {\n            var uvArgs = Args.ToString().Replace(\"install \", string.Empty);\n            await venvRunner.PipInstall(uvArgs, progress.AsProcessOutputHandler()).ConfigureAwait(false);\n        }\n        else\n        {\n            venvRunner.RunDetached(Args.Prepend([\"-m\", \"pip\"]), progress.AsProcessOutputHandler());\n        }\n\n        await ProcessRunner.WaitForExitConditionAsync(venvRunner.Process).ConfigureAwait(false);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/ProcessStep.cs",
    "content": "﻿using System.Collections.Immutable;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class ProcessStep : ICancellablePackageStep\n{\n    public required string FileName { get; init; }\n\n    public ProcessArgs Args { get; init; } = \"\";\n\n    public DirectoryPath? WorkingDirectory { get; init; }\n\n    public ImmutableDictionary<string, string> EnvironmentVariables { get; init; } =\n        ImmutableDictionary<string, string>.Empty;\n\n    public bool UseAnsiParsing { get; init; } = true;\n\n    public string ProgressTitle { get; init; } = \"Running Process\";\n\n    /// <inheritdoc />\n    public async Task ExecuteAsync(\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(\n            new ProgressReport\n            {\n                Message = \"Running Process\",\n                IsIndeterminate = true,\n                PrintToConsole = true\n            }\n        );\n\n        if (UseAnsiParsing)\n        {\n            using var process = ProcessRunner.StartAnsiProcess(\n                fileName: FileName,\n                arguments: Args.ToString(),\n                workingDirectory: WorkingDirectory?.FullPath,\n                environmentVariables: EnvironmentVariables,\n                outputDataReceived: progress.AsProcessOutputHandler()\n            );\n\n            await ProcessRunner\n                .WaitForExitConditionAsync(process, cancelToken: cancellationToken)\n                .ConfigureAwait(false);\n\n            await process.WaitUntilOutputEOF(cancellationToken).ConfigureAwait(false);\n        }\n        else\n        {\n            using var process = ProcessRunner.StartProcess(\n                fileName: FileName,\n                arguments: Args.ToString(),\n                workingDirectory: WorkingDirectory?.FullPath,\n                environmentVariables: EnvironmentVariables,\n                outputDataReceived: progress is null\n                    ? null\n                    : output =>\n                    {\n                        progress.Report(\n                            new ProgressReport\n                            {\n                                Message = output,\n                                IsIndeterminate = true,\n                                PrintToConsole = true\n                            }\n                        );\n                    }\n            );\n\n            await ProcessRunner\n                .WaitForExitConditionAsync(process, cancelToken: cancellationToken)\n                .ConfigureAwait(false);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/ScanMetadataStep.cs",
    "content": "﻿using StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class ScanMetadataStep(\n    DirectoryPath directoryPath,\n    IMetadataImportService metadataImportService,\n    bool updateExistingMetadata = false\n) : IPackageStep\n{\n    public Task ExecuteAsync(IProgress<ProgressReport>? progress = null) =>\n        updateExistingMetadata\n            ? metadataImportService.UpdateExistingMetadata(directoryPath, progress)\n            : metadataImportService.ScanDirectoryForMissingInfo(directoryPath, progress);\n\n    public string ProgressTitle => \"Updating Metadata\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/SetPackageInstallingStep.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class SetPackageInstallingStep : IPackageStep\n{\n    private readonly ISettingsManager settingsManager;\n    private readonly string packageName;\n\n    public SetPackageInstallingStep(ISettingsManager settingsManager, string packageName)\n    {\n        this.settingsManager = settingsManager;\n        this.packageName = packageName;\n    }\n\n    public Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        settingsManager.PackageInstallsInProgress.Add(packageName);\n        return Task.CompletedTask;\n    }\n\n    public string ProgressTitle => \"Starting Package Installation\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/SetupModelFoldersStep.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class SetupModelFoldersStep : IPackageStep\n{\n    private readonly BasePackage package;\n    private readonly SharedFolderMethod sharedFolderMethod;\n    private readonly string installPath;\n\n    public SetupModelFoldersStep(\n        BasePackage package,\n        SharedFolderMethod sharedFolderMethod,\n        string installPath\n    )\n    {\n        this.package = package;\n        this.sharedFolderMethod = sharedFolderMethod;\n        this.installPath = installPath;\n    }\n\n    public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        progress?.Report(\n            new ProgressReport(-1f, \"Setting up shared folder links...\", isIndeterminate: true)\n        );\n        await package.SetupModelFolders(installPath, sharedFolderMethod).ConfigureAwait(false);\n    }\n\n    public string ProgressTitle => \"Setting up shared folder links...\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/SetupOutputSharingStep.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class SetupOutputSharingStep(BasePackage package, string installPath) : IPackageStep\n{\n    public Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        package.SetupOutputFolderLinks(installPath);\n        return Task.CompletedTask;\n    }\n\n    public string ProgressTitle => \"Setting up output sharing...\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/SetupPrerequisitesStep.cs",
    "content": "﻿using StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class SetupPrerequisitesStep(\n    IPrerequisiteHelper prerequisiteHelper,\n    BasePackage package,\n    PyVersion? pythonVersion = null\n) : IPackageStep\n{\n    public async Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        await prerequisiteHelper\n            .InstallPackageRequirements(package, pythonVersion, progress)\n            .ConfigureAwait(false);\n    }\n\n    public string ProgressTitle => \"Installing prerequisites...\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/UninstallExtensionStep.cs",
    "content": "using StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class UninstallExtensionStep(\n    IPackageExtensionManager extensionManager,\n    InstalledPackage installedPackage,\n    InstalledPackageExtension packageExtension\n) : IPackageStep\n{\n    public Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        return extensionManager.UninstallExtensionAsync(packageExtension, installedPackage, progress);\n    }\n\n    public string ProgressTitle => $\"Uninstalling Extension {packageExtension.Title}\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/UpdateExtensionStep.cs",
    "content": "using StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class UpdateExtensionStep(\n    IPackageExtensionManager extensionManager,\n    InstalledPackage installedPackage,\n    InstalledPackageExtension installedExtension,\n    PackageExtensionVersion? extensionVersion = null\n) : IPackageStep\n{\n    public Task ExecuteAsync(IProgress<ProgressReport>? progress = null)\n    {\n        return extensionManager.UpdateExtensionAsync(\n            installedExtension,\n            installedPackage,\n            extensionVersion,\n            progress\n        );\n    }\n\n    public string ProgressTitle => $\"Updating Extension {installedExtension.Title}\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageModification/UpdatePackageStep.cs",
    "content": "﻿using StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.PackageModification;\n\npublic class UpdatePackageStep(\n    ISettingsManager settingsManager,\n    BasePackage basePackage,\n    string installLocation,\n    InstalledPackage installedPackage,\n    UpdatePackageOptions options\n) : ICancellablePackageStep\n{\n    public async Task ExecuteAsync(\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var updateResult = await basePackage\n            .Update(\n                installLocation,\n                installedPackage,\n                options,\n                progress,\n                progress.AsProcessOutputHandler(),\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        await using (settingsManager.BeginTransaction())\n        {\n            installedPackage.Version = updateResult;\n            installedPackage.UpdateAvailable = false;\n        }\n    }\n\n    public string ProgressTitle => $\"Updating {installedPackage.DisplayName}\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackagePair.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Packages;\n\nnamespace StabilityMatrix.Core.Models;\n\n\n/// <summary>\n/// Pair of InstalledPackage and BasePackage\n/// </summary>\npublic record PackagePair(InstalledPackage InstalledPackage, BasePackage BasePackage);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackagePrerequisite.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic enum PackagePrerequisite\n{\n    Python310,\n    Python31017,\n    VcRedist,\n    Git,\n    HipSdk,\n    Node,\n    Dotnet,\n    Tkinter,\n    VcBuildTools\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageType.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic enum PackageType\n{\n    SdInference,\n    SdTraining,\n    Legacy,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageVersion.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic record PackageVersion\n{\n    public required string TagName { get; set; }\n    public string? ReleaseNotesMarkdown { get; set; }\n    public bool IsPrerelease { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PackageVersionType.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\n[Flags]\npublic enum PackageVersionType\n{\n    None = 0,\n    GithubRelease = 1 << 0,\n    Commit = 1 << 1\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/A3WebUI.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Text.Json;\nusing System.Text.Json.Nodes;\nusing System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, A3WebUI>(Duplicate = DuplicateStrategy.Append)]\npublic class A3WebUI(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    public override string Name => \"stable-diffusion-webui\";\n    public override string DisplayName { get; set; } = \"Stable Diffusion WebUI\";\n    public override string Author => \"AUTOMATIC1111\";\n    public override string LicenseType => \"AGPL-3.0\";\n    public override string LicenseUrl =>\n        \"https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/master/LICENSE.txt\";\n    public override string Blurb => \"A browser interface based on Gradio library for Stable Diffusion\";\n    public override string LaunchCommand => \"launch.py\";\n    public override Uri PreviewImageUri =>\n        new(\"https://github.com/AUTOMATIC1111/stable-diffusion-webui/raw/master/screenshot.png\");\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Simple;\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;\n    public override PackageType PackageType => PackageType.SdInference;\n\n    // From https://github.com/AUTOMATIC1111/stable-diffusion-webui/tree/master/models\n    public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>\n        new()\n        {\n            [SharedFolderType.StableDiffusion] = [\"models/Stable-diffusion/sd\"],\n            [SharedFolderType.ESRGAN] = [\"models/ESRGAN\"],\n            [SharedFolderType.GFPGAN] = [\"models/GFPGAN\"],\n            [SharedFolderType.RealESRGAN] = [\"models/RealESRGAN\"],\n            [SharedFolderType.SwinIR] = [\"models/SwinIR\"],\n            [SharedFolderType.Lora] = [\"models/Lora\"],\n            [SharedFolderType.LyCORIS] = [\"models/LyCORIS\"],\n            [SharedFolderType.ApproxVAE] = [\"models/VAE-approx\"],\n            [SharedFolderType.VAE] = [\"models/VAE\"],\n            [SharedFolderType.DeepDanbooru] = [\"models/deepbooru\"],\n            [SharedFolderType.Karlo] = [\"models/karlo\"],\n            [SharedFolderType.Embeddings] = [\"embeddings\"],\n            [SharedFolderType.Hypernetwork] = [\"models/hypernetworks\"],\n            [SharedFolderType.ControlNet] = [\"models/controlnet/ControlNet\"],\n            [SharedFolderType.Codeformer] = [\"models/Codeformer\"],\n            [SharedFolderType.LDSR] = [\"models/LDSR\"],\n            [SharedFolderType.AfterDetailer] = [\"models/adetailer\"],\n            [SharedFolderType.T2IAdapter] = [\"models/controlnet/T2IAdapter\"],\n            [SharedFolderType.IpAdapter] = [\"models/controlnet/IpAdapter\"],\n            [SharedFolderType.IpAdapters15] = [\"models/controlnet/DiffusersIpAdapters\"],\n            [SharedFolderType.IpAdaptersXl] = [\"models/controlnet/DiffusersIpAdaptersXL\"],\n            [SharedFolderType.SVD] = [\"models/svd\"],\n            [SharedFolderType.TextEncoders] = [\"models/text_encoder\"],\n            [SharedFolderType.DiffusionModels] = [\"models/Stable-diffusion/unet\"],\n        };\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders =>\n        new()\n        {\n            [SharedOutputType.Extras] = [\"outputs/extras-images\"],\n            [SharedOutputType.Saved] = [\"log/images\"],\n            [SharedOutputType.Img2Img] = [\"outputs/img2img-images\"],\n            [SharedOutputType.Text2Img] = [\"outputs/txt2img-images\"],\n            [SharedOutputType.Img2ImgGrids] = [\"outputs/img2img-grids\"],\n            [SharedOutputType.Text2ImgGrids] = [\"outputs/txt2img-grids\"],\n            [SharedOutputType.SVD] = [\"outputs/svd\"],\n        };\n\n    [SuppressMessage(\"ReSharper\", \"ArrangeObjectCreationWhenTypeNotEvident\")]\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new()\n            {\n                Name = \"Host\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"localhost\",\n                Options = [\"--server-name\"],\n            },\n            new()\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"7860\",\n                Options = [\"--port\"],\n            },\n            new()\n            {\n                Name = \"Share\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to share on Gradio\",\n                Options = { \"--share\" },\n            },\n            new()\n            {\n                Name = \"VRAM\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch\n                {\n                    MemoryLevel.Low => \"--lowvram\",\n                    MemoryLevel.Medium => \"--medvram\",\n                    _ => null,\n                },\n                Options = [\"--lowvram\", \"--medvram\", \"--medvram-sdxl\"],\n            },\n            new()\n            {\n                Name = \"Xformers\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = HardwareHelper.HasNvidiaGpu(),\n                Options = [\"--xformers\"],\n            },\n            new()\n            {\n                Name = \"API\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = true,\n                Options = [\"--api\"],\n            },\n            new()\n            {\n                Name = \"Auto Launch Web UI\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = false,\n                Options = [\"--autolaunch\"],\n            },\n            new()\n            {\n                Name = \"Skip Torch CUDA Check\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = !HardwareHelper.HasNvidiaGpu(),\n                Options = [\"--skip-torch-cuda-test\"],\n            },\n            new()\n            {\n                Name = \"Skip Python Version Check\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = true,\n                Options = [\"--skip-python-version-check\"],\n            },\n            new()\n            {\n                Name = \"No Half\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Do not switch the model to 16-bit floats\",\n                InitialValue =\n                    HardwareHelper.PreferRocm() || HardwareHelper.PreferDirectMLOrZluda() || Compat.IsMacOS,\n                Options = [\"--no-half\"],\n            },\n            new()\n            {\n                Name = \"Skip SD Model Download\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = false,\n                Options = [\"--no-download-sd-model\"],\n            },\n            new()\n            {\n                Name = \"Skip Install\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--skip-install\"],\n            },\n            LaunchOptionDefinition.Extras,\n        ];\n\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        [SharedFolderMethod.Symlink, SharedFolderMethod.None];\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        [TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.Rocm, TorchIndex.Mps];\n\n    public override string MainBranch => \"dev\";\n\n    public override string OutputFolderName => \"outputs\";\n\n    public override IPackageExtensionManager ExtensionManager => new A3WebUiExtensionManager(this);\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        var torchIndex = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n        var isBlackwell =\n            torchIndex is TorchIndex.Cuda\n            && (SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu());\n\n        // 1. Configure the entire install process declaratively.\n        var config = new PipInstallConfig\n        {\n            RequirementsFilePaths = [\"requirements_versions.txt\"],\n            TorchVersion = torchIndex == TorchIndex.Mps ? \"==2.3.1\" : (isBlackwell ? \"\" : \"==2.1.2\"),\n            TorchvisionVersion = torchIndex == TorchIndex.Mps ? \"==0.18.1\" : (isBlackwell ? \"\" : \"==0.16.2\"),\n            XformersVersion = isBlackwell ? \" \" : \"==0.0.23.post1\",\n            CudaIndex = isBlackwell ? \"cu128\" : \"cu121\",\n            RocmIndex = \"rocm5.6\",\n            ExtraPipArgs =\n            [\n                \"https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip\",\n            ],\n        };\n\n        // 2. Execute the standardized installation process.\n        await StandardPipInstallProcessAsync(\n                venvRunner,\n                options,\n                installedPackage,\n                config,\n                onConsoleOutput,\n                progress,\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        // 3. Perform any final, package-specific tasks.\n        progress?.Report(new ProgressReport(-1f, \"Updating configuration\", isIndeterminate: true));\n        var configPath = Path.Combine(installLocation, \"config.json\");\n        if (!File.Exists(configPath))\n        {\n            var configJson = new JsonObject { { \"show_progress_type\", \"TAESD\" } };\n            await File.WriteAllTextAsync(configPath, configJson.ToString(), cancellationToken)\n                .ConfigureAwait(false);\n        }\n\n        progress?.Report(new ProgressReport(1f, \"Install complete\", isIndeterminate: false));\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        VenvRunner.UpdateEnvironmentVariables(GetEnvVars);\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (!s.Text.Contains(\"Running on\", StringComparison.OrdinalIgnoreCase))\n                return;\n\n            var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n            var match = regex.Match(s.Text);\n            if (!match.Success)\n                return;\n\n            WebUrl = match.Value;\n            OnStartupComplete(WebUrl);\n        }\n\n        VenvRunner.RunDetached(\n            [\n                Path.Combine(installLocation, options.Command ?? LaunchCommand),\n                .. options.Arguments,\n                .. ExtraLaunchArguments,\n            ],\n            HandleConsoleOutput,\n            OnExit\n        );\n    }\n\n    public override IReadOnlyList<string> ExtraLaunchArguments =>\n        settingsManager.IsLibraryDirSet ? [\"--gradio-allowed-path\", settingsManager.ImagesDirectory] : [];\n\n    protected virtual ImmutableDictionary<string, string> GetEnvVars(ImmutableDictionary<string, string> env)\n    {\n        // Set the Stable Diffusion repository URL to a working fork\n        // This is required because the original Stability-AI/stablediffusion repo was removed\n        // See: https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/17212\n        env = env.SetItem(\"STABLE_DIFFUSION_REPO\", \"https://github.com/w-e-w/stablediffusion.git\");\n\n        return env;\n    }\n\n    private class A3WebUiExtensionManager(A3WebUI package)\n        : GitPackageExtensionManager(package.PrerequisiteHelper)\n    {\n        public override string RelativeInstallDirectory => \"extensions\";\n\n        public override IEnumerable<ExtensionManifest> DefaultManifests =>\n            [\n                new ExtensionManifest(\n                    new Uri(\n                        \"https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui-extensions/master/index.json\"\n                    )\n                ),\n            ];\n\n        public override async Task<IEnumerable<PackageExtension>> GetManifestExtensionsAsync(\n            ExtensionManifest manifest,\n            CancellationToken cancellationToken = default\n        )\n        {\n            try\n            {\n                // Get json\n                var content = await package\n                    .DownloadService.GetContentAsync(manifest.Uri.ToString(), cancellationToken)\n                    .ConfigureAwait(false);\n\n                // Parse json\n                var jsonManifest = JsonSerializer.Deserialize<A1111ExtensionManifest>(\n                    content,\n                    A1111ExtensionManifestSerializerContext.Default.Options\n                );\n\n                return jsonManifest?.GetPackageExtensions() ?? Enumerable.Empty<PackageExtension>();\n            }\n            catch (Exception e)\n            {\n                Logger.Error(e, \"Failed to get extensions from manifest\");\n                return Enumerable.Empty<PackageExtension>();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/AiToolkit.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\nusing LogLevel = Microsoft.Extensions.Logging.LogLevel;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, AiToolkit>(Duplicate = DuplicateStrategy.Append)]\npublic class AiToolkit(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    private AnsiProcess? npmProcess;\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    public override string Name => \"ai-toolkit\";\n    public override string DisplayName { get; set; } = \"AI-Toolkit\";\n    public override string Author => \"ostris\";\n    public override string Blurb => \"AI Toolkit is an all in one training suite for diffusion models\";\n    public override string LicenseType => \"MIT\";\n    public override string LicenseUrl => \"https://github.com/ostris/ai-toolkit/blob/main/LICENSE\";\n    public override string LaunchCommand => string.Empty;\n\n    public override Uri PreviewImageUri => new(\"https://cdn.lykos.ai/sm/packages/aitoolkit/preview.webp\");\n\n    public override string OutputFolderName => \"output\";\n    public override IEnumerable<TorchIndex> AvailableTorchIndices => [TorchIndex.Cuda];\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced;\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;\n    public override List<LaunchOptionDefinition> LaunchOptions => [];\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders => [];\n    public override string MainBranch => \"main\";\n    public override bool IsCompatible => HardwareHelper.HasNvidiaGpu();\n\n    public override TorchIndex GetRecommendedTorchVersion() => TorchIndex.Cuda;\n\n    public override PackageType PackageType => PackageType.SdTraining;\n    public override bool OfferInOneClickInstaller => false;\n    public override bool ShouldIgnoreReleases => true;\n    public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_11_13;\n\n    public override IEnumerable<PackagePrerequisite> Prerequisites =>\n        base.Prerequisites.Concat([PackagePrerequisite.Node]);\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n        venvRunner.UpdateEnvironmentVariables(GetEnvVars);\n\n        var isBlackwell =\n            SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu();\n\n        var config = new PipInstallConfig\n        {\n            RequirementsFilePaths = [\"requirements.txt\"],\n            TorchVersion = \"==2.7.0\",\n            TorchvisionVersion = \"==0.22.0\",\n            TorchaudioVersion = \"==2.7.0\",\n            CudaIndex = isBlackwell ? \"cu128\" : \"cu126\",\n            ExtraPipArgs = [Compat.IsWindows ? \"triton-windows\" : \"triton\"],\n            UpgradePackages = true,\n        };\n\n        await StandardPipInstallProcessAsync(\n                venvRunner,\n                options,\n                installedPackage,\n                config,\n                onConsoleOutput,\n                progress,\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(-1f, \"Installing AI Toolkit UI...\", isIndeterminate: true));\n\n        var uiDirectory = new DirectoryPath(installLocation, \"ui\");\n        var envVars = GetEnvVars(venvRunner.EnvironmentVariables);\n        await PrerequisiteHelper\n            .RunNpm(\"install\", uiDirectory, progress?.AsProcessOutputHandler(), envVars)\n            .ConfigureAwait(false);\n        await PrerequisiteHelper\n            .RunNpm(\"run update_db\", uiDirectory, progress?.AsProcessOutputHandler(), envVars)\n            .ConfigureAwait(false);\n        await PrerequisiteHelper\n            .RunNpm(\"run build\", uiDirectory, progress?.AsProcessOutputHandler(), envVars)\n            .ConfigureAwait(false);\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n        VenvRunner.UpdateEnvironmentVariables(GetEnvVars);\n\n        var uiDirectory = new DirectoryPath(installLocation, \"ui\");\n        var envVars = GetEnvVars(VenvRunner.EnvironmentVariables);\n        npmProcess = PrerequisiteHelper.RunNpmDetached(\n            \"run start\",\n            uiDirectory,\n            HandleConsoleOutput,\n            envVars\n        );\n        npmProcess.EnableRaisingEvents = true;\n        if (Compat.IsWindows)\n        {\n            ProcessTracker.AttachExitHandlerJobToProcess(npmProcess);\n        }\n\n        return;\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (!s.Text.Contains(\"Local:  \", StringComparison.OrdinalIgnoreCase))\n                return;\n\n            var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n            var match = regex.Match(s.Text);\n            if (match.Success)\n            {\n                WebUrl = match.Value;\n            }\n            OnStartupComplete(WebUrl);\n        }\n    }\n\n    public override async Task WaitForShutdown()\n    {\n        if (npmProcess is { HasExited: false })\n        {\n            npmProcess.Kill(true);\n            try\n            {\n                await npmProcess\n                    .WaitForExitAsync(new CancellationTokenSource(5000).Token)\n                    .ConfigureAwait(false);\n            }\n            catch (OperationCanceledException e)\n            {\n                Logger.Warn(e, \"Timed out waiting for npm to exit\");\n                npmProcess.CancelStreamReaders();\n            }\n        }\n        npmProcess = null;\n    }\n\n    private ImmutableDictionary<string, string> GetEnvVars(ImmutableDictionary<string, string> env)\n    {\n        // set SETUPTOOLS_USE_DISTUTILS=setuptools to avoid job errors\n        env = env.SetItem(\"SETUPTOOLS_USE_DISTUTILS\", \"setuptools\");\n\n        var pathBuilder = new EnvPathBuilder();\n\n        if (env.TryGetValue(\"PATH\", out var value))\n        {\n            pathBuilder.AddPath(value);\n        }\n\n        pathBuilder.AddPath(\n            Compat.IsWindows\n                ? Environment.GetFolderPath(Environment.SpecialFolder.System)\n                : Path.Combine(SettingsManager.LibraryDir, \"Assets\", \"nodejs\", \"bin\")\n        );\n\n        pathBuilder.AddPath(Path.Combine(SettingsManager.LibraryDir, \"Assets\", \"nodejs\"));\n\n        return env.SetItem(\"PATH\", pathBuilder.ToString());\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.IO.Compression;\nusing NLog;\nusing Octokit;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n/// <summary>\n/// Base class for packages that are hosted on Github.\n/// Author and Name should be the Github username and repository name respectively.\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\n[SuppressMessage(\"ReSharper\", \"VirtualMemberNeverOverridden.Global\")]\npublic abstract class BaseGitPackage : BasePackage\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    protected readonly IGithubApiCache GithubApi;\n    protected readonly IDownloadService DownloadService;\n    protected readonly IPrerequisiteHelper PrerequisiteHelper;\n    protected readonly IPyInstallationManager PyInstallationManager;\n    protected readonly IPipWheelService PipWheelService;\n    public IPyVenvRunner? VenvRunner;\n\n    public virtual string RepositoryName => Name;\n    public virtual string RepositoryAuthor => Author;\n\n    /// <summary>\n    /// URL of the hosted web page on launch\n    /// </summary>\n    protected string WebUrl = string.Empty;\n\n    public override string GithubUrl => $\"https://github.com/{RepositoryAuthor}/{RepositoryName}\";\n\n    public string DownloadLocation => Path.Combine(SettingsManager.LibraryDir, \"Packages\", $\"{Name}.zip\");\n\n    protected string GetDownloadUrl(DownloadPackageVersionOptions versionOptions)\n    {\n        if (!string.IsNullOrWhiteSpace(versionOptions.CommitHash))\n        {\n            return $\"https://github.com/{RepositoryAuthor}/{RepositoryName}/archive/{versionOptions.CommitHash}.zip\";\n        }\n\n        if (!string.IsNullOrWhiteSpace(versionOptions.VersionTag))\n        {\n            return $\"https://api.github.com/repos/{RepositoryAuthor}/{RepositoryName}/zipball/{versionOptions.VersionTag}\";\n        }\n\n        if (!string.IsNullOrWhiteSpace(versionOptions.BranchName))\n        {\n            return $\"https://api.github.com/repos/{RepositoryAuthor}/{RepositoryName}/zipball/{versionOptions.BranchName}\";\n        }\n\n        throw new Exception(\"No download URL available\");\n    }\n\n    protected BaseGitPackage(\n        IGithubApiCache githubApi,\n        ISettingsManager settingsManager,\n        IDownloadService downloadService,\n        IPrerequisiteHelper prerequisiteHelper,\n        IPyInstallationManager pyInstallationManager,\n        IPipWheelService pipWheelService\n    )\n        : base(settingsManager)\n    {\n        GithubApi = githubApi;\n        DownloadService = downloadService;\n        PrerequisiteHelper = prerequisiteHelper;\n        PyInstallationManager = pyInstallationManager;\n        PipWheelService = pipWheelService;\n    }\n\n    public override async Task<DownloadPackageVersionOptions?> GetLatestVersion(\n        bool includePrerelease = false\n    )\n    {\n        if (ShouldIgnoreReleases)\n        {\n            var commits = await GithubApi\n                .GetAllCommits(RepositoryAuthor, RepositoryName, MainBranch)\n                .ConfigureAwait(false);\n            return new DownloadPackageVersionOptions\n            {\n                IsLatest = true,\n                IsPrerelease = false,\n                BranchName = MainBranch,\n                CommitHash = commits?.FirstOrDefault()?.Sha,\n            };\n        }\n\n        var releases = await GithubApi.GetAllReleases(RepositoryAuthor, RepositoryName).ConfigureAwait(false);\n        var releaseList = releases.ToList();\n        if (releaseList.Count == 0)\n        {\n            return new DownloadPackageVersionOptions\n            {\n                IsLatest = true,\n                IsPrerelease = false,\n                BranchName = MainBranch,\n            };\n        }\n\n        var latestRelease = includePrerelease ? releaseList.First() : releaseList.First(x => !x.Prerelease);\n\n        return new DownloadPackageVersionOptions\n        {\n            IsLatest = true,\n            IsPrerelease = latestRelease.Prerelease,\n            VersionTag = latestRelease.TagName!,\n        };\n    }\n\n    public override Task<IEnumerable<GitCommit>?> GetAllCommits(string branch, int page = 1, int perPage = 10)\n    {\n        return GithubApi.GetAllCommits(RepositoryAuthor, RepositoryName, branch, page, perPage);\n    }\n\n    public override async Task<PackageVersionOptions> GetAllVersionOptions()\n    {\n        var packageVersionOptions = new PackageVersionOptions();\n\n        if (!ShouldIgnoreReleases)\n        {\n            var allReleases = await GithubApi\n                .GetAllReleases(RepositoryAuthor, RepositoryName)\n                .ConfigureAwait(false);\n            var releasesList = allReleases.ToList();\n            if (releasesList.Any())\n            {\n                packageVersionOptions.AvailableVersions = releasesList.Select(r => new PackageVersion\n                {\n                    TagName = r.TagName!,\n                    ReleaseNotesMarkdown = r.Body,\n                    IsPrerelease = r.Prerelease,\n                });\n            }\n        }\n\n        // Branch mode\n        var allBranches = await GithubApi\n            .GetAllBranches(RepositoryAuthor, RepositoryName)\n            .ConfigureAwait(false);\n        packageVersionOptions.AvailableBranches = allBranches.Select(b => new PackageVersion\n        {\n            TagName = $\"{b.Name}\",\n            ReleaseNotesMarkdown = string.Empty,\n        });\n\n        return packageVersionOptions;\n    }\n\n    /// <summary>\n    /// Setup the virtual environment for the package.\n    /// </summary>\n    [MemberNotNull(nameof(VenvRunner))]\n    public async Task<IPyVenvRunner> SetupVenv(\n        string installedPackagePath,\n        string venvName = \"venv\",\n        bool forceRecreate = false,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        PyVersion? pythonVersion = null\n    )\n    {\n        if (Interlocked.Exchange(ref VenvRunner, null) is { } oldRunner)\n        {\n            await oldRunner.DisposeAsync().ConfigureAwait(false);\n        }\n\n        var venvRunner = await SetupVenvPure(\n                installedPackagePath,\n                venvName,\n                forceRecreate,\n                onConsoleOutput,\n                pythonVersion\n            )\n            .ConfigureAwait(false);\n\n        if (Interlocked.Exchange(ref VenvRunner, venvRunner) is { } oldRunner2)\n        {\n            await oldRunner2.DisposeAsync().ConfigureAwait(false);\n        }\n\n        Debug.Assert(VenvRunner != null, \"VenvRunner != null\");\n\n        return venvRunner;\n    }\n\n    /// <summary>\n    /// Like <see cref=\"SetupVenv\"/>, but does not set the <see cref=\"VenvRunner\"/> property.\n    /// Returns a new <see cref=\"PyVenvRunner\"/> instance.\n    /// </summary>\n    public async Task<IPyVenvRunner> SetupVenvPure(\n        string installedPackagePath,\n        string venvName = \"venv\",\n        bool forceRecreate = false,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        PyVersion? pythonVersion = null\n    )\n    {\n        // Use either the specific version or the default one\n        var baseInstall = pythonVersion.HasValue\n            ? new PyBaseInstall(\n                await PyInstallationManager.GetInstallationAsync(pythonVersion.Value).ConfigureAwait(false)\n            )\n            : PyBaseInstall.Default;\n\n        if (!PrerequisiteHelper.IsUvInstalled)\n        {\n            await PrerequisiteHelper.InstallUvIfNecessary().ConfigureAwait(false);\n        }\n\n        var venvRunner = await baseInstall\n            .CreateVenvRunnerAsync(\n                Path.Combine(installedPackagePath, venvName),\n                workingDirectory: installedPackagePath,\n                environmentVariables: SettingsManager.Settings.EnvironmentVariables,\n                withDefaultTclTkEnv: Compat.IsWindows,\n                withQueriedTclTkEnv: Compat.IsUnix\n            )\n            .ConfigureAwait(false);\n\n        if (forceRecreate || !venvRunner.Exists())\n        {\n            await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false);\n        }\n\n        // Constrain setuptools<82 in uv's isolated build environments.\n        // setuptools 82+ removed pkg_resources, breaking source builds that import it.\n        var buildConstraintsPath = Path.Combine(installedPackagePath, venvName, \"uv-build-constraints.txt\");\n        await File.WriteAllTextAsync(buildConstraintsPath, \"setuptools<82\\n\").ConfigureAwait(false);\n        // Use relative path because uv splits UV_BUILD_CONSTRAINT on spaces (it's a list-type env var),\n        // which breaks when the absolute path contains spaces. The working directory is installedPackagePath,\n        // so the relative path resolves correctly.\n        var relativeBuildConstraintsPath = Path.Combine(venvName, \"uv-build-constraints.txt\");\n        venvRunner.UpdateEnvironmentVariables(env =>\n            env.SetItem(\"UV_BUILD_CONSTRAINT\", relativeBuildConstraintsPath)\n        );\n\n        // ensure pip is installed\n        await venvRunner.PipInstall(\"pip\", onConsoleOutput).ConfigureAwait(false);\n\n        if (!Compat.IsWindows)\n            return venvRunner;\n\n        try\n        {\n            await PrerequisiteHelper\n                .AddMissingLibsToVenv(installedPackagePath, baseInstall)\n                .ConfigureAwait(false);\n        }\n        catch (Exception e)\n        {\n            Logger.Warn(e, \"Failed to add missing libs to venv\");\n        }\n\n        return venvRunner;\n    }\n\n    public override async Task<IEnumerable<Release>> GetReleaseTags()\n    {\n        var allReleases = await GithubApi\n            .GetAllReleases(RepositoryAuthor, RepositoryName)\n            .ConfigureAwait(false);\n        return allReleases;\n    }\n\n    public override async Task DownloadPackage(\n        string installLocation,\n        DownloadPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var versionOptions = options.VersionOptions;\n\n        const long fiveGigs = 5 * SystemInfo.Gibibyte;\n\n        if (SystemInfo.GetDiskFreeSpaceBytes(installLocation) is < fiveGigs)\n        {\n            throw new ApplicationException(\n                $\"Not enough space to download {Name} to {installLocation}, need at least 5GB\"\n            );\n        }\n\n        var gitArgs = new List<string> { \"clone\" };\n\n        var branchArg = !string.IsNullOrWhiteSpace(versionOptions.VersionTag)\n            ? versionOptions.VersionTag\n            : versionOptions.BranchName;\n\n        if (!string.IsNullOrWhiteSpace(branchArg))\n        {\n            gitArgs.Add(\"--branch\");\n            gitArgs.Add(branchArg);\n        }\n\n        gitArgs.Add(GithubUrl);\n        gitArgs.Add(installLocation);\n\n        await PrerequisiteHelper\n            .RunGit(gitArgs.ToArray(), progress?.AsProcessOutputHandler())\n            .ConfigureAwait(false);\n\n        if (!versionOptions.IsLatest && !string.IsNullOrWhiteSpace(versionOptions.CommitHash))\n        {\n            await PrerequisiteHelper\n                .RunGit(\n                    [\"checkout\", versionOptions.CommitHash],\n                    progress?.AsProcessOutputHandler(),\n                    installLocation\n                )\n                .ConfigureAwait(false);\n        }\n\n        progress?.Report(new ProgressReport(100, message: \"Download Complete\"));\n    }\n\n    protected Task UnzipPackage(string installLocation, IProgress<ProgressReport>? progress = null)\n    {\n        using var zip = ZipFile.OpenRead(DownloadLocation);\n        var zipDirName = string.Empty;\n        var totalEntries = zip.Entries.Count;\n        var currentEntry = 0;\n\n        foreach (var entry in zip.Entries)\n        {\n            currentEntry++;\n            if (string.IsNullOrWhiteSpace(entry.Name) && entry.FullName.EndsWith(\"/\"))\n            {\n                if (string.IsNullOrWhiteSpace(zipDirName))\n                {\n                    zipDirName = entry.FullName;\n                }\n\n                var folderPath = Path.Combine(\n                    installLocation,\n                    entry.FullName.Replace(zipDirName, string.Empty)\n                );\n                Directory.CreateDirectory(folderPath);\n                continue;\n            }\n\n            var destinationPath = Path.GetFullPath(\n                Path.Combine(installLocation, entry.FullName.Replace(zipDirName, string.Empty))\n            );\n            entry.ExtractToFile(destinationPath, true);\n\n            progress?.Report(\n                new ProgressReport(\n                    current: Convert.ToUInt64(currentEntry),\n                    total: Convert.ToUInt64(totalEntries)\n                )\n            );\n        }\n\n        return Task.CompletedTask;\n    }\n\n    public override async Task<bool> CheckForUpdates(InstalledPackage package)\n    {\n        var currentVersion = package.Version;\n        if (currentVersion is null or { InstalledReleaseVersion: null, InstalledBranch: null })\n        {\n            Logger.Warn(\n                \"Could not check updates for package {Name}, version is invalid: {@currentVersion}\",\n                Name,\n                currentVersion\n            );\n            return false;\n        }\n\n        try\n        {\n            if (currentVersion.IsReleaseMode)\n            {\n                var latestVersion = await GetLatestVersion(currentVersion.IsPrerelease).ConfigureAwait(false);\n                UpdateAvailable = latestVersion.VersionTag != currentVersion.InstalledReleaseVersion;\n                return UpdateAvailable;\n            }\n\n            var allCommits = (\n                await GetAllCommits(currentVersion.InstalledBranch!).ConfigureAwait(false)\n            )?.ToList();\n\n            if (allCommits == null || allCommits.Count == 0)\n            {\n                Logger.Warn(\"No commits found for {Package}\", package.PackageName);\n                return false;\n            }\n            var latestCommitHash = allCommits.First().Sha;\n            return latestCommitHash != currentVersion.InstalledCommitSha;\n        }\n        catch (ApiException e)\n        {\n            Logger.Warn(e, \"Failed to check for package updates\");\n            return false;\n        }\n    }\n\n    public override async Task<DownloadPackageVersionOptions?> GetUpdate(InstalledPackage installedPackage)\n    {\n        var currentVersion = installedPackage.Version;\n        if (currentVersion is null or { InstalledReleaseVersion: null, InstalledBranch: null })\n        {\n            Logger.Warn(\n                \"Could not check updates for package {Name}, version is invalid: {@currentVersion}\",\n                Name,\n                currentVersion\n            );\n            return null;\n        }\n\n        var versionOptions = new DownloadPackageVersionOptions { IsLatest = true };\n\n        try\n        {\n            if (currentVersion.IsReleaseMode)\n            {\n                var latestVersion = await GetLatestVersion(currentVersion.IsPrerelease).ConfigureAwait(false);\n                versionOptions.IsPrerelease = latestVersion.IsPrerelease;\n                versionOptions.VersionTag = latestVersion.VersionTag;\n                return versionOptions;\n            }\n\n            var allCommits = (\n                await GetAllCommits(currentVersion.InstalledBranch!).ConfigureAwait(false)\n            )?.ToList();\n\n            if (allCommits == null || allCommits.Count == 0)\n            {\n                Logger.Warn(\"No commits found for {Package}\", installedPackage.PackageName);\n                return null;\n            }\n            var latestCommitHash = allCommits.First().Sha;\n\n            versionOptions.CommitHash = latestCommitHash;\n            versionOptions.BranchName = currentVersion.InstalledBranch;\n\n            return versionOptions;\n        }\n        catch (ApiException e)\n        {\n            Logger.Warn(e, \"Failed to check for package updates\");\n            return null;\n        }\n    }\n\n    public override async Task<InstalledPackageVersion> Update(\n        string installLocation,\n        InstalledPackage installedPackage,\n        UpdatePackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (installedPackage.Version == null)\n            throw new NullReferenceException(\"Version is null\");\n\n        if (!Directory.Exists(Path.Combine(installedPackage.FullPath!, \".git\")))\n        {\n            Logger.Info(\"not a git repo, initializing...\");\n            progress?.Report(new ProgressReport(-1f, \"Initializing git repo\", isIndeterminate: true));\n            await PrerequisiteHelper\n                .RunGit(\"init\", onConsoleOutput, installedPackage.FullPath)\n                .ConfigureAwait(false);\n            await PrerequisiteHelper\n                .RunGit(\n                    new[] { \"remote\", \"add\", \"origin\", GithubUrl },\n                    onConsoleOutput,\n                    installedPackage.FullPath\n                )\n                .ConfigureAwait(false);\n        }\n\n        var sharedFolderMethodToUse =\n            installedPackage.PreferredSharedFolderMethod ?? RecommendedSharedFolderMethod;\n        // Temporarily remove symlinks if using Symlink method\n        if (sharedFolderMethodToUse == SharedFolderMethod.Symlink)\n        {\n            if (SharedFolders is not null)\n            {\n                try\n                {\n                    Helper.SharedFolders.RemoveLinksForPackage(\n                        SharedFolders,\n                        new DirectoryPath(installedPackage.FullPath!)\n                    );\n                }\n                catch (Exception e)\n                {\n                    Logger.Warn(\n                        e,\n                        \"Failed to remove symlinks for package {Package}\",\n                        installedPackage.PackageName\n                    );\n                }\n            }\n\n            if (SharedOutputFolders is not null && installedPackage.UseSharedOutputFolder)\n            {\n                try\n                {\n                    Helper.SharedFolders.RemoveLinksForPackage(\n                        SharedOutputFolders,\n                        new DirectoryPath(installedPackage.FullPath!)\n                    );\n                }\n                catch (Exception e)\n                {\n                    Logger.Warn(\n                        e,\n                        \"Failed to remove output symlinks for package {Package}\",\n                        installedPackage.PackageName\n                    );\n                }\n            }\n        }\n\n        var versionOptions = options.VersionOptions;\n\n        if (!string.IsNullOrWhiteSpace(versionOptions.VersionTag))\n        {\n            progress?.Report(new ProgressReport(-1f, \"Fetching tags...\", isIndeterminate: true));\n            await PrerequisiteHelper\n                .RunGit(new[] { \"fetch\", \"--tags\", \"--force\" }, onConsoleOutput, installedPackage.FullPath)\n                .ConfigureAwait(false);\n\n            progress?.Report(\n                new ProgressReport(-1f, $\"Checking out {versionOptions.VersionTag}\", isIndeterminate: true)\n            );\n            await PrerequisiteHelper\n                .RunGit(\n                    new[] { \"checkout\", versionOptions.VersionTag, \"--force\" },\n                    onConsoleOutput,\n                    installedPackage.FullPath\n                )\n                .ConfigureAwait(false);\n\n            await InstallPackage(\n                    installLocation,\n                    installedPackage,\n                    options.AsInstallOptions(),\n                    progress,\n                    onConsoleOutput,\n                    cancellationToken\n                )\n                .ConfigureAwait(false);\n\n            return new InstalledPackageVersion\n            {\n                InstalledReleaseVersion = versionOptions.VersionTag,\n                IsPrerelease = versionOptions.IsPrerelease,\n            };\n        }\n\n        // fetch\n        progress?.Report(new ProgressReport(-1f, \"Fetching data...\", isIndeterminate: true));\n        await PrerequisiteHelper\n            .RunGit(new[] { \"fetch\", \"--force\" }, onConsoleOutput, installedPackage.FullPath)\n            .ConfigureAwait(false);\n\n        if (versionOptions.IsLatest)\n        {\n            // checkout\n            progress?.Report(\n                new ProgressReport(\n                    -1f,\n                    $\"Checking out {installedPackage.Version.InstalledBranch}...\",\n                    isIndeterminate: true\n                )\n            );\n            await PrerequisiteHelper\n                .RunGit(\n                    new[] { \"checkout\", versionOptions.BranchName!, \"--force\" },\n                    onConsoleOutput,\n                    installedPackage.FullPath\n                )\n                .ConfigureAwait(false);\n\n            // pull\n            progress?.Report(new ProgressReport(-1f, \"Pulling changes...\", isIndeterminate: true));\n            // Try fast-forward-only first\n            var ffOnly = await PrerequisiteHelper\n                .GetGitOutput(\n                    [\"pull\", \"--ff-only\", \"--autostash\", \"origin\", installedPackage.Version.InstalledBranch!],\n                    installedPackage.FullPath!\n                )\n                .ConfigureAwait(false);\n\n            if (ffOnly.ExitCode != 0)\n            {\n                // Fallback to rebase to preserve local changes if any\n                var rebaseRes = await PrerequisiteHelper\n                    .GetGitOutput(\n                        [\n                            \"pull\",\n                            \"--rebase\",\n                            \"--autostash\",\n                            \"origin\",\n                            installedPackage.Version.InstalledBranch!,\n                        ],\n                        installedPackage.FullPath!\n                    )\n                    .ConfigureAwait(false);\n                rebaseRes.EnsureSuccessExitCode();\n            }\n        }\n        else\n        {\n            // checkout\n            progress?.Report(\n                new ProgressReport(\n                    -1f,\n                    $\"Checking out {installedPackage.Version.InstalledBranch}...\",\n                    isIndeterminate: true\n                )\n            );\n            await PrerequisiteHelper\n                .RunGit(\n                    new[] { \"checkout\", versionOptions.CommitHash!, \"--force\" },\n                    onConsoleOutput,\n                    installedPackage.FullPath\n                )\n                .ConfigureAwait(false);\n        }\n\n        await InstallPackage(\n                installLocation,\n                installedPackage,\n                options.AsInstallOptions(),\n                progress,\n                onConsoleOutput,\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        return new InstalledPackageVersion\n        {\n            InstalledBranch = versionOptions.BranchName,\n            InstalledCommitSha = versionOptions.CommitHash,\n            IsPrerelease = versionOptions.IsPrerelease,\n        };\n    }\n\n    private async Task FixInfinityFolders(DirectoryPath rootDirectory, string infinityFolderName)\n    {\n        // Skip if first infinity not found\n        if (\n            rootDirectory.JoinDir(infinityFolderName)\n            is not { Exists: true, IsSymbolicLink: false } firstInfinity\n        )\n        {\n            return;\n        }\n\n        var depth = 0;\n        var currentDir = rootDirectory;\n\n        while (currentDir.JoinDir(infinityFolderName) is { Exists: true, IsSymbolicLink: false } newInfinity)\n        {\n            depth++;\n            currentDir = newInfinity;\n        }\n\n        Logger.Info(\"Found {Depth} infinity folders from {FirstPath}\", depth, firstInfinity.ToString());\n\n        // Move all items in infinity folder to root\n        Logger.Info(\"Moving infinity folders content to root: {Path}\", currentDir.ToString());\n        await FileTransfers.MoveAllFilesAndDirectories(currentDir, rootDirectory).ConfigureAwait(false);\n\n        // Move any files from first infinity by enumeration just in case\n        foreach (var file in firstInfinity.EnumerateFiles())\n        {\n            await file.MoveToDirectoryAsync(rootDirectory).ConfigureAwait(false);\n        }\n\n        // Delete infinity folders chain from first\n        Logger.Info(\"Deleting infinity folders: {Path}\", currentDir.ToString());\n        await firstInfinity.DeleteAsync(true).ConfigureAwait(false);\n    }\n\n    private async Task FixForgeInfinity()\n    {\n        var modelsDir = new DirectoryPath(SettingsManager.ModelsDirectory);\n        var rootDirectory = modelsDir.JoinDir(\"StableDiffusion\").JoinDir(\"sd\");\n        var infinityFolderName = \"sd\";\n        var firstInfinity = rootDirectory.JoinDir(infinityFolderName);\n\n        var depth = 0;\n        var currentDir = rootDirectory;\n\n        while (currentDir.JoinDir(infinityFolderName) is { Exists: true, IsSymbolicLink: false } newInfinity)\n        {\n            depth++;\n            currentDir = newInfinity;\n        }\n\n        if (depth <= 5)\n        {\n            Logger.Info(\"not really that infinity, aborting\");\n            return;\n        }\n\n        Logger.Info(\"Found {Depth} infinity folders from {FirstPath}\", depth, firstInfinity.ToString());\n\n        // Move all items in infinity folder to root\n        Logger.Info(\"Moving infinity folders content to root: {Path}\", currentDir.ToString());\n        await FileTransfers\n            .MoveAllFilesAndDirectories(currentDir, rootDirectory, overwriteIfHashMatches: true)\n            .ConfigureAwait(false);\n\n        // Move any files from first infinity by enumeration just in case\n        var leftoverFiles = firstInfinity.EnumerateFiles(searchOption: SearchOption.AllDirectories);\n        foreach (var file in leftoverFiles)\n        {\n            await file.MoveToWithIncrementAsync(rootDirectory.JoinFile(file.Name)).ConfigureAwait(false);\n        }\n\n        if (!firstInfinity.EnumerateFiles(searchOption: SearchOption.AllDirectories).Any())\n        {\n            // Delete infinity folders chain from first\n            Logger.Info(\"Deleting infinity folders: {Path}\", currentDir.ToString());\n            await firstInfinity.DeleteAsync(true).ConfigureAwait(false);\n        }\n    }\n\n    public override async Task SetupModelFolders(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    )\n    {\n        if (sharedFolderMethod is SharedFolderMethod.Configuration && SharedFolderLayout is not null)\n        {\n            await SharedFoldersConfigHelper\n                .UpdateConfigFileForSharedAsync(\n                    SharedFolderLayout,\n                    installDirectory.FullPath,\n                    SettingsManager.ModelsDirectory\n                )\n                .ConfigureAwait(false);\n        }\n        else if (sharedFolderMethod is SharedFolderMethod.Symlink && SharedFolders is { } sharedFolders)\n        {\n            var modelsDir = new DirectoryPath(SettingsManager.ModelsDirectory);\n\n            // fix infinity controlnet folders\n            await FixInfinityFolders(modelsDir.JoinDir(\"ControlNet\"), \"ControlNet\").ConfigureAwait(false);\n            await FixForgeInfinity().ConfigureAwait(false);\n\n            // fix duplicate links in models dir\n            // see https://github.com/LykosAI/StabilityMatrix/issues/338\n            string[] duplicatePaths =\n            [\n                Path.Combine(\"ControlNet\", \"ControlNet\"),\n                Path.Combine(\"IPAdapter\", \"base\"),\n                Path.Combine(\"IPAdapter\", \"sd15\"),\n                Path.Combine(\"IPAdapter\", \"sdxl\"),\n            ];\n\n            foreach (var duplicatePath in duplicatePaths)\n            {\n                var linkDir = modelsDir.JoinDir(duplicatePath);\n                if (!linkDir.IsSymbolicLink)\n                    continue;\n\n                Logger.Info(\"Removing duplicate junction at {Path}\", linkDir.ToString());\n                await linkDir.DeleteAsync(false).ConfigureAwait(false);\n            }\n\n            await Helper\n                .SharedFolders.UpdateLinksForPackage(\n                    sharedFolders,\n                    SettingsManager.ModelsDirectory,\n                    installDirectory\n                )\n                .ConfigureAwait(false);\n        }\n    }\n\n    public override Task UpdateModelFolders(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    ) => SetupModelFolders(installDirectory, sharedFolderMethod);\n\n    public override Task RemoveModelFolderLinks(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    )\n    {\n        // Auto handling for SharedFolderLayout\n        if (sharedFolderMethod is SharedFolderMethod.Configuration && SharedFolderLayout is not null)\n        {\n            return SharedFoldersConfigHelper.UpdateConfigFileForDefaultAsync(\n                SharedFolderLayout,\n                installDirectory.FullPath\n            );\n        }\n\n        if (SharedFolders is not null && sharedFolderMethod is SharedFolderMethod.Symlink)\n        {\n            Helper.SharedFolders.RemoveLinksForPackage(SharedFolders, installDirectory);\n        }\n        return Task.CompletedTask;\n    }\n\n    public override Task SetupOutputFolderLinks(DirectoryPath installDirectory)\n    {\n        if (SharedOutputFolders is { } sharedOutputFolders)\n        {\n            return Helper.SharedFolders.UpdateLinksForPackage(\n                sharedOutputFolders,\n                SettingsManager.ImagesDirectory,\n                installDirectory,\n                recursiveDelete: true\n            );\n        }\n\n        return Task.CompletedTask;\n    }\n\n    public override Task RemoveOutputFolderLinks(DirectoryPath installDirectory)\n    {\n        if (SharedOutputFolders is { } sharedOutputFolders)\n        {\n            Helper.SharedFolders.RemoveLinksForPackage(sharedOutputFolders, installDirectory);\n        }\n        return Task.CompletedTask;\n    }\n\n    // Send input to the running process.\n    public virtual void SendInput(string input)\n    {\n        var process = VenvRunner?.Process;\n        if (process == null)\n        {\n            Logger.Warn(\"No process running for {Name}\", Name);\n            return;\n        }\n        process.StandardInput.WriteLine(input);\n    }\n\n    public virtual async Task SendInputAsync(string input)\n    {\n        var process = VenvRunner?.Process;\n        if (process == null)\n        {\n            Logger.Warn(\"No process running for {Name}\", Name);\n            return;\n        }\n        await process.StandardInput.WriteLineAsync(input).ConfigureAwait(false);\n    }\n\n    protected PipInstallArgs GetTorchPipArgs(\n        TorchIndex torchIndex,\n        string torchVersion = \"\",\n        string torchvisionVersion = \"\",\n        string torchaudioVersion = \"\",\n        string xformersVersion = \"\",\n        string cudaIndex = \"cu130\",\n        string rocmIndex = \"rocm6.4\"\n    )\n    {\n        var pipArgs = new PipInstallArgs();\n\n        if (torchIndex == TorchIndex.DirectMl)\n        {\n            return pipArgs.WithTorchDirectML();\n        }\n\n        pipArgs = pipArgs.WithTorch(torchVersion).WithTorchVision(torchvisionVersion);\n\n        if (!string.IsNullOrEmpty(torchaudioVersion))\n        {\n            pipArgs = pipArgs.WithTorchAudio(torchaudioVersion);\n        }\n\n        var extraIndex = torchIndex switch\n        {\n            TorchIndex.Cpu => \"cpu\",\n            TorchIndex.Cuda => cudaIndex,\n            TorchIndex.Rocm => rocmIndex,\n            TorchIndex.Mps => \"cpu\",\n            TorchIndex.Zluda => cudaIndex,\n            _ => \"cpu\",\n        };\n\n        pipArgs = pipArgs.WithTorchExtraIndex(extraIndex);\n\n        if (torchIndex is TorchIndex.Cuda or TorchIndex.Zluda && !string.IsNullOrEmpty(xformersVersion))\n        {\n            pipArgs = pipArgs.WithXFormers(xformersVersion);\n        }\n\n        return pipArgs;\n    }\n\n    /// <summary>\n    /// Executes a standardized pip installation workflow: requirements first, then a forced torch install.\n    /// </summary>\n    protected async Task StandardPipInstallProcessAsync(\n        IPyVenvRunner venvRunner,\n        InstallPackageOptions options,\n        InstalledPackage installedPackage,\n        PipInstallConfig config,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, \"Upgrading pip...\", isIndeterminate: true));\n        await venvRunner.PipInstall(\"--upgrade pip wheel\", onConsoleOutput).ConfigureAwait(false);\n\n        if (config.PrePipInstallArgs.Any())\n        {\n            await venvRunner\n                .PipInstall(new PipInstallArgs([.. config.PrePipInstallArgs]), onConsoleOutput)\n                .ConfigureAwait(false);\n        }\n\n        progress?.Report(\n            new ProgressReport(-1f, \"Installing package requirements...\", isIndeterminate: true)\n        );\n        var requirementsPipArgs = new PipInstallArgs([.. config.ExtraPipArgs]);\n\n        if (config.UpgradePackages)\n        {\n            requirementsPipArgs = requirementsPipArgs.AddArg(\"--upgrade\");\n        }\n\n        foreach (var path in config.RequirementsFilePaths)\n        {\n            var requirementsFile = new FilePath(venvRunner.WorkingDirectory!, path);\n            if (!requirementsFile.Exists)\n                continue;\n\n            var content = await requirementsFile.ReadAllTextAsync(cancellationToken).ConfigureAwait(false);\n            requirementsPipArgs = requirementsPipArgs.WithParsedFromRequirementsTxt(\n                content,\n                config.RequirementsExcludePattern\n            );\n        }\n\n        if (installedPackage.PipOverrides != null)\n        {\n            requirementsPipArgs = requirementsPipArgs.WithUserOverrides(installedPackage.PipOverrides);\n        }\n\n        await venvRunner.PipInstall(requirementsPipArgs, onConsoleOutput).ConfigureAwait(false);\n\n        if (config.SkipTorchInstall)\n            return;\n\n        progress?.Report(new ProgressReport(-1f, \"Installing torch...\", isIndeterminate: true));\n        var torchIndex = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n\n        var torchPipArgs = GetTorchPipArgs(\n            torchIndex,\n            config.TorchVersion,\n            config.TorchvisionVersion,\n            config.TorchaudioVersion,\n            config.XformersVersion,\n            config.CudaIndex,\n            config.RocmIndex\n        );\n\n        if (config.UpgradePackages)\n        {\n            torchPipArgs = torchPipArgs.AddArg(\"--upgrade\");\n        }\n\n        if (config.ForceReinstallTorch)\n        {\n            torchPipArgs = torchPipArgs.AddArg(\"--force-reinstall\");\n        }\n\n        if (installedPackage.PipOverrides != null)\n        {\n            torchPipArgs = torchPipArgs.WithUserOverrides(installedPackage.PipOverrides);\n        }\n\n        await venvRunner.PipInstall(torchPipArgs, onConsoleOutput).ConfigureAwait(false);\n\n        if (config.PostInstallPipArgs.Any())\n        {\n            var postInstallPipArgs = new PipInstallArgs([.. config.PostInstallPipArgs]);\n            if (installedPackage.PipOverrides != null)\n            {\n                postInstallPipArgs = postInstallPipArgs.WithUserOverrides(installedPackage.PipOverrides);\n            }\n\n            await venvRunner.PipInstall(postInstallPipArgs, onConsoleOutput).ConfigureAwait(false);\n        }\n    }\n\n    /// <inheritdoc />\n    public override void Shutdown()\n    {\n        if (VenvRunner is not null)\n        {\n            VenvRunner.Dispose();\n            VenvRunner = null;\n        }\n    }\n\n    /// <inheritdoc />\n    public override async Task WaitForShutdown()\n    {\n        if (VenvRunner is not null)\n        {\n            await VenvRunner.DisposeAsync().ConfigureAwait(false);\n            VenvRunner = null;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/BasePackage.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Diagnostics.CodeAnalysis;\nusing Octokit;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\npublic abstract class BasePackage(ISettingsManager settingsManager)\n{\n    protected readonly ISettingsManager SettingsManager = settingsManager;\n\n    public string ByAuthor => $\"By {Author}\";\n\n    public abstract string Name { get; }\n    public abstract string DisplayName { get; set; }\n    public abstract string Author { get; }\n    public abstract string Blurb { get; }\n    public abstract string GithubUrl { get; }\n    public abstract string LicenseType { get; }\n    public abstract string LicenseUrl { get; }\n    public virtual string Disclaimer => string.Empty;\n    public virtual bool OfferInOneClickInstaller => true;\n\n    /// <summary>\n    /// Primary command to launch the package. 'Launch' buttons uses this.\n    /// </summary>\n    public abstract string LaunchCommand { get; }\n\n    /// <summary>\n    /// Optional commands (e.g. 'config') that are on the launch button split drop-down.\n    /// </summary>\n    public virtual IReadOnlyDictionary<string, string> ExtraLaunchCommands { get; } =\n        new Dictionary<string, string>();\n\n    public abstract Uri PreviewImageUri { get; }\n    public virtual bool ShouldIgnoreReleases => false;\n    public virtual bool ShouldIgnoreBranches => false;\n    public virtual bool UpdateAvailable { get; set; }\n\n    public virtual bool IsInferenceCompatible => false;\n\n    public abstract string OutputFolderName { get; }\n\n    public abstract IEnumerable<TorchIndex> AvailableTorchIndices { get; }\n\n    public virtual bool IsCompatible => GetRecommendedTorchVersion() != TorchIndex.Cpu;\n\n    public abstract PackageDifficulty InstallerSortOrder { get; }\n\n    public virtual PackageType PackageType => PackageType.SdInference;\n    public virtual bool UsesVenv => true;\n    public virtual bool InstallRequiresAdmin => false;\n    public virtual string? AdminRequiredReason => null;\n    public virtual PyVersion RecommendedPythonVersion => PyInstallationManager.Python_3_10_17;\n\n    /// <summary>\n    /// Minimum Python version required for updates. When set, updating a package with a lower\n    /// installed Python version will prompt for venv recreation. Null means no minimum enforced.\n    /// </summary>\n    public virtual PyVersion? MinimumPythonVersion => null;\n\n    /// <summary>\n    /// Returns a list of extra commands that can be executed for this package.\n    /// The function takes an InstalledPackage parameter to operate on a specific installation.\n    /// </summary>\n    public virtual List<ExtraPackageCommand> GetExtraCommands() => [];\n\n    public abstract Task DownloadPackage(\n        string installLocation,\n        DownloadPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    );\n\n    public abstract Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    );\n\n    public abstract Task<bool> CheckForUpdates(InstalledPackage package);\n\n    public abstract Task<InstalledPackageVersion> Update(\n        string installLocation,\n        InstalledPackage installedPackage,\n        UpdatePackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    );\n\n    public abstract Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    );\n\n    public virtual IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        new[] { SharedFolderMethod.Symlink, SharedFolderMethod.Configuration, SharedFolderMethod.None };\n\n    public abstract SharedFolderMethod RecommendedSharedFolderMethod { get; }\n\n    public abstract Task SetupModelFolders(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    );\n\n    public abstract Task UpdateModelFolders(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    );\n\n    public abstract Task RemoveModelFolderLinks(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    );\n\n    public abstract Task SetupOutputFolderLinks(DirectoryPath installDirectory);\n    public abstract Task RemoveOutputFolderLinks(DirectoryPath installDirectory);\n\n    public virtual TorchIndex GetRecommendedTorchVersion()\n    {\n        // if there's only one AvailableTorchVersion, return that\n        if (AvailableTorchIndices.Count() == 1)\n        {\n            return AvailableTorchIndices.First();\n        }\n\n        var preferNvidia = SettingsManager.Settings.PreferredGpu?.IsNvidia ?? HardwareHelper.HasNvidiaGpu();\n        if (AvailableTorchIndices.Contains(TorchIndex.Cuda) && preferNvidia)\n        {\n            return TorchIndex.Cuda;\n        }\n\n        var preferAmd = SettingsManager.Settings.PreferredGpu?.IsAmd ?? HardwareHelper.HasAmdGpu();\n        if (AvailableTorchIndices.Contains(TorchIndex.Zluda) && preferAmd)\n        {\n            return TorchIndex.Zluda;\n        }\n\n        var preferIntel = SettingsManager.Settings.PreferredGpu?.IsIntel ?? HardwareHelper.HasIntelGpu();\n        if (AvailableTorchIndices.Contains(TorchIndex.Ipex) && preferIntel)\n        {\n            return TorchIndex.Ipex;\n        }\n\n        var preferRocm =\n            Compat.IsLinux && (SettingsManager.Settings.PreferredGpu?.IsAmd ?? HardwareHelper.PreferRocm());\n        if (AvailableTorchIndices.Contains(TorchIndex.Rocm) && preferRocm)\n        {\n            return TorchIndex.Rocm;\n        }\n\n        var preferDirectMl =\n            Compat.IsWindows\n            && (SettingsManager.Settings.PreferredGpu?.IsAmd ?? HardwareHelper.PreferDirectMLOrZluda());\n        if (AvailableTorchIndices.Contains(TorchIndex.DirectMl) && preferDirectMl)\n        {\n            return TorchIndex.DirectMl;\n        }\n\n        if (Compat.IsMacOS && Compat.IsArm && AvailableTorchIndices.Contains(TorchIndex.Mps))\n        {\n            return TorchIndex.Mps;\n        }\n\n        return TorchIndex.Cpu;\n    }\n\n    /// <summary>\n    /// Shuts down the subprocess, canceling any pending streams.\n    /// </summary>\n    public abstract void Shutdown();\n\n    /// <summary>\n    /// Shuts down the process, returning a Task to wait for output EOF.\n    /// </summary>\n    public abstract Task WaitForShutdown();\n    public abstract Task<IEnumerable<Release>> GetReleaseTags();\n\n    public abstract List<LaunchOptionDefinition> LaunchOptions { get; }\n    public virtual IReadOnlyList<string> ExtraLaunchArguments { get; } = Array.Empty<string>();\n\n    /// <summary>\n    /// Layout of the shared folders. For both Symlink and Config.\n    /// </summary>\n    public virtual SharedFolderLayout? SharedFolderLayout { get; } = new();\n\n    /// <summary>\n    /// The shared folders that this package supports.\n    /// Mapping of <see cref=\"SharedFolderType\"/> to the relative paths from the package root.\n    /// (Legacy format for Symlink only, computed from SharedFolderLayout.)\n    /// </summary>\n    public virtual Dictionary<SharedFolderType, IReadOnlyList<string>>? SharedFolders =>\n        GetLegacySharedFolders();\n\n    private Dictionary<SharedFolderType, IReadOnlyList<string>>? GetLegacySharedFolders()\n    {\n        if (SharedFolderLayout is null)\n            return null;\n\n        // Keep track of unique paths since symbolic links can't do multiple targets\n        // So we'll ignore duplicates once they appear here\n        var addedPaths = new HashSet<string>();\n        var result = new Dictionary<SharedFolderType, IReadOnlyList<string>>();\n\n        foreach (var rule in SharedFolderLayout.Rules)\n        {\n            // Ignore empty\n            if (rule.TargetRelativePaths is not { Length: > 0 })\n            {\n                continue;\n            }\n\n            // If there are multi SourceTypes <-> TargetRelativePaths:\n            // We'll add a sub-path later\n            var isMultiSource = rule.SourceTypes.Length > 1;\n\n            foreach (var folderTypeKey in rule.SourceTypes)\n            {\n                var existingList =\n                    (ImmutableList<string>)\n                        result.GetValueOrDefault(folderTypeKey, ImmutableList<string>.Empty);\n\n                var folderName = folderTypeKey.GetStringValue();\n\n                foreach (var path in rule.TargetRelativePaths)\n                {\n                    var currentPath = path;\n\n                    if (isMultiSource)\n                    {\n                        // Add a sub-path for each source type\n                        currentPath = $\"{path}/{folderName}\";\n                    }\n\n                    // Skip if the path is already in the list\n                    if (existingList.Contains(currentPath))\n                        continue;\n\n                    // Skip if the path is already added globally\n                    if (!addedPaths.Add(currentPath))\n                        continue;\n\n                    result[folderTypeKey] = existingList.Add(currentPath);\n                }\n            }\n        }\n\n        return result;\n    }\n\n    /// <summary>\n    /// Represents a mapping of shared output types to their corresponding folder paths.\n    /// This property defines where various output files, such as images or grids,\n    /// are stored for the package. The dictionary keys represent specific\n    /// output types, and the values are lists of associated folder paths.\n    /// </summary>\n    public abstract Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders { get; }\n\n    /// <summary>\n    /// If defined, this package supports extensions using this manager.\n    /// </summary>\n    public virtual IPackageExtensionManager? ExtensionManager => null;\n\n    /// <summary>\n    /// True if this package supports extensions.\n    /// </summary>\n    [MemberNotNullWhen(true, nameof(ExtensionManager))]\n    public virtual bool SupportsExtensions => ExtensionManager is not null;\n\n    public abstract Task<PackageVersionOptions> GetAllVersionOptions();\n    public abstract Task<IEnumerable<GitCommit>?> GetAllCommits(\n        string branch,\n        int page = 1,\n        int perPage = 10\n    );\n    public abstract Task<DownloadPackageVersionOptions?> GetLatestVersion(bool includePrerelease = false);\n    public abstract string MainBranch { get; }\n    public event EventHandler<int>? Exited;\n    public event EventHandler<string>? StartupComplete;\n\n    public void OnExit(int exitCode) => Exited?.Invoke(this, exitCode);\n\n    public void OnStartupComplete(string url) => StartupComplete?.Invoke(this, url);\n\n    public virtual PackageVersionType AvailableVersionTypes =>\n        ShouldIgnoreReleases\n            ? PackageVersionType.Commit\n            : PackageVersionType.GithubRelease | PackageVersionType.Commit;\n\n    public virtual IEnumerable<PackagePrerequisite> Prerequisites =>\n        [PackagePrerequisite.Git, PackagePrerequisite.Python310, PackagePrerequisite.VcRedist];\n\n    public abstract Task<DownloadPackageVersionOptions?> GetUpdate(InstalledPackage installedPackage);\n\n    /// <summary>\n    /// List of known vulnerabilities for this package\n    /// </summary>\n    public virtual IReadOnlyList<PackageVulnerability> KnownVulnerabilities { get; protected set; } =\n        Array.Empty<PackageVulnerability>();\n\n    /// <summary>\n    /// Whether this package has any known vulnerabilities\n    /// </summary>\n    public bool HasVulnerabilities => KnownVulnerabilities.Any();\n\n    /// <summary>\n    /// Whether this package has any critical vulnerabilities\n    /// </summary>\n    public bool HasCriticalVulnerabilities =>\n        KnownVulnerabilities.Any(v => v.Severity == VulnerabilitySeverity.Critical);\n\n    /// <summary>\n    /// Check for any new vulnerabilities from external sources\n    /// </summary>\n    public virtual Task CheckForVulnerabilities(CancellationToken cancellationToken = default)\n    {\n        // Base implementation does nothing - derived classes should implement their own vulnerability checking\n        return Task.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Cogstudio.cs",
    "content": "﻿using System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, Cogstudio>(Duplicate = DuplicateStrategy.Append)]\npublic class Cogstudio(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"Cogstudio\";\n    public override string DisplayName { get; set; } = \"Cogstudio\";\n    public override string RepositoryName => \"CogVideo\";\n    public override string RepositoryAuthor => \"THUDM\";\n    public override string Author => \"pinokiofactory\";\n    public override string Blurb =>\n        \"An advanced gradio web ui for generating and editing videos with CogVideo.\";\n    public override string LicenseType => \"Apache-2.0\";\n    public override string LicenseUrl => \"https://github.com/THUDM/CogVideo/blob/main/LICENSE\";\n    public override string LaunchCommand => \"inference/gradio_composite_demo/cogstudio.py\";\n    public override Uri PreviewImageUri =>\n        new(\"https://raw.githubusercontent.com/pinokiofactory/cogstudio/main/img2vid.gif\");\n    public override List<LaunchOptionDefinition> LaunchOptions => new() { LaunchOptionDefinition.Extras };\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        new[] { SharedFolderMethod.None };\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>> SharedOutputFolders =>\n        new() { [SharedOutputType.Text2Vid] = new[] { \"output\" } };\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        new[] { TorchIndex.Cpu, TorchIndex.Cuda };\n    public override string MainBranch => \"main\";\n    public override bool ShouldIgnoreReleases => true;\n    public override string OutputFolderName => \"output\";\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced;\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        const string cogstudioUrl =\n            \"https://raw.githubusercontent.com/pinokiofactory/cogstudio/refs/heads/main/cogstudio.py\";\n\n        progress?.Report(new ProgressReport(-1f, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(-1f, \"Setting up Cogstudio files\", isIndeterminate: true));\n        var gradioCompositeDemo = new FilePath(installLocation, \"inference/gradio_composite_demo\");\n        var cogstudioFile = new FilePath(gradioCompositeDemo, \"cogstudio.py\");\n        gradioCompositeDemo.Directory?.Create();\n        await DownloadService\n            .DownloadToFileAsync(cogstudioUrl, cogstudioFile, cancellationToken: cancellationToken)\n            .ConfigureAwait(false);\n\n        progress?.Report(\n            new ProgressReport(\n                -1f,\n                \"Patching cogstudio.py to allow writing to the output folder\",\n                isIndeterminate: true\n            )\n        );\n        var outputDir = new FilePath(installLocation, \"output\");\n        if (Compat.IsWindows)\n        {\n            outputDir = outputDir.ToString().Replace(\"\\\\\", \"\\\\\\\\\");\n        }\n        var cogstudioContent = await cogstudioFile.ReadAllTextAsync(cancellationToken).ConfigureAwait(false);\n        cogstudioContent = cogstudioContent.Replace(\n            \"demo.launch()\",\n            $\"demo.launch(allowed_paths=['{outputDir}'])\"\n        );\n        await cogstudioFile.WriteAllTextAsync(cogstudioContent, cancellationToken).ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(-1f, \"Installing requirements\", isIndeterminate: true));\n        var requirements = new FilePath(installLocation, \"requirements.txt\");\n        var pipArgs = new PipInstallArgs()\n            .WithTorch(\"==2.3.1\")\n            .WithTorchVision(\"==0.18.1\")\n            .WithTorchAudio(\"==2.3.1\")\n            .WithTorchExtraIndex(\"cu121\")\n            .WithParsedFromRequirementsTxt(\n                await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),\n                excludePattern: Compat.IsWindows\n                    ? \"torch.*|moviepy.*|SwissArmyTransformer.*\"\n                    : \"torch.*|moviepy.*\"\n            );\n\n        if (installedPackage.PipOverrides != null)\n        {\n            pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);\n        }\n\n        // SwissArmyTransformer is not available on Windows and DeepSpeed needs prebuilt wheels\n        if (Compat.IsWindows)\n        {\n            await venvRunner\n                .PipInstall(\n                    \" https://github.com/daswer123/deepspeed-windows/releases/download/11.2/deepspeed-0.11.2+cuda121-cp310-cp310-win_amd64.whl\",\n                    onConsoleOutput\n                )\n                .ConfigureAwait(false);\n            await venvRunner\n                .PipInstall(\"spandrel opencv-python scikit-video\", onConsoleOutput)\n                .ConfigureAwait(false);\n        }\n\n        await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);\n        await venvRunner.PipInstall(\"moviepy==2.0.0.dev2\", onConsoleOutput).ConfigureAwait(false);\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (s.Text.Contains(\"Running on local URL\", StringComparison.OrdinalIgnoreCase))\n            {\n                var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n                var match = regex.Match(s.Text);\n\n                if (match.Success)\n                {\n                    WebUrl = match.Value;\n                }\n                OnStartupComplete(WebUrl);\n            }\n        }\n\n        VenvRunner.RunDetached(\n            [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],\n            HandleConsoleOutput,\n            OnExit\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/ComfyUI.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Text.Json;\nusing System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Packages.Config;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, ComfyUI>(Duplicate = DuplicateStrategy.Append)]\npublic class ComfyUI(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    public override string Name => \"ComfyUI\";\n    public override string DisplayName { get; set; } = \"ComfyUI\";\n    public override string Author => \"comfyanonymous\";\n    public override string LicenseType => \"GPL-3.0\";\n    public override string LicenseUrl => \"https://github.com/comfyanonymous/ComfyUI/blob/master/LICENSE\";\n    public override string Blurb => \"A powerful and modular stable diffusion GUI and backend\";\n    public override string LaunchCommand => \"main.py\";\n\n    public override Uri PreviewImageUri => new(\"https://cdn.lykos.ai/sm/packages/comfyui/preview.webp\");\n    public override bool IsInferenceCompatible => true;\n    public override string OutputFolderName => \"output\";\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.InferenceCompatible;\n\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;\n    public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_12_10;\n\n    // https://github.com/comfyanonymous/ComfyUI/blob/master/folder_paths.py#L11\n    public override SharedFolderLayout SharedFolderLayout =>\n        new()\n        {\n            RelativeConfigPath = \"extra_model_paths.yaml\",\n            ConfigFileType = ConfigFileType.Yaml,\n            ConfigSharingOptions =\n            {\n                RootKey = \"stability_matrix\",\n                ConfigDefaultType = ConfigDefaultType.ClearRoot,\n            },\n            Rules =\n            [\n                new SharedFolderLayoutRule // Checkpoints\n                {\n                    SourceTypes = [SharedFolderType.StableDiffusion],\n                    TargetRelativePaths = [\"models/checkpoints\"],\n                    ConfigDocumentPaths = [\"checkpoints\"],\n                },\n                new SharedFolderLayoutRule // Diffusers\n                {\n                    SourceTypes = [SharedFolderType.Diffusers],\n                    TargetRelativePaths = [\"models/diffusers\"],\n                    ConfigDocumentPaths = [\"diffusers\"],\n                },\n                new SharedFolderLayoutRule // Loras\n                {\n                    SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS],\n                    TargetRelativePaths = [\"models/loras\"],\n                    ConfigDocumentPaths = [\"loras\"],\n                },\n                new SharedFolderLayoutRule // CLIP (Text Encoders)\n                {\n                    SourceTypes = [SharedFolderType.TextEncoders],\n                    TargetRelativePaths = [\"models/clip\"],\n                    ConfigDocumentPaths = [\"clip\"],\n                },\n                new SharedFolderLayoutRule // CLIP Vision\n                {\n                    SourceTypes = [SharedFolderType.ClipVision],\n                    TargetRelativePaths = [\"models/clip_vision\"],\n                    ConfigDocumentPaths = [\"clip_vision\"],\n                },\n                new SharedFolderLayoutRule // Embeddings / Textual Inversion\n                {\n                    SourceTypes = [SharedFolderType.Embeddings],\n                    TargetRelativePaths = [\"models/embeddings\"],\n                    ConfigDocumentPaths = [\"embeddings\"],\n                },\n                new SharedFolderLayoutRule // VAE\n                {\n                    SourceTypes = [SharedFolderType.VAE],\n                    TargetRelativePaths = [\"models/vae\"],\n                    ConfigDocumentPaths = [\"vae\"],\n                },\n                new SharedFolderLayoutRule // VAE Approx\n                {\n                    SourceTypes = [SharedFolderType.ApproxVAE],\n                    TargetRelativePaths = [\"models/vae_approx\"],\n                    ConfigDocumentPaths = [\"vae_approx\"],\n                },\n                new SharedFolderLayoutRule // ControlNet / T2IAdapter\n                {\n                    SourceTypes = [SharedFolderType.ControlNet, SharedFolderType.T2IAdapter],\n                    TargetRelativePaths = [\"models/controlnet\"],\n                    ConfigDocumentPaths = [\"controlnet\"],\n                },\n                new SharedFolderLayoutRule // GLIGEN\n                {\n                    SourceTypes = [SharedFolderType.GLIGEN],\n                    TargetRelativePaths = [\"models/gligen\"],\n                    ConfigDocumentPaths = [\"gligen\"],\n                },\n                new SharedFolderLayoutRule // Upscalers\n                {\n                    SourceTypes =\n                    [\n                        SharedFolderType.ESRGAN,\n                        SharedFolderType.RealESRGAN,\n                        SharedFolderType.SwinIR,\n                    ],\n                    TargetRelativePaths = [\"models/upscale_models\"],\n                    ConfigDocumentPaths = [\"upscale_models\"],\n                },\n                new SharedFolderLayoutRule // Hypernetworks\n                {\n                    SourceTypes = [SharedFolderType.Hypernetwork],\n                    TargetRelativePaths = [\"models/hypernetworks\"],\n                    ConfigDocumentPaths = [\"hypernetworks\"],\n                },\n                new SharedFolderLayoutRule // IP-Adapter Base, SD1.5, SDXL\n                {\n                    SourceTypes =\n                    [\n                        SharedFolderType.IpAdapter,\n                        SharedFolderType.IpAdapters15,\n                        SharedFolderType.IpAdaptersXl,\n                    ],\n                    TargetRelativePaths = [\"models/ipadapter\"], // Single target path\n                    ConfigDocumentPaths = [\"ipadapter\"],\n                },\n                new SharedFolderLayoutRule // Prompt Expansion\n                {\n                    SourceTypes = [SharedFolderType.PromptExpansion],\n                    TargetRelativePaths = [\"models/prompt_expansion\"],\n                    ConfigDocumentPaths = [\"prompt_expansion\"],\n                },\n                new SharedFolderLayoutRule // Ultralytics\n                {\n                    SourceTypes = [SharedFolderType.Ultralytics], // Might need specific UltralyticsBbox/Segm if symlinks differ\n                    TargetRelativePaths = [\"models/ultralytics\"],\n                    ConfigDocumentPaths = [\"ultralytics\"],\n                },\n                // Config only rules for Ultralytics bbox/segm\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Ultralytics],\n                    SourceSubPath = \"bbox\",\n                    ConfigDocumentPaths = [\"ultralytics_bbox\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Ultralytics],\n                    SourceSubPath = \"segm\",\n                    ConfigDocumentPaths = [\"ultralytics_segm\"],\n                },\n                new SharedFolderLayoutRule // SAMs\n                {\n                    SourceTypes = [SharedFolderType.Sams],\n                    TargetRelativePaths = [\"models/sams\"],\n                    ConfigDocumentPaths = [\"sams\"],\n                },\n                new SharedFolderLayoutRule // Diffusion Models / Unet\n                {\n                    SourceTypes = [SharedFolderType.DiffusionModels],\n                    TargetRelativePaths = [\"models/diffusion_models\"],\n                    ConfigDocumentPaths = [\"diffusion_models\"],\n                },\n            ],\n        };\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders =>\n        new() { [SharedOutputType.Text2Img] = [\"output\"] };\n\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new()\n            {\n                Name = \"Host\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"127.0.0.1\",\n                Options = [\"--listen\"],\n            },\n            new()\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"8188\",\n                Options = [\"--port\"],\n            },\n            new()\n            {\n                Name = \"VRAM\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch\n                {\n                    MemoryLevel.Low => \"--lowvram\",\n                    MemoryLevel.Medium => \"--normalvram\",\n                    _ => null,\n                },\n                Options = [\"--highvram\", \"--normalvram\", \"--lowvram\", \"--novram\"],\n            },\n            new()\n            {\n                Name = \"Reserve VRAM\",\n                Type = LaunchOptionType.String,\n                InitialValue = Compat.IsWindows && HardwareHelper.HasAmdGpu() ? \"0.9\" : null,\n                Description =\n                    \"Sets the amount of VRAM (in GB) you want to reserve for use by your OS/other software\",\n                Options = [\"--reserve-vram\"],\n            },\n            new()\n            {\n                Name = \"Preview Method\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = \"--preview-method auto\",\n                Options = [\"--preview-method auto\", \"--preview-method latent2rgb\", \"--preview-method taesd\"],\n            },\n            new()\n            {\n                Name = \"Enable DirectML\",\n                Type = LaunchOptionType.Bool,\n                InitialValue =\n                    !HardwareHelper.HasWindowsRocmSupportedGpu()\n                    && HardwareHelper.PreferDirectMLOrZluda()\n                    && this is not ComfyZluda,\n                Options = [\"--directml\"],\n            },\n            new()\n            {\n                Name = \"Use CPU only\",\n                Type = LaunchOptionType.Bool,\n                InitialValue =\n                    !Compat.IsMacOS && !HardwareHelper.HasNvidiaGpu() && !HardwareHelper.HasAmdGpu(),\n                Options = [\"--cpu\"],\n            },\n            new()\n            {\n                Name = \"Cross Attention Method\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = \"--use-pytorch-cross-attention\",\n                Options =\n                [\n                    \"--use-split-cross-attention\",\n                    \"--use-quad-cross-attention\",\n                    \"--use-pytorch-cross-attention\",\n                    \"--use-sage-attention\",\n                ],\n            },\n            new()\n            {\n                Name = \"Force Floating Point Precision\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = Compat.IsMacOS ? \"--force-fp16\" : null,\n                Options = [\"--force-fp32\", \"--force-fp16\"],\n            },\n            new()\n            {\n                Name = \"VAE Precision\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--fp16-vae\", \"--fp32-vae\", \"--bf16-vae\"],\n            },\n            new()\n            {\n                Name = \"Disable Xformers\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = !HardwareHelper.HasNvidiaGpu(),\n                Options = [\"--disable-xformers\"],\n            },\n            new()\n            {\n                Name = \"Disable upcasting of attention\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--dont-upcast-attention\"],\n            },\n            new()\n            {\n                Name = \"Auto-Launch\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--auto-launch\"],\n            },\n            new()\n            {\n                Name = \"Enable Manager\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = true,\n                Options = [\"--enable-manager\"],\n            },\n            LaunchOptionDefinition.Extras,\n        ];\n\n    public override string MainBranch => \"master\";\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        [TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.DirectMl, TorchIndex.Rocm, TorchIndex.Mps];\n\n    public override List<ExtraPackageCommand> GetExtraCommands()\n    {\n        var commands = new List<ExtraPackageCommand>();\n\n        if (Compat.IsWindows && SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() is true)\n        {\n            commands.Add(\n                new ExtraPackageCommand\n                {\n                    CommandName = \"Install Triton and SageAttention\",\n                    Command = InstallTritonAndSageAttention,\n                }\n            );\n        }\n\n        if (!Compat.IsMacOS && SettingsManager.Settings.PreferredGpu?.ComputeCapabilityValue is >= 7.5m)\n        {\n            commands.Add(\n                new ExtraPackageCommand { CommandName = \"Install Nunchaku\", Command = InstallNunchaku }\n            );\n        }\n\n        return commands;\n    }\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        var torchIndex = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n        var gfxArch =\n            SettingsManager.Settings.PreferredGpu?.GetAmdGfxArch()\n            ?? HardwareHelper.GetWindowsRocmSupportedGpu()?.GetAmdGfxArch();\n\n        // Special case for Windows ROCm Nightly builds\n        if (\n            Compat.IsWindows\n            && !string.IsNullOrWhiteSpace(gfxArch)\n            && torchIndex is TorchIndex.Rocm\n            && options.PythonOptions.PythonVersion >= PyVersion.Parse(\"3.11.0\")\n        )\n        {\n            var config = new PipInstallConfig\n            {\n                RequirementsFilePaths = [\"requirements.txt\"],\n                ExtraPipArgs = [\"numpy<2\"],\n                SkipTorchInstall = true,\n                PostInstallPipArgs = [\"typing-extensions>=4.15.0\"],\n            };\n            await StandardPipInstallProcessAsync(\n                    venvRunner,\n                    options,\n                    installedPackage,\n                    config,\n                    onConsoleOutput,\n                    progress,\n                    cancellationToken\n                )\n                .ConfigureAwait(false);\n\n            progress?.Report(\n                new ProgressReport(-1f, \"Installing ROCm nightly torch...\", isIndeterminate: true)\n            );\n            var indexUrl = gfxArch switch\n            {\n                \"gfx1150\" => \"https://rocm.nightlies.amd.com/v2-staging/gfx1150\", // Strix/Gorgon Point\n                \"gfx1151\" => \"https://rocm.nightlies.amd.com/v2/gfx1151\", // Strix Halo\n                _ when gfxArch.StartsWith(\"gfx110\") => \"https://rocm.nightlies.amd.com/v2/gfx110X-all\",\n                _ when gfxArch.StartsWith(\"gfx120\") => \"https://rocm.nightlies.amd.com/v2/gfx120X-all\",\n                _ => throw new ArgumentOutOfRangeException(\n                    nameof(gfxArch),\n                    $\"Unsupported GFX Arch: {gfxArch}\"\n                ),\n            };\n\n            var torchPipArgs = new PipInstallArgs()\n                .AddArgs(\"--pre\", \"--upgrade\")\n                .WithTorch()\n                .WithTorchVision()\n                .WithTorchAudio()\n                .AddArgs(\"--index-url\", indexUrl);\n\n            await venvRunner.PipInstall(torchPipArgs, onConsoleOutput).ConfigureAwait(false);\n        }\n        else // Standard installation path for all other cases\n        {\n            var isLegacyNvidia =\n                torchIndex == TorchIndex.Cuda\n                && (\n                    SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu()\n                    ?? HardwareHelper.HasLegacyNvidiaGpu()\n                );\n\n            var config = new PipInstallConfig\n            {\n                RequirementsFilePaths = [\"requirements.txt\"],\n                ExtraPipArgs = [\"numpy<2\"],\n                TorchaudioVersion = \" \", // Request torchaudio without a specific version\n                CudaIndex = isLegacyNvidia ? \"cu126\" : \"cu130\",\n                RocmIndex = \"rocm7.1\",\n                UpgradePackages = true,\n                PostInstallPipArgs = [\"typing-extensions>=4.15.0\"],\n            };\n\n            await StandardPipInstallProcessAsync(\n                    venvRunner,\n                    options,\n                    installedPackage,\n                    config,\n                    onConsoleOutput,\n                    progress,\n                    cancellationToken\n                )\n                .ConfigureAwait(false);\n        }\n\n        try\n        {\n            var sageVersion = await venvRunner.PipShow(\"sageattention\").ConfigureAwait(false);\n            var torchVersion = await venvRunner.PipShow(\"torch\").ConfigureAwait(false);\n\n            if (torchVersion is not null && sageVersion is not null)\n            {\n                var version = torchVersion.Version;\n                var plusPos = version.IndexOf('+');\n                var index = plusPos >= 0 ? version[(plusPos + 1)..] : string.Empty;\n                var versionWithoutIndex = plusPos >= 0 ? version[..plusPos] : version;\n\n                if (\n                    !sageVersion.Version.Contains(index) || !sageVersion.Version.Contains(versionWithoutIndex)\n                )\n                {\n                    progress?.Report(\n                        new ProgressReport(-1f, \"Updating SageAttention...\", isIndeterminate: true)\n                    );\n\n                    var step = new InstallSageAttentionStep(\n                        downloadService,\n                        prerequisiteHelper,\n                        pyInstallationManager\n                    )\n                    {\n                        InstalledPackage = installedPackage,\n                        IsBlackwellGpu =\n                            SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu()\n                            ?? HardwareHelper.HasBlackwellGpu(),\n                        WorkingDirectory = installLocation,\n                        EnvironmentVariables = GetEnvVars(venvRunner.EnvironmentVariables),\n                    };\n\n                    await step.ExecuteAsync(progress).ConfigureAwait(false);\n                }\n            }\n        }\n        catch (Exception e)\n        {\n            Logger.Error(e, \"Failed to verify/update SageAttention after installation\");\n        }\n\n        // Install Comfy Manager (built-in to ComfyUI)\n        try\n        {\n            var managerRequirementsFile = Path.Combine(installLocation, \"manager_requirements.txt\");\n            if (File.Exists(managerRequirementsFile))\n            {\n                progress?.Report(\n                    new ProgressReport(-1f, \"Installing Comfy Manager requirements...\", isIndeterminate: true)\n                );\n\n                var pipArgs = new PipInstallArgs().AddArg(\"-r\").AddArg(managerRequirementsFile);\n                await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);\n\n                progress?.Report(\n                    new ProgressReport(-1f, \"Comfy Manager installed successfully\", isIndeterminate: true)\n                );\n            }\n        }\n        catch (Exception e)\n        {\n            Logger.Error(e, \"Failed to install Comfy Manager requirements\");\n        }\n\n        progress?.Report(new ProgressReport(1, \"Install complete\", isIndeterminate: false));\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        // Use the same Python version that was used for installation\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        VenvRunner.UpdateEnvironmentVariables(GetEnvVars);\n\n        // Check for old NVIDIA driver version with cu130 installations\n        var isNvidia = SettingsManager.Settings.PreferredGpu?.IsNvidia ?? HardwareHelper.HasNvidiaGpu();\n        var isLegacyNvidia =\n            SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() ?? HardwareHelper.HasLegacyNvidiaGpu();\n\n        if (isNvidia && !isLegacyNvidia)\n        {\n            var driverVersion = HardwareHelper.GetNvidiaDriverVersion();\n            if (driverVersion is not null && driverVersion.Major < 580)\n            {\n                // Check if torch is installed with cu130 index\n                var torchInfo = await VenvRunner.PipShow(\"torch\").ConfigureAwait(false);\n                if (torchInfo is not null)\n                {\n                    var version = torchInfo.Version;\n                    var plusPos = version.IndexOf('+');\n                    var torchIndex = plusPos >= 0 ? version[(plusPos + 1)..] : string.Empty;\n\n                    // Only warn if using cu130 (which requires driver 580+)\n                    if (torchIndex.Equals(\"cu130\", StringComparison.OrdinalIgnoreCase))\n                    {\n                        var warningMessage = $\"\"\"\n\n                            ============================================================\n                                            NVIDIA DRIVER WARNING\n                            ============================================================\n\n                            Your NVIDIA driver version ({driverVersion}) is older than\n                            the minimum required version (580.x) for CUDA 13.0 (cu130).\n\n                            This may cause ComfyUI to fail to start or experience issues.\n\n                            Recommended actions:\n                              1. Update your NVIDIA driver to version 580 or newer\n                              2. Or manually downgrade your torch version to use an\n                                 older torch index (e.g. cu128)\n\n                            ============================================================\n\n                            \"\"\";\n\n                        Logger.Warn(\n                            \"NVIDIA driver version {DriverVersion} is below 580.x minimum for cu130 (torch index: {TorchIndex})\",\n                            driverVersion,\n                            torchIndex\n                        );\n                        onConsoleOutput?.Invoke(ProcessOutput.FromStdErrLine(warningMessage));\n                        return;\n                    }\n                }\n            }\n        }\n\n        VenvRunner.RunDetached(\n            [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],\n            HandleConsoleOutput,\n            OnExit\n        );\n\n        return;\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (!s.Text.Contains(\"To see the GUI go to\", StringComparison.OrdinalIgnoreCase))\n                return;\n\n            var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n            var match = regex.Match(s.Text);\n            if (match.Success)\n            {\n                WebUrl = match.Value;\n            }\n            OnStartupComplete(WebUrl);\n        }\n    }\n\n    public override TorchIndex GetRecommendedTorchVersion()\n    {\n        var preferRocm =\n            (Compat.IsLinux && (SettingsManager.Settings.PreferredGpu?.IsAmd ?? HardwareHelper.PreferRocm()))\n            || (\n                Compat.IsWindows\n                && (\n                    SettingsManager.Settings.PreferredGpu?.IsWindowsRocmSupportedGpu()\n                    ?? HardwareHelper.HasWindowsRocmSupportedGpu()\n                )\n            );\n\n        if (AvailableTorchIndices.Contains(TorchIndex.Rocm) && preferRocm)\n        {\n            return TorchIndex.Rocm;\n        }\n\n        return base.GetRecommendedTorchVersion();\n    }\n\n    public override IPackageExtensionManager ExtensionManager =>\n        new ComfyExtensionManager(this, settingsManager);\n\n    private class ComfyExtensionManager(ComfyUI package, ISettingsManager settingsManager)\n        : GitPackageExtensionManager(package.PrerequisiteHelper)\n    {\n        public override string RelativeInstallDirectory => \"custom_nodes\";\n\n        public override IEnumerable<ExtensionManifest> DefaultManifests =>\n            [\n                \"https://cdn.jsdelivr.net/gh/ltdrdata/ComfyUI-Manager/custom-node-list.json\",\n                \"https://cdn.jsdelivr.net/gh/LykosAI/ComfyUI-Extensions-Index/custom-node-list.json\",\n            ];\n\n        public override async Task<IEnumerable<PackageExtension>> GetManifestExtensionsAsync(\n            ExtensionManifest manifest,\n            CancellationToken cancellationToken = default\n        )\n        {\n            try\n            {\n                // Get json\n                var content = await package\n                    .DownloadService.GetContentAsync(manifest.Uri.ToString(), cancellationToken)\n                    .ConfigureAwait(false);\n\n                // Parse json\n                var jsonManifest = JsonSerializer.Deserialize<ComfyExtensionManifest>(\n                    content,\n                    ComfyExtensionManifestSerializerContext.Default.Options\n                );\n\n                if (jsonManifest == null)\n                    return [];\n\n                var extensions = jsonManifest.GetPackageExtensions().ToList();\n                return extensions;\n            }\n            catch (Exception e)\n            {\n                Logger.Error(e, \"Failed to get package extensions\");\n                return [];\n            }\n        }\n\n        /// <inheritdoc />\n        public override async Task UpdateExtensionAsync(\n            InstalledPackageExtension installedExtension,\n            InstalledPackage installedPackage,\n            PackageExtensionVersion? version = null,\n            IProgress<ProgressReport>? progress = null,\n            CancellationToken cancellationToken = default\n        )\n        {\n            await base.UpdateExtensionAsync(\n                    installedExtension,\n                    installedPackage,\n                    version,\n                    progress,\n                    cancellationToken\n                )\n                .ConfigureAwait(false);\n\n            cancellationToken.ThrowIfCancellationRequested();\n\n            var installedDirs = installedExtension.Paths.OfType<DirectoryPath>().Where(dir => dir.Exists);\n\n            await PostInstallAsync(\n                    installedPackage,\n                    installedDirs,\n                    installedExtension.Definition!,\n                    progress,\n                    cancellationToken\n                )\n                .ConfigureAwait(false);\n        }\n\n        /// <inheritdoc />\n        public override async Task InstallExtensionAsync(\n            PackageExtension extension,\n            InstalledPackage installedPackage,\n            PackageExtensionVersion? version = null,\n            IProgress<ProgressReport>? progress = null,\n            CancellationToken cancellationToken = default\n        )\n        {\n            await base.InstallExtensionAsync(\n                    extension,\n                    installedPackage,\n                    version,\n                    progress,\n                    cancellationToken\n                )\n                .ConfigureAwait(false);\n\n            cancellationToken.ThrowIfCancellationRequested();\n\n            var cloneRoot = new DirectoryPath(installedPackage.FullPath!, RelativeInstallDirectory);\n\n            var installedDirs = extension\n                .Files.Select(uri => uri.Segments.LastOrDefault())\n                .Where(path => !string.IsNullOrEmpty(path))\n                .Select(path => cloneRoot.JoinDir(path!))\n                .Where(dir => dir.Exists);\n\n            await PostInstallAsync(installedPackage, installedDirs, extension, progress, cancellationToken)\n                .ConfigureAwait(false);\n        }\n\n        /// <summary>\n        /// Runs post install / update tasks (i.e. install.py, requirements.txt)\n        /// </summary>\n        private async Task PostInstallAsync(\n            InstalledPackage installedPackage,\n            IEnumerable<DirectoryPath> installedDirs,\n            PackageExtension extension,\n            IProgress<ProgressReport>? progress = null,\n            CancellationToken cancellationToken = default\n        )\n        {\n            // do pip installs\n            if (extension.Pip != null)\n            {\n                await using var venvRunner = await package\n                    .SetupVenvPure(\n                        installedPackage.FullPath!,\n                        pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)\n                    )\n                    .ConfigureAwait(false);\n\n                var pipArgs = new PipInstallArgs();\n                pipArgs = extension.Pip.Aggregate(pipArgs, (current, pip) => current.AddArg(pip));\n\n                await venvRunner\n                    .PipInstall(pipArgs, progress?.AsProcessOutputHandler())\n                    .ConfigureAwait(false);\n            }\n\n            foreach (var installedDir in installedDirs)\n            {\n                cancellationToken.ThrowIfCancellationRequested();\n\n                // Install requirements.txt if found\n                if (installedDir.JoinFile(\"requirements.txt\") is { Exists: true } requirementsFile)\n                {\n                    var requirementsContent = await requirementsFile\n                        .ReadAllTextAsync(cancellationToken)\n                        .ConfigureAwait(false);\n\n                    if (!string.IsNullOrWhiteSpace(requirementsContent))\n                    {\n                        progress?.Report(\n                            new ProgressReport(\n                                0f,\n                                $\"Installing requirements.txt for {installedDir.Name}\",\n                                isIndeterminate: true\n                            )\n                        );\n\n                        await using var venvRunner = await package\n                            .SetupVenvPure(\n                                installedPackage.FullPath!,\n                                pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)\n                            )\n                            .ConfigureAwait(false);\n\n                        var pipArgs = new PipInstallArgs().WithParsedFromRequirementsTxt(requirementsContent);\n\n                        await venvRunner\n                            .PipInstall(pipArgs, progress.AsProcessOutputHandler())\n                            .ConfigureAwait(false);\n\n                        progress?.Report(\n                            new ProgressReport(1f, $\"Installed requirements.txt for {installedDir.Name}\")\n                        );\n                    }\n                }\n\n                cancellationToken.ThrowIfCancellationRequested();\n\n                // Run install.py if found\n                if (installedDir.JoinFile(\"install.py\") is { Exists: true } installScript)\n                {\n                    progress?.Report(\n                        new ProgressReport(\n                            0f,\n                            $\"Running install.py for {installedDir.Name}\",\n                            isIndeterminate: true\n                        )\n                    );\n\n                    await using var venvRunner = await package\n                        .SetupVenvPure(\n                            installedPackage.FullPath!,\n                            pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)\n                        )\n                        .ConfigureAwait(false);\n\n                    venvRunner.WorkingDirectory = installScript.Directory;\n                    venvRunner.UpdateEnvironmentVariables(env =>\n                    {\n                        // set env vars for Impact Pack for Face Detailer\n                        env = env.SetItem(\"COMFYUI_PATH\", installedPackage.FullPath!);\n\n                        var modelPath =\n                            installedPackage.PreferredSharedFolderMethod == SharedFolderMethod.None\n                                ? Path.Combine(installedPackage.FullPath!, \"models\")\n                                : settingsManager.ModelsDirectory;\n\n                        env = env.SetItem(\"COMFYUI_MODEL_PATH\", modelPath);\n                        return env;\n                    });\n\n                    venvRunner.RunDetached([\"install.py\"], progress.AsProcessOutputHandler());\n\n                    await venvRunner.Process.WaitUntilOutputEOF(cancellationToken).ConfigureAwait(false);\n                    await venvRunner.Process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);\n\n                    if (venvRunner.Process.HasExited && venvRunner.Process.ExitCode != 0)\n                    {\n                        throw new ProcessException(\n                            $\"install.py for {installedDir.Name} exited with code {venvRunner.Process.ExitCode}\"\n                        );\n                    }\n\n                    progress?.Report(new ProgressReport(1f, $\"Ran launch.py for {installedDir.Name}\"));\n                }\n            }\n        }\n    }\n\n    private async Task InstallTritonAndSageAttention(InstalledPackage? installedPackage)\n    {\n        if (installedPackage?.FullPath is null)\n            return;\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            ModificationCompleteMessage = \"Triton and SageAttention installed successfully\",\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n\n        await runner\n            .ExecuteSteps(\n                [\n                    new ActionPackageStep(\n                        async progress =>\n                        {\n                            await using var venvRunner = await SetupVenvPure(\n                                    installedPackage.FullPath,\n                                    pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)\n                                )\n                                .ConfigureAwait(false);\n\n                            var gpuInfo =\n                                SettingsManager.Settings.PreferredGpu\n                                ?? HardwareHelper.IterGpuInfo().FirstOrDefault(x => x.IsNvidia);\n\n                            await PipWheelService\n                                .InstallTritonAsync(venvRunner, progress)\n                                .ConfigureAwait(false);\n                            await PipWheelService\n                                .InstallSageAttentionAsync(venvRunner, gpuInfo, progress)\n                                .ConfigureAwait(false);\n                        },\n                        \"Installing Triton and SageAttention\"\n                    ),\n                ]\n            )\n            .ConfigureAwait(false);\n\n        if (runner.Failed)\n            return;\n\n        await using var transaction = settingsManager.BeginTransaction();\n        var attentionOptions = transaction\n            .Settings.InstalledPackages.First(x => x.Id == installedPackage.Id)\n            .LaunchArgs?.Where(opt => opt.Name.Contains(\"attention\"));\n\n        if (attentionOptions is not null)\n        {\n            foreach (var option in attentionOptions)\n            {\n                option.OptionValue = false;\n            }\n        }\n\n        var sageAttention = transaction\n            .Settings.InstalledPackages.First(x => x.Id == installedPackage.Id)\n            .LaunchArgs?.FirstOrDefault(opt => opt.Name.Contains(\"sage-attention\"));\n\n        if (sageAttention is not null)\n        {\n            sageAttention.OptionValue = true;\n        }\n        else\n        {\n            transaction\n                .Settings.InstalledPackages.First(x => x.Id == installedPackage.Id)\n                .LaunchArgs?.Add(\n                    new LaunchOption\n                    {\n                        Name = \"--use-sage-attention\",\n                        Type = LaunchOptionType.Bool,\n                        OptionValue = true,\n                    }\n                );\n        }\n    }\n\n    private async Task InstallNunchaku(InstalledPackage? installedPackage)\n    {\n        if (installedPackage?.FullPath is null)\n            return;\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            ModificationCompleteMessage = \"Nunchaku installed successfully\",\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n\n        await runner\n            .ExecuteSteps(\n                [\n                    new ActionPackageStep(\n                        async progress =>\n                        {\n                            await using var venvRunner = await SetupVenvPure(\n                                    installedPackage.FullPath,\n                                    pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)\n                                )\n                                .ConfigureAwait(false);\n\n                            var gpuInfo =\n                                SettingsManager.Settings.PreferredGpu\n                                ?? HardwareHelper.IterGpuInfo().FirstOrDefault(x => x.IsNvidia || x.IsAmd);\n\n                            await PipWheelService\n                                .InstallNunchakuAsync(venvRunner, gpuInfo, progress)\n                                .ConfigureAwait(false);\n                        },\n                        \"Installing Nunchaku\"\n                    ),\n                ]\n            )\n            .ConfigureAwait(false);\n    }\n\n    private ImmutableDictionary<string, string> GetEnvVars(ImmutableDictionary<string, string> env)\n    {\n        // if we're not on windows or we don't have a windows rocm gpu, return original env\n        var hasRocmGpu =\n            SettingsManager.Settings.PreferredGpu?.IsWindowsRocmSupportedGpu()\n            ?? HardwareHelper.HasWindowsRocmSupportedGpu();\n\n        if (!Compat.IsWindows || !hasRocmGpu)\n            return env;\n\n        // set some experimental speed improving env vars for Windows ROCm\n        return env.SetItem(\"PYTORCH_TUNABLEOP_ENABLED\", \"1\")\n            .SetItem(\"MIOPEN_FIND_MODE\", \"2\")\n            .SetItem(\"TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL\", \"1\")\n            .SetItem(\"PYTORCH_ALLOC_CONF\", \"max_split_size_mb:6144,garbage_collection_threshold:0.8\") // greatly helps prevent GPU OOM and instability/driver timeouts/OS hard locks and decreases dependency on Tiled VAE at standard res's\n            .SetItem(\"COMFYUI_ENABLE_MIOPEN\", \"1\"); // re-enables \"cudnn\" in ComfyUI as it's needed for MiOpen to function properly\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/ComfyZluda.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, ComfyZluda>(Duplicate = DuplicateStrategy.Append)]\npublic class ComfyZluda(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : ComfyUI(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    private const string ZludaPatchDownloadUrl =\n        \"https://github.com/lshqqytiger/ZLUDA/releases/download/rel.5e717459179dc272b7d7d23391f0fad66c7459cf/ZLUDA-nightly-windows-rocm6-amd64.zip\";\n\n    private const string HipSdkExtensionDownloadUrl = \"https://cdn.lykos.ai/HIP-SDK-extension.7z\";\n    private const string VenvDirectoryName = \"venv\";\n\n    private Process? zludaProcess;\n\n    public override string Name => \"ComfyUI-Zluda\";\n    public override string DisplayName => \"ComfyUI-Zluda\";\n    public override string Author => \"patientx\";\n    public override string LicenseUrl => \"https://github.com/patientx/ComfyUI-Zluda/blob/master/LICENSE\";\n    public override string Blurb =>\n        \"Windows-only version of ComfyUI which uses ZLUDA to get better performance with AMD GPUs.\";\n    public override string Disclaimer =>\n        \"Prerequisite install may require admin privileges and a reboot. \"\n        + \"Visual Studio Build Tools for C++ Desktop Development will be installed automatically. \"\n        + \"AMD GPUs under the RX 6800 may require additional manual setup. \";\n    public override string LaunchCommand => Path.Combine(\"zluda\", \"zluda.exe\");\n\n    public override List<LaunchOptionDefinition> LaunchOptions\n    {\n        get\n        {\n            var options = new List<LaunchOptionDefinition>\n            {\n                new()\n                {\n                    Name = \"Cross Attention Method\",\n                    Type = LaunchOptionType.Bool,\n                    InitialValue = \"--use-quad-cross-attention\",\n                    Options =\n                    [\n                        \"--use-split-cross-attention\",\n                        \"--use-quad-cross-attention\",\n                        \"--use-pytorch-cross-attention\",\n                        \"--use-sage-attention\",\n                    ],\n                },\n                new()\n                {\n                    Name = \"Disable Async Offload\",\n                    Type = LaunchOptionType.Bool,\n                    InitialValue = true,\n                    Options = [\"--disable-async-offload\"],\n                },\n                new()\n                {\n                    Name = \"Disable Pinned Memory\",\n                    Type = LaunchOptionType.Bool,\n                    InitialValue = true,\n                    Options = [\"--disable-pinned-memory\"],\n                },\n                new()\n                {\n                    Name = \"Disable Smart Memory\",\n                    Type = LaunchOptionType.Bool,\n                    InitialValue = false,\n                    Options = [\"--disable-smart-memory\"],\n                },\n                new()\n                {\n                    Name = \"Disable Model/Node Caching\",\n                    Type = LaunchOptionType.Bool,\n                    InitialValue = false,\n                    Options = [\"--cache-none\"],\n                },\n            };\n\n            options.AddRange(base.LaunchOptions.Where(x => x.Name != \"Cross Attention Method\"));\n            return options;\n        }\n    }\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices => [TorchIndex.Zluda];\n\n    public override TorchIndex GetRecommendedTorchVersion() => TorchIndex.Zluda;\n\n    public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_11_13;\n\n    public override bool IsCompatible => HardwareHelper.PreferDirectMLOrZluda();\n\n    public override bool ShouldIgnoreReleases => true;\n\n    public override IEnumerable<PackagePrerequisite> Prerequisites =>\n        base.Prerequisites.Concat([PackagePrerequisite.HipSdk, PackagePrerequisite.VcBuildTools]);\n\n    public override bool InstallRequiresAdmin => true;\n    public override string AdminRequiredReason =>\n        \"HIP SDK and Visual Studio Build Tools installation, as well as (if applicable) ROCmLibs patching, require admin privileges for accessing files in the Program Files directory. This may take several minutes to complete.\";\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (!PrerequisiteHelper.IsHipSdkInstalled) // for updates\n        {\n            progress?.Report(new ProgressReport(-1, \"Installing HIP SDK 6.4\", isIndeterminate: true));\n            await PrerequisiteHelper\n                .InstallPackageRequirements(this, options.PythonOptions.PythonVersion, progress)\n                .ConfigureAwait(false);\n        }\n\n        if (options.IsUpdate)\n        {\n            return;\n        }\n\n        progress?.Report(new ProgressReport(-1, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        var installNBatPath = new FilePath(installLocation, \"install-n.bat\");\n        var newInstallBatPath = new FilePath(installLocation, \"install-sm.bat\");\n\n        var installNText = await installNBatPath.ReadAllTextAsync(cancellationToken).ConfigureAwait(false);\n        var installNLines = installNText.Split(Environment.NewLine);\n        var cutoffIndex = Array.FindIndex(installNLines, line => line.Contains(\"Installation is completed\"));\n\n        IEnumerable<string> filtered = installNLines;\n        if (cutoffIndex >= 0)\n        {\n            filtered = installNLines.Take(cutoffIndex);\n        }\n\n        newInstallBatPath.Create();\n        await newInstallBatPath\n            .WriteAllTextAsync(string.Join(Environment.NewLine, filtered), cancellationToken)\n            .ConfigureAwait(false);\n\n        var installProcess = ProcessRunner.StartAnsiProcess(\n            newInstallBatPath,\n            [],\n            installLocation,\n            onConsoleOutput,\n            GetEnvVars(true)\n        );\n        await installProcess.WaitForExitAsync(cancellationToken).ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(1, \"Installed Successfully\", isIndeterminate: false));\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (!PrerequisiteHelper.IsHipSdkInstalled)\n        {\n            throw new MissingPrerequisiteException(\n                \"HIP SDK\",\n                \"Your package has not yet been upgraded to use HIP SDK 6.4. To continue, please update this package or select \\\"Change Version\\\" from the 3-dots menu to have it upgraded automatically for you\"\n            );\n        }\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        var zludaPath = Path.Combine(installLocation, LaunchCommand);\n        ProcessArgs args = [\"--\", VenvRunner.PythonPath.ToString(), \"main.py\", .. options.Arguments];\n        zludaProcess = ProcessRunner.StartAnsiProcess(\n            zludaPath,\n            args,\n            installLocation,\n            HandleConsoleOutput,\n            GetEnvVars(false)\n        );\n\n        return;\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (!s.Text.Contains(\"To see the GUI go to\", StringComparison.OrdinalIgnoreCase))\n                return;\n\n            var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n            var match = regex.Match(s.Text);\n            if (match.Success)\n            {\n                WebUrl = match.Value;\n            }\n            OnStartupComplete(WebUrl);\n        }\n    }\n\n    public override async Task WaitForShutdown()\n    {\n        if (zludaProcess is { HasExited: false })\n        {\n            zludaProcess.Kill(true);\n            try\n            {\n                await zludaProcess\n                    .WaitForExitAsync(new CancellationTokenSource(5000).Token)\n                    .ConfigureAwait(false);\n            }\n            catch (OperationCanceledException e)\n            {\n                Console.WriteLine(e);\n            }\n        }\n\n        zludaProcess = null;\n        GC.SuppressFinalize(this);\n    }\n\n    private Dictionary<string, string> GetEnvVars(bool isInstall)\n    {\n        var portableGitBin = new DirectoryPath(PrerequisiteHelper.GitBinPath);\n        var hipPath = Path.Combine(\n            Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),\n            \"AMD\",\n            \"ROCm\",\n            \"6.4\"\n        );\n        var hipBinPath = Path.Combine(hipPath, \"bin\");\n        var envVars = new Dictionary<string, string>\n        {\n            [\"ZLUDA_COMGR_LOG_LEVEL\"] = \"1\",\n            [\"HIP_PATH\"] = hipPath,\n            [\"HIP_PATH_64\"] = hipPath,\n            [\"GIT\"] = portableGitBin.JoinFile(\"git.exe\"),\n        };\n\n        if (isInstall)\n        {\n            envVars[\"VIRTUAL_ENV\"] = VenvDirectoryName;\n        }\n\n        if (envVars.TryGetValue(\"PATH\", out var pathValue))\n        {\n            envVars[\"PATH\"] = Compat.GetEnvPathWithExtensions(hipBinPath, portableGitBin, pathValue);\n        }\n        else\n        {\n            envVars[\"PATH\"] = Compat.GetEnvPathWithExtensions(hipBinPath, portableGitBin);\n        }\n\n        if (isInstall)\n            return envVars;\n\n        envVars[\"FLASH_ATTENTION_TRITON_AMD_ENABLE\"] = \"TRUE\";\n        envVars[\"MIOPEN_FIND_MODE\"] = \"2\";\n        envVars[\"MIOPEN_LOG_LEVEL\"] = \"3\";\n\n        var gfxArch = PrerequisiteHelper.GetGfxArchFromAmdGpuName();\n        if (!string.IsNullOrWhiteSpace(gfxArch))\n        {\n            envVars[\"TRITON_OVERRIDE_ARCH\"] = gfxArch;\n        }\n\n        envVars.Update(settingsManager.Settings.EnvironmentVariables);\n\n        return envVars;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Config/ConfigDefaultType.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages.Config;\n\npublic enum ConfigDefaultType\n{\n    /// <summary>\n    /// Set as SharedFolderLayout.TargetRelativePaths.\n    /// </summary>\n    TargetRelativePaths,\n\n    /// <summary>\n    /// Clear the root key when defaulting.\n    /// </summary>\n    ClearRoot,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Config/ConfigFileType.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages.Config;\n\npublic enum ConfigFileType\n{\n    Json,\n    Yaml,\n    Fds // Frenetic Data Syntax\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Config/ConfigSharingOptions.cs",
    "content": "﻿using System.Text.Json;\n\nnamespace StabilityMatrix.Core.Models.Packages.Config;\n\n// Options might need expansion later if format-specific settings are required\npublic record ConfigSharingOptions\n{\n    public static ConfigSharingOptions Default { get; } = new();\n\n    // For JSON:\n    public JsonSerializerOptions JsonSerializerOptions { get; set; } = new() { WriteIndented = true };\n\n    // For JSON/YAML: Write single paths as arrays?\n    public bool AlwaysWriteArray { get; set; } = false;\n\n    // For YAML/FDS: Key under which to store SM paths (e.g., \"stability_matrix\")\n    public string? RootKey { get; set; }\n\n    // Do we want to clear the root key / set to relative paths when clearing?\n    public ConfigDefaultType ConfigDefaultType { get; set; } = ConfigDefaultType.TargetRelativePaths;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Config/FdsConfigSharingStrategy.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Text;\nusing System.Text.Json.Nodes;\nusing FreneticUtilities.FreneticDataSyntax;\n\nnamespace StabilityMatrix.Core.Models.Packages.Config;\n\npublic class FdsConfigSharingStrategy : IConfigSharingStrategy\n{\n    public async Task UpdateAndWriteAsync(\n        Stream configStream,\n        SharedFolderLayout layout,\n        Func<SharedFolderLayoutRule, IEnumerable<string>> pathsSelector,\n        IEnumerable<string> clearPaths,\n        ConfigSharingOptions options,\n        CancellationToken cancellationToken = default\n    )\n    {\n        FDSSection rootSection;\n        var initialPosition = configStream.Position;\n        var isEmpty = configStream.Length - initialPosition == 0;\n\n        if (!isEmpty)\n        {\n            try\n            {\n                // FDSUtility reads from the current position\n                using var reader = new StreamReader(configStream, leaveOpen: true);\n                var fdsContent = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);\n                rootSection = new FDSSection(fdsContent);\n            }\n            catch (Exception ex) // FDSUtility might throw various exceptions on parse errors\n            {\n                System.Diagnostics.Debug.WriteLine(\n                    $\"Error deserializing FDS config: {ex.Message}. Treating as new.\"\n                );\n                rootSection = new FDSSection();\n                isEmpty = true;\n            }\n        }\n        else\n        {\n            rootSection = new FDSSection();\n        }\n\n        // Debug.WriteLine($\"-- Current Fds --\\n\\n{rootSection.SaveToString()}\");\n\n        UpdateFdsConfig(layout, rootSection, pathsSelector, clearPaths, options);\n\n        // Reset stream to original position before writing\n        configStream.Seek(initialPosition, SeekOrigin.Begin);\n        // Truncate the stream\n        configStream.SetLength(initialPosition + 0);\n\n        // Save using a StreamWriter to control encoding and leave stream open\n        // Use BOM-less UTF-8 encoding !! (FSD not UTF-8 BOM compatible)\n        var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);\n        await using (var writer = new StreamWriter(configStream, encoding, leaveOpen: true))\n        {\n            // Debug.WriteLine($\"-- Saved Fds: --\\n\\n{rootSection.SaveToString()}\");\n\n            await writer\n                .WriteAsync(rootSection.SaveToString().AsMemory(), cancellationToken)\n                .ConfigureAwait(false);\n            await writer.FlushAsync(cancellationToken).ConfigureAwait(false); // Ensure content is written\n        }\n        await configStream.FlushAsync(cancellationToken).ConfigureAwait(false); // Flush the underlying stream\n    }\n\n    private static void UpdateFdsConfig(\n        SharedFolderLayout layout,\n        FDSSection rootSection,\n        Func<SharedFolderLayoutRule, IEnumerable<string>> pathsSelector,\n        IEnumerable<string> clearPaths,\n        ConfigSharingOptions options\n    )\n    {\n        var rulesByConfigPath = layout.GetRulesByConfigPath();\n\n        // SwarmUI typically stores paths under a \"Paths\" section\n        // Get or create the Paths section\n        var pathsSection = rootSection.GetSection(\"Paths\");\n        if (pathsSection is null)\n        {\n            pathsSection = new FDSSection();\n            rootSection.Set(\"Paths\", pathsSection); // Add Paths section to the root\n        }\n\n        // Keep track of keys managed by the layout to remove old ones\n        var allRuleKeys = rulesByConfigPath.Keys.ToHashSet();\n        var currentKeysInPathsSection = pathsSection.GetRootKeys(); // Assuming FDS has a way to list keys\n\n        foreach (var (configPath, rule) in rulesByConfigPath)\n        {\n            var paths = pathsSelector(rule).ToArray();\n\n            // Normalize paths for FDS - likely prefers native OS slashes or forward slashes\n            var normalizedPaths = paths.Select(p => p.Replace('/', Path.DirectorySeparatorChar)).ToList();\n\n            if (normalizedPaths.Count > 0)\n            {\n                // FDS lists are separated by semicolon\n                pathsSection.Set(configPath, string.Join(';', normalizedPaths));\n\n                // If FDS supports lists explicitly (e.g., via SetList), use that:\n                // pathsSection.SetList(configPath, normalizedPaths);\n            }\n            else\n            {\n                // No paths for this rule, remove the key\n                // pathsSection.Remove(configPath); // Assuming Remove method exists\n            }\n        }\n\n        // Remove any keys in the Paths section that are no longer defined by any rule\n        /*foreach (var existingKey in currentKeysInPathsSection)\n        {\n            if (!allRuleKeys.Contains(existingKey))\n            {\n                pathsSection.Remove(existingKey);\n            }\n        }*/\n\n        /*// If the Paths section is not empty, add/update it in the root\n        if (pathsSection.GetRootKeys().Any()) // Check if the section has content\n        {\n            rootSection.Set(\"Paths\", pathsSection);\n            // rootSection.SetSection(\"Paths\", pathsSection);\n        }\n        else // Otherwise, remove the empty Paths section from the root\n        {\n            rootSection.Remove(\"Paths\"); // Assuming Remove method exists for sections too\n        }*/\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Config/IConfigSharingStrategy.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages.Config;\n\npublic interface IConfigSharingStrategy\n{\n    /// <summary>\n    /// Reads the config stream, updates paths based on the layout and selector, and writes back to the stream.\n    /// </summary>\n    Task UpdateAndWriteAsync(\n        Stream configStream,\n        SharedFolderLayout layout,\n        Func<SharedFolderLayoutRule, IEnumerable<string>> pathsSelector,\n        IEnumerable<string> clearPaths,\n        ConfigSharingOptions options,\n        CancellationToken cancellationToken = default\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Config/JsonConfigSharingStrategy.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Nodes;\n\nnamespace StabilityMatrix.Core.Models.Packages.Config;\n\npublic class JsonConfigSharingStrategy : IConfigSharingStrategy\n{\n    public async Task UpdateAndWriteAsync(\n        Stream configStream,\n        SharedFolderLayout layout,\n        Func<SharedFolderLayoutRule, IEnumerable<string>> pathsSelector,\n        IEnumerable<string> clearPaths,\n        ConfigSharingOptions options,\n        CancellationToken cancellationToken = default\n    )\n    {\n        JsonObject jsonNode;\n        var initialPosition = configStream.Position;\n        var isEmpty = configStream.Length - initialPosition == 0;\n\n        if (isEmpty)\n        {\n            jsonNode = new JsonObject();\n        }\n        else\n        {\n            try\n            {\n                // Ensure we read from the current position, respecting potential BOMs etc.\n                jsonNode =\n                    await JsonSerializer\n                        .DeserializeAsync<JsonObject>(\n                            configStream,\n                            options.JsonSerializerOptions,\n                            cancellationToken\n                        )\n                        .ConfigureAwait(false) ?? new JsonObject();\n            }\n            catch (JsonException ex)\n            {\n                // Handle cases where the file might exist but be invalid JSON\n                // Log the error, maybe throw a specific exception or return default\n                // For now, we'll treat it as empty/new\n                System.Diagnostics.Debug.WriteLine(\n                    $\"Error deserializing JSON config: {ex.Message}. Treating as new.\"\n                );\n                jsonNode = new JsonObject();\n                isEmpty = true; // Ensure we overwrite if deserialization failed\n            }\n        }\n\n        UpdateJsonConfig(layout, jsonNode, pathsSelector, clearPaths, options);\n\n        // Reset stream to original position (or beginning if new/failed) before writing\n        configStream.Seek(initialPosition, SeekOrigin.Begin);\n        // Truncate the stream in case the new content is shorter\n        configStream.SetLength(initialPosition + 0); // Truncate from the original position onwards\n\n        await JsonSerializer\n            .SerializeAsync(configStream, jsonNode, options.JsonSerializerOptions, cancellationToken)\n            .ConfigureAwait(false);\n        await configStream.FlushAsync(cancellationToken).ConfigureAwait(false);\n    }\n\n    private static void UpdateJsonConfig(\n        SharedFolderLayout layout,\n        JsonObject rootNode, // Changed parameter name for clarity\n        Func<SharedFolderLayoutRule, IEnumerable<string>> pathsSelector,\n        IEnumerable<string> clearPaths,\n        ConfigSharingOptions options\n    )\n    {\n        var rulesByConfigPath = layout.GetRulesByConfigPath();\n        var allRuleConfigPaths = rulesByConfigPath.Keys.ToHashSet(); // For cleanup\n\n        foreach (var (configPath, rule) in rulesByConfigPath)\n        {\n            var paths = pathsSelector(rule).ToArray();\n            var normalizedPaths = paths.Select(p => p.Replace('\\\\', '/')).ToArray();\n\n            JsonNode? valueNode = null;\n            if (normalizedPaths.Length > 1 || options.AlwaysWriteArray)\n            {\n                valueNode = new JsonArray(\n                    normalizedPaths.Select(p => JsonValue.Create(p)).OfType<JsonNode>().ToArray()\n                );\n            }\n            else if (normalizedPaths.Length == 1)\n            {\n                valueNode = JsonValue.Create(normalizedPaths[0]);\n            }\n\n            SetJsonValue(rootNode, configPath, valueNode); // Use helper to set/remove value\n        }\n\n        // Optional: Cleanup - Remove keys defined in layout but now empty?\n        // This might be complex if paths overlap. Current SetJsonValue(..., null) handles removal.\n        // We might need a separate cleanup pass if strictly necessary.\n    }\n\n    private static void SetJsonValue(JsonObject root, string dottedPath, JsonNode? value)\n    {\n        var segments = dottedPath.Split('.');\n        JsonObject currentNode = root;\n\n        // Traverse or create nodes up to the parent of the target\n        for (int i = 0; i < segments.Length - 1; i++)\n        {\n            var segment = segments[i];\n            if (\n                !currentNode.TryGetPropertyValue(segment, out var nextNode)\n                || nextNode is not JsonObject nextObj\n            )\n            {\n                // If node doesn't exist or isn't an object, create it (overwriting if necessary)\n                nextObj = new JsonObject();\n                currentNode[segment] = nextObj;\n            }\n            currentNode = nextObj;\n        }\n\n        var finalSegment = segments[^1]; // Get the last segment (the key name)\n\n        if (value != null)\n        {\n            // Set or replace the value\n            currentNode[finalSegment] = value.DeepClone(); // Use DeepClone to avoid node reuse issues\n        }\n        else\n        {\n            // Remove the key if value is null\n            currentNode.Remove(finalSegment);\n            // Optional: Clean up empty parent nodes recursively if desired (more complex)\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Config/YamlConfigSharingStrategy.cs",
    "content": "﻿using YamlDotNet.Core;\nusing YamlDotNet.RepresentationModel;\nusing YamlDotNet.Serialization;\nusing YamlDotNet.Serialization.NamingConventions;\n\nnamespace StabilityMatrix.Core.Models.Packages.Config;\n\npublic class YamlConfigSharingStrategy : IConfigSharingStrategy\n{\n    public async Task UpdateAndWriteAsync(\n        Stream configStream,\n        SharedFolderLayout layout,\n        Func<SharedFolderLayoutRule, IEnumerable<string>> pathsSelector,\n        IEnumerable<string> clearPaths,\n        ConfigSharingOptions options,\n        CancellationToken cancellationToken = default\n    )\n    {\n        YamlMappingNode rootNode;\n        YamlStream yamlStream = [];\n        var initialPosition = configStream.Position;\n        var isEmpty = configStream.Length - initialPosition == 0;\n\n        if (!isEmpty)\n        {\n            try\n            {\n                using var reader = new StreamReader(configStream, leaveOpen: true);\n                yamlStream.Load(reader); // Load existing YAML\n                if (\n                    yamlStream.Documents.Count > 0\n                    && yamlStream.Documents[0].RootNode is YamlMappingNode mapping\n                )\n                {\n                    rootNode = mapping;\n                }\n                else\n                {\n                    // File exists but isn't a valid mapping node at the root, start fresh\n                    System.Diagnostics.Debug.WriteLine(\n                        $\"YAML config exists but is not a mapping node. Treating as new.\"\n                    );\n                    rootNode = [];\n                    yamlStream = new YamlStream(new YamlDocument(rootNode)); // Reset stream content\n                    isEmpty = true;\n                }\n            }\n            catch (YamlException ex)\n            {\n                // Handle cases where the file might exist but be invalid YAML\n                System.Diagnostics.Debug.WriteLine(\n                    $\"Error deserializing YAML config: {ex.Message}. Treating as new.\"\n                );\n                rootNode = [];\n                yamlStream = new YamlStream(new YamlDocument(rootNode)); // Reset stream content\n                isEmpty = true;\n            }\n        }\n        else\n        {\n            // Stream is empty, create new structure\n            rootNode = [];\n            yamlStream.Add(new YamlDocument(rootNode));\n        }\n\n        UpdateYamlConfig(layout, rootNode, pathsSelector, clearPaths, options);\n\n        // Reset stream to original position (or beginning if new/failed) before writing\n        configStream.Seek(initialPosition, SeekOrigin.Begin);\n        // Truncate the stream in case the new content is shorter\n        configStream.SetLength(initialPosition + 0);\n\n        // Use StreamWriter to write back to the original stream\n        // Use default encoding (UTF8 without BOM is common for YAML)\n        await using (var writer = new StreamWriter(configStream, leaveOpen: true))\n        {\n            // Configure serializer for better readability if desired\n            var serializer = new SerializerBuilder()\n                .WithNamingConvention(UnderscoredNamingConvention.Instance) // Common for ComfyUI paths\n                .WithDefaultScalarStyle(ScalarStyle.Literal)\n                .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults) // Optional: omit nulls/defaults\n                .Build();\n            serializer.Serialize(writer, yamlStream.Documents[0].RootNode); // Serialize the modified root node\n            await writer.FlushAsync(cancellationToken).ConfigureAwait(false); // Ensure content is written to stream\n        }\n        await configStream.FlushAsync(cancellationToken).ConfigureAwait(false); // Flush the underlying stream\n    }\n\n    private static void UpdateYamlConfig(\n        SharedFolderLayout layout,\n        YamlMappingNode rootNode,\n        Func<SharedFolderLayoutRule, IEnumerable<string>> pathsSelector,\n        IEnumerable<string> clearPaths,\n        ConfigSharingOptions options\n    )\n    {\n        var rulesByConfigPath = layout.GetRulesByConfigPath();\n        YamlNode currentNode = rootNode; // Start at the actual root\n\n        // Handle RootKey (like stability_matrix) if specified\n        if (!string.IsNullOrEmpty(options.RootKey))\n        {\n            var rootKeyNode = new YamlScalarNode(options.RootKey);\n            if (\n                !rootNode.Children.TryGetValue(rootKeyNode, out var subNode)\n                || subNode is not YamlMappingNode subMapping\n            )\n            {\n                if (subNode != null)\n                    rootNode.Children.Remove(rootKeyNode); // Remove if exists but wrong type\n                subMapping = [];\n                rootNode.Add(rootKeyNode, subMapping);\n            }\n            currentNode = subMapping; // Operate within the specified RootKey node\n        }\n\n        if (currentNode is not YamlMappingNode writableNode)\n        {\n            // This should not happen if RootKey logic is correct, but handle defensively\n            System.Diagnostics.Debug.WriteLine($\"Error: Target node for YAML updates is not a mapping node.\");\n            return;\n        }\n\n        foreach (var (configPath, rule) in rulesByConfigPath)\n        {\n            var paths = pathsSelector(rule).ToArray();\n            var normalizedPaths = paths.Select(p => p.Replace('\\\\', '/')).ToArray();\n\n            YamlNode? valueNode = null;\n            if (normalizedPaths.Length > 0)\n            {\n                // Use Sequence for multiple paths\n                /*valueNode = new YamlSequenceNode(\n                    normalizedPaths.Select(p => new YamlScalarNode(p)).Cast<YamlNode>()\n                );*/\n                // --- Multi-line literal scalar (ComfyUI default) ---\n                var multiLinePath = string.Join(\"\\n\", normalizedPaths);\n                valueNode = new YamlScalarNode(multiLinePath) { Style = ScalarStyle.Literal };\n            }\n\n            SetYamlValue(writableNode, configPath, valueNode); // Use helper\n        }\n\n        // Clear specified paths\n        foreach (var clearPath in clearPaths)\n        {\n            SetYamlValue(rootNode, clearPath, null); // Note we use root node here instead\n        }\n\n        // Optional: Cleanup empty nodes after setting values (could be complex)\n    }\n\n    /*private static void UpdateYamlConfig(\n        SharedFolderLayout layout,\n        YamlMappingNode rootNode,\n        Func<SharedFolderLayoutRule, IEnumerable<string>> pathsSelector,\n        ConfigSharingOptions options\n    )\n    {\n        var rulesByConfigPath = layout.GetRulesByConfigPath();\n        var smKeyNode = new YamlScalarNode(options.RootKey);\n\n        // Find or create the root key node (e.g., \"stability_matrix:\")\n        if (\n            !rootNode.Children.TryGetValue(smKeyNode, out var smPathsNode)\n            || smPathsNode is not YamlMappingNode smPathsMapping\n        )\n        {\n            // If it exists but isn't a mapping, remove it first (or handle error)\n            if (smPathsNode != null)\n            {\n                rootNode.Children.Remove(smKeyNode);\n            }\n            smPathsMapping = new YamlMappingNode();\n            rootNode.Add(smKeyNode, smPathsMapping);\n        }\n\n        // Get all keys defined in the layout rules to manage removal later\n        var allRuleKeys = rulesByConfigPath.Keys.Select(k => new YamlScalarNode(k)).ToHashSet();\n        var currentKeysInSmNode = smPathsMapping.Children.Keys.ToHashSet();\n\n        foreach (var (configPath, rule) in rulesByConfigPath)\n        {\n            var paths = pathsSelector(rule).ToArray();\n            var keyNode = new YamlScalarNode(configPath);\n\n            if (paths.Length > 0)\n            {\n                // Represent multiple paths as a YAML sequence (list) for clarity\n                // Normalize paths - YAML generally prefers forward slashes\n                var normalizedPaths = paths\n                    .Select(p => new YamlScalarNode(p.Replace('\\\\', '/')))\n                    .Cast<YamlNode>()\n                    .ToList();\n                smPathsMapping.Children[keyNode] = new YamlSequenceNode(normalizedPaths);\n\n                // --- Alternatively, represent as multi-line literal scalar (like ComfyUI default) ---\n                // var multiLinePath = string.Join(\"\\n\", paths.Select(p => p.Replace('\\\\', '/')));\n                // var valueNode = new YamlScalarNode(multiLinePath) { Style = ScalarStyle.Literal };\n                // smPathsMapping.Children[keyNode] = valueNode;\n                // ---------------------------------------------------------------------------------\n            }\n            else\n            {\n                // No paths for this rule, remove the key from the SM node\n                smPathsMapping.Children.Remove(keyNode);\n            }\n        }\n\n        // Remove any keys under the SM node that are no longer defined by any rule\n        foreach (var existingKey in currentKeysInSmNode)\n        {\n            if (!allRuleKeys.Any(ruleKey => ruleKey.Value == existingKey.ToString()))\n            {\n                smPathsMapping.Children.Remove(existingKey);\n            }\n        }\n\n        // If the SM node becomes empty, remove it entirely\n        if (smPathsMapping.Children.Count == 0)\n        {\n            rootNode.Children.Remove(smKeyNode);\n        }\n    }*/\n\n    private static void SetYamlValue(YamlMappingNode rootMapping, string dottedPath, YamlNode? value)\n    {\n        var segments = dottedPath.Split('.');\n        var currentMapping = rootMapping;\n\n        // Traverse or create nodes up to the parent of the target\n        for (var i = 0; i < segments.Length - 1; i++)\n        {\n            var segmentNode = new YamlScalarNode(segments[i]);\n            if (\n                !currentMapping.Children.TryGetValue(segmentNode, out var nextNode)\n                || nextNode is not YamlMappingNode nextMapping\n            )\n            {\n                // If node doesn't exist or isn't a mapping, create it\n                if (nextNode != null)\n                    currentMapping.Children.Remove(segmentNode); // Remove if wrong type\n                nextMapping = [];\n                currentMapping.Add(segmentNode, nextMapping);\n            }\n            currentMapping = nextMapping;\n        }\n\n        var finalSegmentNode = new YamlScalarNode(segments[^1]);\n\n        if (value != null)\n        {\n            // Set or replace the value\n            currentMapping.Children[finalSegmentNode] = value;\n        }\n        else\n        {\n            // Remove the key if value is null\n            currentMapping.Children.Remove(finalSegmentNode);\n            // Optional: Cleanup empty parent nodes recursively (more complex)\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/DankDiffusion.cs",
    "content": "﻿using StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\npublic class DankDiffusion : BaseGitPackage\n{\n    public DankDiffusion(\n        IGithubApiCache githubApi,\n        ISettingsManager settingsManager,\n        IDownloadService downloadService,\n        IPrerequisiteHelper prerequisiteHelper,\n        IPyInstallationManager pyInstallationManager,\n        IPipWheelService pipWheelService\n    )\n        : base(\n            githubApi,\n            settingsManager,\n            downloadService,\n            prerequisiteHelper,\n            pyInstallationManager,\n            pipWheelService\n        ) { }\n\n    public override string Name => \"dank-diffusion\";\n    public override string DisplayName { get; set; } = \"Dank Diffusion\";\n    public override string Author => \"mohnjiles\";\n    public override string LicenseType => \"AGPL-3.0\";\n    public override string LicenseUrl => \"https://github.com/LykosAI/StabilityMatrix/blob/main/LICENSE\";\n    public override string Blurb => \"A dank interface for diffusion\";\n    public override string LaunchCommand => \"test\";\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;\n\n    public override IReadOnlyDictionary<string, string> ExtraLaunchCommands =>\n        new Dictionary<string, string> { [\"test-config\"] = \"test-config\" };\n\n    public override Uri PreviewImageUri { get; }\n\n    public override string OutputFolderName { get; }\n    public override PackageDifficulty InstallerSortOrder { get; }\n\n    public override Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        throw new NotImplementedException();\n    }\n\n    public override Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        throw new NotImplementedException();\n    }\n\n    public override Task SetupModelFolders(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    )\n    {\n        throw new NotImplementedException();\n    }\n\n    public override Task UpdateModelFolders(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    )\n    {\n        throw new NotImplementedException();\n    }\n\n    public override Task RemoveModelFolderLinks(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    )\n    {\n        throw new NotImplementedException();\n    }\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices { get; }\n\n    public override List<LaunchOptionDefinition> LaunchOptions { get; }\n\n    public override Dictionary<SharedFolderType, IReadOnlyList<string>>? SharedFolders { get; }\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders { get; }\n\n    public override string MainBranch { get; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Extensions/A1111ExtensionManifest.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Packages.Extensions;\n\npublic record A1111ExtensionManifest\n{\n    public required IEnumerable<ManifestEntry> Extensions { get; init; }\n\n    public IEnumerable<PackageExtension> GetPackageExtensions()\n    {\n        return Extensions.Select(\n            x =>\n                new PackageExtension\n                {\n                    Author = x.FullName?.Split('/').FirstOrDefault() ?? \"Unknown\",\n                    Title = x.Name,\n                    Reference = x.Url,\n                    Files = [x.Url],\n                    Description = x.Description,\n                    InstallType = \"git-clone\"\n                }\n        );\n    }\n\n    public record ManifestEntry\n    {\n        public string? FullName { get; init; }\n\n        public required string Name { get; init; }\n\n        public required Uri Url { get; init; }\n\n        public string? Description { get; init; }\n    }\n}\n\n[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)]\n[JsonSerializable(typeof(A1111ExtensionManifest))]\ninternal partial class A1111ExtensionManifestSerializerContext : JsonSerializerContext;\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Extensions/ComfyExtensionManifest.cs",
    "content": "using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Packages.Extensions;\n\npublic record ComfyExtensionManifest\n{\n    public required IEnumerable<ManifestEntry> CustomNodes { get; init; }\n\n    public IEnumerable<PackageExtension> GetPackageExtensions()\n    {\n        return CustomNodes.Select(\n            x =>\n                new PackageExtension\n                {\n                    Author = x.Author,\n                    Title = x.Title,\n                    Reference = x.Reference,\n                    Files = x.Files,\n                    Pip = x.Pip,\n                    Description = x.Description,\n                    InstallType = x.InstallType\n                }\n        );\n    }\n\n    public record ManifestEntry\n    {\n        public required string Author { get; init; }\n\n        public required string Title { get; init; }\n\n        public required Uri Reference { get; init; }\n\n        public required IEnumerable<Uri> Files { get; init; }\n\n        public IEnumerable<string>? Pip { get; init; }\n\n        public string? Description { get; init; }\n\n        public string? InstallType { get; init; }\n    }\n}\n\n[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)]\n[JsonSerializable(typeof(ComfyExtensionManifest))]\ninternal partial class ComfyExtensionManifestSerializerContext : JsonSerializerContext;\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Extensions/ExtensionManifest.cs",
    "content": "namespace StabilityMatrix.Core.Models.Packages.Extensions;\n\npublic record ExtensionManifest(Uri Uri)\n{\n    public static implicit operator ExtensionManifest(string uri) => new(new Uri(uri, UriKind.Absolute));\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Extensions/ExtensionPack.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages.Extensions;\n\npublic class ExtensionPack\n{\n    public required string Name { get; set; }\n    public required string PackageType { get; set; }\n    public List<SavedPackageExtension> Extensions { get; set; } = [];\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Extensions/ExtensionSpecifier.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text.RegularExpressions;\nusing JetBrains.Annotations;\nusing Semver;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Models.Packages.Extensions;\n\n/// <summary>\n/// Extension specifier with optional version constraints.\n/// </summary>\n[PublicAPI]\npublic partial class ExtensionSpecifier\n{\n    public required string Name { get; init; }\n\n    public string? Constraint { get; init; }\n\n    public string? Version { get; init; }\n\n    public string? VersionConstraint => Constraint is null || Version is null ? null : Constraint + Version;\n\n    public bool TryGetSemVersionRange([NotNullWhen(true)] out SemVersionRange? semVersionRange)\n    {\n        if (!string.IsNullOrEmpty(VersionConstraint))\n        {\n            return SemVersionRange.TryParse(\n                VersionConstraint,\n                SemVersionRangeOptions.Loose,\n                out semVersionRange\n            );\n        }\n\n        semVersionRange = null;\n        return false;\n    }\n\n    public static ExtensionSpecifier Parse(string value)\n    {\n        TryParse(value, true, out var packageSpecifier);\n\n        return packageSpecifier!;\n    }\n\n    public static bool TryParse(string value, [NotNullWhen(true)] out ExtensionSpecifier? extensionSpecifier)\n    {\n        return TryParse(value, false, out extensionSpecifier);\n    }\n\n    private static bool TryParse(\n        string value,\n        bool throwOnFailure,\n        [NotNullWhen(true)] out ExtensionSpecifier? packageSpecifier\n    )\n    {\n        var match = ExtensionSpecifierRegex().Match(value);\n        if (!match.Success)\n        {\n            if (throwOnFailure)\n            {\n                throw new ArgumentException($\"Invalid extension specifier: {value}\");\n            }\n\n            packageSpecifier = null;\n            return false;\n        }\n\n        packageSpecifier = new ExtensionSpecifier\n        {\n            Name = match.Groups[\"extension_name\"].Value,\n            Constraint = match.Groups[\"version_constraint\"].Value,\n            Version = match.Groups[\"version\"].Value\n        };\n\n        return true;\n    }\n\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        return Name + VersionConstraint;\n    }\n\n    public Argument ToArgument()\n    {\n        if (VersionConstraint is not null)\n        {\n            // Use Name as key\n            return new Argument(key: Name, value: ToString());\n        }\n\n        return new Argument(ToString());\n    }\n\n    public static implicit operator Argument(ExtensionSpecifier specifier)\n    {\n        return specifier.ToArgument();\n    }\n\n    public static implicit operator ExtensionSpecifier(string specifier)\n    {\n        return Parse(specifier);\n    }\n\n    /// <summary>\n    /// Regex to match a pip package specifier.\n    /// </summary>\n    [GeneratedRegex(\n        @\"(?<extension_name>\\S+)\\s*(?<version_constraint>==|>=|<=|>|<|~=|!=)?\\s*(?<version>[a-zA-Z0-9_.]+)?\",\n        RegexOptions.CultureInvariant,\n        5000\n    )]\n    private static partial Regex ExtensionSpecifierRegex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Extensions/GitPackageExtensionManager.cs",
    "content": "using System.Text.RegularExpressions;\nusing KGySoft.CoreLibraries;\nusing Microsoft.Extensions.Caching.Memory;\nusing NLog;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Models.Packages.Extensions;\n\npublic abstract partial class GitPackageExtensionManager(IPrerequisiteHelper prerequisiteHelper)\n    : IPackageExtensionManager\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    // Cache checks of installed extensions\n    private readonly MemoryCache installedExtensionsCache = new(new MemoryCacheOptions());\n\n    public abstract string RelativeInstallDirectory { get; }\n\n    public virtual IEnumerable<ExtensionManifest> DefaultManifests { get; } =\n        Enumerable.Empty<ExtensionManifest>();\n\n    protected virtual IEnumerable<string> IndexRelativeDirectories => [RelativeInstallDirectory];\n\n    public abstract Task<IEnumerable<PackageExtension>> GetManifestExtensionsAsync(\n        ExtensionManifest manifest,\n        CancellationToken cancellationToken = default\n    );\n\n    /// <inheritdoc />\n    Task<IEnumerable<PackageExtension>> IPackageExtensionManager.GetManifestExtensionsAsync(\n        ExtensionManifest manifest,\n        CancellationToken cancellationToken\n    )\n    {\n        return GetManifestExtensionsAsync(manifest, cancellationToken);\n    }\n\n    protected virtual IEnumerable<ExtensionManifest> GetManifests(InstalledPackage installedPackage)\n    {\n        if (installedPackage.ExtraExtensionManifestUrls is not { } customUrls)\n        {\n            return DefaultManifests;\n        }\n\n        var manifests = DefaultManifests.ToList();\n\n        foreach (var url in customUrls)\n        {\n            if (!string.IsNullOrEmpty(url) && Uri.TryCreate(url, UriKind.Absolute, out var uri))\n            {\n                manifests.Add(new ExtensionManifest(uri));\n            }\n        }\n\n        return manifests;\n    }\n\n    /// <inheritdoc />\n    IEnumerable<ExtensionManifest> IPackageExtensionManager.GetManifests(InstalledPackage installedPackage)\n    {\n        return GetManifests(installedPackage);\n    }\n\n    /// <inheritdoc />\n    public virtual async Task<IEnumerable<InstalledPackageExtension>> GetInstalledExtensionsAsync(\n        InstalledPackage installedPackage,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (installedPackage.FullPath is not { } packagePath)\n        {\n            return Enumerable.Empty<InstalledPackageExtension>();\n        }\n\n        var extensions = new List<InstalledPackageExtension>();\n\n        // Search for installed extensions in the package's index directories.\n        foreach (\n            var indexDirectory in IndexRelativeDirectories.Select(path => new DirectoryPath(\n                packagePath,\n                path\n            ))\n        )\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            // Skip directory if not exists\n            if (!indexDirectory.Exists)\n            {\n                continue;\n            }\n\n            // Check subdirectories of the index directory\n            foreach (var subDirectory in indexDirectory.EnumerateDirectories())\n            {\n                cancellationToken.ThrowIfCancellationRequested();\n\n                // Skip if not valid git repository\n                if (await prerequisiteHelper.CheckIsGitRepository(subDirectory).ConfigureAwait(false) != true)\n                    continue;\n\n                // Get git version\n                var version = await prerequisiteHelper\n                    .GetGitRepositoryVersion(subDirectory)\n                    .ConfigureAwait(false);\n\n                // Get git remote\n                var remoteUrlResult = await prerequisiteHelper\n                    .GetGitRepositoryRemoteOriginUrl(subDirectory)\n                    .ConfigureAwait(false);\n\n                extensions.Add(\n                    new InstalledPackageExtension\n                    {\n                        Paths = [subDirectory],\n                        Version = new PackageExtensionVersion\n                        {\n                            Tag = version.Tag,\n                            Branch = version.Branch,\n                            CommitSha = version.CommitSha,\n                        },\n                        GitRepositoryUrl = remoteUrlResult.IsSuccessExitCode\n                            ? remoteUrlResult.StandardOutput?.Trim()\n                            : null,\n                    }\n                );\n            }\n        }\n\n        return extensions;\n    }\n\n    /// <summary>\n    /// Like <see cref=\"GetInstalledExtensionsAsync\"/>, but does not check git version and repository url.\n    /// </summary>\n    public virtual async Task<IEnumerable<InstalledPackageExtension>> GetInstalledExtensionsLiteAsync(\n        InstalledPackage installedPackage,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (installedPackage.FullPath is not { } packagePath)\n        {\n            return Enumerable.Empty<InstalledPackageExtension>();\n        }\n\n        var extensions = new List<InstalledPackageExtension>();\n\n        // Search for installed extensions in the package's index directories.\n        foreach (\n            var indexDirectory in IndexRelativeDirectories.Select(path => new DirectoryPath(\n                packagePath,\n                path\n            ))\n        )\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            // Skip directory if not exists\n            if (!indexDirectory.Exists)\n            {\n                continue;\n            }\n\n            // Check subdirectories of the index directory\n            foreach (var subDirectory in indexDirectory.EnumerateDirectories())\n            {\n                cancellationToken.ThrowIfCancellationRequested();\n\n                // Skip if not valid git repository\n                if (!subDirectory.JoinDir(\".git\").Exists)\n                {\n                    continue;\n                }\n\n                // Get remote url with manual parsing\n                string? remoteUrl = null;\n\n                var gitConfigPath = subDirectory.JoinDir(\".git\").JoinFile(\"config\");\n                if (\n                    gitConfigPath.Exists\n                    && await gitConfigPath.ReadAllTextAsync(cancellationToken).ConfigureAwait(false)\n                        is { } gitConfigText\n                )\n                {\n                    var pattern = GitConfigRemoteOriginUrlRegex();\n                    var match = pattern.Match(gitConfigText);\n                    if (match.Success)\n                    {\n                        remoteUrl = match.Groups[1].Value;\n                    }\n                }\n\n                extensions.Add(\n                    new InstalledPackageExtension { Paths = [subDirectory], GitRepositoryUrl = remoteUrl }\n                );\n            }\n        }\n\n        return extensions;\n    }\n\n    public virtual async Task<InstalledPackageExtension> GetInstalledExtensionInfoAsync(\n        InstalledPackageExtension installedExtension\n    )\n    {\n        if (installedExtension.PrimaryPath is not DirectoryPath extensionDirectory)\n        {\n            return installedExtension;\n        }\n\n        // Get git version\n        var version = await prerequisiteHelper\n            .GetGitRepositoryVersion(extensionDirectory)\n            .ConfigureAwait(false);\n\n        return installedExtension with\n        {\n            Version = new PackageExtensionVersion\n            {\n                Tag = version.Tag,\n                Branch = version.Branch,\n                CommitSha = version.CommitSha,\n            },\n        };\n    }\n\n    /// <inheritdoc />\n    public virtual async Task InstallExtensionAsync(\n        PackageExtension extension,\n        InstalledPackage installedPackage,\n        PackageExtensionVersion? version = null,\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        ArgumentNullException.ThrowIfNull(installedPackage.FullPath);\n\n        // Ensure type\n        if (extension.InstallType?.ToLowerInvariant() != \"git-clone\")\n        {\n            throw new ArgumentException(\n                $\"Extension must have install type 'git-clone' but has '{extension.InstallType}'.\",\n                nameof(extension)\n            );\n        }\n\n        // Git clone all files\n        var cloneRoot = new DirectoryPath(installedPackage.FullPath, RelativeInstallDirectory);\n\n        foreach (var repositoryUri in extension.Files)\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            progress?.Report(\n                new ProgressReport(0f, message: $\"Cloning {repositoryUri}\", isIndeterminate: true)\n            );\n\n            try\n            {\n                await prerequisiteHelper\n                    .CloneGitRepository(\n                        cloneRoot,\n                        repositoryUri.ToString(),\n                        version,\n                        progress.AsProcessOutputHandler()\n                    )\n                    .ConfigureAwait(false);\n            }\n            catch (ProcessException ex)\n            {\n                if (ex.Message.Contains(\"Git exited with code 128\"))\n                {\n                    progress?.Report(\n                        new ProgressReport(\n                            -1f,\n                            $\"Unable to check out commit {version?.CommitSha} - continuing with latest commit from {version?.Branch}\\n\\n{ex.ProcessResult?.StandardError}\\n\",\n                            isIndeterminate: true\n                        )\n                    );\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            progress?.Report(new ProgressReport(1f, message: $\"Cloned {repositoryUri}\"));\n        }\n    }\n\n    /// <inheritdoc />\n    public virtual async Task UpdateExtensionAsync(\n        InstalledPackageExtension installedExtension,\n        InstalledPackage installedPackage,\n        PackageExtensionVersion? version = null,\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        ArgumentNullException.ThrowIfNull(installedPackage.FullPath);\n\n        foreach (var repoPath in installedExtension.Paths.OfType<DirectoryPath>())\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            // Check git\n            if (!await prerequisiteHelper.CheckIsGitRepository(repoPath.FullPath).ConfigureAwait(false))\n                continue;\n\n            // Get remote url\n            var remoteUrlResult = await prerequisiteHelper\n                .GetGitRepositoryRemoteOriginUrl(repoPath.FullPath)\n                .EnsureSuccessExitCode()\n                .ConfigureAwait(false);\n\n            progress?.Report(\n                new ProgressReport(\n                    0f,\n                    message: $\"Updating git repository {repoPath.Name}\",\n                    isIndeterminate: true\n                )\n            );\n\n            // If version not provided, use current branch\n            if (version is null)\n            {\n                ArgumentNullException.ThrowIfNull(installedExtension.Version?.Branch);\n\n                version = new PackageExtensionVersion { Branch = installedExtension.Version?.Branch };\n            }\n\n            await prerequisiteHelper\n                .UpdateGitRepository(\n                    repoPath,\n                    remoteUrlResult.StandardOutput!.Trim(),\n                    version,\n                    progress.AsProcessOutputHandler()\n                )\n                .ConfigureAwait(false);\n\n            progress?.Report(new ProgressReport(1f, message: $\"Updated git repository {repoPath.Name}\"));\n        }\n    }\n\n    /// <inheritdoc />\n    public virtual async Task UninstallExtensionAsync(\n        InstalledPackageExtension installedExtension,\n        InstalledPackage installedPackage,\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        foreach (var path in installedExtension.Paths.Where(p => p.Exists))\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            if (path is DirectoryPath directoryPath)\n            {\n                await directoryPath\n                    .DeleteVerboseAsync(cancellationToken: cancellationToken)\n                    .ConfigureAwait(false);\n            }\n            else\n            {\n                await path.DeleteAsync().ConfigureAwait(false);\n            }\n        }\n\n        progress?.Report(new ProgressReport(1f, message: \"Uninstalled extension\"));\n    }\n\n    [GeneratedRegex(\"\"\"\\[remote \"origin\"\\][\\s\\S]*?url\\s*=\\s*(.+)\"\"\")]\n    private static partial Regex GitConfigRemoteOriginUrlRegex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Extensions/IPackageExtensionManager.cs",
    "content": "using StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Models.Packages.Extensions;\n\n/// <summary>\n/// Interface for a package extension manager.\n/// </summary>\npublic interface IPackageExtensionManager\n{\n    /// <summary>\n    /// Default manifests for this extension manager.\n    /// </summary>\n    IEnumerable<ExtensionManifest> DefaultManifests { get; }\n\n    /// <summary>\n    /// Get manifests given an installed package.\n    /// By default returns <see cref=\"DefaultManifests\"/>.\n    /// </summary>\n    IEnumerable<ExtensionManifest> GetManifests(InstalledPackage installedPackage)\n    {\n        return DefaultManifests;\n    }\n\n    /// <summary>\n    /// Get extensions from the provided manifest.\n    /// </summary>\n    Task<IEnumerable<PackageExtension>> GetManifestExtensionsAsync(\n        ExtensionManifest manifest,\n        CancellationToken cancellationToken = default\n    );\n\n    /// <summary>\n    /// Get extensions from all provided manifests.\n    /// </summary>\n    async Task<IEnumerable<PackageExtension>> GetManifestExtensionsAsync(\n        IEnumerable<ExtensionManifest> manifests,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var extensions = Enumerable.Empty<PackageExtension>();\n\n        foreach (var manifest in manifests)\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            extensions = extensions.Concat(\n                await GetManifestExtensionsAsync(manifest, cancellationToken).ConfigureAwait(false)\n            );\n        }\n\n        return extensions;\n    }\n\n    /// <summary>\n    /// Get unique extensions from all provided manifests. As a mapping of their reference.\n    /// </summary>\n    async Task<IDictionary<string, PackageExtension>> GetManifestExtensionsMapAsync(\n        IEnumerable<ExtensionManifest> manifests,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var result = new Dictionary<string, PackageExtension>();\n\n        foreach (\n            var extension in await GetManifestExtensionsAsync(manifests, cancellationToken)\n                .ConfigureAwait(false)\n        )\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            var key = extension.Reference.ToString();\n\n            if (!result.TryAdd(key, extension))\n            {\n                // Replace\n                result[key] = extension;\n            }\n        }\n\n        return result;\n    }\n\n    /// <summary>\n    /// Get all installed extensions for the provided package.\n    /// </summary>\n    Task<IEnumerable<InstalledPackageExtension>> GetInstalledExtensionsAsync(\n        InstalledPackage installedPackage,\n        CancellationToken cancellationToken = default\n    );\n\n    /// <summary>\n    /// Like <see cref=\"GetInstalledExtensionsAsync\"/>, but does not check version.\n    /// </summary>\n    Task<IEnumerable<InstalledPackageExtension>> GetInstalledExtensionsLiteAsync(\n        InstalledPackage installedPackage,\n        CancellationToken cancellationToken = default\n    );\n\n    /// <summary>\n    /// Get updated info (version) for an installed extension.\n    /// </summary>\n    Task<InstalledPackageExtension> GetInstalledExtensionInfoAsync(\n        InstalledPackageExtension installedExtension\n    );\n\n    /// <summary>\n    /// Install an extension to the provided package.\n    /// </summary>\n    Task InstallExtensionAsync(\n        PackageExtension extension,\n        InstalledPackage installedPackage,\n        PackageExtensionVersion? version = null,\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    );\n\n    /// <summary>\n    /// Update an installed extension to the provided version.\n    /// If no version is provided, the latest version will be used.\n    /// </summary>\n    Task UpdateExtensionAsync(\n        InstalledPackageExtension installedExtension,\n        InstalledPackage installedPackage,\n        PackageExtensionVersion? version = null,\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    );\n\n    /// <summary>\n    /// Uninstall an installed extension.\n    /// </summary>\n    Task UninstallExtensionAsync(\n        InstalledPackageExtension installedExtension,\n        InstalledPackage installedPackage,\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Extensions/InstalledPackageExtension.cs",
    "content": "using StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Core.Models.Packages.Extensions;\n\npublic record InstalledPackageExtension\n{\n    /// <summary>\n    /// All folders or files of the extension.\n    /// </summary>\n    public required IEnumerable<IPathObject> Paths { get; init; }\n\n    /// <summary>\n    /// Primary path of the extension.\n    /// </summary>\n    public IPathObject? PrimaryPath => Paths.FirstOrDefault();\n\n    /// <summary>\n    /// The version of the extension.\n    /// </summary>\n    public PackageExtensionVersion? Version { get; init; }\n\n    /// <summary>\n    /// Remote git repository url, if the extension is a git repository.\n    /// </summary>\n    public string? GitRepositoryUrl { get; init; }\n\n    /// <summary>\n    /// The PackageExtension definition, if available.\n    /// </summary>\n    public PackageExtension? Definition { get; init; }\n\n    public string Title\n    {\n        get\n        {\n            if (Definition?.Title is { } title)\n            {\n                return title;\n            }\n\n            if (Paths.FirstOrDefault()?.Name is { } pathName)\n            {\n                return pathName;\n            }\n\n            return \"\";\n        }\n    }\n\n    /// <summary>\n    /// Path containing PrimaryPath and its parent.\n    /// </summary>\n    public string DisplayPath =>\n        PrimaryPath switch\n        {\n            null => \"\",\n            DirectoryPath { Parent: { } parentDir } dir => $\"{parentDir.Name}/{dir.Name}\",\n            _ => PrimaryPath.Name\n        };\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Extensions/PackageExtension.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages.Extensions;\n\npublic record PackageExtension\n{\n    public required string Author { get; init; }\n\n    public required string Title { get; init; }\n\n    public required Uri Reference { get; init; }\n\n    public required IEnumerable<Uri> Files { get; init; }\n\n    public IEnumerable<string>? Pip { get; init; }\n\n    public string? Description { get; init; }\n\n    public string? InstallType { get; init; }\n\n    public bool IsInstalled { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Extensions/PackageExtensionVersion.cs",
    "content": "namespace StabilityMatrix.Core.Models.Packages.Extensions;\n\npublic record PackageExtensionVersion : GitVersion\n{\n    public override string ToString() => base.ToString();\n};\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Extensions/SavedPackageExtension.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages.Extensions;\n\npublic class SavedPackageExtension\n{\n    public required PackageExtension PackageExtension { get; set; }\n    public PackageExtensionVersion? Version { get; set; }\n    public bool AlwaysUseLatest { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Extensions/VladExtensionItem.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages.Extensions;\n\npublic class VladExtensionItem\n{\n    public required string Name { get; set; }\n    public required Uri Url { get; set; }\n    public string? Long { get; set; }\n    public string? Description { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/FluxGym.cs",
    "content": "﻿using System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, FluxGym>(Duplicate = DuplicateStrategy.Append)]\npublic class FluxGym(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"FluxGym\";\n    public override string DisplayName { get; set; } = \"FluxGym\";\n    public override string Author => \"cocktailpeanut\";\n\n    public override string Blurb => \"Dead simple FLUX LoRA training UI with LOW VRAM support\";\n\n    public override string LicenseType => \"N/A\";\n    public override string LicenseUrl => \"\";\n    public override string LaunchCommand => \"app.py\";\n\n    public override Uri PreviewImageUri => new(\"https://cdn.lykos.ai/sm/packages/fluxgym/fluxgym.webp\");\n\n    public override List<LaunchOptionDefinition> LaunchOptions => [LaunchOptionDefinition.Extras];\n\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;\n\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None };\n\n    public override SharedFolderLayout SharedFolderLayout =>\n        new()\n        {\n            Rules =\n            [\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.TextEncoders],\n                    TargetRelativePaths = [\"models/clip\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.DiffusionModels],\n                    TargetRelativePaths = [\"models/unet\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.VAE],\n                    TargetRelativePaths = [\"models/vae\"],\n                },\n            ],\n        };\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders => null;\n    public override IEnumerable<TorchIndex> AvailableTorchIndices => new[] { TorchIndex.Cuda };\n    public override string MainBranch => \"main\";\n    public override bool ShouldIgnoreReleases => true;\n    public override string OutputFolderName => string.Empty;\n    public override bool IsCompatible => HardwareHelper.HasNvidiaGpu();\n    public override PackageType PackageType => PackageType.SdTraining;\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Simple;\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, \"Cloning / updating sd-scripts\", isIndeterminate: true));\n        // check if sd-scripts is already installed - if so: pull, else: clone\n        if (Directory.Exists(Path.Combine(installLocation, \"sd-scripts\")))\n        {\n            await PrerequisiteHelper\n                .RunGit([\"pull\"], onConsoleOutput, Path.Combine(installLocation, \"sd-scripts\"))\n                .ConfigureAwait(false);\n        }\n        else\n        {\n            await PrerequisiteHelper\n                .RunGit(\n                    [\"clone\", \"-b\", \"sd3\", \"https://github.com/kohya-ss/sd-scripts\"],\n                    onConsoleOutput,\n                    installLocation\n                )\n                .ConfigureAwait(false);\n        }\n\n        progress?.Report(new ProgressReport(-1f, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        var isLegacyNvidiaGpu =\n            SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() ?? HardwareHelper.HasLegacyNvidiaGpu();\n\n        var config = new PipInstallConfig\n        {\n            RequirementsFilePaths = [\"sd-scripts/requirements.txt\", \"requirements.txt\"],\n            RequirementsExcludePattern =\n                \"(diffusers\\\\[torch\\\\]==0.32.1|torch|torchvision|torchaudio|xformers|bitsandbytes|-e\\\\s\\\\.)\",\n            TorchaudioVersion = \" \",\n            CudaIndex = isLegacyNvidiaGpu ? \"cu126\" : \"cu128\",\n            ExtraPipArgs = [\"bitsandbytes>=0.46.0\"],\n            PostInstallPipArgs = [\"diffusers[torch]==0.32.1\"],\n        };\n\n        await StandardPipInstallProcessAsync(\n                venvRunner,\n                options,\n                installedPackage,\n                config,\n                onConsoleOutput,\n                progress,\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        await venvRunner.PipInstall([\"-e\", \"./sd-scripts\"], onConsoleOutput).ConfigureAwait(false);\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (s.Text.Contains(\"Running on local URL\", StringComparison.OrdinalIgnoreCase))\n            {\n                var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n                var match = regex.Match(s.Text);\n\n                if (match.Success)\n                {\n                    WebUrl = match.Value;\n                }\n                OnStartupComplete(WebUrl);\n            }\n        }\n\n        VenvRunner.RunDetached(\n            [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],\n            HandleConsoleOutput,\n            OnExit\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/FocusControlNet.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, FocusControlNet>(Duplicate = DuplicateStrategy.Append)]\npublic class FocusControlNet(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : Fooocus(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"Fooocus-ControlNet-SDXL\";\n    public override string DisplayName { get; set; } = \"Fooocus-ControlNet\";\n    public override string Author => \"fenneishi\";\n    public override string Blurb => \"Fooocus-ControlNet adds more control to the original Fooocus software.\";\n    public override string Disclaimer => \"This package may no longer receive updates from its author.\";\n    public override string LicenseUrl =>\n        \"https://github.com/fenneishi/Fooocus-ControlNet-SDXL/blob/main/LICENSE\";\n    public override Uri PreviewImageUri =>\n        new(\"https://github.com/fenneishi/Fooocus-ControlNet-SDXL/raw/main/asset/canny/snip.png\");\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Impossible;\n    public override bool OfferInOneClickInstaller => false;\n    public override PackageType PackageType => PackageType.Legacy;\n\n    public override SharedFolderLayout SharedFolderLayout =>\n        base.SharedFolderLayout with\n        {\n            RelativeConfigPath = \"user_path_config.txt\",\n        };\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Fooocus.cs",
    "content": "﻿using System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages.Config;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, Fooocus>(Duplicate = DuplicateStrategy.Append)]\npublic class Fooocus(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"Fooocus\";\n    public override string DisplayName { get; set; } = \"Fooocus\";\n    public override string Author => \"lllyasviel\";\n\n    public override string Blurb => \"Fooocus is a rethinking of Stable Diffusion and Midjourney’s designs\";\n    public override string LicenseType => \"GPL-3.0\";\n    public override string LicenseUrl => \"https://github.com/lllyasviel/Fooocus/blob/main/LICENSE\";\n    public override string LaunchCommand => \"launch.py\";\n    public override PackageType PackageType => PackageType.Legacy;\n\n    public override Uri PreviewImageUri =>\n        new(\n            \"https://user-images.githubusercontent.com/19834515/261830306-f79c5981-cf80-4ee3-b06b-3fef3f8bfbc7.png\"\n        );\n\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        new()\n        {\n            new LaunchOptionDefinition\n            {\n                Name = \"Preset\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--preset anime\", \"--preset realistic\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                Description = \"Sets the listen port\",\n                Options = { \"--port\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Share\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to share on Gradio\",\n                Options = { \"--share\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Listen\",\n                Type = LaunchOptionType.String,\n                Description = \"Set the listen interface\",\n                Options = { \"--listen\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Output Directory\",\n                Type = LaunchOptionType.String,\n                Description = \"Override the output directory\",\n                Options = { \"--output-path\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Language\",\n                Type = LaunchOptionType.String,\n                Description = \"Change the language of the UI\",\n                Options = { \"--language\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Disable Image Log\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--disable-image-log\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Disable Analytics\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--disable-analytics\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Disable Preset Model Downloads\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--disable-preset-download\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Always Download Newer Models\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--always-download-new-model\" },\n            },\n            new()\n            {\n                Name = \"VRAM\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch\n                {\n                    MemoryLevel.Low => \"--always-low-vram\",\n                    MemoryLevel.Medium => \"--always-normal-vram\",\n                    _ => null,\n                },\n                Options =\n                {\n                    \"--always-high-vram\",\n                    \"--always-normal-vram\",\n                    \"--always-low-vram\",\n                    \"--always-no-vram\",\n                },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Use DirectML\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Use pytorch with DirectML support\",\n                InitialValue = HardwareHelper.PreferDirectMLOrZluda(),\n                Options = { \"--directml\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Disable Xformers\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = !HardwareHelper.HasNvidiaGpu(),\n                Options = { \"--disable-xformers\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Disable Offload from VRAM\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = Compat.IsMacOS,\n                Options = { \"--disable-offload-from-vram\" },\n            },\n            LaunchOptionDefinition.Extras,\n        };\n\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;\n\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        new[] { SharedFolderMethod.Symlink, SharedFolderMethod.Configuration, SharedFolderMethod.None };\n\n    public override SharedFolderLayout SharedFolderLayout =>\n        new()\n        {\n            RelativeConfigPath = \"config.txt\",\n            ConfigFileType = ConfigFileType.Json,\n            Rules =\n            [\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.StableDiffusion],\n                    TargetRelativePaths = [\"models/checkpoints\"],\n                    ConfigDocumentPaths = [\"path_checkpoints\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Diffusers],\n                    TargetRelativePaths = [\"models/diffusers\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.TextEncoders],\n                    TargetRelativePaths = [\"models/clip\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.GLIGEN],\n                    TargetRelativePaths = [\"models/gligen\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.ESRGAN],\n                    TargetRelativePaths = [\"models/upscale_models\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Hypernetwork],\n                    TargetRelativePaths = [\"models/hypernetworks\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Embeddings],\n                    TargetRelativePaths = [\"models/embeddings\"],\n                    ConfigDocumentPaths = [\"path_embeddings\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.VAE],\n                    TargetRelativePaths = [\"models/vae\"],\n                    ConfigDocumentPaths = [\"path_vae\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.ApproxVAE],\n                    TargetRelativePaths = [\"models/vae_approx\"],\n                    ConfigDocumentPaths = [\"path_vae_approx\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS],\n                    TargetRelativePaths = [\"models/loras\"],\n                    ConfigDocumentPaths = [\"path_loras\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.ClipVision],\n                    TargetRelativePaths = [\"models/clip_vision\"],\n                    ConfigDocumentPaths = [\"path_clip_vision\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.ControlNet],\n                    TargetRelativePaths = [\"models/controlnet\"],\n                    ConfigDocumentPaths = [\"path_controlnet\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    TargetRelativePaths = [\"models/inpaint\"],\n                    ConfigDocumentPaths = [\"path_inpaint\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    TargetRelativePaths = [\"models/prompt_expansion/fooocus_expansion\"],\n                    ConfigDocumentPaths = [\"path_fooocus_expansion\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    TargetRelativePaths = [OutputFolderName],\n                    ConfigDocumentPaths = [\"path_outputs\"],\n                },\n            ],\n        };\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>> SharedOutputFolders =>\n        new() { [SharedOutputType.Text2Img] = new[] { \"outputs\" } };\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        new[] { TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.DirectMl, TorchIndex.Rocm, TorchIndex.Mps };\n\n    public override string MainBranch => \"main\";\n\n    public override bool ShouldIgnoreReleases => true;\n\n    public override string OutputFolderName => \"outputs\";\n\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Simple;\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        var torchIndex = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n        var isBlackwell =\n            torchIndex is TorchIndex.Cuda\n            && (SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu());\n\n        var config = new PipInstallConfig\n        {\n            // Pip version 24.1 deprecated numpy requirement spec used by torchsde 0.2.5\n            PrePipInstallArgs = [\"pip==23.3.2\"],\n            RequirementsFilePaths = [\"requirements_versions.txt\"],\n            TorchVersion = isBlackwell ? \"\" : \"==2.1.0\",\n            TorchvisionVersion = isBlackwell ? \"\" : \"==0.16.0\",\n            CudaIndex = isBlackwell ? \"cu128\" : \"cu121\",\n            RocmIndex = \"rocm5.6\",\n        };\n\n        await StandardPipInstallProcessAsync(\n                venvRunner,\n                options,\n                installedPackage,\n                config,\n                onConsoleOutput,\n                progress,\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (s.Text.Contains(\"Use the app with\", StringComparison.OrdinalIgnoreCase))\n            {\n                var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n                var match = regex.Match(s.Text);\n                if (match.Success)\n                {\n                    WebUrl = match.Value;\n                }\n                OnStartupComplete(WebUrl);\n            }\n        }\n\n        VenvRunner.RunDetached(\n            [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],\n            HandleConsoleOutput,\n            OnExit\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/FooocusMre.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, FooocusMre>(Duplicate = DuplicateStrategy.Append)]\npublic class FooocusMre(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"Fooocus-MRE\";\n    public override string DisplayName { get; set; } = \"Fooocus-MRE\";\n    public override string Author => \"MoonRide303\";\n\n    public override string Blurb =>\n        \"Fooocus-MRE is an image generating software, enhanced variant of the original Fooocus dedicated for a bit more advanced users\";\n\n    public override string LicenseType => \"GPL-3.0\";\n\n    public override string LicenseUrl =>\n        \"https://github.com/MoonRide303/Fooocus-MRE/blob/moonride-main/LICENSE\";\n    public override string LaunchCommand => \"launch.py\";\n\n    public override Uri PreviewImageUri =>\n        new(\n            \"https://user-images.githubusercontent.com/130458190/265366059-ce430ea0-0995-4067-98dd-cef1d7dc1ab6.png\"\n        );\n\n    public override string Disclaimer => \"This package may no longer receive updates from its author.\";\n\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Impossible;\n    public override bool OfferInOneClickInstaller => false;\n    public override PackageType PackageType => PackageType.Legacy;\n\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        new()\n        {\n            new LaunchOptionDefinition\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                Description = \"Sets the listen port\",\n                Options = { \"--port\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Share\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to share on Gradio\",\n                Options = { \"--share\" },\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Listen\",\n                Type = LaunchOptionType.String,\n                Description = \"Set the listen interface\",\n                Options = { \"--listen\" },\n            },\n            LaunchOptionDefinition.Extras,\n        };\n\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;\n\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None };\n\n    public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>\n        new()\n        {\n            [SharedFolderType.StableDiffusion] = new[] { \"models/checkpoints\" },\n            [SharedFolderType.Diffusers] = new[] { \"models/diffusers\" },\n            [SharedFolderType.Lora] = new[] { \"models/loras\" },\n            [SharedFolderType.TextEncoders] = new[] { \"models/clip\" },\n            [SharedFolderType.Embeddings] = new[] { \"models/embeddings\" },\n            [SharedFolderType.VAE] = new[] { \"models/vae\" },\n            [SharedFolderType.ApproxVAE] = new[] { \"models/vae_approx\" },\n            [SharedFolderType.ControlNet] = new[] { \"models/controlnet\" },\n            [SharedFolderType.GLIGEN] = new[] { \"models/gligen\" },\n            [SharedFolderType.ESRGAN] = new[] { \"models/upscale_models\" },\n            [SharedFolderType.Hypernetwork] = new[] { \"models/hypernetworks\" },\n        };\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders =>\n        new() { [SharedOutputType.Text2Img] = new[] { \"outputs\" } };\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        new[] { TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.Rocm };\n\n    public override string MainBranch => \"moonride-main\";\n\n    public override string OutputFolderName => \"outputs\";\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(-1f, \"Installing torch...\", isIndeterminate: true));\n\n        var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n        var pipInstallArgs = new PipInstallArgs();\n\n        if (torchVersion == TorchIndex.DirectMl)\n        {\n            pipInstallArgs = pipInstallArgs.WithTorchDirectML();\n        }\n        else\n        {\n            var extraIndex = torchVersion switch\n            {\n                TorchIndex.Cpu => \"cpu\",\n                TorchIndex.Cuda => \"cu118\",\n                TorchIndex.Rocm => \"rocm5.4.2\",\n                _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null),\n            };\n\n            pipInstallArgs = pipInstallArgs\n                .WithTorch(\"==2.0.1\")\n                .WithTorchVision(\"==0.15.2\")\n                .WithTorchExtraIndex(extraIndex);\n        }\n\n        var requirements = new FilePath(installLocation, \"requirements_versions.txt\");\n        pipInstallArgs = pipInstallArgs.WithParsedFromRequirementsTxt(\n            await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),\n            excludePattern: \"torch\"\n        );\n\n        if (installedPackage.PipOverrides != null)\n        {\n            pipInstallArgs = pipInstallArgs.WithUserOverrides(installedPackage.PipOverrides);\n        }\n\n        await venvRunner.PipInstall(pipInstallArgs, onConsoleOutput).ConfigureAwait(false);\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (s.Text.Contains(\"Use the app with\", StringComparison.OrdinalIgnoreCase))\n            {\n                var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n                var match = regex.Match(s.Text);\n                if (match.Success)\n                {\n                    WebUrl = match.Value;\n                }\n                OnStartupComplete(WebUrl);\n            }\n        }\n\n        VenvRunner.RunDetached(\n            [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],\n            HandleConsoleOutput,\n            OnExit\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/ForgeAmdGpu.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, ForgeAmdGpu>(Duplicate = DuplicateStrategy.Append)]\npublic class ForgeAmdGpu(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : SDWebForge(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"stable-diffusion-webui-amdgpu-forge\";\n    public override string DisplayName => \"Stable Diffusion WebUI AMDGPU Forge\";\n    public override string Author => \"lshqqytiger\";\n    public override string RepositoryName => \"stable-diffusion-webui-amdgpu-forge\";\n    public override string Blurb => \"A fork of Stable Diffusion WebUI Forge with support for AMD GPUs\";\n\n    public override string LicenseUrl =>\n        \"https://github.com/lshqqytiger/stable-diffusion-webui-amdgpu-forge/blob/main/LICENSE.txt\";\n\n    public override string Disclaimer =>\n        \"Prerequisite install may require admin privileges and a reboot. \"\n        + \"AMD GPUs under the RX 6800 may require additional manual setup.\";\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices => [TorchIndex.Zluda];\n\n    public override TorchIndex GetRecommendedTorchVersion() => TorchIndex.Zluda;\n\n    public override bool IsCompatible => HardwareHelper.PreferDirectMLOrZluda();\n\n    public override PackageType PackageType => PackageType.Legacy;\n\n    public override IEnumerable<PackagePrerequisite> Prerequisites =>\n        base.Prerequisites.Concat([PackagePrerequisite.HipSdk]);\n\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        base\n            .LaunchOptions.Concat(\n                [\n                    new LaunchOptionDefinition\n                    {\n                        Name = \"Use ZLUDA\",\n                        Description = \"Use ZLUDA for CUDA acceleration on AMD GPUs\",\n                        Type = LaunchOptionType.Bool,\n                        InitialValue = HardwareHelper.PreferDirectMLOrZluda(),\n                        Options = [\"--use-zluda\"],\n                    },\n                    new LaunchOptionDefinition\n                    {\n                        Name = \"Use DirectML\",\n                        Description = \"Use DirectML for DirectML acceleration on compatible GPUs\",\n                        Type = LaunchOptionType.Bool,\n                        InitialValue = false,\n                        Options = [\"--use-directml\"],\n                    },\n                ]\n            )\n            .ToList();\n\n    public override bool InstallRequiresAdmin => true;\n\n    public override string AdminRequiredReason =>\n        \"HIP SDK installation and (if applicable) ROCmLibs patching requires admin \"\n        + \"privileges for accessing the HIP SDK files in the Program Files directory.\";\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (!PrerequisiteHelper.IsHipSdkInstalled) // for updates\n        {\n            progress?.Report(new ProgressReport(-1, \"Installing HIP SDK 6.4\", isIndeterminate: true));\n            await PrerequisiteHelper\n                .InstallPackageRequirements(this, options.PythonOptions.PythonVersion, progress)\n                .ConfigureAwait(false);\n        }\n\n        progress?.Report(new ProgressReport(-1, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n        await venvRunner.PipInstall(\"--upgrade pip wheel\", onConsoleOutput).ConfigureAwait(false);\n        progress?.Report(new ProgressReport(1, \"Install finished\", isIndeterminate: false));\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (!PrerequisiteHelper.IsHipSdkInstalled)\n        {\n            throw new MissingPrerequisiteException(\n                \"HIP SDK\",\n                \"Your package has not yet been upgraded to use HIP SDK 6.4. To continue, please update this package or select \\\"Change Version\\\" from the 3-dots menu to have it upgraded automatically for you\"\n            );\n        }\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n        var portableGitBin = new DirectoryPath(PrerequisiteHelper.GitBinPath);\n        var hipPath = Path.Combine(\n            Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),\n            \"AMD\",\n            \"ROCm\",\n            \"6.4\"\n        );\n        var hipBinPath = Path.Combine(hipPath, \"bin\");\n        var envVars = new Dictionary<string, string>\n        {\n            [\"ZLUDA_COMGR_LOG_LEVEL\"] = \"1\",\n            [\"HIP_PATH\"] = hipPath,\n            [\"HIP_PATH_64\"] = hipPath,\n            [\"GIT\"] = portableGitBin.JoinFile(\"git.exe\"),\n        };\n        envVars.Update(settingsManager.Settings.EnvironmentVariables);\n\n        if (envVars.TryGetValue(\"PATH\", out var pathValue))\n        {\n            envVars[\"PATH\"] = Compat.GetEnvPathWithExtensions(hipBinPath, portableGitBin, pathValue);\n        }\n        else\n        {\n            envVars[\"PATH\"] = Compat.GetEnvPathWithExtensions(hipBinPath, portableGitBin);\n        }\n\n        VenvRunner.UpdateEnvironmentVariables(env => envVars.ToImmutableDictionary());\n\n        VenvRunner.RunDetached(\n            [\n                Path.Combine(installLocation, options.Command ?? LaunchCommand),\n                .. options.Arguments,\n                .. ExtraLaunchArguments,\n            ],\n            HandleConsoleOutput,\n            OnExit\n        );\n        return;\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (!s.Text.Contains(\"Running on\", StringComparison.OrdinalIgnoreCase))\n                return;\n\n            var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n            var match = regex.Match(s.Text);\n            if (!match.Success)\n                return;\n\n            WebUrl = match.Value;\n            OnStartupComplete(WebUrl);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/ForgeClassic.cs",
    "content": "﻿using Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, ForgeClassic>(Duplicate = DuplicateStrategy.Append)]\npublic class ForgeClassic(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : SDWebForge(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private const string LegacyUpgradeAlert = \"You are updating from an old version\";\n    private const string ContinuePrompt = \"Press Enter to Continue\";\n    public override PyVersion? MinimumPythonVersion => Python.PyInstallationManager.Python_3_13_12;\n\n    public override string Name => \"forge-classic\";\n    public override string Author => \"Haoming02\";\n    public override string RepositoryName => \"sd-webui-forge-classic\";\n    public override string DisplayName { get; set; } = \"Stable Diffusion WebUI Forge - Classic\";\n    public override string MainBranch => \"classic\";\n\n    public override string Blurb =>\n        \"This fork is focused exclusively on SD1 and SDXL checkpoints, having various optimizations implemented, with the main goal of being the lightest WebUI without any bloatwares.\";\n    public override string LicenseUrl =>\n        \"https://github.com/Haoming02/sd-webui-forge-classic/blob/classic/LICENSE\";\n    public override Uri PreviewImageUri =>\n        new(\"https://github.com/Haoming02/sd-webui-forge-classic/raw/classic/html/ui.webp\");\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.ReallyRecommended;\n    public override IEnumerable<TorchIndex> AvailableTorchIndices => [TorchIndex.Cuda];\n    public override bool IsCompatible => HardwareHelper.HasNvidiaGpu();\n    public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_13_12;\n    public override PackageType PackageType => PackageType.Legacy;\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>> SharedOutputFolders =>\n        new()\n        {\n            [SharedOutputType.Extras] = [\"output/extras-images\"],\n            [SharedOutputType.Saved] = [\"output/images\"],\n            [SharedOutputType.Img2Img] = [\"output/img2img-images\"],\n            [SharedOutputType.Text2Img] = [\"output/txt2img-images\"],\n            [SharedOutputType.Img2ImgGrids] = [\"output/img2img-grids\"],\n            [SharedOutputType.Text2ImgGrids] = [\"output/txt2img-grids\"],\n            [SharedOutputType.SVD] = [\"output/videos\"],\n        };\n\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new()\n            {\n                Name = \"Host\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"localhost\",\n                Options = [\"--server-name\"],\n            },\n            new()\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"7860\",\n                Options = [\"--port\"],\n            },\n            new()\n            {\n                Name = \"Share\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to share on Gradio\",\n                Options = { \"--share\" },\n            },\n            new()\n            {\n                Name = \"Xformers\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to use xformers\",\n                Options = { \"--xformers\" },\n            },\n            new()\n            {\n                Name = \"Use SageAttention\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to use sage attention\",\n                Options = { \"--sage\" },\n            },\n            new()\n            {\n                Name = \"Pin Shared Memory\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--pin-shared-memory\" },\n                InitialValue = SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() ?? false,\n            },\n            new()\n            {\n                Name = \"CUDA Malloc\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--cuda-malloc\" },\n                InitialValue = SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() ?? false,\n            },\n            new()\n            {\n                Name = \"CUDA Stream\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--cuda-stream\" },\n                InitialValue = SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() ?? false,\n            },\n            new()\n            {\n                Name = \"Auto Launch\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to auto launch the webui\",\n                Options = { \"--autolaunch\" },\n            },\n            new()\n            {\n                Name = \"Skip Python Version Check\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to skip python version check\",\n                Options = { \"--skip-python-version-check\" },\n                InitialValue = true,\n            },\n            LaunchOptionDefinition.Extras,\n        ];\n\n    public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>\n        new()\n        {\n            [SharedFolderType.StableDiffusion] = [\"models/Stable-diffusion/sd\"],\n            [SharedFolderType.ESRGAN] = [\"models/ESRGAN\"],\n            [SharedFolderType.Lora] = [\"models/Lora\"],\n            [SharedFolderType.LyCORIS] = [\"models/LyCORIS\"],\n            [SharedFolderType.ApproxVAE] = [\"models/VAE-approx\"],\n            [SharedFolderType.VAE] = [\"models/VAE\"],\n            [SharedFolderType.DeepDanbooru] = [\"models/deepbooru\"],\n            [SharedFolderType.Embeddings] = [\"models/embeddings\"],\n            [SharedFolderType.Hypernetwork] = [\"models/hypernetworks\"],\n            [SharedFolderType.ControlNet] = [\"models/controlnet/ControlNet\"],\n            [SharedFolderType.AfterDetailer] = [\"models/adetailer\"],\n            [SharedFolderType.T2IAdapter] = [\"models/controlnet/T2IAdapter\"],\n            [SharedFolderType.IpAdapter] = [\"models/controlnet/IpAdapter\"],\n            [SharedFolderType.IpAdapters15] = [\"models/controlnet/DiffusersIpAdapters\"],\n            [SharedFolderType.IpAdaptersXl] = [\"models/controlnet/DiffusersIpAdaptersXL\"],\n            [SharedFolderType.TextEncoders] = [\"models/text_encoder\"],\n            [SharedFolderType.DiffusionModels] = [\"models/Stable-diffusion/unet\"],\n        };\n\n    public override List<ExtraPackageCommand> GetExtraCommands()\n    {\n        var commands = new List<ExtraPackageCommand>();\n\n        if (Compat.IsWindows && SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() is true)\n        {\n            commands.Add(\n                new ExtraPackageCommand\n                {\n                    CommandName = \"Install Triton and SageAttention\",\n                    Command = InstallTritonAndSageAttention,\n                }\n            );\n        }\n\n        return commands;\n    }\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var requestedPythonVersion =\n            options.PythonOptions.PythonVersion\n            ?? (\n                PyVersion.TryParse(installedPackage.PythonVersion, out var parsedVersion)\n                    ? parsedVersion\n                    : RecommendedPythonVersion\n            );\n\n        var shouldUpgradePython = options.IsUpdate && requestedPythonVersion < MinimumPythonVersion;\n        var targetPythonVersion = shouldUpgradePython ? MinimumPythonVersion!.Value : requestedPythonVersion;\n\n        if (shouldUpgradePython)\n        {\n            onConsoleOutput?.Invoke(\n                ProcessOutput.FromStdOutLine(\n                    $\"Upgrading venv Python from {requestedPythonVersion} to {targetPythonVersion}\"\n                )\n            );\n\n            ResetVenvForPythonUpgrade(installLocation, onConsoleOutput);\n        }\n\n        progress?.Report(new ProgressReport(-1f, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                forceRecreate: shouldUpgradePython,\n                pythonVersion: targetPythonVersion\n            )\n            .ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(-1f, \"Running install script...\", isIndeterminate: true));\n\n        // Build args for their launch.py - use --uv for fast installs, --exit to quit after setup\n        var launchArgs = new List<string> { \"launch.py\", \"--uv\", \"--exit\" };\n\n        // For Ampere or newer GPUs, enable sage attention, flash attention, and nunchaku\n        var isAmpereOrNewer =\n            SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu()\n            ?? HardwareHelper.IterGpuInfo().Any(x => x.IsNvidia && x.IsAmpereOrNewerGpu());\n\n        if (isAmpereOrNewer)\n        {\n            launchArgs.Add(\"--sage\");\n            launchArgs.Add(\"--flash\");\n            launchArgs.Add(\"--nunchaku\");\n        }\n\n        // Run their install script with our venv Python\n        venvRunner.WorkingDirectory = new DirectoryPath(installLocation);\n\n        var sawLegacyUpdatePrompt = false;\n\n        var exitCode = await RunInstallScriptWithPromptHandling(\n                venvRunner,\n                launchArgs,\n                onConsoleOutput,\n                cancellationToken,\n                onLegacyPromptDetected: () => sawLegacyUpdatePrompt = true\n            )\n            .ConfigureAwait(false);\n\n        // If legacy prompt was detected, back up old config files regardless of exit code.\n        if (options.IsUpdate && sawLegacyUpdatePrompt)\n        {\n            BackupLegacyConfigFiles(installLocation, onConsoleOutput);\n\n            // If it also failed, retry once after the backup.\n            if (exitCode != 0)\n            {\n                onConsoleOutput?.Invoke(\n                    ProcessOutput.FromStdOutLine(\n                        \"[ForgeClassic] Retrying install after backing up legacy config files...\"\n                    )\n                );\n\n                exitCode = await RunInstallScriptWithPromptHandling(\n                        venvRunner,\n                        launchArgs,\n                        onConsoleOutput,\n                        cancellationToken\n                    )\n                    .ConfigureAwait(false);\n            }\n        }\n\n        if (exitCode != 0)\n        {\n            throw new InvalidOperationException($\"Install script failed with exit code {exitCode}\");\n        }\n\n        if (\n            !string.Equals(\n                installedPackage.PythonVersion,\n                targetPythonVersion.StringValue,\n                StringComparison.Ordinal\n            )\n        )\n        {\n            installedPackage.PythonVersion = targetPythonVersion.StringValue;\n        }\n\n        progress?.Report(new ProgressReport(1f, \"Install complete\", isIndeterminate: false));\n    }\n\n    private async Task<int> RunInstallScriptWithPromptHandling(\n        IPyVenvRunner venvRunner,\n        IReadOnlyCollection<string> launchArgs,\n        Action<ProcessOutput>? onConsoleOutput,\n        CancellationToken cancellationToken,\n        Action? onLegacyPromptDetected = null\n    )\n    {\n        var enterSent = false;\n\n        void HandleInstallOutput(ProcessOutput output)\n        {\n            onConsoleOutput?.Invoke(output);\n\n            var isLegacyPrompt =\n                output.Text.Contains(LegacyUpgradeAlert, StringComparison.OrdinalIgnoreCase)\n                || output.Text.Contains(ContinuePrompt, StringComparison.OrdinalIgnoreCase);\n\n            if (!isLegacyPrompt)\n                return;\n\n            onLegacyPromptDetected?.Invoke();\n\n            if (enterSent || venvRunner.Process is null || venvRunner.Process.HasExited)\n                return;\n\n            try\n            {\n                venvRunner.Process.StandardInput.WriteLine();\n                enterSent = true;\n\n                onConsoleOutput?.Invoke(\n                    ProcessOutput.FromStdOutLine(\n                        \"[ForgeClassic] Detected legacy update prompt. Sent Enter automatically.\"\n                    )\n                );\n            }\n            catch (Exception e)\n            {\n                Logger.Warn(e, \"Failed to auto-submit Enter for Forge Classic update prompt\");\n            }\n        }\n\n        venvRunner.RunDetached([.. launchArgs], HandleInstallOutput);\n        var process =\n            venvRunner.Process\n            ?? throw new InvalidOperationException(\"Failed to start Forge Classic install process\");\n        await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);\n        return process.ExitCode;\n    }\n\n    private void ResetVenvForPythonUpgrade(string installLocation, Action<ProcessOutput>? onConsoleOutput)\n    {\n        var venvPath = Path.Combine(installLocation, \"venv\");\n        if (!Directory.Exists(venvPath))\n            return;\n\n        try\n        {\n            Directory.Delete(venvPath, recursive: true);\n            onConsoleOutput?.Invoke(\n                ProcessOutput.FromStdOutLine(\"[ForgeClassic] Removed existing venv before Python upgrade.\")\n            );\n        }\n        catch (Exception e)\n        {\n            Logger.Warn(e, \"Failed to remove existing venv during Forge Classic Python upgrade\");\n            throw new InvalidOperationException(\n                \"Failed to remove existing venv for Python upgrade. Ensure Forge is not running and retry.\",\n                e\n            );\n        }\n    }\n\n    private void BackupLegacyConfigFiles(string installLocation, Action<ProcessOutput>? onConsoleOutput)\n    {\n        BackupLegacyConfigFile(installLocation, \"config.json\", onConsoleOutput);\n        BackupLegacyConfigFile(installLocation, \"ui-config.json\", onConsoleOutput);\n    }\n\n    private void BackupLegacyConfigFile(\n        string installLocation,\n        string fileName,\n        Action<ProcessOutput>? onConsoleOutput\n    )\n    {\n        var sourcePath = Path.Combine(installLocation, fileName);\n        if (!File.Exists(sourcePath))\n            return;\n\n        var backupPath = GetBackupPath(sourcePath);\n        File.Move(sourcePath, backupPath);\n\n        var message = $\"[ForgeClassic] Backed up {fileName} to {Path.GetFileName(backupPath)}\";\n        Logger.Info(message);\n        onConsoleOutput?.Invoke(ProcessOutput.FromStdOutLine(message));\n    }\n\n    private static string GetBackupPath(string sourcePath)\n    {\n        var nextPath = sourcePath + \".bak\";\n        if (!File.Exists(nextPath))\n            return nextPath;\n\n        var index = 1;\n        while (true)\n        {\n            nextPath = sourcePath + $\".bak.{index}\";\n            if (!File.Exists(nextPath))\n                return nextPath;\n\n            index++;\n        }\n    }\n\n    private async Task InstallTritonAndSageAttention(InstalledPackage? installedPackage)\n    {\n        if (installedPackage?.FullPath is null)\n            return;\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            ModificationCompleteMessage = \"Triton and SageAttention installed successfully\",\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n\n        await runner\n            .ExecuteSteps(\n                [\n                    new ActionPackageStep(\n                        async progress =>\n                        {\n                            await using var venvRunner = await SetupVenvPure(\n                                    installedPackage.FullPath,\n                                    pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)\n                                )\n                                .ConfigureAwait(false);\n\n                            var gpuInfo =\n                                SettingsManager.Settings.PreferredGpu\n                                ?? HardwareHelper.IterGpuInfo().FirstOrDefault(x => x.IsNvidia);\n\n                            var tritonVersion = Compat.IsWindows ? \"3.5.1.post22\" : \"3.5.1\";\n\n                            await PipWheelService\n                                .InstallTritonAsync(venvRunner, progress, tritonVersion)\n                                .ConfigureAwait(false);\n                            await PipWheelService\n                                .InstallSageAttentionAsync(venvRunner, gpuInfo, progress, \"2.2.0\")\n                                .ConfigureAwait(false);\n                        },\n                        \"Installing Triton and SageAttention\"\n                    ),\n                ]\n            )\n            .ConfigureAwait(false);\n\n        if (runner.Failed)\n            return;\n\n        await using var transaction = settingsManager.BeginTransaction();\n        var packageInSettings = transaction.Settings.InstalledPackages.FirstOrDefault(x =>\n            x.Id == installedPackage.Id\n        );\n\n        if (packageInSettings is null)\n            return;\n\n        var attentionOptions = packageInSettings.LaunchArgs?.Where(opt =>\n            opt.Name.Contains(\"attention\", StringComparison.OrdinalIgnoreCase)\n        );\n\n        if (attentionOptions is not null)\n        {\n            foreach (var option in attentionOptions)\n            {\n                option.OptionValue = false;\n            }\n        }\n\n        var sageAttention = packageInSettings.LaunchArgs?.FirstOrDefault(opt =>\n            opt.Name.Contains(\"sage\", StringComparison.OrdinalIgnoreCase)\n        );\n\n        if (sageAttention is not null)\n        {\n            sageAttention.OptionValue = true;\n        }\n        else\n        {\n            packageInSettings.LaunchArgs?.Add(\n                new LaunchOption\n                {\n                    Name = \"--sage\",\n                    Type = LaunchOptionType.Bool,\n                    OptionValue = true,\n                }\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/ForgeNeo.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, ForgeNeo>(Duplicate = DuplicateStrategy.Append)]\npublic class ForgeNeo(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : ForgeClassic(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"forge-neo\";\n    public override string DisplayName { get; set; } = \"Stable Diffusion WebUI Forge - Neo\";\n    public override string MainBranch => \"neo\";\n    public override PackageType PackageType => PackageType.SdInference;\n\n    public override string Blurb =>\n        \"Neo mainly serves as an continuation for the \\\"latest\\\" version of Forge. Additionally, this fork is focused on optimization and usability, with the main goal of being the lightest WebUI without any bloatwares.\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/FramePack.cs",
    "content": "﻿using System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.PackageModification;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, FramePack>(Duplicate = DuplicateStrategy.Append)]\npublic class FramePack(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"framepack\";\n    public override string DisplayName { get; set; } = \"FramePack\";\n    public override string Author => \"lllyasviel\";\n\n    public override string Blurb =>\n        \"FramePack is a next-frame (next-frame-section) prediction neural network structure that generates videos progressively.\";\n\n    public override string LicenseType => \"Apache-2.0\";\n    public override string LicenseUrl => \"https://github.com/lllyasviel/FramePack/blob/main/LICENSE\";\n    public override string LaunchCommand => \"demo_gradio.py\";\n    public override Uri PreviewImageUri => new(\"https://cdn.lykos.ai/sm/packages/framepack/framepack.png\");\n    public override string OutputFolderName => \"outputs\";\n    public override IEnumerable<TorchIndex> AvailableTorchIndices => [TorchIndex.Cuda];\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced;\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;\n\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new()\n            {\n                Name = \"Server\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"0.0.0.0\",\n                InitialValue = \"127.0.0.1\",\n                Options = [\"--server\"],\n            },\n            new()\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"7860\",\n                Options = [\"--port\"],\n            },\n            new()\n            {\n                Name = \"Share\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to share on Gradio\",\n                Options = [\"--share\"],\n            },\n            new()\n            {\n                Name = \"In Browser\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--inbrowser\"],\n                InitialValue = true,\n            },\n        ];\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders =>\n        new() { [SharedOutputType.Img2Vid] = [\"outputs\"] };\n\n    public override string MainBranch => \"main\";\n    public override bool ShouldIgnoreReleases => true;\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods => [SharedFolderMethod.None];\n    public override bool IsCompatible => HardwareHelper.HasNvidiaGpu();\n    public override IReadOnlyList<string> ExtraLaunchArguments =>\n        settingsManager.IsLibraryDirSet ? [\"--gradio-allowed-paths\", settingsManager.ImagesDirectory] : [];\n\n    public override IReadOnlyDictionary<string, string> ExtraLaunchCommands =>\n        new Dictionary<string, string> { [\"FramePack F1\"] = \"demo_gradio_f1.py\" };\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        var isLegacyNvidia =\n            SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() ?? HardwareHelper.HasLegacyNvidiaGpu();\n        var isNewerNvidia =\n            SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu()\n            ?? HardwareHelper.HasAmpereOrNewerGpu();\n\n        var extraArgs = new List<string>();\n        if (isNewerNvidia)\n        {\n            extraArgs.Add(Compat.IsWindows ? \"triton-windows\" : \"triton\");\n        }\n\n        var config = new PipInstallConfig\n        {\n            RequirementsFilePaths = [\"requirements.txt\"],\n            TorchaudioVersion = \" \", // Request torchaudio install\n            XformersVersion = \" \", // Request xformers install\n            CudaIndex = isLegacyNvidia ? \"cu126\" : \"cu128\",\n            UpgradePackages = true,\n            ExtraPipArgs = extraArgs,\n            PostInstallPipArgs = [\"numpy==1.26.4\"],\n        };\n\n        await StandardPipInstallProcessAsync(\n                venvRunner,\n                options,\n                installedPackage,\n                config,\n                onConsoleOutput,\n                progress,\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(1, \"Install complete\", isIndeterminate: false));\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        // Path to the original demo_gradio.py file\n        var originalDemoPath = Path.Combine(installLocation, options.Command ?? LaunchCommand);\n        var modifiedDemoPath = Path.Combine(installLocation, \"demo_gradio_modified.py\");\n\n        // Read the original demo_gradio.py file\n        var originalContent = await File.ReadAllTextAsync(originalDemoPath, cancellationToken)\n            .ConfigureAwait(false);\n\n        // Modify the content to add --gradio-allowed-paths support\n        var modifiedContent = AddGradioAllowedPathsSupport(originalContent);\n\n        // Write the modified content to a new file\n        await File.WriteAllTextAsync(modifiedDemoPath, modifiedContent, cancellationToken)\n            .ConfigureAwait(false);\n\n        VenvRunner.RunDetached(\n            [modifiedDemoPath, .. options.Arguments, .. ExtraLaunchArguments],\n            HandleConsoleOutput,\n            OnExit\n        );\n\n        return;\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (!s.Text.Contains(\"Running on local URL\", StringComparison.OrdinalIgnoreCase))\n                return;\n\n            var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n            var match = regex.Match(s.Text);\n            if (match.Success)\n                WebUrl = match.Value;\n            OnStartupComplete(WebUrl);\n        }\n    }\n\n    public override List<ExtraPackageCommand> GetExtraCommands()\n    {\n        return Compat.IsWindows && SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu() is true\n            ?\n            [\n                new ExtraPackageCommand\n                {\n                    CommandName = \"Install Triton and SageAttention\",\n                    Command = async installedPackage =>\n                    {\n                        if (installedPackage == null || string.IsNullOrEmpty(installedPackage.FullPath))\n                            throw new InvalidOperationException(\n                                \"Package not found or not installed correctly\"\n                            );\n\n                        await InstallTritonAndSageAttention(installedPackage).ConfigureAwait(false);\n                    },\n                },\n            ]\n            : [];\n    }\n\n    private static string AddGradioAllowedPathsSupport(string originalContent)\n    {\n        // Add the --gradio-allowed-paths argument to the argument parser\n        var parserPattern =\n            @\"(parser\\.add_argument\\(\"\"--inbrowser\"\", action='store_true'\\)\\s*\\n)(args = parser\\.parse_args\\(\\))\";\n        var parserReplacement =\n            \"$1parser.add_argument('--gradio-allowed-paths', nargs='*', default=[], help='Allowed paths for Gradio file access')\\n$2\";\n\n        var modifiedContent = Regex.Replace(\n            originalContent,\n            parserPattern,\n            parserReplacement,\n            RegexOptions.Multiline\n        );\n\n        // Add the allowed_paths parameter to the block.launch() call\n        var launchPattern =\n            @\"(block\\.launch\\(\\s*\\n\\s*server_name=args\\.server,\\s*\\n\\s*server_port=args\\.port,\\s*\\n\\s*share=args\\.share,\\s*\\n\\s*inbrowser=args\\.inbrowser,)\\s*\\n(\\))\";\n        var launchReplacement = \"$1\\n    allowed_paths=args.gradio_allowed_paths,\\n$2\";\n\n        modifiedContent = Regex.Replace(\n            modifiedContent,\n            launchPattern,\n            launchReplacement,\n            RegexOptions.Multiline\n        );\n\n        return modifiedContent;\n    }\n\n    private async Task InstallTritonAndSageAttention(InstalledPackage installedPackage)\n    {\n        if (installedPackage.FullPath is null)\n            return;\n\n        var installSageStep = new InstallSageAttentionStep(\n            DownloadService,\n            PrerequisiteHelper,\n            PyInstallationManager\n        )\n        {\n            InstalledPackage = installedPackage,\n            WorkingDirectory = new DirectoryPath(installedPackage.FullPath),\n            EnvironmentVariables = SettingsManager.Settings.EnvironmentVariables,\n            IsBlackwellGpu =\n                SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu(),\n        };\n\n        var runner = new PackageModificationRunner\n        {\n            ShowDialogOnStart = true,\n            ModificationCompleteMessage = \"Triton and SageAttention installed successfully\",\n        };\n        EventManager.Instance.OnPackageInstallProgressAdded(runner);\n        await runner.ExecuteSteps([installSageStep]).ConfigureAwait(false);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/FramePackStudio.cs",
    "content": "﻿using System.Collections.ObjectModel;\nusing System.Text.Json;\nusing System.Text.Json.Nodes;\nusing System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages.Config;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, FramePackStudio>(Duplicate = DuplicateStrategy.Append)]\npublic class FramePackStudio(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : FramePack(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"framepack-studio\";\n    public override string DisplayName { get; set; } = \"FramePack Studio\";\n    public override string Author => \"colinurbs\";\n    public override string RepositoryName => \"FramePack-Studio\";\n    public override string Blurb =>\n        \"FramePack Studio is an AI video generation application based on FramePack that strives to provide everything you need to create high quality video projects.\";\n    public override string LicenseType => \"Apache-2.0\";\n    public override string LicenseUrl => \"https://github.com/colinurbs/FramePack-Studio/blob/main/LICENSE\";\n    public override string LaunchCommand => \"studio.py\";\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        [SharedFolderMethod.None, SharedFolderMethod.Configuration];\n\n    public override SharedFolderLayout? SharedFolderLayout =>\n        new()\n        {\n            ConfigFileType = ConfigFileType.Json,\n            RelativeConfigPath = new FilePath(\".framepack\", \"settings.json\"),\n            Rules =\n            [\n                new SharedFolderLayoutRule\n                {\n                    ConfigDocumentPaths = [\"lora_dir\"],\n                    TargetRelativePaths = [\"loras\"],\n                    SourceTypes = [SharedFolderType.Lora],\n                },\n            ],\n        };\n\n    public override IReadOnlyDictionary<string, string> ExtraLaunchCommands =>\n        new Dictionary<string, string>();\n    public override IReadOnlyList<string> ExtraLaunchArguments => [];\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (installedPackage.PreferredSharedFolderMethod is SharedFolderMethod.Configuration)\n        {\n            var settingsPath = new FilePath(installLocation, \".framepack\", \"settings.json\");\n            if (!settingsPath.Exists)\n            {\n                settingsPath.Create();\n            }\n\n            // set the output_dir and metadata_dir\n            var settingsText = await settingsPath.ReadAllTextAsync(cancellationToken).ConfigureAwait(false);\n            var json = JsonSerializer.Deserialize<JsonObject>(settingsText) ?? new JsonObject();\n            json[\"output_dir\"] = SettingsManager\n                .ImagesDirectory.JoinDir(nameof(SharedOutputType.Img2Vid))\n                .ToString();\n            json[\"metadata_dir\"] = SettingsManager\n                .ImagesDirectory.JoinDir(nameof(SharedOutputType.Img2Vid))\n                .ToString();\n\n            await settingsPath\n                .WriteAllTextAsync(\n                    JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true }),\n                    cancellationToken\n                )\n                .ConfigureAwait(false);\n        }\n\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        VenvRunner.RunDetached(\n            [LaunchCommand, .. options.Arguments, .. ExtraLaunchArguments],\n            HandleConsoleOutput,\n            OnExit\n        );\n\n        return;\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (!s.Text.Contains(\"Running on local URL\", StringComparison.OrdinalIgnoreCase))\n                return;\n\n            var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n            var match = regex.Match(s.Text);\n            if (match.Success)\n                WebUrl = match.Value;\n            OnStartupComplete(WebUrl);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/IArgParsable.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages;\n\n/// <summary>\n/// Supports parsing launch options from a python script.\n/// </summary>\npublic interface IArgParsable\n{\n    /// <summary>\n    /// Defines the relative path to the python script that defines the launch options.\n    /// </summary>\n    public string RelativeArgsDefinitionScriptPath { get; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/InvokeAI.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Text.Json;\nusing System.Text.Json.Nodes;\nusing System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing NLog;\nusing Refit;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.Api.Invoke;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, InvokeAI>(Duplicate = DuplicateStrategy.Append)]\npublic class InvokeAI(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private const string RelativeRootPath = \"invokeai-root\";\n    private readonly string relativeFrontendBuildPath = Path.Combine(\"invokeai\", \"frontend\", \"web\", \"dist\");\n\n    public override string Name => \"InvokeAI\";\n    public override string DisplayName { get; set; } = \"InvokeAI\";\n    public override string Author => \"invoke-ai\";\n    public override string LicenseType => \"Apache-2.0\";\n    public override string LicenseUrl => \"https://github.com/invoke-ai/InvokeAI/blob/main/LICENSE\";\n\n    public override string Blurb => \"Professional Creative Tools for Stable Diffusion\";\n    public override string LaunchCommand => \"invokeai-web\";\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced;\n\n    public override Uri PreviewImageUri =>\n        new(\"https://raw.githubusercontent.com/invoke-ai/InvokeAI/main/docs/assets/canvas_preview.png\");\n\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        [SharedFolderMethod.None, SharedFolderMethod.Configuration];\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;\n\n    public override string MainBranch => \"main\";\n    public override bool ShouldIgnoreBranches => true;\n\n    public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>\n        new()\n        {\n            [SharedFolderType.StableDiffusion] = [Path.Combine(RelativeRootPath, \"autoimport\", \"main\")],\n            [SharedFolderType.Lora] = [Path.Combine(RelativeRootPath, \"autoimport\", \"lora\")],\n            [SharedFolderType.Embeddings] = [Path.Combine(RelativeRootPath, \"autoimport\", \"embedding\")],\n            [SharedFolderType.ControlNet] = [Path.Combine(RelativeRootPath, \"autoimport\", \"controlnet\")],\n            [SharedFolderType.IpAdapters15] =\n            [\n                Path.Combine(RelativeRootPath, \"models\", \"sd-1\", \"ip_adapter\"),\n            ],\n            [SharedFolderType.IpAdaptersXl] =\n            [\n                Path.Combine(RelativeRootPath, \"models\", \"sdxl\", \"ip_adapter\"),\n            ],\n            [SharedFolderType.ClipVision] = [Path.Combine(RelativeRootPath, \"models\", \"any\", \"clip_vision\")],\n            [SharedFolderType.T2IAdapter] = [Path.Combine(RelativeRootPath, \"autoimport\", \"t2i_adapter\")],\n        };\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders =>\n        new() { [SharedOutputType.Text2Img] = [Path.Combine(\"invokeai-root\", \"outputs\", \"images\")] };\n\n    public override string OutputFolderName => Path.Combine(\"invokeai-root\", \"outputs\", \"images\");\n\n    // https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/CONFIGURATION.md\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new()\n            {\n                Name = \"Root Directory\",\n                Type = LaunchOptionType.String,\n                Options = [\"--root\"],\n            },\n            new()\n            {\n                Name = \"Config File\",\n                Type = LaunchOptionType.String,\n                Options = [\"--config\"],\n            },\n            LaunchOptionDefinition.Extras,\n        ];\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        [TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.Rocm, TorchIndex.Mps];\n\n    public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_12_10;\n\n    public override TorchIndex GetRecommendedTorchVersion()\n    {\n        if (Compat.IsMacOS && Compat.IsArm)\n        {\n            return TorchIndex.Mps;\n        }\n\n        return base.GetRecommendedTorchVersion();\n    }\n\n    public override IEnumerable<PackagePrerequisite> Prerequisites =>\n        [PackagePrerequisite.Python310, PackagePrerequisite.VcRedist, PackagePrerequisite.Git];\n\n    public override Task DownloadPackage(\n        string installLocation,\n        DownloadPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        return Task.CompletedTask;\n    }\n\n    public override Task<bool> CheckForUpdates(InstalledPackage package) =>\n        package.PythonVersion == Python.PyInstallationManager.Python_3_10_11.ToString()\n            ? Task.FromResult(false)\n            : base.CheckForUpdates(package);\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        // Backup existing files/folders except for known directories\n        try\n        {\n            var excludedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)\n            {\n                \"invokeai-root\",\n                \"invoke.old\",\n                \"venv\",\n            };\n\n            if (Directory.Exists(installLocation))\n            {\n                var entriesToMove = Directory\n                    .EnumerateFileSystemEntries(installLocation)\n                    .Where(p => !excludedNames.Contains(Path.GetFileName(p)))\n                    .ToList();\n\n                if (entriesToMove.Count > 0)\n                {\n                    var backupFolderName = \"invoke.old\";\n                    var backupFolderPath = Path.Combine(installLocation, backupFolderName);\n\n                    if (Directory.Exists(backupFolderPath) || File.Exists(backupFolderPath))\n                    {\n                        backupFolderPath = Path.Combine(\n                            installLocation,\n                            $\"invoke.old.{DateTime.Now:yyyyMMddHHmmss}\"\n                        );\n                    }\n\n                    Directory.CreateDirectory(backupFolderPath);\n\n                    foreach (var entry in entriesToMove)\n                    {\n                        var destinationPath = Path.Combine(backupFolderPath, Path.GetFileName(entry));\n\n                        // Ensure we do not overwrite existing files if names collide\n                        if (File.Exists(destinationPath) || Directory.Exists(destinationPath))\n                        {\n                            var name = Path.GetFileNameWithoutExtension(entry);\n                            var ext = Path.GetExtension(entry);\n                            var uniqueName = $\"{name}_{DateTime.Now:yyyyMMddHHmmss}{ext}\";\n                            destinationPath = Path.Combine(backupFolderPath, uniqueName);\n                        }\n\n                        if (Directory.Exists(entry))\n                        {\n                            Directory.Move(entry, destinationPath);\n                        }\n                        else if (File.Exists(entry))\n                        {\n                            File.Move(entry, destinationPath);\n                        }\n                    }\n\n                    Logger.Info($\"Moved {entriesToMove.Count} item(s) to '{backupFolderPath}'.\");\n                }\n            }\n        }\n        catch (Exception e)\n        {\n            Logger.Warn(e, \"Failed to move existing files to 'invoke.old'. Continuing with installation.\");\n        }\n\n        // Setup venv\n        progress?.Report(new ProgressReport(-1f, \"Setting up venv\", isIndeterminate: true));\n\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n        venvRunner.UpdateEnvironmentVariables(env => GetEnvVars(env, installLocation));\n\n        progress?.Report(new ProgressReport(-1f, \"Installing Package\", isIndeterminate: true));\n\n        var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n        var isLegacyNvidiaGpu =\n            SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() ?? HardwareHelper.HasLegacyNvidiaGpu();\n        var fallbackIndex = torchVersion switch\n        {\n            TorchIndex.Cpu when Compat.IsLinux => \"https://download.pytorch.org/whl/cpu\",\n            TorchIndex.Cuda when isLegacyNvidiaGpu => \"https://download.pytorch.org/whl/cu126\",\n            TorchIndex.Cuda => \"https://download.pytorch.org/whl/cu128\",\n            TorchIndex.Rocm => \"https://download.pytorch.org/whl/rocm6.3\",\n            _ => string.Empty,\n        };\n\n        var invokeInstallArgs = new PipInstallArgs($\"invokeai=={options.VersionOptions.VersionTag}\");\n\n        var contentStream = await DownloadService\n            .GetContentAsync(\n                $\"https://raw.githubusercontent.com/invoke-ai/InvokeAI/refs/tags/{options.VersionOptions.VersionTag}/pins.json\",\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        // read to json, just deserialize as JObject or whtaever it is in System.Text>json\n        using var reader = new StreamReader(contentStream);\n        var json = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);\n        var pins = JsonNode.Parse(json);\n        var platform =\n            Compat.IsWindows ? \"win32\"\n            : Compat.IsMacOS ? \"darwin\"\n            : \"linux\";\n        var index = pins?[\"torchIndexUrl\"]?[platform]?[\n            torchVersion.ToString().ToLowerInvariant()\n        ]?.GetValue<string>();\n\n        if (!string.IsNullOrWhiteSpace(index) && !isLegacyNvidiaGpu)\n        {\n            invokeInstallArgs = invokeInstallArgs.AddArg(\"--index\").AddArg(index);\n        }\n        else if (!string.IsNullOrWhiteSpace(fallbackIndex))\n        {\n            invokeInstallArgs = invokeInstallArgs.AddArg(\"--index\").AddArg(fallbackIndex);\n        }\n\n        invokeInstallArgs = invokeInstallArgs.AddArg(\"--force-reinstall\");\n\n        await venvRunner.PipInstall(invokeInstallArgs, onConsoleOutput).ConfigureAwait(false);\n        progress?.Report(new ProgressReport(1f, \"Done!\", isIndeterminate: false));\n    }\n\n    public override Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    ) =>\n        RunInvokeCommand(\n            installLocation,\n            options.Command ?? LaunchCommand,\n            options.Arguments,\n            true,\n            installedPackage,\n            onConsoleOutput\n        );\n\n    private async Task RunInvokeCommand(\n        string installedPackagePath,\n        string command,\n        string arguments,\n        bool runDetached,\n        InstalledPackage installedPackage,\n        Action<ProcessOutput>? onConsoleOutput,\n        bool spam3 = false\n    )\n    {\n        if (spam3 && !runDetached)\n        {\n            throw new InvalidOperationException(\"Cannot spam 3 if not running detached\");\n        }\n\n        await SetupVenv(installedPackagePath, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        VenvRunner.UpdateEnvironmentVariables(env => GetEnvVars(env, installedPackagePath));\n\n        // Check for legacy Python 3.10.11 installations\n        if (installedPackage.PythonVersion == Python.PyInstallationManager.Python_3_10_11.ToString())\n        {\n            var warningMessage = \"\"\"\n\n                ============================================================\n                            LEGACY INVOKEAI INSTALLATION\n                ============================================================\n\n                This InvokeAI installation is using Python 3.10.11, which\n                is no longer supported by InvokeAI.\n\n                Automatic updates have been disabled for this installation\n                to prevent compatibility issues.\n\n                Your current installation will continue to work, but will\n                not receive updates.\n\n                Recommended actions:\n                  1. Install a new InvokeAI instance with Python 3.12+\n                  2. Copy your settings and data from this installation\n                     to the new one\n                  3. Once verified, you can delete this old installation\n\n                Note: You can run both installations side-by-side during\n                the migration.\n\n                ============================================================\n\n                \"\"\";\n\n            Logger.Warn(\n                \"InvokeAI installation using legacy Python {PythonVersion} - updates disabled\",\n                installedPackage.PythonVersion\n            );\n            onConsoleOutput?.Invoke(ProcessOutput.FromStdErrLine(warningMessage));\n        }\n\n        // Launch command is for a console entry point, and not a direct script\n        var entryPoint = await VenvRunner.GetEntryPoint(command).ConfigureAwait(false);\n\n        // Split at ':' to get package and function\n        var split = entryPoint?.Split(':');\n\n        // Console message because Invoke takes forever to start sometimes with no output of what its doing\n        onConsoleOutput?.Invoke(new ProcessOutput { Text = \"Starting InvokeAI...\\n\" });\n\n        if (split is not { Length: > 1 })\n        {\n            throw new Exception($\"Could not find entry point for InvokeAI: {entryPoint.ToRepr()}\");\n        }\n\n        // Compile a startup command according to\n        // https://packaging.python.org/en/latest/specifications/entry-points/#use-for-scripts\n        // For invokeai, also patch the shutil.get_terminal_size function to return a fixed value\n        // above the minimum in invokeai.frontend.install.widgets\n\n        var code = $\"\"\"\n            import sys\n            from {split[0]} import {split[1]}\n            sys.exit({split[1]}())\n            \"\"\";\n\n        if (runDetached)\n        {\n            async void HandleConsoleOutput(ProcessOutput s)\n            {\n                if (s.Text.Contains(\"running on\", StringComparison.OrdinalIgnoreCase))\n                {\n                    var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n                    var match = regex.Match(s.Text);\n                    if (!match.Success)\n                        return;\n\n                    if (installedPackage.PreferredSharedFolderMethod == SharedFolderMethod.Configuration)\n                    {\n                        try\n                        {\n                            // returns true if we printed the url already cuz it took too long\n                            if (\n                                await SetupInvokeModelSharingConfig(onConsoleOutput, match, s)\n                                    .ConfigureAwait(false)\n                            )\n                                return;\n                        }\n                        catch (Exception e)\n                        {\n                            Logger.Error(e, \"Failed to setup InvokeAI model sharing config\");\n                        }\n                    }\n\n                    onConsoleOutput?.Invoke(s);\n\n                    WebUrl = match.Value;\n                    OnStartupComplete(WebUrl);\n                }\n                else\n                {\n                    onConsoleOutput?.Invoke(s);\n\n                    if (\n                        spam3\n                        && s.Text.Contains(\"[3] Accept the best guess;\", StringComparison.OrdinalIgnoreCase)\n                    )\n                    {\n                        VenvRunner.Process?.StandardInput.WriteLine(\"3\");\n                    }\n                }\n            }\n\n            VenvRunner.RunDetached($\"-c \\\"{code}\\\" {arguments}\".TrimEnd(), HandleConsoleOutput, OnExit);\n        }\n        else\n        {\n            var result = await VenvRunner.Run($\"-c \\\"{code}\\\" {arguments}\".TrimEnd()).ConfigureAwait(false);\n            onConsoleOutput?.Invoke(new ProcessOutput { Text = result.StandardOutput });\n        }\n    }\n\n    public override async Task<InstalledPackageVersion> Update(\n        string installLocation,\n        InstalledPackage installedPackage,\n        UpdatePackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await InstallPackage(\n                installLocation,\n                installedPackage,\n                options.AsInstallOptions(),\n                progress,\n                onConsoleOutput,\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        if (!string.IsNullOrWhiteSpace(options.VersionOptions.VersionTag))\n        {\n            return new InstalledPackageVersion\n            {\n                InstalledReleaseVersion = options.VersionOptions.VersionTag,\n                IsPrerelease = options.VersionOptions.IsPrerelease,\n            };\n        }\n\n        return new InstalledPackageVersion\n        {\n            InstalledBranch = options.VersionOptions.BranchName,\n            InstalledCommitSha = options.VersionOptions.CommitHash,\n            IsPrerelease = options.VersionOptions.IsPrerelease,\n        };\n    }\n\n    // Invoke doing shared folders on startup instead\n    public override Task SetupModelFolders(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    ) => Task.CompletedTask;\n\n    public override Task RemoveModelFolderLinks(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    ) => Task.CompletedTask;\n\n    private async Task<bool> SetupInvokeModelSharingConfig(\n        Action<ProcessOutput>? onConsoleOutput,\n        Match match,\n        ProcessOutput s\n    )\n    {\n        var invokeAiUrl = match.Value;\n        if (invokeAiUrl.Contains(\"0.0.0.0\"))\n        {\n            invokeAiUrl = invokeAiUrl.Replace(\"0.0.0.0\", \"127.0.0.1\");\n        }\n\n        var invokeAiApi = RestService.For<IInvokeAiApi>(\n            invokeAiUrl,\n            new RefitSettings\n            {\n                ContentSerializer = new SystemTextJsonContentSerializer(\n                    new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }\n                ),\n            }\n        );\n\n        var result = await invokeAiApi.ScanFolder(SettingsManager.ModelsDirectory).ConfigureAwait(false);\n        var modelsToScan = result.Where(x => !x.IsInstalled).ToList();\n        if (modelsToScan.Count <= 0)\n            return false;\n\n        foreach (var model in modelsToScan)\n        {\n            Logger.Info($\"Installing model {model.Path}\");\n            await invokeAiApi\n                .InstallModel(\n                    new InstallModelRequest\n                    {\n                        Name = Path.GetFileNameWithoutExtension(model.Path),\n                        Description = Path.GetFileName(model.Path),\n                    },\n                    source: model.Path,\n                    inplace: true\n                )\n                .ConfigureAwait(false);\n        }\n\n        var installStatus = await invokeAiApi.GetModelInstallStatus().ConfigureAwait(false);\n\n        var installCheckCount = 0;\n\n        while (\n            !installStatus.All(x =>\n                (x.Status != null && x.Status.Equals(\"completed\", StringComparison.OrdinalIgnoreCase))\n                || (x.Status != null && x.Status.Equals(\"error\", StringComparison.OrdinalIgnoreCase))\n            )\n        )\n        {\n            installCheckCount++;\n            if (installCheckCount > 5)\n            {\n                onConsoleOutput?.Invoke(\n                    new ProcessOutput\n                    {\n                        Text =\n                            \"This may take awhile, feel free to use the web interface while the rest of your models are imported.\\n\",\n                    }\n                );\n\n                onConsoleOutput?.Invoke(s);\n\n                WebUrl = match.Value;\n                OnStartupComplete(WebUrl);\n\n                break;\n            }\n\n            onConsoleOutput?.Invoke(\n                new ProcessOutput\n                {\n                    Text =\n                        $\"\\nWaiting for model import... ({installStatus.Count(x => (x.Status != null && !x.Status.Equals(\"completed\",\n                        StringComparison.OrdinalIgnoreCase)) && !x.Status.Equals(\"error\", StringComparison.OrdinalIgnoreCase))} remaining)\\n\",\n                }\n            );\n            await Task.Delay(5000).ConfigureAwait(false);\n            try\n            {\n                installStatus = await invokeAiApi.GetModelInstallStatus().ConfigureAwait(false);\n            }\n            catch (Exception e)\n            {\n                Logger.Error(e, \"Failed to get model install status\");\n            }\n        }\n\n        return installCheckCount > 5;\n    }\n\n    private ImmutableDictionary<string, string> GetEnvVars(\n        ImmutableDictionary<string, string> env,\n        DirectoryPath installPath\n    )\n    {\n        // Set additional required environment variables\n\n        // Need to make subdirectory because they store config in the\n        // directory *above* the root directory\n        var root = installPath.JoinDir(RelativeRootPath);\n        root.Create();\n        env = env.SetItem(\"INVOKEAI_ROOT\", root);\n\n        var path = env.GetValueOrDefault(\"PATH\", string.Empty);\n\n        if (string.IsNullOrEmpty(path))\n        {\n            path += $\"{Compat.PathDelimiter}{Path.Combine(SettingsManager.LibraryDir, \"Assets\", \"nodejs\")}\";\n        }\n        else\n        {\n            path += Path.Combine(SettingsManager.LibraryDir, \"Assets\", \"nodejs\");\n        }\n\n        path += $\"{Compat.PathDelimiter}{Path.Combine(installPath, \"node_modules\", \".bin\")}\";\n\n        if (Compat.IsMacOS || Compat.IsLinux)\n        {\n            path +=\n                $\"{Compat.PathDelimiter}{Path.Combine(SettingsManager.LibraryDir, \"Assets\", \"nodejs\", \"bin\")}\";\n        }\n\n        if (Compat.IsWindows)\n        {\n            path += $\"{Compat.PathDelimiter}{Environment.GetFolderPath(Environment.SpecialFolder.System)}\";\n        }\n\n        return env.SetItem(\"PATH\", path);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/KohyaSs.cs",
    "content": "﻿using System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, KohyaSs>(Duplicate = DuplicateStrategy.Append)]\npublic class KohyaSs(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyRunner runner,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"kohya_ss\";\n    public override string DisplayName { get; set; } = \"kohya_ss\";\n    public override string Author => \"bmaltais\";\n    public override string Blurb => \"A Windows-focused Gradio GUI for Kohya's Stable Diffusion trainers\";\n    public override string LicenseType => \"Apache-2.0\";\n    public override string LicenseUrl => \"https://github.com/bmaltais/kohya_ss/blob/master/LICENSE.md\";\n    public override string LaunchCommand => \"kohya_gui.py\";\n    public override Uri PreviewImageUri => new(\"https://cdn.lykos.ai/sm/packages/kohyass/preview.webp\");\n    public override string OutputFolderName => string.Empty;\n    public override bool IsCompatible => HardwareHelper.HasNvidiaGpu();\n\n    public override TorchIndex GetRecommendedTorchVersion() => TorchIndex.Cuda;\n\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.UltraNightmare;\n    public override PackageType PackageType => PackageType.SdTraining;\n    public override bool OfferInOneClickInstaller => false;\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;\n    public override IEnumerable<TorchIndex> AvailableTorchIndices => [TorchIndex.Cuda];\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods => [SharedFolderMethod.None];\n    public override IEnumerable<PackagePrerequisite> Prerequisites =>\n        base.Prerequisites.Concat([PackagePrerequisite.Tkinter]);\n\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new LaunchOptionDefinition\n            {\n                Name = \"Listen Address\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"127.0.0.1\",\n                Options = [\"--listen\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                Options = [\"--port\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Skip Requirements Verification\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--noverify\"],\n                InitialValue = true,\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Username\",\n                Type = LaunchOptionType.String,\n                Options = [\"--username\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Password\",\n                Type = LaunchOptionType.String,\n                Options = [\"--password\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Auto-Launch Browser\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--inbrowser\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Share\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--share\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Headless\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--headless\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Language\",\n                Type = LaunchOptionType.String,\n                Options = [\"--language\"],\n            },\n            LaunchOptionDefinition.Extras,\n        ];\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, \"Updating submodules\", isIndeterminate: true));\n        await PrerequisiteHelper\n            .RunGit(\n                [\"submodule\", \"update\", \"--init\", \"--recursive\", \"--quiet\"],\n                onConsoleOutput,\n                installLocation\n            )\n            .ConfigureAwait(false);\n\n        if (Compat.IsWindows)\n        {\n            await PrerequisiteHelper.FixGitLongPaths().ConfigureAwait(false);\n        }\n\n        progress?.Report(new ProgressReport(-1f, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        // --- Platform-Specific Installation ---\n        if (Compat.IsLinux)\n        {\n            // Linux path is too custom for the orchestrator, so it remains as is.\n            await venvRunner\n                .PipInstall(\"rich packaging setuptools uv\", onConsoleOutput)\n                .ConfigureAwait(false);\n            await venvRunner\n                .CustomInstall(\n                    [\n                        \"setup/setup_linux.py\",\n                        \"--platform-requirements-file=requirements_linux.txt\",\n                        \"--no_run_accelerate\",\n                    ],\n                    onConsoleOutput\n                )\n                .ConfigureAwait(false);\n        }\n        else if (Compat.IsWindows)\n        {\n            // Windows path is a perfect fit for the orchestrator.\n            var isLegacyNvidia =\n                SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu()\n                ?? HardwareHelper.HasLegacyNvidiaGpu();\n\n            var config = new PipInstallConfig\n            {\n                PrePipInstallArgs = [\"rich\", \"packaging\", \"setuptools\", \"uv\"],\n                RequirementsFilePaths = [\"requirements_windows.txt\"],\n                // Exclude torch ecosystem (default) AND the specific bitsandbytes version\n                RequirementsExcludePattern =\n                    \"(torch|torchvision|torchaudio|xformers|bitsandbytes==0\\\\.44\\\\.0)\",\n                TorchaudioVersion = \" \",\n                XformersVersion = \" \",\n                CudaIndex = isLegacyNvidia ? \"cu126\" : \"cu128\",\n                // Add back the generic bitsandbytes and the specific numpy version\n                ExtraPipArgs = [\"bitsandbytes\"],\n                PostInstallPipArgs = [\"numpy==1.26.4\"],\n            };\n\n            await StandardPipInstallProcessAsync(\n                    venvRunner,\n                    options,\n                    installedPackage,\n                    config,\n                    onConsoleOutput,\n                    progress,\n                    cancellationToken\n                )\n                .ConfigureAwait(false);\n        }\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (!s.Text.Contains(\"Running on\", StringComparison.OrdinalIgnoreCase))\n                return;\n\n            var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n            var match = regex.Match(s.Text);\n            if (!match.Success)\n                return;\n\n            WebUrl = match.Value;\n            OnStartupComplete(WebUrl);\n        }\n\n        VenvRunner.RunDetached(\n            [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],\n            HandleConsoleOutput,\n            OnExit\n        );\n    }\n\n    public override Dictionary<SharedFolderType, IReadOnlyList<string>>? SharedFolders { get; }\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders { get; }\n\n    public override string MainBranch => \"master\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Mashb1tFooocus.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, Mashb1tFooocus>(Duplicate = DuplicateStrategy.Append)]\npublic class Mashb1tFooocus(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : Fooocus(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"mashb1t-fooocus\";\n    public override string Author => \"mashb1t\";\n    public override string RepositoryName => \"Fooocus\";\n    public override string DisplayName { get; set; } = \"Fooocus - mashb1t's 1-Up Edition\";\n\n    public override string Blurb =>\n        \"The purpose of this fork is to add new features / fix bugs and contribute back to Fooocus.\";\n\n    public override string LicenseUrl => \"https://github.com/mashb1t/Fooocus/blob/main/LICENSE\";\n\n    public override bool ShouldIgnoreReleases => false;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/OneTrainer.cs",
    "content": "﻿using System.Diagnostics;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, OneTrainer>(Duplicate = DuplicateStrategy.Append)]\npublic class OneTrainer(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"OneTrainer\";\n    public override string DisplayName { get; set; } = \"OneTrainer\";\n    public override string Author => \"Nerogar\";\n    public override string Blurb =>\n        \"OneTrainer is a one-stop solution for all your stable diffusion training needs\";\n    public override string LicenseType => \"AGPL-3.0\";\n    public override string LicenseUrl => \"https://github.com/Nerogar/OneTrainer/blob/master/LICENSE.txt\";\n    public override string LaunchCommand => \"scripts/train_ui.py\";\n\n    public override Uri PreviewImageUri =>\n        new(\"https://github.com/Nerogar/OneTrainer/blob/master/resources/icons/icon.png?raw=true\");\n\n    public override string OutputFolderName => string.Empty;\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;\n    public override IEnumerable<TorchIndex> AvailableTorchIndices => [TorchIndex.Cuda, TorchIndex.Rocm];\n    public override PackageType PackageType => PackageType.SdTraining;\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        new[] { SharedFolderMethod.None };\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Nightmare;\n    public override bool OfferInOneClickInstaller => false;\n    public override bool ShouldIgnoreReleases => true;\n    public override IEnumerable<PackagePrerequisite> Prerequisites =>\n        base.Prerequisites.Concat([PackagePrerequisite.Tkinter]);\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(-1f, \"Installing requirements\", isIndeterminate: true));\n        var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n        var requirementsFileName = torchVersion switch\n        {\n            TorchIndex.Cuda => \"requirements-cuda.txt\",\n            TorchIndex.Rocm => \"requirements-rocm.txt\",\n            _ => \"requirements-default.txt\",\n        };\n\n        await venvRunner.PipInstall([\"-r\", requirementsFileName], onConsoleOutput).ConfigureAwait(false);\n\n        var requirementsGlobal = new FilePath(installLocation, \"requirements-global.txt\");\n        var pipArgs = new PipInstallArgs().WithParsedFromRequirementsTxt(\n            (await requirementsGlobal.ReadAllTextAsync(cancellationToken).ConfigureAwait(false)).Replace(\n                \"-e \",\n                \"\"\n            ),\n            \"scipy==1.15.1; sys_platform != 'win32'\"\n        );\n\n        if (installedPackage.PipOverrides != null)\n        {\n            pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);\n        }\n\n        await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        VenvRunner.RunDetached(\n            [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],\n            onConsoleOutput,\n            OnExit\n        );\n    }\n\n    public override List<LaunchOptionDefinition> LaunchOptions => [LaunchOptionDefinition.Extras];\n    public override Dictionary<SharedFolderType, IReadOnlyList<string>>? SharedFolders { get; }\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders { get; }\n    public override string MainBranch => \"master\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Options/DownloadPackageOptions.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages;\n\npublic class DownloadPackageOptions\n{\n    public required DownloadPackageVersionOptions VersionOptions { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Options/InstallPackageOptions.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages;\n\npublic class InstallPackageOptions\n{\n    public DownloadPackageVersionOptions VersionOptions { get; init; } = new();\n\n    public PythonPackageOptions PythonOptions { get; init; } = new();\n\n    public SharedFolderMethod SharedFolderMethod { get; init; } = SharedFolderMethod.None;\n\n    public bool IsUpdate { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Options/PythonPackageOptions.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\npublic class PythonPackageOptions\n{\n    [JsonConverter(typeof(JsonStringEnumConverter<TorchIndex>))]\n    public TorchIndex? TorchIndex { get; set; }\n\n    public string? TorchVersion { get; set; }\n\n    public PyVersion? PythonVersion { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Options/RunPackageOptions.cs",
    "content": "﻿using StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\npublic class RunPackageOptions\n{\n    public string? Command { get; set; }\n\n    public ProcessArgs Arguments { get; set; } = [];\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Options/UpdatePackageOptions.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages;\n\npublic class UpdatePackageOptions\n{\n    public DownloadPackageVersionOptions VersionOptions { get; init; } = new();\n\n    public PythonPackageOptions PythonOptions { get; init; } = new();\n\n    public InstallPackageOptions AsInstallOptions()\n    {\n        return new InstallPackageOptions\n        {\n            VersionOptions = VersionOptions,\n            PythonOptions = PythonOptions,\n            IsUpdate = true\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/PackageVersionOptions.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages;\n\npublic class PackageVersionOptions\n{\n    public IEnumerable<PackageVersion>? AvailableVersions { get; set; } =\n        Enumerable.Empty<PackageVersion>();\n    public IEnumerable<PackageVersion>? AvailableBranches { get; set; } =\n        Enumerable.Empty<PackageVersion>();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/PackageVulnerability.cs",
    "content": "using System;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n/// <summary>\n/// Represents a security vulnerability in a package\n/// </summary>\npublic class PackageVulnerability\n{\n    /// <summary>\n    /// Unique identifier for the vulnerability (e.g. CVE number)\n    /// </summary>\n    public string Id { get; set; } = string.Empty;\n\n    /// <summary>\n    /// Short title describing the vulnerability\n    /// </summary>\n    public string Title { get; set; } = string.Empty;\n\n    /// <summary>\n    /// Detailed description of the vulnerability\n    /// </summary>\n    public string Description { get; set; } = string.Empty;\n\n    /// <summary>\n    /// URL with more information about the vulnerability\n    /// </summary>\n    public Uri? InfoUrl { get; set; }\n\n    /// <summary>\n    /// Severity level of the vulnerability\n    /// </summary>\n    public VulnerabilitySeverity Severity { get; set; }\n\n    /// <summary>\n    /// When this vulnerability was discovered/published\n    /// </summary>\n    public DateTimeOffset PublishedDate { get; set; }\n\n    /// <summary>\n    /// Version ranges affected by this vulnerability\n    /// </summary>\n    public string[] AffectedVersions { get; set; } = Array.Empty<string>();\n\n    /// <summary>\n    /// Version that fixes this vulnerability, if available\n    /// </summary>\n    public string? FixedInVersion { get; set; }\n}\n\n/// <summary>\n/// Severity levels for package vulnerabilities\n/// </summary>\npublic enum VulnerabilitySeverity\n{\n    Low,\n    Medium,\n    High,\n    Critical\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/PipInstallConfig.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages;\n\n/// <summary>\n/// Configuration for the standard pip installation process.\n/// </summary>\npublic record PipInstallConfig\n{\n    public IEnumerable<string> RequirementsFilePaths { get; init; } = [];\n    public string RequirementsExcludePattern { get; init; } = \"(torch|torchvision|torchaudio|xformers)\";\n    public IEnumerable<string> PrePipInstallArgs { get; init; } = [];\n    public IEnumerable<string> ExtraPipArgs { get; init; } = [];\n    public IEnumerable<string> PostInstallPipArgs { get; init; } = [];\n    public string TorchVersion { get; init; } = \"\";\n    public string TorchvisionVersion { get; init; } = \"\";\n    public string TorchaudioVersion { get; init; } = \"\";\n    public string XformersVersion { get; init; } = \"\";\n    public string CudaIndex { get; init; } = \"cu130\";\n    public string RocmIndex { get; init; } = \"rocm6.4\";\n    public bool ForceReinstallTorch { get; init; } = true;\n    public bool UpgradePackages { get; init; } = false;\n    public bool SkipTorchInstall { get; init; } = false;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Reforge.cs",
    "content": "﻿using System.Collections.Immutable;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, Reforge>(Duplicate = DuplicateStrategy.Append)]\npublic class Reforge(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : SDWebForge(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"reforge\";\n    public override string Author => \"Panchovix\";\n    public override string RepositoryName => \"stable-diffusion-webui-reForge\";\n    public override string DisplayName { get; set; } = \"Stable Diffusion WebUI reForge\";\n    public override string Blurb =>\n        \"Stable Diffusion WebUI reForge is a platform on top of Stable Diffusion WebUI (based on Gradio) to make development easier, optimize resource management, speed up inference, and study experimental features.\";\n    public override string LicenseUrl =>\n        \"https://github.com/Panchovix/stable-diffusion-webui-reForge/blob/main/LICENSE.txt\";\n    public override Uri PreviewImageUri => new(\"https://cdn.lykos.ai/sm/packages/reforge/preview.webp\");\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Recommended;\n    public override bool OfferInOneClickInstaller => true;\n    public override PackageType PackageType => PackageType.SdInference;\n\n    protected override ImmutableDictionary<string, string> GetEnvVars(\n        ImmutableDictionary<string, string> env\n    ) => env;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, RuinedFooocus>(Duplicate = DuplicateStrategy.Append)]\npublic class RuinedFooocus(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : Fooocus(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"RuinedFooocus\";\n    public override string DisplayName { get; set; } = \"RuinedFooocus\";\n    public override string Author => \"runew0lf\";\n    public override string Blurb =>\n        \"RuinedFooocus combines the best aspects of Stable Diffusion and Midjourney into one seamless, cutting-edge experience\";\n    public override string LicenseUrl => \"https://github.com/runew0lf/RuinedFooocus/blob/main/LICENSE\";\n    public override Uri PreviewImageUri =>\n        new(\"https://raw.githubusercontent.com/runew0lf/pmmconfigs/main/RuinedFooocus_ss.png\");\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Expert;\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        [SharedFolderMethod.Symlink, SharedFolderMethod.None];\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;\n\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new()\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                Description = \"Sets the listen port\",\n                Options = { \"--port\" },\n            },\n            new()\n            {\n                Name = \"Share\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to share on Gradio\",\n                Options = { \"--share\" },\n            },\n            new()\n            {\n                Name = \"Listen\",\n                Type = LaunchOptionType.String,\n                Description = \"Set the listen interface\",\n                Options = { \"--listen\" },\n            },\n            new()\n            {\n                Name = \"Auth\",\n                Type = LaunchOptionType.String,\n                Description = \"Set credentials username/password\",\n                Options = { \"--auth\" },\n            },\n            new()\n            {\n                Name = \"No Browser\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Do not launch in browser\",\n                Options = { \"--nobrowser\" },\n            },\n            LaunchOptionDefinition.Extras,\n        ];\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n\n        if (torchVersion == TorchIndex.Cuda)\n        {\n            await using var venvRunner = await SetupVenvPure(\n                    installLocation,\n                    forceRecreate: true,\n                    pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)\n                )\n                .ConfigureAwait(false);\n\n            progress?.Report(new ProgressReport(-1f, \"Installing requirements...\", isIndeterminate: true));\n\n            var isLegacyNvidia =\n                SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu()\n                ?? HardwareHelper.HasLegacyNvidiaGpu();\n            var torchExtraIndex = isLegacyNvidia ? \"cu126\" : \"cu128\";\n\n            var requirements = new FilePath(installLocation, \"requirements_versions.txt\");\n            var pipArgs = new PipInstallArgs()\n                .WithTorchExtraIndex(torchExtraIndex)\n                .WithParsedFromRequirementsTxt(\n                    await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),\n                    \"--extra-index-url.*|--index-url.*\"\n                );\n\n            if (installedPackage.PipOverrides != null)\n            {\n                pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);\n            }\n\n            await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);\n        }\n        else\n        {\n            await base.InstallPackage(\n                    installLocation,\n                    installedPackage,\n                    options,\n                    progress,\n                    onConsoleOutput,\n                    cancellationToken\n                )\n                .ConfigureAwait(false);\n        }\n\n        // Create output folder since it's not created by default\n        var outputFolder = new DirectoryPath(installLocation, OutputFolderName);\n        outputFolder.Create();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/SDWebForge.cs",
    "content": "﻿using System.Text;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, SDWebForge>(Duplicate = DuplicateStrategy.Append)]\npublic class SDWebForge(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : A3WebUI(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"stable-diffusion-webui-forge\";\n    public override string DisplayName { get; set; } = \"Stable Diffusion WebUI Forge\";\n    public override string Author => \"lllyasviel\";\n\n    public override string Blurb =>\n        \"Stable Diffusion WebUI Forge is a platform on top of Stable Diffusion WebUI (based on Gradio) to make development easier, optimize resource management, and speed up inference.\";\n\n    public override string LicenseUrl =>\n        \"https://github.com/lllyasviel/stable-diffusion-webui-forge/blob/main/LICENSE.txt\";\n\n    public override Uri PreviewImageUri => new(\"https://cdn.lykos.ai/sm/packages/sdwebforge/preview.webp\");\n\n    public override string MainBranch => \"main\";\n    public override bool ShouldIgnoreReleases => true;\n    public override IPackageExtensionManager ExtensionManager => null;\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Simple;\n    public override PackageType PackageType => PackageType.Legacy;\n\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new()\n            {\n                Name = \"Host\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"localhost\",\n                Options = [\"--server-name\"],\n            },\n            new()\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"7860\",\n                Options = [\"--port\"],\n            },\n            new()\n            {\n                Name = \"Share\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to share on Gradio\",\n                Options = { \"--share\" },\n            },\n            new()\n            {\n                Name = \"Pin Shared Memory\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--pin-shared-memory\" },\n                InitialValue =\n                    HardwareHelper.HasNvidiaGpu()\n                    && (\n                        SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() is false\n                        || !HardwareHelper.HasLegacyNvidiaGpu()\n                    ),\n            },\n            new()\n            {\n                Name = \"CUDA Malloc\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--cuda-malloc\" },\n                InitialValue =\n                    HardwareHelper.HasNvidiaGpu()\n                    && (\n                        SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() is false\n                        || !HardwareHelper.HasLegacyNvidiaGpu()\n                    ),\n            },\n            new()\n            {\n                Name = \"CUDA Stream\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--cuda-stream\" },\n                InitialValue =\n                    HardwareHelper.HasNvidiaGpu()\n                    && (\n                        SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() is false\n                        || !HardwareHelper.HasLegacyNvidiaGpu()\n                    ),\n            },\n            new()\n            {\n                Name = \"Skip Install\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = true,\n                Options = [\"--skip-install\"],\n            },\n            new()\n            {\n                Name = \"Always Offload from VRAM\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--always-offload-from-vram\"],\n            },\n            new()\n            {\n                Name = \"Always GPU\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--always-gpu\"],\n            },\n            new()\n            {\n                Name = \"Always CPU\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--always-cpu\"],\n            },\n            new()\n            {\n                Name = \"Skip Torch CUDA Test\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = Compat.IsMacOS,\n                Options = [\"--skip-torch-cuda-test\"],\n            },\n            new()\n            {\n                Name = \"No half-precision VAE\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = Compat.IsMacOS,\n                Options = [\"--no-half-vae\"],\n            },\n            LaunchOptionDefinition.Extras,\n        ];\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        [TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.Rocm, TorchIndex.Mps];\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        // Dynamically discover all requirements files\n        var requirementsPaths = new List<string> { \"requirements_versions.txt\" };\n        var extensionsBuiltinDir = new DirectoryPath(installLocation, \"extensions-builtin\");\n        if (extensionsBuiltinDir.Exists)\n        {\n            requirementsPaths.AddRange(\n                extensionsBuiltinDir\n                    .EnumerateFiles(\"requirements.txt\", EnumerationOptionConstants.AllDirectories)\n                    .Select(f => Path.GetRelativePath(installLocation, f.ToString()))\n            );\n        }\n\n        var torchIndex = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n        var isBlackwell =\n            torchIndex is TorchIndex.Cuda\n            && (SettingsManager.Settings.PreferredGpu?.IsBlackwellGpu() ?? HardwareHelper.HasBlackwellGpu());\n\n        var config = new PipInstallConfig\n        {\n            PrePipInstallArgs = [\"joblib\"],\n            RequirementsFilePaths = requirementsPaths,\n            TorchVersion = \"\",\n            TorchvisionVersion = \"\",\n            CudaIndex = isBlackwell ? \"cu128\" : \"cu126\",\n            RocmIndex = \"rocm7.1\",\n            ExtraPipArgs =\n            [\n                \"https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip\",\n            ],\n            PostInstallPipArgs = [\"numpy==1.26.4\"],\n        };\n\n        await StandardPipInstallProcessAsync(\n                venvRunner,\n                options,\n                installedPackage,\n                config,\n                onConsoleOutput,\n                progress,\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(1f, \"Install complete\", isIndeterminate: false));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Sdfx.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Text.Json;\nusing System.Text.Json.Nodes;\nusing System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages.Config;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, Sdfx>(Duplicate = DuplicateStrategy.Append)]\npublic class Sdfx(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"sdfx\";\n    public override string DisplayName { get; set; } = \"SDFX\";\n    public override string Author => \"sdfxai\";\n    public override string Blurb =>\n        \"The ultimate no-code platform to build and share AI apps with beautiful UI.\";\n    public override string LicenseType => \"AGPL-3.0\";\n    public override string LicenseUrl => \"https://github.com/sdfxai/sdfx/blob/main/LICENSE\";\n    public override string LaunchCommand => \"setup.py\";\n    public override Uri PreviewImageUri =>\n        new(\"https://github.com/sdfxai/sdfx/raw/main/docs/static/screen-sdfx.png\");\n    public override string OutputFolderName => Path.Combine(\"data\", \"media\", \"output\");\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        [TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.DirectMl, TorchIndex.Rocm, TorchIndex.Mps];\n\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Impossible;\n    public override bool OfferInOneClickInstaller => false;\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;\n    public override List<LaunchOptionDefinition> LaunchOptions => [LaunchOptionDefinition.Extras];\n    public override string Disclaimer => \"This package may no longer receive updates from its author.\";\n    public override PackageType PackageType => PackageType.Legacy;\n\n    public override SharedFolderLayout SharedFolderLayout =>\n        new()\n        {\n            RelativeConfigPath = \"sdfx.config.json\",\n            ConfigFileType = ConfigFileType.Json,\n            Rules =\n            [\n                // Assuming JSON keys are top-level, adjust ConfigDocumentPaths if nested (e.g., \"paths.models.checkpoints\")\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.StableDiffusion],\n                    TargetRelativePaths = [\"data/models/checkpoints\"],\n                    ConfigDocumentPaths = [\"path.models.checkpoints\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Diffusers],\n                    TargetRelativePaths = [\"data/models/diffusers\"],\n                    ConfigDocumentPaths = [\"path.models.diffusers\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.VAE],\n                    TargetRelativePaths = [\"data/models/vae\"],\n                    ConfigDocumentPaths = [\"path.models.vae\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS],\n                    TargetRelativePaths = [\"data/models/loras\"],\n                    ConfigDocumentPaths = [\"path.models.loras\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Embeddings],\n                    TargetRelativePaths = [\"data/models/embeddings\"],\n                    ConfigDocumentPaths = [\"path.models.embeddings\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Hypernetwork],\n                    TargetRelativePaths = [\"data/models/hypernetworks\"],\n                    ConfigDocumentPaths = [\"path.models.hypernetworks\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes =\n                    [\n                        SharedFolderType.ESRGAN,\n                        SharedFolderType.RealESRGAN,\n                        SharedFolderType.SwinIR,\n                    ],\n                    TargetRelativePaths = [\"data/models/upscale_models\"],\n                    ConfigDocumentPaths = [\"path.models.upscale_models\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.TextEncoders],\n                    TargetRelativePaths = [\"data/models/clip\"],\n                    ConfigDocumentPaths = [\"path.models.clip\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.ClipVision],\n                    TargetRelativePaths = [\"data/models/clip_vision\"],\n                    ConfigDocumentPaths = [\"path.models.clip_vision\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.ControlNet, SharedFolderType.T2IAdapter],\n                    TargetRelativePaths = [\"data/models/controlnet\"],\n                    ConfigDocumentPaths = [\"path.models.controlnet\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.GLIGEN],\n                    TargetRelativePaths = [\"data/models/gligen\"],\n                    ConfigDocumentPaths = [\"path.models.gligen\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.ApproxVAE],\n                    TargetRelativePaths = [\"data/models/vae_approx\"],\n                    ConfigDocumentPaths = [\"path.models.vae_approx\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes =\n                    [\n                        SharedFolderType.IpAdapter,\n                        SharedFolderType.IpAdapters15,\n                        SharedFolderType.IpAdaptersXl,\n                    ],\n                    TargetRelativePaths = [\"data/models/ipadapter\"],\n                    ConfigDocumentPaths = [\"path.models.ipadapter\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.PromptExpansion],\n                    TargetRelativePaths = [\"data/models/prompt_expansion\"],\n                    ConfigDocumentPaths = [\"path.models.prompt_expansion\"],\n                },\n            ],\n        };\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>> SharedOutputFolders =>\n        new() { [SharedOutputType.Text2Img] = new[] { \"data/media/output\" } };\n    public override string MainBranch => \"main\";\n    public override bool ShouldIgnoreReleases => true;\n\n    public override IEnumerable<PackagePrerequisite> Prerequisites =>\n        [\n            PackagePrerequisite.Python310,\n            PackagePrerequisite.VcRedist,\n            PackagePrerequisite.Git,\n            PackagePrerequisite.Node,\n        ];\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1, \"Setting up venv\", isIndeterminate: true));\n        // Setup venv\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n        venvRunner.UpdateEnvironmentVariables(GetEnvVars);\n\n        progress?.Report(\n            new ProgressReport(-1f, \"Installing Package Requirements...\", isIndeterminate: true)\n        );\n\n        var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n\n        var gpuArg = torchVersion switch\n        {\n            TorchIndex.Cuda => \"--nvidia\",\n            TorchIndex.Rocm => \"--amd\",\n            TorchIndex.DirectMl => \"--directml\",\n            TorchIndex.Cpu => \"--cpu\",\n            TorchIndex.Mps => \"--mac\",\n            _ => throw new NotSupportedException($\"Torch version {torchVersion} is not supported.\"),\n        };\n\n        await venvRunner\n            .CustomInstall([\"setup.py\", \"--install\", gpuArg], onConsoleOutput)\n            .ConfigureAwait(false);\n\n        if (installedPackage.PipOverrides != null)\n        {\n            var pipArgs = new PipInstallArgs().WithUserOverrides(installedPackage.PipOverrides);\n            await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);\n        }\n\n        progress?.Report(new ProgressReport(1, \"Installed Package Requirements\", isIndeterminate: false));\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var venvRunner = await SetupVenv(\n                installLocation,\n                pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)\n            )\n            .ConfigureAwait(false);\n        venvRunner.UpdateEnvironmentVariables(GetEnvVars);\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (!s.Text.Contains(\"Running on\", StringComparison.OrdinalIgnoreCase))\n                return;\n\n            var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n            var match = regex.Match(s.Text);\n            if (!match.Success)\n                return;\n\n            WebUrl = match.Value;\n            OnStartupComplete(WebUrl);\n        }\n\n        venvRunner.RunDetached(\n            [Path.Combine(installLocation, options.Command ?? LaunchCommand), \"--run\", .. options.Arguments],\n            HandleConsoleOutput,\n            OnExit\n        );\n\n        // Cuz node was getting detached on process exit\n        if (Compat.IsWindows)\n        {\n            ProcessTracker.AttachExitHandlerJobToProcess(venvRunner.Process);\n        }\n    }\n\n    private ImmutableDictionary<string, string> GetEnvVars(ImmutableDictionary<string, string> env)\n    {\n        var pathBuilder = new EnvPathBuilder();\n\n        if (env.TryGetValue(\"PATH\", out var value))\n        {\n            pathBuilder.AddPath(value);\n        }\n\n        pathBuilder.AddPath(\n            Compat.IsWindows\n                ? Environment.GetFolderPath(Environment.SpecialFolder.System)\n                : Path.Combine(SettingsManager.LibraryDir, \"Assets\", \"nodejs-18\", \"bin\")\n        );\n\n        pathBuilder.AddPath(Path.Combine(SettingsManager.LibraryDir, \"Assets\", \"nodejs-18\"));\n\n        return env.SetItem(\"PATH\", pathBuilder.ToString());\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/SharedFolderLayout.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.ComponentModel;\nusing StabilityMatrix.Core.Models.Packages.Config;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[Localizable(false)]\npublic record SharedFolderLayout\n{\n    /// <summary>\n    /// Optional config file path, relative from package installation directory\n    /// </summary>\n    public string? RelativeConfigPath { get; set; }\n\n    public ConfigFileType? ConfigFileType { get; set; }\n\n    public ConfigSharingOptions ConfigSharingOptions { get; set; } = ConfigSharingOptions.Default;\n\n    public IImmutableList<SharedFolderLayoutRule> Rules { get; set; } = [];\n\n    public Dictionary<string, SharedFolderLayoutRule> GetRulesByConfigPath()\n    {\n        // Dictionary of config path to rule\n        var configPathToRule = new Dictionary<string, SharedFolderLayoutRule>();\n\n        foreach (var rule in Rules)\n        {\n            // Ignore rules without config paths\n            if (rule.ConfigDocumentPaths is not { Length: > 0 } configPaths)\n            {\n                continue;\n            }\n\n            foreach (var configPath in configPaths)\n            {\n                // Get or create rule\n                var existingRule = configPathToRule.GetValueOrDefault(\n                    configPath,\n                    new SharedFolderLayoutRule()\n                );\n\n                // Add unique\n                configPathToRule[configPath] = existingRule.Union(rule);\n            }\n        }\n\n        return configPathToRule;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/SharedFolderLayoutRule.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Packages;\n\npublic readonly record struct SharedFolderLayoutRule()\n{\n    public SharedFolderType[] SourceTypes { get; init; } = [];\n\n    public string[] TargetRelativePaths { get; init; } = [];\n\n    public string[] ConfigDocumentPaths { get; init; } = [];\n\n    /// <summary>\n    /// For rules that use the root models folder instead of a specific SharedFolderType\n    /// </summary>\n    public bool IsRoot { get; init; }\n\n    /// <summary>\n    /// Optional sub-path from all source types to the target path.\n    /// </summary>\n    public string? SourceSubPath { get; init; }\n\n    public SharedFolderLayoutRule Union(SharedFolderLayoutRule other)\n    {\n        return this with\n        {\n            SourceTypes = SourceTypes.Union(other.SourceTypes).ToArray(),\n            TargetRelativePaths = TargetRelativePaths.Union(other.TargetRelativePaths).ToArray(),\n            ConfigDocumentPaths = ConfigDocumentPaths.Union(other.ConfigDocumentPaths).ToArray(),\n            IsRoot = IsRoot || other.IsRoot,\n            SourceSubPath = SourceSubPath ?? other.SourceSubPath\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/SimpleSDXL.cs",
    "content": "﻿using Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, SimpleSDXL>(Duplicate = DuplicateStrategy.Append)]\npublic class SimpleSDXL(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : Fooocus(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"SimpleSDXL\";\n    public override string DisplayName { get; set; } = \"SimpleSDXL\";\n    public override string Author => \"metercai\";\n    public override string Blurb =>\n        \"Enhanced version of Fooocus for SDXL, more suitable for Chinese and Cloud. Supports Flux.\";\n    public override string LicenseUrl => \"https://github.com/metercai/SimpleSDXL/blob/SimpleSDXL/LICENSE\";\n    public override Uri PreviewImageUri => new(\"https://cdn.lykos.ai/sm/packages/simplesdxl/preview.webp\");\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Expert;\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        [SharedFolderMethod.Configuration, SharedFolderMethod.Symlink, SharedFolderMethod.None];\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;\n    public override string MainBranch => \"SimpleSDXL\";\n    public override IEnumerable<TorchIndex> AvailableTorchIndices => [TorchIndex.Cuda];\n    public override bool IsCompatible => HardwareHelper.HasNvidiaGpu();\n\n    public override IReadOnlyList<PackageVulnerability> KnownVulnerabilities =>\n        [\n            new()\n            {\n                Id = \"GHSA-qq8j-phpf-c63j\",\n                Title = \"Undisclosed Data Collection and Remote Access in simpleai_base Dependency\",\n                Description =\n                    \"SimpleSDXL depends on simpleai_base which contains compiled Rust code with:\\n\"\n                    + \"- Undisclosed remote access functionality using rathole\\n\"\n                    + \"- Hidden system information gathering via concealed executable calls\\n\"\n                    + \"- Covert data upload to tokentm.net (blockchain-associated domain)\\n\"\n                    + \"- Undisclosed VPN functionality pointing to servers blocked by Chinese authorities\\n\\n\"\n                    + \"This poses significant security and privacy risks as system information is uploaded without consent \"\n                    + \"and the compiled nature of the code means the full extent of the remote access capabilities cannot be verified.\",\n                Severity = VulnerabilitySeverity.Critical,\n                PublishedDate = DateTimeOffset.Parse(\"2025-01-11\"),\n                InfoUrl = new Uri(\"https://github.com/metercai/SimpleSDXL/issues/97\"),\n                AffectedVersions = [\"*\"], // Affects all versions\n            },\n        ];\n\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new()\n            {\n                Name = \"Preset\",\n                Description = \"Apply specified UI preset.\",\n                Type = LaunchOptionType.Bool,\n                Options =\n                {\n                    \"--preset anime\",\n                    \"--preset realistic\",\n                    \"--preset Flux\",\n                    \"--preset Kolors\",\n                    \"--preset pony_v6\",\n                },\n            },\n            new()\n            {\n                Name = \"Language\",\n                Type = LaunchOptionType.String,\n                Description = \"Translate UI using json files in [language] folder.\",\n                InitialValue = \"en\",\n                Options = { \"--language\" },\n            },\n            new()\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                Description = \"Sets the listen port\",\n                Options = { \"--port\" },\n            },\n            new()\n            {\n                Name = \"Share\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to share on Gradio\",\n                Options = { \"--share\" },\n            },\n            new()\n            {\n                Name = \"Listen\",\n                Type = LaunchOptionType.String,\n                Description = \"Set the listen interface\",\n                Options = { \"--listen\" },\n            },\n            new()\n            {\n                Name = \"Disable preset download\",\n                Description = \"Disables downloading models for presets\",\n                DefaultValue = false,\n                Type = LaunchOptionType.Bool,\n                Options = { \"--disable-preset-download\" },\n            },\n            new()\n            {\n                Name = \"Theme\",\n                Description = \"Launches the UI with light or dark theme\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"dark\",\n                Options = { \"--theme\" },\n            },\n            new()\n            {\n                Name = \"Disable offload from VRAM\",\n                Description =\n                    \"Force loading models to vram when the unload can be avoided. Some Mac users may need this.\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = Compat.IsMacOS,\n                Options = { \"--disable-offload-from-vram\" },\n            },\n            new()\n            {\n                Name = \"Disable image log\",\n                Description = \"Prevent writing images and logs to the outputs folder.\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--disable-image-log\" },\n            },\n            new()\n            {\n                Name = \"Disable metadata\",\n                Description = \"Disables saving metadata to images.\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--disable-metadata\" },\n            },\n            new()\n            {\n                Name = \"Disable enhance output sorting\",\n                Description = \"Disables enhance output sorting for final image gallery.\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--disable-enhance-output-sorting\" },\n            },\n            new()\n            {\n                Name = \"Enable auto describe image\",\n                Description = \"Enables automatic description of uov and enhance image when prompt is empty\",\n                DefaultValue = true,\n                Type = LaunchOptionType.Bool,\n                Options = { \"--enable-auto-describe-image\" },\n            },\n            new()\n            {\n                Name = \"Always download new models\",\n                Description = \"Always download newer models.\",\n                DefaultValue = false,\n                Type = LaunchOptionType.Bool,\n                Options = { \"--always-download-new-model\" },\n            },\n            new()\n            {\n                Name = \"Disable comfyd\",\n                Description = \"Disable auto start comfyd server at launch\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--disable-comfyd\" },\n            },\n            LaunchOptionDefinition.Extras,\n        ];\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                forceRecreate: true,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(-1f, \"Installing requirements...\", isIndeterminate: true));\n        // Get necessary dependencies\n        await venvRunner.PipInstall(\"--upgrade pip\", onConsoleOutput).ConfigureAwait(false);\n        await venvRunner.PipInstall(\"nvidia-pyindex pygit2\", onConsoleOutput).ConfigureAwait(false);\n        await venvRunner.PipInstall(\"facexlib cpm_kernels\", onConsoleOutput).ConfigureAwait(false);\n\n        if (Compat.IsWindows)\n        {\n            // Download and Install pre-built insightface\n            const string wheelUrl =\n                \"https://github.com/Gourieff/Assets/raw/main/Insightface/insightface-0.7.3-cp310-cp310-win_amd64.whl\";\n\n            var wheelPath = new FilePath(installLocation, \"insightface-0.7.3-cp310-cp310-win_amd64.whl\");\n            await DownloadService\n                .DownloadToFileAsync(wheelUrl, wheelPath, cancellationToken: cancellationToken)\n                .ConfigureAwait(false);\n            await venvRunner.PipInstall($\"{wheelPath}\", onConsoleOutput).ConfigureAwait(false);\n            await wheelPath.DeleteAsync(cancellationToken).ConfigureAwait(false);\n        }\n\n        var requirements = new FilePath(installLocation, \"requirements_versions.txt\");\n\n        var pipArgs = new PipInstallArgs()\n            .WithTorch(\"==2.3.1\")\n            .WithTorchVision(\"==0.18.1\")\n            .WithTorchAudio(\"==2.3.1\")\n            .WithTorchExtraIndex(\"cu121\")\n            .WithParsedFromRequirementsTxt(\n                await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),\n                \"torch\"\n            );\n\n        if (installedPackage.PipOverrides != null)\n        {\n            pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);\n        }\n\n        await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);\n\n        // Create output folder since it's not created by default\n        var outputFolder = new DirectoryPath(installLocation, OutputFolderName);\n        outputFolder.Create();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/StableDiffusionDirectMl.cs",
    "content": "﻿using Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, StableDiffusionDirectMl>(Duplicate = DuplicateStrategy.Append)]\npublic class StableDiffusionDirectMl(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : A3WebUI(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    public override string Name => \"stable-diffusion-webui-directml\";\n    public override string DisplayName { get; set; } = \"Stable Diffusion Web UI\";\n    public override string Author => \"lshqqytiger\";\n    public override string LicenseType => \"AGPL-3.0\";\n    public override string LicenseUrl =>\n        \"https://github.com/lshqqytiger/stable-diffusion-webui-directml/blob/master/LICENSE.txt\";\n    public override string Blurb => \"A fork of Automatic1111's Stable Diffusion WebUI with DirectML support\";\n    public override string LaunchCommand => \"launch.py\";\n    public override Uri PreviewImageUri =>\n        new(\"https://github.com/lshqqytiger/stable-diffusion-webui-directml/raw/master/screenshot.png\");\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;\n\n    public override TorchIndex GetRecommendedTorchVersion() =>\n        HardwareHelper.PreferDirectMLOrZluda() ? TorchIndex.DirectMl : base.GetRecommendedTorchVersion();\n\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Simple;\n\n    public override List<LaunchOptionDefinition> LaunchOptions\n    {\n        get\n        {\n            var baseLaunchOptions = base.LaunchOptions;\n            baseLaunchOptions.Insert(\n                0,\n                new LaunchOptionDefinition\n                {\n                    Name = \"Use DirectML\",\n                    Type = LaunchOptionType.Bool,\n                    InitialValue = HardwareHelper.PreferDirectMLOrZluda(),\n                    Options = [\"--use-directml\"],\n                }\n            );\n\n            return baseLaunchOptions;\n        }\n    }\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        new[] { TorchIndex.Cpu, TorchIndex.DirectMl };\n\n    public override bool ShouldIgnoreReleases => true;\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, \"Setting up venv\", isIndeterminate: true));\n        // Setup venv\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n        var pipArgs = new PipInstallArgs()\n            .WithTorch(\"==2.3.1\")\n            .WithTorchVision(\"==0.18.1\")\n            .AddArg(\"httpx==0.24.1\");\n\n        if (torchVersion == TorchIndex.DirectMl)\n        {\n            pipArgs = pipArgs.WithTorchDirectML();\n        }\n\n        // Install requirements file\n        progress?.Report(new ProgressReport(-1f, \"Installing Package Requirements\", isIndeterminate: true));\n        Logger.Info(\"Installing requirements_versions.txt\");\n\n        var requirements = new FilePath(installLocation, \"requirements_versions.txt\");\n        pipArgs = pipArgs.WithParsedFromRequirementsTxt(\n            await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),\n            excludePattern: \"torch\"\n        );\n\n        if (installedPackage.PipOverrides != null)\n        {\n            pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);\n        }\n\n        await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(1f, \"Install complete\", isIndeterminate: false));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text.Json;\nusing System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, StableDiffusionUx>(Duplicate = DuplicateStrategy.Append)]\npublic class StableDiffusionUx(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    public override string Name => \"stable-diffusion-webui-ux\";\n    public override string DisplayName { get; set; } = \"Stable Diffusion Web UI-UX\";\n    public override string Author => \"anapnoe\";\n    public override string LicenseType => \"AGPL-3.0\";\n    public override string LicenseUrl =>\n        \"https://github.com/anapnoe/stable-diffusion-webui-ux/blob/master/LICENSE.txt\";\n    public override string Blurb =>\n        \"A pixel perfect design, mobile friendly, customizable interface that adds accessibility, \"\n        + \"ease of use and extended functionallity to the stable diffusion web ui.\";\n    public override string LaunchCommand => \"launch.py\";\n    public override Uri PreviewImageUri =>\n        new(\"https://raw.githubusercontent.com/anapnoe/stable-diffusion-webui-ux/master/screenshot.png\");\n    public override string Disclaimer => \"This package may no longer receive updates from its author.\";\n\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;\n\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Impossible;\n\n    public override IPackageExtensionManager? ExtensionManager => new A3WebUiExtensionManager(this);\n    public override PackageType PackageType => PackageType.Legacy;\n\n    public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>\n        new()\n        {\n            [SharedFolderType.StableDiffusion] = new[] { \"models/Stable-diffusion\" },\n            [SharedFolderType.ESRGAN] = new[] { \"models/ESRGAN\" },\n            [SharedFolderType.RealESRGAN] = new[] { \"models/RealESRGAN\" },\n            [SharedFolderType.SwinIR] = new[] { \"models/SwinIR\" },\n            [SharedFolderType.Lora] = new[] { \"models/Lora\" },\n            [SharedFolderType.LyCORIS] = new[] { \"models/LyCORIS\" },\n            [SharedFolderType.ApproxVAE] = new[] { \"models/VAE-approx\" },\n            [SharedFolderType.VAE] = new[] { \"models/VAE\" },\n            [SharedFolderType.DeepDanbooru] = new[] { \"models/deepbooru\" },\n            [SharedFolderType.Karlo] = new[] { \"models/karlo\" },\n            [SharedFolderType.Embeddings] = new[] { \"embeddings\" },\n            [SharedFolderType.Hypernetwork] = new[] { \"models/hypernetworks\" },\n            [SharedFolderType.ControlNet] = new[] { \"models/ControlNet\" },\n            [SharedFolderType.Codeformer] = new[] { \"models/Codeformer\" },\n            [SharedFolderType.LDSR] = new[] { \"models/LDSR\" },\n            [SharedFolderType.AfterDetailer] = new[] { \"models/adetailer\" },\n        };\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders =>\n        new()\n        {\n            [SharedOutputType.Extras] = new[] { \"outputs/extras-images\" },\n            [SharedOutputType.Saved] = new[] { \"log/images\" },\n            [SharedOutputType.Img2Img] = new[] { \"outputs/img2img-images\" },\n            [SharedOutputType.Text2Img] = new[] { \"outputs/txt2img-images\" },\n            [SharedOutputType.Img2ImgGrids] = new[] { \"outputs/img2img-grids\" },\n            [SharedOutputType.Text2ImgGrids] = new[] { \"outputs/txt2img-grids\" },\n        };\n\n    [SuppressMessage(\"ReSharper\", \"ArrangeObjectCreationWhenTypeNotEvident\")]\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new()\n            {\n                Name = \"Host\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"localhost\",\n                Options = [\"--server-name\"],\n            },\n            new()\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"7860\",\n                Options = [\"--port\"],\n            },\n            new()\n            {\n                Name = \"VRAM\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch\n                {\n                    MemoryLevel.Low => \"--lowvram\",\n                    MemoryLevel.Medium => \"--medvram\",\n                    _ => null,\n                },\n                Options = [\"--lowvram\", \"--medvram\", \"--medvram-sdxl\"],\n            },\n            new()\n            {\n                Name = \"Xformers\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = HardwareHelper.HasNvidiaGpu(),\n                Options = [\"--xformers\"],\n            },\n            new()\n            {\n                Name = \"API\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = true,\n                Options = [\"--api\"],\n            },\n            new()\n            {\n                Name = \"Auto Launch Web UI\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = false,\n                Options = [\"--autolaunch\"],\n            },\n            new()\n            {\n                Name = \"Skip Torch CUDA Check\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = !HardwareHelper.HasNvidiaGpu(),\n                Options = [\"--skip-torch-cuda-test\"],\n            },\n            new()\n            {\n                Name = \"Skip Python Version Check\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = true,\n                Options = [\"--skip-python-version-check\"],\n            },\n            new()\n            {\n                Name = \"No Half\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Do not switch the model to 16-bit floats\",\n                InitialValue =\n                    HardwareHelper.PreferRocm() || HardwareHelper.PreferDirectMLOrZluda() || Compat.IsMacOS,\n                Options = [\"--no-half\"],\n            },\n            new()\n            {\n                Name = \"Skip SD Model Download\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = false,\n                Options = [\"--no-download-sd-model\"],\n            },\n            new()\n            {\n                Name = \"Skip Install\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--skip-install\"],\n            },\n            LaunchOptionDefinition.Extras,\n        ];\n\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None };\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        new[] { TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.Rocm, TorchIndex.Mps };\n\n    public override string MainBranch => \"master\";\n\n    public override bool ShouldIgnoreReleases => true;\n\n    public override string OutputFolderName => \"outputs\";\n\n    public override IReadOnlyList<string> ExtraLaunchArguments =>\n        settingsManager.IsLibraryDirSet ? [\"--gradio-allowed-path\", settingsManager.ImagesDirectory] : [];\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, \"Setting up venv\", isIndeterminate: true));\n\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n        var pipArgs = new PipInstallArgs();\n\n        switch (torchVersion)\n        {\n            case TorchIndex.Cpu:\n                pipArgs = pipArgs.WithTorch(\"==2.1.2\").WithTorchVision(\"==0.16.2\");\n                break;\n            case TorchIndex.Cuda:\n                pipArgs = pipArgs\n                    .WithTorch(\"==2.1.2\")\n                    .WithTorchVision(\"==0.16.2\")\n                    .WithXFormers(\"==0.0.23post1\")\n                    .WithTorchExtraIndex(\"cu121\");\n                break;\n            case TorchIndex.Rocm:\n                pipArgs = pipArgs\n                    .WithTorch(\"==2.0.1\")\n                    .WithTorchVision(\"==0.15.2\")\n                    .WithTorchExtraIndex(\"rocm5.4.2\");\n                break;\n            case TorchIndex.Mps:\n                pipArgs = pipArgs.WithTorch(\"==2.1.2\").WithTorchVision(\"==0.16.2\").WithTorchExtraIndex(\"cpu\");\n                break;\n        }\n\n        // Install requirements file\n        progress?.Report(new ProgressReport(-1f, \"Installing Package Requirements\", isIndeterminate: true));\n        Logger.Info(\"Installing requirements_versions.txt\");\n\n        var requirements = new FilePath(installLocation, \"requirements_versions.txt\");\n        pipArgs = pipArgs.WithParsedFromRequirementsTxt(\n            await requirements.ReadAllTextAsync(cancellationToken).ConfigureAwait(false),\n            excludePattern: \"torch\"\n        );\n\n        if (installedPackage.PipOverrides != null)\n        {\n            pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);\n        }\n\n        await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(1f, \"Install complete\", isIndeterminate: false));\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (!s.Text.Contains(\"Running on\", StringComparison.OrdinalIgnoreCase))\n                return;\n\n            var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n            var match = regex.Match(s.Text);\n            if (!match.Success)\n                return;\n\n            WebUrl = match.Value;\n            OnStartupComplete(WebUrl);\n        }\n\n        VenvRunner.RunDetached(\n            [\n                Path.Combine(installLocation, options.Command ?? LaunchCommand),\n                .. options.Arguments,\n                .. ExtraLaunchArguments,\n            ],\n            HandleConsoleOutput,\n            OnExit\n        );\n    }\n\n    private class A3WebUiExtensionManager(StableDiffusionUx package)\n        : GitPackageExtensionManager(package.PrerequisiteHelper)\n    {\n        public override string RelativeInstallDirectory => \"extensions\";\n\n        public override IEnumerable<ExtensionManifest> DefaultManifests =>\n            [\n                new ExtensionManifest(\n                    new Uri(\n                        \"https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui-extensions/master/index.json\"\n                    )\n                ),\n            ];\n\n        public override async Task<IEnumerable<PackageExtension>> GetManifestExtensionsAsync(\n            ExtensionManifest manifest,\n            CancellationToken cancellationToken = default\n        )\n        {\n            try\n            {\n                // Get json\n                var content = await package\n                    .DownloadService.GetContentAsync(manifest.Uri.ToString(), cancellationToken)\n                    .ConfigureAwait(false);\n\n                // Parse json\n                var jsonManifest = JsonSerializer.Deserialize<A1111ExtensionManifest>(\n                    content,\n                    A1111ExtensionManifestSerializerContext.Default.Options\n                );\n\n                return jsonManifest?.GetPackageExtensions() ?? Enumerable.Empty<PackageExtension>();\n            }\n            catch (Exception e)\n            {\n                Logger.Error(e, \"Failed to get extensions from manifest\");\n                return Enumerable.Empty<PackageExtension>();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/StableSwarm.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Text.RegularExpressions;\nusing FreneticUtilities.FreneticDataSyntax;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models.FDS;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages.Config;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, StableSwarm>(Duplicate = DuplicateStrategy.Append)]\npublic class StableSwarm(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    private Process? dotnetProcess;\n\n    public override string Name => \"StableSwarmUI\";\n    public override string RepositoryName => \"SwarmUI\";\n    public override string DisplayName { get; set; } = \"SwarmUI\";\n    public override string Author => \"mcmonkeyprojects\";\n    public override string Blurb =>\n        \"A Modular Stable Diffusion Web-User-Interface, with an emphasis on making powertools easily accessible, high performance, and extensibility.\";\n\n    public override string LicenseType => \"MIT\";\n    public override string LicenseUrl =>\n        \"https://github.com/mcmonkeyprojects/SwarmUI/blob/master/LICENSE.txt\";\n    public override string LaunchCommand => string.Empty;\n    public override Uri PreviewImageUri =>\n        new(\"https://github.com/mcmonkeyprojects/SwarmUI/raw/master/.github/images/swarmui.jpg\");\n    public override string OutputFolderName => \"Output\";\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        [SharedFolderMethod.Symlink, SharedFolderMethod.Configuration, SharedFolderMethod.None];\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Configuration;\n    public override bool OfferInOneClickInstaller => false;\n    public override bool UsesVenv => false;\n\n    public override List<ExtraPackageCommand> GetExtraCommands() =>\n        [\n            new()\n            {\n                CommandName = \"Rebuild .NET Project\",\n                Command = async installedPackage =>\n                {\n                    if (installedPackage == null || string.IsNullOrEmpty(installedPackage.FullPath))\n                    {\n                        throw new InvalidOperationException(\"Package not found or not installed correctly\");\n                    }\n\n                    var srcFolder = Path.Combine(installedPackage.FullPath, \"src\");\n                    var csprojName = \"StableSwarmUI.csproj\";\n                    if (File.Exists(Path.Combine(srcFolder, \"SwarmUI.csproj\")))\n                    {\n                        csprojName = \"SwarmUI.csproj\";\n                    }\n\n                    await RebuildDotnetProject(installedPackage.FullPath, csprojName, null)\n                        .ConfigureAwait(false);\n                },\n            },\n        ];\n\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new LaunchOptionDefinition\n            {\n                Name = \"Host\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"127.0.0.1\",\n                Options = [\"--host\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"7801\",\n                Options = [\"--port\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Ngrok Path\",\n                Type = LaunchOptionType.String,\n                Options = [\"--ngrok-path\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Ngrok Basic Auth\",\n                Type = LaunchOptionType.String,\n                Options = [\"--ngrok-basic-auth\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Cloudflared Path\",\n                Type = LaunchOptionType.String,\n                Options = [\"--cloudflared-path\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Proxy Region\",\n                Type = LaunchOptionType.String,\n                Options = [\"--proxy-region\"],\n            },\n            new LaunchOptionDefinition\n            {\n                Name = \"Launch Mode\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--launch-mode web\", \"--launch-mode webinstall\"],\n            },\n            LaunchOptionDefinition.Extras,\n        ];\n\n    public override SharedFolderLayout SharedFolderLayout =>\n        new()\n        {\n            RelativeConfigPath = Path.Combine(\"Data/Settings.fds\"),\n            ConfigFileType = ConfigFileType.Fds,\n            Rules =\n            [\n                new SharedFolderLayoutRule { IsRoot = true, ConfigDocumentPaths = [\"ModelRoot\"] },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.StableDiffusion],\n                    TargetRelativePaths = [\"Models/Stable-Diffusion\"],\n                    ConfigDocumentPaths = [\"SDModelFolder\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS],\n                    TargetRelativePaths = [\"Models/Lora\"],\n                    ConfigDocumentPaths = [\"SDLoraFolder\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.VAE],\n                    TargetRelativePaths = [\"Models/VAE\"],\n                    ConfigDocumentPaths = [\"SDVAEFolder\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Embeddings],\n                    TargetRelativePaths = [\"Models/Embeddings\"],\n                    ConfigDocumentPaths = [\"SDEmbeddingFolder\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.ControlNet, SharedFolderType.T2IAdapter],\n                    TargetRelativePaths = [\"Models/controlnet\"],\n                    ConfigDocumentPaths = [\"SDControlNetsFolder\"],\n                }, // Assuming Swarm maps T2I to ControlNet folder\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.ClipVision],\n                    TargetRelativePaths = [\"Models/clip_vision\"],\n                    ConfigDocumentPaths = [\"SDClipVisionFolder\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.TextEncoders],\n                    TargetRelativePaths = [\"Models/clip\"],\n                    ConfigDocumentPaths = [\"SDClipFolder\"],\n                },\n            ],\n        };\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>> SharedOutputFolders =>\n        new() { [SharedOutputType.Text2Img] = [OutputFolderName] };\n    public override string MainBranch => \"master\";\n    public override bool ShouldIgnoreReleases => true;\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        [TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.DirectMl, TorchIndex.Rocm, TorchIndex.Mps];\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Simple;\n    public override IEnumerable<PackagePrerequisite> Prerequisites =>\n        [\n            PackagePrerequisite.Git,\n            PackagePrerequisite.Dotnet,\n            PackagePrerequisite.Python310,\n            PackagePrerequisite.VcRedist,\n        ];\n\n    private FilePath GetSettingsPath(string installLocation) =>\n        Path.Combine(installLocation, \"Data\", \"Settings.fds\");\n\n    private FilePath GetBackendsPath(string installLocation) =>\n        Path.Combine(installLocation, \"Data\", \"Backends.fds\");\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, \"Installing SwarmUI...\", isIndeterminate: true));\n\n        var comfy = settingsManager.Settings.InstalledPackages.FirstOrDefault(x =>\n            x.PackageName is nameof(ComfyUI) or \"ComfyUI-Zluda\"\n        );\n\n        if (comfy == null)\n        {\n            throw new InvalidOperationException(\"ComfyUI must be installed to use SwarmUI\");\n        }\n\n        try\n        {\n            await prerequisiteHelper\n                .RunDotnet(\n                    [\n                        \"nuget\",\n                        \"add\",\n                        \"source\",\n                        \"https://api.nuget.org/v3/index.json\",\n                        \"--name\",\n                        \"\\\"NuGet official package source\\\"\",\n                    ],\n                    workingDirectory: installLocation,\n                    onProcessOutput: onConsoleOutput\n                )\n                .ConfigureAwait(false);\n        }\n        catch (ProcessException e)\n        {\n            // ignore, probably means the source is already there\n        }\n\n        var srcFolder = Path.Combine(installLocation, \"src\");\n        var csprojName = \"StableSwarmUI.csproj\";\n        if (File.Exists(Path.Combine(srcFolder, \"SwarmUI.csproj\")))\n        {\n            csprojName = \"SwarmUI.csproj\";\n        }\n\n        await prerequisiteHelper\n            .RunDotnet(\n                [\"build\", $\"src/{csprojName}\", \"--configuration\", \"Release\", \"-o\", \"src/bin/live_release\"],\n                workingDirectory: installLocation,\n                onProcessOutput: onConsoleOutput\n            )\n            .ConfigureAwait(false);\n\n        if (!options.IsUpdate)\n        {\n            // set default settings\n            var settings = new StableSwarmSettings { IsInstalled = true };\n\n            if (options.SharedFolderMethod is SharedFolderMethod.Configuration)\n            {\n                settings.Paths = new StableSwarmSettings.PathsData\n                {\n                    ModelRoot = settingsManager.ModelsDirectory,\n                    SDModelFolder = Path.Combine(\n                        settingsManager.ModelsDirectory,\n                        SharedFolderType.StableDiffusion.ToString()\n                    ),\n                    SDLoraFolder = Path.Combine(\n                        settingsManager.ModelsDirectory,\n                        SharedFolderType.Lora.ToString()\n                    ),\n                    SDVAEFolder = Path.Combine(\n                        settingsManager.ModelsDirectory,\n                        SharedFolderType.VAE.ToString()\n                    ),\n                    SDEmbeddingFolder = Path.Combine(\n                        settingsManager.ModelsDirectory,\n                        SharedFolderType.Embeddings.ToString()\n                    ),\n                    SDControlNetsFolder = Path.Combine(\n                        settingsManager.ModelsDirectory,\n                        SharedFolderType.ControlNet.ToString()\n                    ),\n                    SDClipVisionFolder = Path.Combine(\n                        settingsManager.ModelsDirectory,\n                        SharedFolderType.ClipVision.ToString()\n                    ),\n                };\n            }\n\n            settings.Save(true).SaveToFile(GetSettingsPath(installLocation));\n\n            var backendsFile = new FDSSection();\n            var dataSection = new FDSSection();\n            dataSection.Set(\"type\", \"comfyui_selfstart\");\n            dataSection.Set(\"title\", \"StabilityMatrix ComfyUI Self-Start\");\n            dataSection.Set(\"enabled\", true);\n\n            var launchArgs = comfy.LaunchArgs ?? [];\n            var comfyArgs = string.Join(\n                ' ',\n                launchArgs\n                    .Select(arg => arg.ToArgString()?.TrimEnd())\n                    .Where(arg => !string.IsNullOrWhiteSpace(arg))\n            );\n\n            if (comfy.PackageName == \"ComfyUI-Zluda\")\n            {\n                var fullComfyZludaPath = Path.Combine(SettingsManager.LibraryDir, comfy.LibraryPath);\n                var zludaPath = Path.Combine(fullComfyZludaPath, \"zluda\", \"zluda.exe\");\n                var comfyVenvPath = Path.Combine(\n                    fullComfyZludaPath,\n                    \"venv\",\n                    Compat.Switch(\n                        (PlatformKind.Windows, Path.Combine(\"Scripts\", \"python.exe\")),\n                        (PlatformKind.Unix, Path.Combine(\"bin\", \"python3\"))\n                    )\n                );\n\n                ProcessArgs args = [\"--\", comfyVenvPath, \"main.py\", comfyArgs];\n\n                // Create a wrapper batch file that runs zluda.exe\n                var wrapperScriptPath = Path.Combine(installLocation, \"Data\", \"zluda_wrapper.bat\");\n                var scriptContent = $\"\"\"\n                    @echo off\n                    \"{zludaPath}\" {args}\n                    \"\"\";\n\n                // Ensure the Data directory exists\n                Directory.CreateDirectory(Path.Combine(installLocation, \"Data\"));\n\n                // Write the batch file\n                await File.WriteAllTextAsync(wrapperScriptPath, scriptContent, cancellationToken)\n                    .ConfigureAwait(false);\n\n                dataSection.Set(\n                    \"settings\",\n                    new ComfyUiSelfStartSettings\n                    {\n                        StartScript = wrapperScriptPath,\n                        DisableInternalArgs = false,\n                        AutoUpdate = false,\n                        UpdateManagedNodes = \"true\",\n                        ExtraArgs = string.Empty, // Arguments are already in the batch file\n                    }.Save(true)\n                );\n            }\n            else\n            {\n                dataSection.Set(\n                    \"settings\",\n                    new ComfyUiSelfStartSettings\n                    {\n                        StartScript = $\"../{comfy.DisplayName}/main.py\",\n                        DisableInternalArgs = false,\n                        AutoUpdate = false,\n                        UpdateManagedNodes = \"true\",\n                        ExtraArgs = comfyArgs,\n                    }.Save(true)\n                );\n            }\n\n            backendsFile.Set(\"0\", dataSection);\n            backendsFile.SaveToFile(GetBackendsPath(installLocation));\n        }\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var portableGitBin = new DirectoryPath(PrerequisiteHelper.GitBinPath);\n        var dotnetDir = PrerequisiteHelper.DotnetDir;\n        var aspEnvVars = new Dictionary<string, string>\n        {\n            [\"ASPNETCORE_ENVIRONMENT\"] = \"Production\",\n            [\"ASPNETCORE_URLS\"] = \"http://*:7801\",\n            [\"GIT\"] = portableGitBin.JoinFile(\"git.exe\"),\n            [\"DOTNET_ROOT\"] = dotnetDir.FullPath,\n        };\n\n        if (aspEnvVars.TryGetValue(\"PATH\", out var pathValue))\n        {\n            aspEnvVars[\"PATH\"] = Compat.GetEnvPathWithExtensions(\n                dotnetDir.FullPath,\n                portableGitBin,\n                pathValue\n            );\n        }\n        else\n        {\n            aspEnvVars[\"PATH\"] = Compat.GetEnvPathWithExtensions(dotnetDir.FullPath, portableGitBin);\n        }\n\n        aspEnvVars.Update(settingsManager.Settings.EnvironmentVariables);\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (s.Text.Contains(\"Starting webserver\", StringComparison.OrdinalIgnoreCase))\n            {\n                var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n                var match = regex.Match(s.Text);\n                if (match.Success)\n                {\n                    WebUrl = match.Value;\n                }\n                OnStartupComplete(WebUrl);\n            }\n        }\n\n        var sharedDiffusionModelsPath = new DirectoryPath(\n            settingsManager.ModelsDirectory,\n            nameof(SharedFolderType.DiffusionModels)\n        );\n        var swarmDiffusionModelsPath = new DirectoryPath(settingsManager.ModelsDirectory, \"diffusion_models\");\n\n        try\n        {\n            swarmDiffusionModelsPath.Create();\n            await Helper\n                .SharedFolders.CreateOrUpdateLink(sharedDiffusionModelsPath, swarmDiffusionModelsPath)\n                .ConfigureAwait(false);\n        }\n        catch (Exception e)\n        {\n            onConsoleOutput?.Invoke(\n                new ProcessOutput\n                {\n                    Text =\n                        $\"Failed to create symlink for {nameof(SharedFolderType.DiffusionModels)}: {e.Message}.\",\n                }\n            );\n        }\n\n        var launchScriptPath = Path.Combine(\n            installLocation,\n            Compat.IsWindows ? \"launch-windows.bat\"\n                : Compat.IsMacOS ? \"launch-macos.sh\"\n                : \"launch-linux.sh\"\n        );\n\n        dotnetProcess = ProcessRunner.StartAnsiProcess(\n            launchScriptPath,\n            options.Arguments,\n            installLocation,\n            HandleConsoleOutput,\n            aspEnvVars\n        );\n    }\n\n    public override async Task<bool> CheckForUpdates(InstalledPackage package)\n    {\n        var needsMigrate = false;\n        try\n        {\n            var output = await prerequisiteHelper\n                .GetGitOutput([\"remote\", \"get-url\", \"origin\"], package.FullPath)\n                .ConfigureAwait(false);\n\n            if (\n                output.StandardOutput != null\n                && output.StandardOutput.Contains(\"Stability\", StringComparison.OrdinalIgnoreCase)\n            )\n            {\n                needsMigrate = true;\n            }\n        }\n        catch (Exception)\n        {\n            needsMigrate = true;\n        }\n\n        if (needsMigrate)\n        {\n            await prerequisiteHelper\n                .RunGit([\"remote\", \"set-url\", \"origin\", GithubUrl], workingDirectory: package.FullPath)\n                .ConfigureAwait(false);\n        }\n\n        return await base.CheckForUpdates(package).ConfigureAwait(false);\n    }\n\n    /*public override Task SetupModelFolders(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    ) =>\n        sharedFolderMethod switch\n        {\n            SharedFolderMethod.Symlink\n                => base.SetupModelFolders(installDirectory, SharedFolderMethod.Symlink),\n            SharedFolderMethod.Configuration => SetupModelFoldersConfig(installDirectory),\n            _ => Task.CompletedTask\n        };\n\n    public override Task RemoveModelFolderLinks(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    ) =>\n        sharedFolderMethod switch\n        {\n            SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod),\n            SharedFolderMethod.Configuration => RemoveModelFoldersConfig(installDirectory),\n            _ => Task.CompletedTask\n        };*/\n\n    public override async Task WaitForShutdown()\n    {\n        if (dotnetProcess is { HasExited: false })\n        {\n            dotnetProcess.Kill(true);\n            try\n            {\n                await dotnetProcess\n                    .WaitForExitAsync(new CancellationTokenSource(5000).Token)\n                    .ConfigureAwait(false);\n            }\n            catch (OperationCanceledException e)\n            {\n                Console.WriteLine(e);\n            }\n        }\n\n        dotnetProcess = null;\n        GC.SuppressFinalize(this);\n    }\n\n    public async Task RebuildDotnetProject(\n        string installLocation,\n        string csprojName,\n        Action<ProcessOutput>? onConsoleOutput\n    )\n    {\n        await prerequisiteHelper\n            .RunDotnet(\n                [\n                    \"build\",\n                    $\"src/{csprojName}\",\n                    \"--no-incremental\",\n                    \"--configuration\",\n                    \"Release\",\n                    \"-o\",\n                    \"src/bin/live_release\",\n                ],\n                workingDirectory: installLocation,\n                onProcessOutput: onConsoleOutput\n            )\n            .ConfigureAwait(false);\n    }\n\n    private Task SetupModelFoldersConfig(DirectoryPath installDirectory)\n    {\n        var settingsPath = GetSettingsPath(installDirectory);\n        var existingSettings = new StableSwarmSettings();\n        var settingsExists = File.Exists(settingsPath);\n        if (settingsExists)\n        {\n            var section = FDSUtility.ReadFile(settingsPath);\n            var paths = section.GetSection(\"Paths\");\n            paths.Set(\"ModelRoot\", settingsManager.ModelsDirectory);\n            paths.Set(\n                \"SDModelFolder\",\n                Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.StableDiffusion.ToString())\n            );\n            paths.Set(\n                \"SDLoraFolder\",\n                Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.Lora.ToString())\n            );\n            paths.Set(\n                \"SDVAEFolder\",\n                Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.VAE.ToString())\n            );\n            paths.Set(\n                \"SDEmbeddingFolder\",\n                Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.Embeddings.ToString())\n            );\n            paths.Set(\n                \"SDControlNetsFolder\",\n                Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.ControlNet.ToString())\n            );\n            paths.Set(\n                \"SDClipVisionFolder\",\n                Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.ClipVision.ToString())\n            );\n            section.Set(\"Paths\", paths);\n            section.SaveToFile(settingsPath);\n            return Task.CompletedTask;\n        }\n\n        existingSettings.Paths = new StableSwarmSettings.PathsData\n        {\n            ModelRoot = settingsManager.ModelsDirectory,\n            SDModelFolder = Path.Combine(\n                settingsManager.ModelsDirectory,\n                SharedFolderType.StableDiffusion.ToString()\n            ),\n            SDLoraFolder = Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.Lora.ToString()),\n            SDVAEFolder = Path.Combine(settingsManager.ModelsDirectory, SharedFolderType.VAE.ToString()),\n            SDEmbeddingFolder = Path.Combine(\n                settingsManager.ModelsDirectory,\n                SharedFolderType.Embeddings.ToString()\n            ),\n            SDControlNetsFolder = Path.Combine(\n                settingsManager.ModelsDirectory,\n                SharedFolderType.ControlNet.ToString()\n            ),\n            SDClipVisionFolder = Path.Combine(\n                settingsManager.ModelsDirectory,\n                SharedFolderType.ClipVision.ToString()\n            ),\n        };\n\n        existingSettings.Save(true).SaveToFile(settingsPath);\n\n        return Task.CompletedTask;\n    }\n\n    private Task RemoveModelFoldersConfig(DirectoryPath installDirectory)\n    {\n        var settingsPath = GetSettingsPath(installDirectory);\n        var existingSettings = new StableSwarmSettings();\n        var settingsExists = File.Exists(settingsPath);\n        if (settingsExists)\n        {\n            var section = FDSUtility.ReadFile(settingsPath);\n            existingSettings.Load(section);\n        }\n\n        existingSettings.Paths = new StableSwarmSettings.PathsData();\n        existingSettings.Save(true).SaveToFile(settingsPath);\n\n        return Task.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/UnknownPackage.cs",
    "content": "﻿using Octokit;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\npublic class UnknownPackage(ISettingsManager settingsManager) : BasePackage(settingsManager)\n{\n    public static string Key => \"unknown-package\";\n    public override string Name => Key;\n    public override string DisplayName { get; set; } = \"Unknown Package\";\n    public override string Author => \"\";\n\n    public override string GithubUrl => \"\";\n    public override string LicenseType => \"AGPL-3.0\";\n    public override string LicenseUrl => \"https://github.com/LykosAI/StabilityMatrix/blob/main/LICENSE\";\n    public override string Blurb => \"A dank interface for diffusion\";\n    public override string LaunchCommand => \"test\";\n\n    public override Uri PreviewImageUri => new(\"\");\n\n    public override IReadOnlyDictionary<string, string> ExtraLaunchCommands =>\n        new Dictionary<string, string>();\n\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;\n\n    public override string OutputFolderName { get; }\n\n    public override PackageDifficulty InstallerSortOrder { get; }\n\n    public override Task DownloadPackage(\n        string installLocation,\n        DownloadPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        throw new NotImplementedException();\n    }\n\n    /// <inheritdoc />\n    public override Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        throw new NotImplementedException();\n    }\n\n    public override Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        throw new NotImplementedException();\n    }\n\n    /// <inheritdoc />\n    public override Task SetupModelFolders(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    )\n    {\n        throw new NotImplementedException();\n    }\n\n    /// <inheritdoc />\n    public override Task UpdateModelFolders(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    )\n    {\n        throw new NotImplementedException();\n    }\n\n    /// <inheritdoc />\n    public override Task RemoveModelFolderLinks(\n        DirectoryPath installDirectory,\n        SharedFolderMethod sharedFolderMethod\n    )\n    {\n        throw new NotImplementedException();\n    }\n\n    public override Task SetupOutputFolderLinks(DirectoryPath installDirectory)\n    {\n        throw new NotImplementedException();\n    }\n\n    public override Task RemoveOutputFolderLinks(DirectoryPath installDirectory)\n    {\n        throw new NotImplementedException();\n    }\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        new[] { TorchIndex.Cuda, TorchIndex.Cpu, TorchIndex.Rocm, TorchIndex.DirectMl };\n\n    /// <inheritdoc />\n    public override void Shutdown()\n    {\n        throw new NotImplementedException();\n    }\n\n    /// <inheritdoc />\n    public override Task WaitForShutdown()\n    {\n        throw new NotImplementedException();\n    }\n\n    /// <inheritdoc />\n    public override Task<bool> CheckForUpdates(InstalledPackage package)\n    {\n        throw new NotImplementedException();\n    }\n\n    /// <inheritdoc />\n    public override Task<InstalledPackageVersion> Update(\n        string installLocation,\n        InstalledPackage installedPackage,\n        UpdatePackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        throw new NotImplementedException();\n    }\n\n    /// <inheritdoc />\n    public override Task<IEnumerable<Release>> GetReleaseTags() =>\n        Task.FromResult(Enumerable.Empty<Release>());\n\n    public override List<LaunchOptionDefinition> LaunchOptions => new();\n\n    public override Dictionary<SharedFolderType, IReadOnlyList<string>>? SharedFolders { get; }\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders { get; }\n\n    public override Task<DownloadPackageVersionOptions> GetLatestVersion(bool includePrerelease = false)\n    {\n        throw new NotImplementedException();\n    }\n\n    public override string MainBranch { get; }\n\n    public override Task<DownloadPackageVersionOptions?> GetUpdate(InstalledPackage installedPackage)\n    {\n        return Task.FromResult(new DownloadPackageVersionOptions { IsLatest = true, VersionTag = \"1.8.0\" });\n    }\n\n    public override Task<PackageVersionOptions> GetAllVersionOptions() =>\n        Task.FromResult(new PackageVersionOptions());\n\n    /// <inheritdoc />\n    public override Task<IEnumerable<GitCommit>?> GetAllCommits(\n        string branch,\n        int page = 1,\n        int perPage = 10\n    ) => Task.FromResult<IEnumerable<GitCommit>?>(null);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/VladAutomatic.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text.Json;\nusing System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Packages.Config;\nusing StabilityMatrix.Core.Models.Packages.Extensions;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, VladAutomatic>(Duplicate = DuplicateStrategy.Append)]\npublic class VladAutomatic(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    public override string Name => \"automatic\";\n    public override string DisplayName { get; set; } = \"SD.Next\";\n    public override string Author => \"vladmandic\";\n    public override string LicenseType => \"Apache License 2.0\";\n    public override string LicenseUrl => \"https://github.com/vladmandic/sdnext/blob/master/LICENSE.txt\";\n    public override string Blurb => \"SD.Next: All-in-one WebUI for AI generative image and video creation\";\n    public override string LaunchCommand => \"launch.py\";\n\n    public override Uri PreviewImageUri => new(\"https://cdn.lykos.ai/sm/packages/vladautomatic/preview.webp\");\n    public override bool ShouldIgnoreReleases => true;\n\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced;\n    public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_12_10;\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        new[]\n        {\n            TorchIndex.Cpu,\n            TorchIndex.Cuda,\n            TorchIndex.DirectMl,\n            TorchIndex.Ipex,\n            TorchIndex.Rocm,\n            TorchIndex.Zluda,\n        };\n\n    // https://github.com/vladmandic/automatic/blob/master/modules/shared.py#L324\n    public override SharedFolderLayout SharedFolderLayout =>\n        new()\n        {\n            RelativeConfigPath = \"config.json\",\n            ConfigFileType = ConfigFileType.Json,\n            Rules =\n            [\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.StableDiffusion],\n                    TargetRelativePaths = [\"models/Stable-diffusion\"],\n                    ConfigDocumentPaths = [\"ckpt_dir\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Diffusers],\n                    TargetRelativePaths = [\"models/Diffusers\"],\n                    ConfigDocumentPaths = [\"diffusers_dir\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.VAE],\n                    TargetRelativePaths = [\"models/VAE\"],\n                    ConfigDocumentPaths = [\"vae_dir\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Embeddings],\n                    TargetRelativePaths = [\"models/embeddings\"],\n                    ConfigDocumentPaths = [\"embeddings_dir\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Hypernetwork],\n                    TargetRelativePaths = [\"models/hypernetworks\"],\n                    ConfigDocumentPaths = [\"hypernetwork_dir\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Codeformer],\n                    TargetRelativePaths = [\"models/Codeformer\"],\n                    ConfigDocumentPaths = [\"codeformer_models_path\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.GFPGAN],\n                    TargetRelativePaths = [\"models/GFPGAN\"],\n                    ConfigDocumentPaths = [\"gfpgan_models_path\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.BSRGAN],\n                    TargetRelativePaths = [\"models/BSRGAN\"],\n                    ConfigDocumentPaths = [\"bsrgan_models_path\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.ESRGAN],\n                    TargetRelativePaths = [\"models/ESRGAN\"],\n                    ConfigDocumentPaths = [\"esrgan_models_path\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.RealESRGAN],\n                    TargetRelativePaths = [\"models/RealESRGAN\"],\n                    ConfigDocumentPaths = [\"realesrgan_models_path\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.ScuNET],\n                    TargetRelativePaths = [\"models/ScuNET\"],\n                    ConfigDocumentPaths = [\"scunet_models_path\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.SwinIR],\n                    TargetRelativePaths = [\"models/SwinIR\"],\n                    ConfigDocumentPaths = [\"swinir_models_path\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.LDSR],\n                    TargetRelativePaths = [\"models/LDSR\"],\n                    ConfigDocumentPaths = [\"ldsr_models_path\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.TextEncoders],\n                    TargetRelativePaths = [\"models/CLIP\"],\n                    ConfigDocumentPaths = [\"clip_models_path\"],\n                }, // CLIP\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Lora],\n                    TargetRelativePaths = [\"models/Lora\"],\n                    ConfigDocumentPaths = [\"lora_dir\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.LyCORIS],\n                    TargetRelativePaths = [\"models/LyCORIS\"],\n                    ConfigDocumentPaths = [\"lyco_dir\"],\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.ControlNet, SharedFolderType.T2IAdapter],\n                    TargetRelativePaths = [\"models/ControlNet\"],\n                    ConfigDocumentPaths = [\"control_net_models_path\"],\n                }, // Combined ControlNet/T2I\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.DiffusionModels],\n                    TargetRelativePaths = [\"models/UNET\"],\n                    ConfigDocumentPaths = [\"unet_dir\"],\n                },\n            ],\n        };\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders =>\n        new()\n        {\n            [SharedOutputType.Text2Img] = new[] { \"outputs/text\" },\n            [SharedOutputType.Img2Img] = new[] { \"outputs/image\" },\n            [SharedOutputType.Extras] = new[] { \"outputs/extras\" },\n            [SharedOutputType.Img2ImgGrids] = new[] { \"outputs/grids\" },\n            [SharedOutputType.Text2ImgGrids] = new[] { \"outputs/grids\" },\n            [SharedOutputType.Saved] = new[] { \"outputs/save\" },\n        };\n\n    public override string OutputFolderName => \"outputs\";\n    public override IPackageExtensionManager ExtensionManager => new VladExtensionManager(this);\n\n    [SuppressMessage(\"ReSharper\", \"ArrangeObjectCreationWhenTypeNotEvident\")]\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new()\n            {\n                Name = \"Host\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"localhost\",\n                Options = [\"--server-name\"],\n            },\n            new()\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"7860\",\n                Options = [\"--port\"],\n            },\n            new()\n            {\n                Name = \"Use uv\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = true,\n                Options = [\"--uv\"],\n            },\n            new()\n            {\n                Name = \"VRAM\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch\n                {\n                    MemoryLevel.Low => \"--lowvram\",\n                    MemoryLevel.Medium => \"--medvram\",\n                    _ => null,\n                },\n                Options = [\"--lowvram\", \"--medvram\"],\n            },\n            new()\n            {\n                Name = \"Auto-Launch Web UI\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--autolaunch\"],\n            },\n            new()\n            {\n                Name = \"Use DirectML if no compatible GPU is detected\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--use-directml\"],\n            },\n            new()\n            {\n                Name = \"Force use of Nvidia CUDA backend\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = HardwareHelper.HasNvidiaGpu(),\n                Options = [\"--use-cuda\"],\n            },\n            new()\n            {\n                Name = \"Force use of Intel OneAPI XPU backend\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = HardwareHelper.HasIntelGpu(),\n                Options = [\"--use-ipex\"],\n            },\n            new()\n            {\n                Name = \"Force use of AMD ROCm backend\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = HardwareHelper.PreferRocm(),\n                Options = [\"--use-rocm\"],\n            },\n            new()\n            {\n                Name = \"Force use of ZLUDA backend\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = HardwareHelper.PreferDirectMLOrZluda(),\n                Options = [\"--use-zluda\"],\n            },\n            new()\n            {\n                Name = \"CUDA Device ID\",\n                Type = LaunchOptionType.String,\n                Options = [\"--device-id\"],\n            },\n            new()\n            {\n                Name = \"API\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--api\"],\n            },\n            new()\n            {\n                Name = \"Debug Logging\",\n                Type = LaunchOptionType.Bool,\n                Options = [\"--debug\"],\n            },\n            LaunchOptionDefinition.Extras,\n        ];\n\n    public override string MainBranch => \"master\";\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, \"Installing package...\", isIndeterminate: true));\n        // Setup venv\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        await venvRunner.PipInstall([\"setuptools\", \"rich\", \"uv\"]).ConfigureAwait(false);\n        if (options.PythonOptions.PythonVersion is { Minor: < 12 })\n        {\n            venvRunner.UpdateEnvironmentVariables(env =>\n                env.SetItem(\"SETUPTOOLS_USE_DISTUTILS\", \"setuptools\")\n            );\n        }\n\n        if (installedPackage.PipOverrides is { Count: > 0 })\n        {\n            var pipArgs = new PipInstallArgs().WithUserOverrides(installedPackage.PipOverrides);\n            await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);\n        }\n\n        var torchVersion = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n        switch (torchVersion)\n        {\n            // Run initial install\n            case TorchIndex.Cuda:\n                await venvRunner\n                    .CustomInstall(\"launch.py --use-cuda --debug --test --uv\", onConsoleOutput)\n                    .ConfigureAwait(false);\n                break;\n            case TorchIndex.Rocm:\n                await venvRunner\n                    .CustomInstall(\"launch.py --use-rocm --debug --test --uv\", onConsoleOutput)\n                    .ConfigureAwait(false);\n                break;\n            case TorchIndex.DirectMl:\n                await venvRunner\n                    .CustomInstall(\"launch.py --use-directml --debug --test --uv\", onConsoleOutput)\n                    .ConfigureAwait(false);\n                break;\n            case TorchIndex.Zluda:\n                await venvRunner\n                    .CustomInstall(\"launch.py --use-zluda --debug --test --uv\", onConsoleOutput)\n                    .ConfigureAwait(false);\n                break;\n            case TorchIndex.Ipex:\n                await venvRunner\n                    .CustomInstall(\"launch.py --use-ipex --debug --test --uv\", onConsoleOutput)\n                    .ConfigureAwait(false);\n                break;\n            default:\n                // CPU\n                await venvRunner\n                    .CustomInstall(\"launch.py --debug --test --uv\", onConsoleOutput)\n                    .ConfigureAwait(false);\n                break;\n        }\n\n        progress?.Report(new ProgressReport(1f, isIndeterminate: false));\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        if (PyVersion.Parse(installedPackage.PythonVersion) is { Minor: < 12 })\n        {\n            VenvRunner.UpdateEnvironmentVariables(env =>\n                env.SetItem(\"SETUPTOOLS_USE_DISTUTILS\", \"setuptools\")\n            );\n        }\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n            if (s.Text.Contains(\"Local URL\", StringComparison.OrdinalIgnoreCase))\n            {\n                var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n                var match = regex.Match(s.Text);\n                if (match.Success)\n                {\n                    WebUrl = match.Value;\n                    OnStartupComplete(WebUrl);\n                }\n            }\n        }\n\n        VenvRunner.RunDetached(\n            [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],\n            HandleConsoleOutput,\n            OnExit\n        );\n    }\n\n    public override async Task<InstalledPackageVersion> Update(\n        string installLocation,\n        InstalledPackage installedPackage,\n        UpdatePackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var baseUpdateResult = await base.Update(\n                installLocation,\n                installedPackage,\n                options,\n                progress,\n                onConsoleOutput,\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        await using var venvRunner = await SetupVenvPure(\n                installedPackage.FullPath!.Unwrap(),\n                pythonVersion: PyVersion.Parse(installedPackage.PythonVersion)\n            )\n            .ConfigureAwait(false);\n\n        await venvRunner.CustomInstall(\"launch.py --upgrade --test\", onConsoleOutput).ConfigureAwait(false);\n\n        try\n        {\n            var result = await PrerequisiteHelper\n                .GetGitOutput([\"rev-parse\", \"HEAD\"], installedPackage.FullPath)\n                .EnsureSuccessExitCode()\n                .ConfigureAwait(false);\n\n            return new InstalledPackageVersion\n            {\n                InstalledBranch = options.VersionOptions.BranchName,\n                InstalledCommitSha = result\n                    .StandardOutput?.Replace(Environment.NewLine, \"\")\n                    .Replace(\"\\n\", \"\"),\n                IsPrerelease = false,\n            };\n        }\n        catch (Exception e)\n        {\n            Logger.Warn(e, \"Could not get current git hash, continuing with update\");\n        }\n        finally\n        {\n            progress?.Report(\n                new ProgressReport(\n                    1f,\n                    message: \"Update Complete\",\n                    isIndeterminate: false,\n                    type: ProgressType.Update\n                )\n            );\n        }\n\n        return baseUpdateResult;\n    }\n\n    private class VladExtensionManager(VladAutomatic package)\n        : GitPackageExtensionManager(package.PrerequisiteHelper)\n    {\n        public override string RelativeInstallDirectory => \"extensions\";\n\n        public override IEnumerable<ExtensionManifest> DefaultManifests =>\n            [new ExtensionManifest(new Uri(\"https://vladmandic.github.io/sd-data/pages/extensions.json\"))];\n\n        public override async Task<IEnumerable<PackageExtension>> GetManifestExtensionsAsync(\n            ExtensionManifest manifest,\n            CancellationToken cancellationToken = default\n        )\n        {\n            try\n            {\n                // Get json\n                var content = await package\n                    .DownloadService.GetContentAsync(manifest.Uri.ToString(), cancellationToken)\n                    .ConfigureAwait(false);\n\n                // Parse json\n                var jsonManifest = JsonSerializer.Deserialize<IEnumerable<VladExtensionItem>>(\n                    content,\n                    new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }\n                );\n\n                return jsonManifest?.Select(entry => new PackageExtension\n                    {\n                        Title = entry.Name,\n                        Author = entry.Long?.Split('/').FirstOrDefault() ?? \"Unknown\",\n                        Reference = entry.Url,\n                        Files = [entry.Url],\n                        Description = entry.Description,\n                        InstallType = \"git-clone\",\n                    }) ?? Enumerable.Empty<PackageExtension>();\n            }\n            catch (Exception e)\n            {\n                Logger.Error(e, \"Failed to get extensions from manifest\");\n                return Enumerable.Empty<PackageExtension>();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/VoltaML.cs",
    "content": "﻿using System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing Python.Runtime;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n[RegisterSingleton<BasePackage, VoltaML>(Duplicate = DuplicateStrategy.Append)]\npublic class VoltaML(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"voltaML-fast-stable-diffusion\";\n    public override string DisplayName { get; set; } = \"VoltaML\";\n    public override string Author => \"VoltaML\";\n    public override string LicenseType => \"GPL-3.0\";\n    public override string LicenseUrl =>\n        \"https://github.com/VoltaML/voltaML-fast-stable-diffusion/blob/main/License\";\n    public override string Blurb => \"Fast Stable Diffusion with support for AITemplate\";\n    public override string LaunchCommand => \"main.py\";\n\n    public override Uri PreviewImageUri => new(\"https://cdn.lykos.ai/sm/packages/voltaml/preview.webp\");\n    public override string Disclaimer => \"This package may no longer receive updates from its author.\";\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Impossible;\n    public override bool OfferInOneClickInstaller => false;\n    public override PackageType PackageType => PackageType.Legacy;\n\n    // There are releases but the manager just downloads the latest commit anyways,\n    // so we'll just limit to commit mode to be more consistent\n    public override bool ShouldIgnoreReleases => true;\n\n    // https://github.com/VoltaML/voltaML-fast-stable-diffusion/blob/main/main.py#L86\n    public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders =>\n        new()\n        {\n            [SharedFolderType.StableDiffusion] = new[] { \"data/models\" },\n            [SharedFolderType.Lora] = new[] { \"data/lora\" },\n            [SharedFolderType.Embeddings] = new[] { \"data/textual-inversion\" },\n        };\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders =>\n        new()\n        {\n            [SharedOutputType.Text2Img] = new[] { \"data/outputs/txt2img\" },\n            [SharedOutputType.Extras] = new[] { \"data/outputs/extra\" },\n            [SharedOutputType.Img2Img] = new[] { \"data/outputs/img2img\" },\n        };\n\n    public override string OutputFolderName => \"data/outputs\";\n\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.Symlink;\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices =>\n        new[] { TorchIndex.Cpu, TorchIndex.Cuda, TorchIndex.DirectMl };\n\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods =>\n        new[] { SharedFolderMethod.Symlink, SharedFolderMethod.None };\n\n    // https://github.com/VoltaML/voltaML-fast-stable-diffusion/blob/main/main.py#L45\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        new List<LaunchOptionDefinition>\n        {\n            new()\n            {\n                Name = \"Log Level\",\n                Type = LaunchOptionType.Bool,\n                DefaultValue = \"--log-level INFO\",\n                Options =\n                {\n                    \"--log-level DEBUG\",\n                    \"--log-level INFO\",\n                    \"--log-level WARNING\",\n                    \"--log-level ERROR\",\n                    \"--log-level CRITICAL\",\n                },\n            },\n            new()\n            {\n                Name = \"Use ngrok to expose the API\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--ngrok\" },\n            },\n            new()\n            {\n                Name = \"Expose the API to the network\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--host\" },\n            },\n            new()\n            {\n                Name = \"Skip virtualenv check\",\n                Type = LaunchOptionType.Bool,\n                InitialValue = true,\n                Options = { \"--in-container\" },\n            },\n            new()\n            {\n                Name = \"Force VoltaML to use a specific type of PyTorch distribution\",\n                Type = LaunchOptionType.Bool,\n                Options =\n                {\n                    \"--pytorch-type cpu\",\n                    \"--pytorch-type cuda\",\n                    \"--pytorch-type rocm\",\n                    \"--pytorch-type directml\",\n                    \"--pytorch-type intel\",\n                    \"--pytorch-type vulkan\",\n                },\n            },\n            new()\n            {\n                Name = \"Run in tandem with the Discord bot\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--bot\" },\n            },\n            new()\n            {\n                Name = \"Enable Cloudflare R2 bucket upload support\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--enable-r2\" },\n            },\n            new()\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"5003\",\n                Options = { \"--port\" },\n            },\n            new()\n            {\n                Name = \"Only install requirements and exit\",\n                Type = LaunchOptionType.Bool,\n                Options = { \"--install-only\" },\n            },\n            LaunchOptionDefinition.Extras,\n        };\n\n    public override string MainBranch => \"main\";\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        // Setup venv\n        progress?.Report(new ProgressReport(-1, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        // Install requirements\n        progress?.Report(new ProgressReport(-1, \"Installing Package Requirements\", isIndeterminate: true));\n\n        var pipArgs = new PipInstallArgs([\"rich\", \"packaging\", \"python-dotenv\"]);\n        if (installedPackage.PipOverrides != null)\n        {\n            pipArgs = pipArgs.WithUserOverrides(installedPackage.PipOverrides);\n        }\n\n        await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(1, \"Installing Package Requirements\", isIndeterminate: false));\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        var foundIndicator = false;\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (s.Text.Contains(\"running on\", StringComparison.OrdinalIgnoreCase))\n            {\n                // Next line will have the Web UI URL, so set a flag & wait for that\n                foundIndicator = true;\n                return;\n            }\n\n            if (!foundIndicator)\n                return;\n\n            var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n            var match = regex.Match(s.Text);\n            if (!match.Success)\n                return;\n\n            WebUrl = match.Value;\n            OnStartupComplete(WebUrl);\n            foundIndicator = false;\n        }\n\n        VenvRunner.RunDetached(\n            [Path.Combine(installLocation, options.Command ?? LaunchCommand), .. options.Arguments],\n            HandleConsoleOutput,\n            OnExit\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Packages/Wan2GP.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Packages;\n\n/// <summary>\n/// Package for Wan2GP - Super Optimized Gradio UI for AI video creation.\n/// Supports Wan 2.1/2.2, Qwen, Hunyuan Video, LTX Video and Flux.\n/// https://github.com/deepbeepmeep/Wan2GP\n/// </summary>\n/// <remarks>\n/// <b>Model Sharing:</b> This package does not support Stability Matrix shared folder configuration.\n/// Wan2GP manages model paths through its own wgp_config.json file, which is created and managed\n/// by the Gradio UI on first launch. Users should configure model paths via the Settings tab\n/// in the Wan2GP UI.\n/// </remarks>\n[RegisterSingleton<BasePackage, Wan2GP>(Duplicate = DuplicateStrategy.Append)]\npublic class Wan2GP(\n    IGithubApiCache githubApi,\n    ISettingsManager settingsManager,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper,\n    IPyInstallationManager pyInstallationManager,\n    IPipWheelService pipWheelService\n)\n    : BaseGitPackage(\n        githubApi,\n        settingsManager,\n        downloadService,\n        prerequisiteHelper,\n        pyInstallationManager,\n        pipWheelService\n    )\n{\n    public override string Name => \"Wan2GP\";\n    public override string DisplayName { get; set; } = \"Wan2GP\";\n    public override string Author => \"deepbeepmeep\";\n    public override string Blurb =>\n        \"Super Optimized Gradio UI for AI video creation for GPU poor machines (6GB+ VRAM). \"\n        + \"Supports Wan 2.1/2.2, Qwen, Hunyuan Video, LTX Video and Flux.\";\n\n    public override string LicenseType => \"Apache-2.0\";\n    public override string LicenseUrl => \"https://github.com/deepbeepmeep/Wan2GP/blob/main/LICENSE.txt\";\n    public override string LaunchCommand => \"wgp.py\";\n\n    public override Uri PreviewImageUri => new(\"https://cdn.lykos.ai/sm/packages/wan2gp/wan2gp.webp\");\n\n    public override string OutputFolderName => \"outputs\";\n    public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Advanced;\n    public override SharedFolderMethod RecommendedSharedFolderMethod => SharedFolderMethod.None;\n\n    public override IEnumerable<SharedFolderMethod> AvailableSharedFolderMethods => [SharedFolderMethod.None];\n\n    public override IEnumerable<TorchIndex> AvailableTorchIndices => [TorchIndex.Cuda, TorchIndex.Rocm];\n\n    public override bool IsCompatible =>\n        HardwareHelper.HasNvidiaGpu()\n        || (Compat.IsWindows ? HardwareHelper.HasWindowsRocmSupportedGpu() : HardwareHelper.HasAmdGpu());\n\n    public override string MainBranch => \"main\";\n    public override bool ShouldIgnoreReleases => true;\n\n    public override Dictionary<SharedOutputType, IReadOnlyList<string>> SharedOutputFolders =>\n        new() { [SharedOutputType.Img2Vid] = [\"outputs\"] };\n\n    // AMD ROCm requires Python 3.11, NVIDIA uses 3.10\n    public override PyVersion RecommendedPythonVersion =>\n        IsAmdRocm ? Python.PyInstallationManager.Python_3_11_13 : Python.PyInstallationManager.Python_3_10_17;\n\n    public override string Disclaimer =>\n        IsAmdRocm && Compat.IsWindows\n            ? \"AMD GPU support on Windows requires RX 7000 series or newer GPU\"\n            : string.Empty;\n\n    /// <summary>\n    /// Helper property to check if we're using AMD ROCm\n    /// </summary>\n    private bool IsAmdRocm => GetRecommendedTorchVersion() == TorchIndex.Rocm;\n\n    /// <summary>\n    /// Python wrapper script that patches logging to also print to stdout/stderr, so\n    /// StabilityMatrix can capture the output. Wan2GP logs through Gradio UI notifications\n    /// (gr.Info/Warning/Error) and callback-driven UI updates that never reach the console.\n    /// This script:\n    /// 1. Configures Python's logging module to output to stderr (captures library logging)\n    /// 2. Prevents transformers from suppressing its own logging (wgp.py calls set_verbosity_error)\n    /// 3. Monkey-patches gr.Info/Warning/Error to also print to stdout/stderr\n    /// 4. Runs the target script (wgp.py) via runpy\n    /// </summary>\n    private const string GradioLogPatchScript = \"\"\"\n        # StabilityMatrix: Patch logging to print to console for capture.\n        import sys\n        import logging\n\n        def _apply_logging_patch():\n            # Configure Python's root logger to output to stderr at INFO level.\n            # Many libraries (torch, diffusers, transformers, etc.) use the logging\n            # module but output may be suppressed without a handler configured.\n            root = logging.getLogger()\n            if not any(isinstance(h, logging.StreamHandler) for h in root.handlers):\n                handler = logging.StreamHandler(sys.stderr)\n                handler.setFormatter(logging.Formatter(\"[%(name)s] %(levelname)s: %(message)s\"))\n                root.addHandler(handler)\n            if root.level > logging.INFO:\n                root.setLevel(logging.INFO)\n\n            # Prevent transformers from suppressing its own logging.\n            # wgp.py calls transformers.utils.logging.set_verbosity_error() which\n            # silences all non-error messages. We neutralize those calls so model\n            # loading and download messages remain visible.\n            try:\n                import transformers.utils.logging as tf_logging\n                tf_logging.set_verbosity_error = lambda: None\n                tf_logging.set_verbosity_warning = lambda: None\n                tf_logging.set_verbosity(logging.INFO)\n            except Exception as e:\n                print(f\"[StabilityMatrix] Failed to patch transformers logging: {e}\", file=sys.stderr, flush=True)\n\n            # Monkey-patch Gradio's UI notification functions to also print to console.\n            # These only fire for validation/error messages, not generation progress.\n            try:\n                import gradio as gr\n                _orig_info = getattr(gr, 'Info', None)\n                _orig_warning = getattr(gr, 'Warning', None)\n                _orig_error = getattr(gr, 'Error', None)\n                if _orig_info is not None:\n                    def patched_info(message, *args, **kwargs):\n                        print(f\"[Gradio] {message}\", flush=True)\n                        return _orig_info(message, *args, **kwargs)\n                    gr.Info = patched_info\n                if _orig_warning is not None:\n                    def patched_warning(message, *args, **kwargs):\n                        print(f\"[Gradio] WARNING: {message}\", flush=True)\n                        return _orig_warning(message, *args, **kwargs)\n                    gr.Warning = patched_warning\n                if _orig_error is not None:\n                    def patched_error(message, *args, **kwargs):\n                        print(f\"[Gradio] ERROR: {message}\", file=sys.stderr, flush=True)\n                        return _orig_error(message, *args, **kwargs)\n                    gr.Error = patched_error\n            except Exception as e:\n                print(f\"[StabilityMatrix] Failed to patch Gradio logging: {e}\", file=sys.stderr, flush=True)\n\n        if __name__ == \"__main__\":\n            _apply_logging_patch()\n            target_script = sys.argv[1]\n            sys.argv = sys.argv[1:]\n            import runpy\n            runpy.run_path(target_script, run_name=\"__main__\")\n        \"\"\";\n\n    public override List<LaunchOptionDefinition> LaunchOptions =>\n        [\n            new()\n            {\n                Name = \"Host\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"127.0.0.1\",\n                Options = [\"--server-name\"],\n            },\n            new()\n            {\n                Name = \"Port\",\n                Type = LaunchOptionType.String,\n                DefaultValue = \"7860\",\n                Options = [\"--server-port\"],\n            },\n            new()\n            {\n                Name = \"Share\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Set whether to share on Gradio\",\n                Options = [\"--share\"],\n            },\n            new()\n            {\n                Name = \"Listen\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Make server accessible on network\",\n                Options = [\"--listen\"],\n            },\n            new()\n            {\n                Name = \"Multiple Images\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Enable multiple images mode\",\n                InitialValue = true,\n                Options = [\"--multiple-images\"],\n            },\n            new()\n            {\n                Name = \"Compile\",\n                Type = LaunchOptionType.Bool,\n                Description = \"Enable model compilation for faster inference (may not work on all systems)\",\n                Options = [\"--compile\"],\n            },\n            LaunchOptionDefinition.Extras,\n        ];\n\n    public override TorchIndex GetRecommendedTorchVersion()\n    {\n        // Check for AMD ROCm support (Windows or Linux)\n        var preferRocm =\n            (\n                Compat.IsWindows\n                && (\n                    SettingsManager.Settings.PreferredGpu?.IsWindowsRocmSupportedGpu()\n                    ?? HardwareHelper.HasWindowsRocmSupportedGpu()\n                )\n            )\n            || (\n                Compat.IsLinux\n                && (SettingsManager.Settings.PreferredGpu?.IsAmd ?? HardwareHelper.PreferRocm())\n            );\n\n        if (preferRocm)\n        {\n            return TorchIndex.Rocm;\n        }\n\n        // NVIDIA CUDA\n        if (SettingsManager.Settings.PreferredGpu?.IsNvidia ?? HardwareHelper.HasNvidiaGpu())\n        {\n            return TorchIndex.Cuda;\n        }\n\n        return base.GetRecommendedTorchVersion();\n    }\n\n    public override async Task InstallPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        InstallPackageOptions options,\n        IProgress<ProgressReport>? progress = null,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var torchIndex = options.PythonOptions.TorchIndex ?? GetRecommendedTorchVersion();\n\n        progress?.Report(new ProgressReport(-1, \"Setting up venv\", isIndeterminate: true));\n        await using var venvRunner = await SetupVenvPure(\n                installLocation,\n                pythonVersion: options.PythonOptions.PythonVersion\n            )\n            .ConfigureAwait(false);\n\n        if (torchIndex == TorchIndex.Rocm)\n        {\n            await InstallAmdRocmAsync(venvRunner, progress, onConsoleOutput).ConfigureAwait(false);\n        }\n        else\n        {\n            await InstallNvidiaAsync(venvRunner, progress, onConsoleOutput).ConfigureAwait(false);\n        }\n\n        progress?.Report(new ProgressReport(1, \"Install complete\", isIndeterminate: false));\n    }\n\n    private async Task InstallNvidiaAsync(\n        IPyVenvRunner venvRunner,\n        IProgress<ProgressReport>? progress,\n        Action<ProcessOutput>? onConsoleOutput\n    )\n    {\n        var isLegacyNvidia =\n            SettingsManager.Settings.PreferredGpu?.IsLegacyNvidiaGpu() ?? HardwareHelper.HasLegacyNvidiaGpu();\n        var isNewerNvidia =\n            SettingsManager.Settings.PreferredGpu?.IsAmpereOrNewerGpu()\n            ?? HardwareHelper.HasAmpereOrNewerGpu();\n\n        // Platform-specific versions from pinokio torch.js\n        // Windows: torch 2.7.1, Linux: torch 2.7.0 (to match prebuilt attention wheel requirements)\n        var torchVersion = Compat.IsWindows ? \"2.7.1\" : \"2.7.0\";\n        var torchvisionVersion = Compat.IsWindows ? \"0.22.1\" : \"0.22.0\";\n        var torchaudioVersion = Compat.IsWindows ? \"2.7.1\" : \"2.7.0\";\n        var cudaIndex = isLegacyNvidia ? \"cu126\" : \"cu128\";\n\n        progress?.Report(new ProgressReport(-1f, \"Upgrading pip...\", isIndeterminate: true));\n        await venvRunner.PipInstall(\"--upgrade pip wheel\", onConsoleOutput).ConfigureAwait(false);\n\n        // Install requirements directly using -r flag (handles @ URL syntax properly)\n        progress?.Report(new ProgressReport(-1f, \"Installing requirements...\", isIndeterminate: true));\n        await venvRunner.PipInstall(\"-r requirements.txt\", onConsoleOutput).ConfigureAwait(false);\n\n        // Install torch with specific versions and CUDA index (force reinstall to ensure correct version)\n        progress?.Report(new ProgressReport(-1f, \"Installing PyTorch...\", isIndeterminate: true));\n        var torchArgs = new PipInstallArgs()\n            .WithTorch($\"=={torchVersion}\")\n            .WithTorchVision($\"=={torchvisionVersion}\")\n            .WithTorchAudio($\"=={torchaudioVersion}\")\n            .WithXFormers(\"==0.0.30\")\n            .WithTorchExtraIndex(cudaIndex)\n            .AddArg(\"--force-reinstall\")\n            .AddArg(\"--no-deps\");\n\n        await venvRunner.PipInstall(torchArgs, onConsoleOutput).ConfigureAwait(false);\n\n        // Install hf-xet and pin setuptools to avoid distutils compatibility issues with Python 3.10\n        await venvRunner.PipInstall(\"hf-xet \\\"setuptools<70.0.0\\\"\", onConsoleOutput).ConfigureAwait(false);\n\n        if (!isNewerNvidia)\n            return;\n\n        // Install triton n stuff for newer NVIDIA GPUs\n        if (Compat.IsWindows)\n        {\n            progress?.Report(new ProgressReport(-1f, \"Installing triton-windows...\", isIndeterminate: true));\n            await venvRunner\n                .PipInstall(\"triton-windows==3.3.1.post19\", onConsoleOutput)\n                .ConfigureAwait(false);\n\n            progress?.Report(new ProgressReport(-1f, \"Installing SageAttention...\", isIndeterminate: true));\n            await venvRunner\n                .PipInstall(\n                    \"https://github.com/woct0rdho/SageAttention/releases/download/v2.2.0-windows/sageattention-2.2.0+cu128torch2.7.1-cp310-cp310-win_amd64.whl\",\n                    onConsoleOutput\n                )\n                .ConfigureAwait(false);\n\n            progress?.Report(new ProgressReport(-1f, \"Installing Flash Attention...\", isIndeterminate: true));\n            await venvRunner\n                .PipInstall(\n                    \"https://huggingface.co/lldacing/flash-attention-windows-wheel/resolve/main/flash_attn-2.7.4.post1%2Bcu128torch2.7.0cxx11abiFALSE-cp310-cp310-win_amd64.whl\",\n                    onConsoleOutput\n                )\n                .ConfigureAwait(false);\n        }\n        else if (Compat.IsLinux)\n        {\n            progress?.Report(new ProgressReport(-1f, \"Installing SageAttention...\", isIndeterminate: true));\n            await venvRunner\n                .PipInstall(\n                    \"https://huggingface.co/MonsterMMORPG/SECourses_Premium_Flash_Attention/resolve/main/sageattention-2.1.1-cp310-cp310-linux_x86_64.whl\",\n                    onConsoleOutput\n                )\n                .ConfigureAwait(false);\n\n            progress?.Report(new ProgressReport(-1f, \"Installing Flash Attention...\", isIndeterminate: true));\n            await venvRunner\n                .PipInstall(\n                    \"https://huggingface.co/cocktailpeanut/wheels/resolve/main/flash_attn-2.8.3%2Bcu128torch2.7-cp310-cp310-linux_x86_64.whl\",\n                    onConsoleOutput\n                )\n                .ConfigureAwait(false);\n\n            await venvRunner.PipInstall(\"numpy==2.1.2\", onConsoleOutput).ConfigureAwait(false);\n        }\n    }\n\n    private async Task InstallAmdRocmAsync(\n        IPyVenvRunner venvRunner,\n        IProgress<ProgressReport>? progress,\n        Action<ProcessOutput>? onConsoleOutput\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, \"Upgrading pip...\", isIndeterminate: true));\n        await venvRunner.PipInstall(\"--upgrade pip wheel\", onConsoleOutput).ConfigureAwait(false);\n\n        if (Compat.IsWindows)\n        {\n            // Windows AMD ROCm - special TheRock wheels\n            progress?.Report(\n                new ProgressReport(-1f, \"Installing PyTorch ROCm wheels...\", isIndeterminate: true)\n            );\n\n            // Set environment variable for wheel filename check bypass\n            venvRunner.UpdateEnvironmentVariables(env => env.SetItem(\"UV_SKIP_WHEEL_FILENAME_CHECK\", \"1\"));\n\n            // Install PyTorch ROCm wheels from TheRock releases (Python 3.11)\n            await venvRunner\n                .PipInstall(\n                    \"https://github.com/scottt/rocm-TheRock/releases/download/v6.5.0rc-pytorch-gfx110x/torch-2.7.0a0+rocm_git3f903c3-cp311-cp311-win_amd64.whl\",\n                    onConsoleOutput\n                )\n                .ConfigureAwait(false);\n\n            await venvRunner\n                .PipInstall(\n                    \"https://github.com/scottt/rocm-TheRock/releases/download/v6.5.0rc-pytorch-gfx110x/torchaudio-2.7.0a0+52638ef-cp311-cp311-win_amd64.whl\",\n                    onConsoleOutput\n                )\n                .ConfigureAwait(false);\n\n            await venvRunner\n                .PipInstall(\n                    \"https://github.com/scottt/rocm-TheRock/releases/download/v6.5.0rc-pytorch-gfx110x/torchvision-0.22.0+9eb57cd-cp311-cp311-win_amd64.whl\",\n                    onConsoleOutput\n                )\n                .ConfigureAwait(false);\n\n            // Install requirements directly using -r flag (handles @ URL syntax properly)\n            progress?.Report(new ProgressReport(-1f, \"Installing requirements...\", isIndeterminate: true));\n            await venvRunner.PipInstall(\"-r requirements.txt\", onConsoleOutput).ConfigureAwait(false);\n        }\n        else\n        {\n            // Linux AMD ROCm - standard PyTorch ROCm\n            // Install requirements directly using -r flag (handles @ URL syntax properly)\n            progress?.Report(new ProgressReport(-1f, \"Installing requirements...\", isIndeterminate: true));\n            await venvRunner.PipInstall(\"-r requirements.txt\", onConsoleOutput).ConfigureAwait(false);\n\n            // Install torch with ROCm index (force reinstall to ensure correct version)\n            progress?.Report(new ProgressReport(-1f, \"Installing PyTorch ROCm...\", isIndeterminate: true));\n            var torchArgs = new PipInstallArgs()\n                .WithTorch(\"==2.7.0\")\n                .WithTorchVision(\"==0.22.0\")\n                .WithTorchAudio(\"==2.7.0\")\n                .WithTorchExtraIndex(\"rocm6.3\")\n                .AddArg(\"--force-reinstall\")\n                .AddArg(\"--no-deps\");\n\n            await venvRunner.PipInstall(torchArgs, onConsoleOutput).ConfigureAwait(false);\n        }\n\n        // Install additional packages\n        await venvRunner.PipInstall(\"hf-xet setuptools numpy==1.26.4\", onConsoleOutput).ConfigureAwait(false);\n    }\n\n    public override async Task RunPackage(\n        string installLocation,\n        InstalledPackage installedPackage,\n        RunPackageOptions options,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage.PythonVersion))\n            .ConfigureAwait(false);\n\n        // Fix for distutils compatibility issue with Python 3.10 and setuptools\n        VenvRunner.UpdateEnvironmentVariables(env => env.SetItem(\"SETUPTOOLS_USE_DISTUTILS\", \"stdlib\"));\n\n        // Write the Gradio logging patch wrapper script so gr.Info/Warning/Error\n        // messages are also printed to stdout/stderr for console capture\n        var patchScriptPath = Path.Combine(installLocation, \"_sm_gradio_log_patch.py\");\n        await File.WriteAllTextAsync(patchScriptPath, GradioLogPatchScript, cancellationToken)\n            .ConfigureAwait(false);\n\n        var targetScript = Path.Combine(installLocation, options.Command ?? LaunchCommand);\n\n        // Notify user that the package is starting (loading can take a while)\n        onConsoleOutput?.Invoke(\n            new ProcessOutput { Text = \"Launching Wan2GP, please wait while the UI initializes...\\n\" }\n        );\n\n        // Launch via the patch wrapper, which monkey-patches Gradio then runs wgp.py\n        VenvRunner.RunDetached(\n            [patchScriptPath, targetScript, .. options.Arguments],\n            HandleConsoleOutput,\n            OnExit\n        );\n\n        return;\n\n        void HandleConsoleOutput(ProcessOutput s)\n        {\n            onConsoleOutput?.Invoke(s);\n\n            if (!s.Text.Contains(\"Running on\", StringComparison.OrdinalIgnoreCase))\n                return;\n\n            var regex = new Regex(@\"(https?:\\/\\/)([^:\\s]+):(\\d+)\");\n            var match = regex.Match(s.Text);\n            if (match.Success)\n            {\n                WebUrl = match.Value;\n                OnStartupComplete(WebUrl);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Progress/ProgressItem.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Progress;\n\npublic record ProgressItem(\n    Guid ProgressId,\n    string Name,\n    ProgressReport Progress,\n    bool Failed = false\n);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Progress/ProgressReport.cs",
    "content": "﻿using StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Models.Progress;\n\npublic readonly record struct ProgressReport\n{\n    /// <summary>\n    /// Progress value as percentage between 0 and 1.\n    /// </summary>\n    public double? Progress { get; init; } = 0;\n\n    /// <summary>\n    /// Current progress count.\n    /// </summary>\n    public ulong? Current { get; init; } = 0;\n\n    /// <summary>\n    /// Total progress count.\n    /// </summary>\n    public ulong? Total { get; init; } = 0;\n    public string? Title { get; init; }\n    public string? Message { get; init; }\n    public ProcessOutput? ProcessOutput { get; init; }\n    public bool IsIndeterminate { get; init; } = false;\n    public float Percentage => (float)Math.Ceiling(Math.Clamp(Progress ?? 0, 0, 1) * 100);\n    public ProgressType Type { get; init; } = ProgressType.Generic;\n    public bool PrintToConsole { get; init; } = true;\n    public double SpeedInMBps { get; init; } = 0f;\n\n    public static ProgressReport ForProcessOutput(ProcessOutput output) =>\n        new(-1f, isIndeterminate: true) { ProcessOutput = output };\n\n    public ProgressReport(\n        double progress,\n        string? title = null,\n        string? message = null,\n        bool isIndeterminate = false,\n        bool printToConsole = true,\n        double speedInMBps = 0,\n        ProgressType type = ProgressType.Generic\n    )\n    {\n        Progress = progress;\n        Title = title;\n        Message = message;\n        IsIndeterminate = isIndeterminate;\n        Type = type;\n        PrintToConsole = printToConsole;\n        SpeedInMBps = speedInMBps;\n    }\n\n    public ProgressReport(\n        ulong current,\n        ulong total,\n        string? title = null,\n        string? message = null,\n        bool isIndeterminate = false,\n        bool printToConsole = true,\n        double speedInMBps = 0,\n        ProgressType type = ProgressType.Generic\n    )\n    {\n        Current = current;\n        Total = total;\n        Progress = (double)current / total;\n        Title = title;\n        Message = message;\n        IsIndeterminate = isIndeterminate;\n        Type = type;\n        PrintToConsole = printToConsole;\n        SpeedInMBps = speedInMBps;\n    }\n\n    public ProgressReport(\n        int current,\n        int total,\n        string? title = null,\n        string? message = null,\n        bool isIndeterminate = false,\n        bool printToConsole = true,\n        double speedInMBps = 0,\n        ProgressType type = ProgressType.Generic\n    )\n    {\n        if (current < 0)\n            throw new ArgumentOutOfRangeException(nameof(current), \"Current progress cannot negative.\");\n        if (total < 0)\n            throw new ArgumentOutOfRangeException(nameof(total), \"Total progress cannot be negative.\");\n        Current = (ulong)current;\n        Total = (ulong)total;\n        Progress = (double)current / total;\n        Title = title;\n        Message = message;\n        IsIndeterminate = isIndeterminate;\n        Type = type;\n        PrintToConsole = printToConsole;\n        SpeedInMBps = speedInMBps;\n    }\n\n    public ProgressReport(\n        ulong current,\n        string? title = null,\n        string? message = null,\n        ProgressType type = ProgressType.Generic\n    )\n    {\n        Current = current;\n        Title = title;\n        Message = message;\n        IsIndeterminate = true;\n        Type = type;\n    }\n\n    // Implicit conversion from action\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Progress/ProgressState.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Progress;\n\npublic enum ProgressState\n{\n    Inactive,\n    Paused,\n    Pending,\n    Working,\n    Success,\n    Failed,\n    Cancelled\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Progress/ProgressType.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Progress;\n\npublic enum ProgressType\n{\n    Generic,\n    Download,\n    Extract,\n    Update,\n    Hashing,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PromptSyntax/PromptNode.cs",
    "content": "﻿using System.Diagnostics;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Core.Models.PromptSyntax;\n\n/// <summary>\n/// Interface for nodes that can have children.\n/// </summary>\npublic interface IHasChildren\n{\n    IEnumerable<PromptNode> Children { get; }\n}\n\n[DebuggerDisplay(\"{GetDebuggerDisplay(), nq}\")]\npublic abstract class PromptNode\n{\n    public PromptNode? Parent { get; set; }\n\n    public int StartIndex { get; set; }\n\n    public int EndIndex\n    {\n        get => StartIndex + Length;\n        set => Length = value - StartIndex;\n    }\n\n    public int Length { get; set; }\n\n    public required TextSpan Span\n    {\n        get => new(StartIndex, Length);\n        set => (StartIndex, Length) = (value.Start, value.Length);\n    }\n\n    /// <summary>\n    /// Gets a list of ancestor nodes\n    /// </summary>\n    public IEnumerable<PromptNode> Ancestors()\n    {\n        return Parent?.AncestorsAndSelf() ?? [];\n    }\n\n    /// <summary>\n    /// Gets a list of ancestor nodes (including this node)\n    /// </summary>\n    public IEnumerable<PromptNode> AncestorsAndSelf()\n    {\n        for (var node = this; node != null; node = Parent)\n        {\n            yield return node;\n        }\n    }\n\n    /// <summary>\n    /// Gets a list of descendant nodes.\n    /// </summary>\n    /// <param name=\"descendIntoChildren\">Determines if the search descends into a node's children.</param>\n    public IEnumerable<PromptNode> DescendantNodes(Func<PromptNode, bool>? descendIntoChildren = null)\n    {\n        if (this is not IHasChildren hasChildren)\n            yield break;\n\n        foreach (var child in hasChildren.Children)\n        {\n            yield return child;\n\n            if (descendIntoChildren == null || descendIntoChildren(child))\n            {\n                foreach (var descendant in child.DescendantNodes(descendIntoChildren))\n                {\n                    yield return descendant;\n                }\n            }\n        }\n    }\n\n    /// <summary>\n    /// Gets a list of descendant nodes.\n    /// </summary>\n    /// <param name=\"span\">The span the node's full span must intersect.</param>\n    /// <param name=\"descendIntoChildren\">Determines if the search descends into a node's children.</param>\n    public IEnumerable<PromptNode> DescendantNodes(\n        TextSpan span,\n        Func<PromptNode, bool>? descendIntoChildren = null\n    )\n    {\n        if (this is not IHasChildren hasChildren)\n            yield break;\n\n        foreach (var child in hasChildren.Children)\n        {\n            // Stop if exceeded\n            if (child.StartIndex > span.End)\n                break;\n\n            // Check span\n            if (!child.Span.IntersectsWith(span))\n                continue;\n\n            yield return child;\n\n            // Check if we should descend into children\n            if (descendIntoChildren != null && !descendIntoChildren(child))\n                continue;\n\n            foreach (var descendant in child.DescendantNodes(span, descendIntoChildren))\n            {\n                yield return descendant;\n            }\n        }\n    }\n\n    /// <summary>\n    /// Finds the descendant node with the smallest span that completely contains the provided target span.\n    /// Returns null if this node does not contain the target span.\n    /// </summary>\n    public PromptNode FindSmallestContainingDescendant(TextSpan span)\n    {\n        // Ensure the current node contains the target span\n        if (!Span.Contains(span))\n        {\n            throw new ArgumentOutOfRangeException(\n                nameof(span),\n                $\"Node span {Span} does not contain the target span {span}\"\n            );\n        }\n\n        var bestMatch = this;\n\n        // Iterate through all descendant nodes\n        foreach (var descendant in DescendantNodes())\n        {\n            // Check if descendant fully contains the target span\n            if (descendant.StartIndex <= span.Start && descendant.EndIndex >= span.End)\n            {\n                // Select this descendant if its span is smaller than the current best match\n                if (descendant.Length < bestMatch.Length)\n                {\n                    bestMatch = descendant;\n                }\n            }\n        }\n\n        return bestMatch;\n    }\n\n    public override string ToString()\n    {\n        return $\"{GetType().Name} {Span}\";\n    }\n\n    private string GetDebuggerDisplay()\n    {\n        return ToString();\n    }\n}\n\npublic class DocumentNode : PromptNode, IHasChildren\n{\n    public List<PromptNode> Content { get; init; } = [];\n\n    IEnumerable<PromptNode> IHasChildren.Children => Content;\n}\n\npublic class IdentifierNode : PromptNode\n{\n    public required string Name { get; set; }\n}\n\npublic class LiteralNode<T> : PromptNode\n{\n    public required string Raw { get; init; }\n\n    public required T Value { get; init; }\n}\n\npublic class TextNode : PromptNode\n{\n    public required string Text { get; set; }\n}\n\npublic class SeperatorNode : TextNode;\n\npublic class NumberNode : LiteralNode<decimal>;\n\npublic class ParenthesizedNode : PromptNode, IHasChildren\n{\n    public List<PromptNode> Content { get; } = [];\n    public NumberNode? Weight { get; set; }\n\n    IEnumerable<PromptNode> IHasChildren.Children => Content.AppendIfNotNull(Weight);\n}\n\npublic class ArrayNode : PromptNode, IHasChildren\n{\n    public List<PromptNode> Elements { get; } = [];\n\n    IEnumerable<PromptNode> IHasChildren.Children => Elements;\n}\n\npublic class NetworkNode : PromptNode, IHasChildren\n{\n    public required IdentifierNode NetworkType { get; init; }\n    public required TextNode ModelName { get; init; }\n    public NumberNode? ModelWeight { get; set; }\n    public NumberNode? ClipWeight { get; set; }\n\n    IEnumerable<PromptNode> IHasChildren.Children =>\n        new List<PromptNode> { NetworkType, ModelName }\n            .AppendIfNotNull(ModelWeight)\n            .AppendIfNotNull(ClipWeight);\n}\n\npublic class WildcardNode : PromptNode, IHasChildren\n{\n    public List<PromptNode> Options { get; } = [];\n\n    IEnumerable<PromptNode> IHasChildren.Children => Options;\n}\n\npublic class CommentNode : PromptNode\n{\n    public string? Text { get; set; }\n}\n\npublic class KeywordNode : PromptNode // AND, BREAK\n{\n    public required string Keyword { get; set; }\n}\n\n// Add other node types as needed (e.g., SeparatorNode, EscapeNode, etc.)\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PromptSyntax/PromptSyntaxBuilder.cs",
    "content": "﻿using System.Globalization;\nusing TextMateSharp.Grammars;\n\nnamespace StabilityMatrix.Core.Models.PromptSyntax;\n\npublic class PromptSyntaxBuilder(ITokenizeLineResult tokenizeResult, string sourceText)\n{\n    private int currentTokenIndex;\n\n    public PromptSyntaxTree BuildAST()\n    {\n        var nodes = new List<PromptNode>();\n\n        while (MoreTokens())\n        {\n            nodes.Add(ParseNode());\n        }\n\n        // Set parents\n        foreach (var node in nodes)\n        {\n            SetParents(node);\n        }\n\n        var startIndex = nodes.FirstOrDefault()?.Span.Start ?? 0;\n        // Ensure we don't exceed source text\n        var endIndex = Math.Min(nodes.LastOrDefault()?.Span.End ?? sourceText.Length, sourceText.Length);\n        var rootNode = new DocumentNode\n        {\n            Span = new TextSpan(startIndex, endIndex - startIndex),\n            Content = nodes,\n        };\n\n        return new PromptSyntaxTree(sourceText, rootNode, tokenizeResult.Tokens.ToList());\n    }\n\n    private static void SetParents(PromptNode node)\n    {\n        if (node is not IHasChildren hasChildren)\n            return;\n\n        foreach (var child in hasChildren.Children)\n        {\n            child.Parent = node;\n            SetParents(child);\n        }\n    }\n\n    private string GetTextSubstring(IToken token)\n    {\n        // IMPORTANT TextMate notes:\n        // 1. IToken.EndIndex is exclusive\n        // 2. Last token may exceed the length of the string, (length 10 string has EndIndex = 11)\n        var length =\n            token.EndIndex > sourceText.Length\n                ? sourceText.Length - token.StartIndex\n                : token.EndIndex - token.StartIndex;\n\n        return sourceText.Substring(token.StartIndex, length);\n    }\n\n    private PromptNode ParseNode()\n    {\n        var token = PeekToken(); // Look at the next token without consuming it.\n\n        if (token is null)\n        {\n            throw new InvalidOperationException(\"Unexpected end of input.\");\n        }\n\n        if (token.Scopes.Contains(\"comment.line.number-sign.prompt\"))\n        {\n            return ParseComment();\n        }\n        else if (\n            token.Scopes.Contains(\"meta.structure.wildcard.prompt\")\n            && token.Scopes.Contains(\"punctuation.definition.wildcard.begin.prompt\")\n        )\n        {\n            return ParseWildcard();\n        }\n        else if (\n            token.Scopes.Contains(\"meta.structure.array.prompt\")\n            && token.Scopes.Contains(\"punctuation.definition.array.begin.prompt\")\n        )\n        {\n            return ParseParenthesized();\n        }\n        else if (\n            token.Scopes.Contains(\"meta.structure.array.prompt\")\n            && token.Scopes.Contains(\"punctuation.definition.array.begin.prompt\")\n        )\n        {\n            return ParseArray();\n        }\n        else if (\n            token.Scopes.Contains(\"meta.structure.network.prompt\")\n            && token.Scopes.Contains(\"punctuation.definition.network.begin.prompt\")\n        )\n        {\n            return ParseNetwork();\n        }\n        else if (token.Scopes.Contains(\"keyword.control\"))\n        {\n            return ParseKeyword();\n        }\n        else if (token.Scopes.Contains(\"meta.embedded\"))\n        {\n            return ParseText();\n        }\n        else\n        {\n            // Handle other token types (separator, escape, etc.)\n            // Or throw an exception for unexpected tokens.\n            return ParseText();\n        }\n    }\n\n    private CommentNode ParseComment()\n    {\n        var token = ConsumeToken();\n        var text = GetTextSubstring(token);\n        return new CommentNode { Span = new TextSpan(token.StartIndex, token.Length), Text = text };\n    }\n\n    private IdentifierNode ParseIdentifier()\n    {\n        var token = ConsumeToken();\n        var text = GetTextSubstring(token);\n        return new IdentifierNode { Span = new TextSpan(token.StartIndex, token.Length), Name = text };\n    }\n\n    private TextNode ParseText()\n    {\n        var token = ConsumeToken(); // Consume the text token.\n        var text = GetTextSubstring(token);\n\n        // Check if it's a separator\n        if (\n            token.Scopes.Contains(\"meta.structure.array.prompt\")\n            && token.Scopes.Contains(\"punctuation.separator.variable.prompt\")\n        )\n        {\n            return new SeperatorNode { Span = new TextSpan(token.StartIndex, token.Length), Text = text };\n        }\n\n        return new TextNode { Span = new TextSpan(token.StartIndex, token.Length), Text = text };\n    }\n\n    private KeywordNode ParseKeyword()\n    {\n        var token = ConsumeToken();\n        var keyword = GetTextSubstring(token);\n        return new KeywordNode { Span = new TextSpan(token.StartIndex, token.Length), Keyword = keyword };\n    }\n\n    private NumberNode ParseNumber()\n    {\n        var token = ConsumeToken();\n        var number = GetTextSubstring(token);\n\n        return new NumberNode\n        {\n            Raw = number,\n            Span = new TextSpan(token.StartIndex, token.Length),\n            Value = decimal.Parse(number, CultureInfo.InvariantCulture),\n        };\n    }\n\n    private ParenthesizedNode ParseParenthesized()\n    {\n        var openParenToken = ConsumeToken(); // Consume the '('\n        if (\n            openParenToken is null\n            || !openParenToken.Scopes.Contains(\"punctuation.definition.array.begin.prompt\")\n        )\n            throw new InvalidOperationException(\"Expected opening parenthesis.\");\n\n        // Set start index\n        var node = new ParenthesizedNode { Span = new TextSpan(openParenToken.StartIndex, 0) };\n\n        while (MoreTokens())\n        {\n            // Check if no more tokens to consume.\n            if (PeekToken() is not { } nextToken)\n            {\n                // Ensure we have length set\n                if (node.Span.Length == 0)\n                    throw new InvalidOperationException(\"Unexpected end of input.\");\n                break;\n            }\n\n            if (nextToken.Scopes.Contains(\"punctuation.separator.weight.prompt\"))\n            {\n                // Parse the weight.\n                ConsumeToken(); // Consume the ':'\n\n                // Check the weight value token.\n                var weightToken = PeekToken();\n                if (weightToken is null || !weightToken.Scopes.Contains(\"constant.numeric\"))\n                {\n                    throw new InvalidOperationException(\"Expected numeric weight value.\");\n                }\n\n                // Consume the weight token.\n                node.Weight = ParseNumber();\n            }\n            // We're supposed to check `punctuation.definition.array.end.prompt` here, textmate is not parsing it\n            // separately always with current tmLanguage grammar, so ALSO use `meta.structure.weight.prompt` for now\n            // We check this AFTER `punctuation.separator.weight.prompt` to avoid consuming the ':'\n            else if (\n                nextToken.Scopes.Contains(\"punctuation.definition.array.end.prompt\")\n                || nextToken.Scopes.Contains(\"meta.structure.weight.prompt\")\n            )\n            {\n                // Verify contents\n                if (GetTextSubstring(nextToken) != \")\")\n                    throw new InvalidOperationException(\"Expected closing parenthesis.\");\n\n                ConsumeToken(); // Consume the ')'\n                node.EndIndex = nextToken.EndIndex; // Set end index\n                break;\n            }\n            else\n            {\n                // It's part of the content.\n                node.Content.Add(ParseNode()); // Recursively parse nested nodes.\n            }\n        }\n\n        return node;\n    }\n\n    private NetworkNode ParseNetwork()\n    {\n        var beginNetworkToken = ConsumeToken();\n        if (\n            beginNetworkToken is null\n            || !beginNetworkToken.Scopes.Contains(\"punctuation.definition.network.begin.prompt\")\n        )\n            throw new InvalidOperationException(\"Expected opening bracket.\");\n\n        // type\n        var typeToken = PeekToken();\n        if (typeToken is null || !typeToken.Scopes.Contains(\"meta.embedded.network.type.prompt\"))\n            throw new InvalidOperationException(\"Expected network type.\");\n        var type = ParseIdentifier();\n\n        // colon\n        var colonToken = ConsumeToken();\n        if (colonToken is null || !colonToken.Scopes.Contains(\"punctuation.separator.variable.prompt\"))\n            throw new InvalidOperationException(\"Expected colon.\");\n\n        // name\n        var nameToken = PeekToken();\n        if (nameToken is null || !nameToken.Scopes.Contains(\"meta.embedded.network.model.prompt\"))\n            throw new InvalidOperationException(\"Expected network name.\");\n        var name = ParseText();\n\n        // model weight, clip weight\n        NumberNode? modelWeight = null;\n        NumberNode? clipWeight = null;\n\n        // colon\n        var nextToken = PeekToken();\n        if (nextToken is not null && nextToken.Scopes.Contains(\"punctuation.separator.variable.prompt\"))\n        {\n            ConsumeToken(); // consume colon\n\n            // Parse the model weight.\n            var modelWeightToken = ConsumeToken();\n            if (modelWeightToken is null || !modelWeightToken.Scopes.Contains(\"constant.numeric\"))\n                throw new InvalidOperationException(\"Expected network weight.\");\n            modelWeight = ParseNumber();\n\n            // colon\n            nextToken = PeekToken();\n            if (nextToken is not null && nextToken.Scopes.Contains(\"punctuation.separator.variable.prompt\"))\n            {\n                ConsumeToken(); // consume colon\n\n                // Parse the clip weight.\n                var clipWeightToken = ConsumeToken();\n                if (clipWeightToken is null || !clipWeightToken.Scopes.Contains(\"constant.numeric\"))\n                    throw new InvalidOperationException(\"Expected network weight.\");\n                clipWeight = ParseNumber();\n            }\n        }\n\n        var endNetworkToken = ConsumeToken();\n        if (\n            endNetworkToken is null\n            || !endNetworkToken.Scopes.Contains(\"punctuation.definition.network.end.prompt\")\n        )\n            throw new InvalidOperationException(\"Expected closing bracket.\");\n\n        return new NetworkNode\n        {\n            Span = TextSpan.FromBounds(beginNetworkToken.StartIndex, endNetworkToken.EndIndex),\n            NetworkType = type,\n            ModelName = name,\n            ModelWeight = modelWeight,\n            ClipWeight = clipWeight,\n        };\n    }\n\n    private ArrayNode ParseArray()\n    {\n        var openBracket = ConsumeToken();\n        if (openBracket is null || !openBracket.Scopes.Contains(\"punctuation.definition.array.begin.prompt\"))\n            throw new InvalidOperationException(\"Expected opening bracket.\");\n\n        var node = new ArrayNode\n        {\n            Span = new TextSpan(openBracket.StartIndex, 0), // Set start index\n        };\n\n        while (MoreTokens())\n        {\n            var nextToken = PeekToken();\n            if (nextToken is null)\n                break;\n\n            if (nextToken.Scopes.Contains(\"punctuation.definition.array.end.prompt\"))\n            {\n                ConsumeToken(); // Consume the ']'\n                node.EndIndex = nextToken.EndIndex; //Set end index\n                break;\n            }\n            else\n            {\n                node.Elements.Add(ParseNode()); // Recursively parse nested nodes.\n            }\n        }\n\n        return node;\n    }\n\n    private WildcardNode ParseWildcard()\n    {\n        var openBraceToken = ConsumeToken(); // Consume the '{'\n        if (\n            openBraceToken is null\n            || !openBraceToken.Scopes.Contains(\"punctuation.definition.wildcard.begin.prompt\")\n        )\n            throw new InvalidOperationException(\"Expected opening brace.\");\n\n        var node = new WildcardNode\n        {\n            Span = new TextSpan(openBraceToken.StartIndex, 0), // Set start index\n        };\n\n        while (MoreTokens())\n        {\n            var nextToken = PeekToken();\n            if (nextToken is null)\n                break;\n\n            if (nextToken.Scopes.Contains(\"punctuation.definition.wildcard.end.prompt\"))\n            {\n                ConsumeToken(); // Consume the '}'\n                node.EndIndex = nextToken.EndIndex;\n                break;\n            }\n            else if (nextToken.Scopes.Contains(\"keyword.operator.choice.prompt\"))\n            {\n                ConsumeToken(); // Consume the '|'\n            }\n            else\n            {\n                node.Options.Add(ParseNode()); // Recursively parse nested nodes.\n            }\n        }\n\n        return node;\n    }\n\n    private IToken? PeekToken()\n    {\n        if (currentTokenIndex < tokenizeResult.Tokens.Length)\n        {\n            var result = tokenizeResult.Tokens[currentTokenIndex];\n\n            // If this is the last token, ensure it doesn't exceed the source text length\n            if (currentTokenIndex == tokenizeResult.Tokens.Length - 1)\n            {\n                if (result.EndIndex > sourceText.Length)\n                {\n                    result = new Token\n                    {\n                        StartIndex = result.StartIndex,\n                        EndIndex = sourceText.Length,\n                        Length = sourceText.Length - result.StartIndex,\n                        Scopes = result.Scopes,\n                    };\n                }\n            }\n\n            return result;\n        }\n        return null;\n    }\n\n    private IToken ConsumeToken()\n    {\n        if (!MoreTokens())\n            throw new InvalidOperationException(\"No more tokens to consume.\");\n\n        var result = tokenizeResult.Tokens[currentTokenIndex++];\n\n        // If this is the last token, ensure it doesn't exceed the source text length\n        if (currentTokenIndex == tokenizeResult.Tokens.Length - 1)\n        {\n            if (result.EndIndex > sourceText.Length)\n            {\n                result = new Token\n                {\n                    StartIndex = result.StartIndex,\n                    EndIndex = sourceText.Length,\n                    Length = sourceText.Length - result.StartIndex,\n                    Scopes = result.Scopes,\n                };\n            }\n        }\n\n        return result;\n    }\n\n    private bool MoreTokens()\n    {\n        return currentTokenIndex < tokenizeResult.Tokens.Length;\n    }\n\n    private class Token : IToken\n    {\n        public int StartIndex { get; set; }\n        public int EndIndex { get; init; }\n        public int Length { get; init; }\n        public List<string> Scopes { get; init; } = [];\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PromptSyntax/PromptSyntaxTree.cs",
    "content": "﻿using System.Text;\nusing TextMateSharp.Grammars;\n\nnamespace StabilityMatrix.Core.Models.PromptSyntax;\n\npublic class PromptSyntaxTree(string sourceText, DocumentNode rootNode, IReadOnlyList<IToken> tokens)\n{\n    public DocumentNode RootNode { get; } = rootNode;\n\n    public IReadOnlyList<IToken> Tokens { get; } = tokens;\n\n    public string SourceText { get; } = sourceText;\n\n    // Get source text for a specific node\n    public string GetSourceText(PromptNode node)\n    {\n        return GetSourceText(node.Span);\n    }\n\n    // Get source text for a specific node\n    public string GetSourceText(TextSpan span)\n    {\n        if (span.Start < 0)\n            throw new ArgumentOutOfRangeException(nameof(span), \"Node indices are out of range.\");\n\n        // Trim length if it exceeds the source text length\n        var length = span.End > SourceText.Length ? SourceText.Length - span.Start : span.Length;\n\n        return SourceText.Substring(span.Start, length);\n    }\n\n    public string ToDebugString()\n    {\n        var sb = new StringBuilder();\n        foreach (var node in RootNode.Content)\n        {\n            AppendNode(node, sb, 0);\n        }\n        return sb.ToString();\n    }\n\n    private void AppendNode(PromptNode node, StringBuilder sb, int indentLevel)\n    {\n        sb.Append(' ', indentLevel * 4); // 4 spaces per indent level\n        sb.Append(\"- \");\n\n        switch (node)\n        {\n            case TextNode textNode:\n                sb.AppendLine(\n                    $\"TextNode: \\\"{textNode.Text.Replace(\"\\n\", \"\\\\n\")}\\\" ({textNode.StartIndex}-{textNode.EndIndex})\"\n                ); // Escape newlines\n                break;\n            case ParenthesizedNode parenNode:\n                sb.AppendLine($\"ParenthesizedNode: ({parenNode.StartIndex}-{parenNode.EndIndex})\");\n                foreach (var child in parenNode.Content)\n                {\n                    AppendNode(child, sb, indentLevel + 1);\n                }\n                if (parenNode.Weight != null)\n                {\n                    AppendNode(parenNode.Weight, sb, indentLevel + 1);\n                }\n                break;\n            case NetworkNode networkNode:\n                sb.AppendLine(\n                    $\"NetworkNode: Type={networkNode.NetworkType}, Model={networkNode.ModelName}, Weight={networkNode.ModelWeight}, ClipWeight={networkNode.ClipWeight} ({networkNode.StartIndex}-{networkNode.EndIndex})\"\n                );\n                break;\n            case WildcardNode wildcardNode:\n                sb.AppendLine($\"WildcardNode: ({node.StartIndex}-{node.EndIndex})\");\n                foreach (var option in wildcardNode.Options)\n                {\n                    AppendNode(option, sb, indentLevel + 1);\n                }\n                break;\n            case CommentNode commentNode:\n                sb.AppendLine($\"CommentNode: \\\"{commentNode.Text}\\\" ({node.StartIndex}-{node.EndIndex})\");\n                break;\n\n            case NumberNode numberNode:\n                sb.AppendLine($\"NumberNode: \\\"{numberNode.Value}\\\" ({node.StartIndex}-{node.EndIndex})\");\n                break;\n\n            case KeywordNode keywordNode:\n                sb.AppendLine($\"KeywordNode: \\\"{keywordNode.Keyword}\\\" ({node.StartIndex}-{node.EndIndex})\");\n                break;\n            case ArrayNode arrayNode:\n                sb.AppendLine($\"ArrayNode: ({node.StartIndex}-{node.EndIndex})\");\n                foreach (var child in arrayNode.Elements)\n                {\n                    AppendNode(child, sb, indentLevel + 1);\n                }\n                break;\n\n            // Add cases for other node types...\n            default:\n                sb.AppendLine(\n                    $\"Unknown Node Type: {node.GetType().Name} ({node.StartIndex}-{node.EndIndex})\"\n                );\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/PromptSyntax/TextSpan.cs",
    "content": "﻿// Licensed to the .NET Foundation under one or more agreements.\n// The .NET Foundation licenses this file to you under the MIT license.\n// See the LICENSE file in the project root for more information.\n\nusing System.Runtime.Serialization;\nusing TextMateSharp.Grammars;\n\nnamespace StabilityMatrix.Core.Models.PromptSyntax;\n\n/// <summary>\n/// Immutable abstract representation of a span of text.  For example, in an error diagnostic that reports a\n/// location, it could come from a parsed string, text from a tool editor buffer, etc.\n/// </summary>\n[DataContract]\npublic readonly struct TextSpan : IEquatable<TextSpan>, IComparable<TextSpan>\n{\n    /// <summary>\n    /// Creates a TextSpan instance beginning with the position Start and having the Length\n    /// specified with <paramref name=\"length\" />.\n    /// </summary>\n    public TextSpan(int start, int length)\n    {\n        if (start < 0)\n        {\n            throw new ArgumentOutOfRangeException(nameof(start));\n        }\n\n        if (start + length < start)\n        {\n            throw new ArgumentOutOfRangeException(nameof(length));\n        }\n\n        Start = start;\n        Length = length;\n    }\n\n    /// <summary>\n    /// Start point of the span.\n    /// </summary>\n    [DataMember(Order = 0)]\n    public int Start { get; }\n\n    /// <summary>\n    /// End of the span.\n    /// </summary>\n    public int End => Start + Length;\n\n    /// <summary>\n    /// Length of the span.\n    /// </summary>\n    [DataMember(Order = 1)]\n    public int Length { get; }\n\n    /// <summary>\n    /// Determines whether or not the span is empty.\n    /// </summary>\n    public bool IsEmpty => Length == 0;\n\n    /// <summary>\n    /// Determines whether the position lies within the span.\n    /// </summary>\n    /// <param name=\"position\">\n    /// The position to check.\n    /// </param>\n    /// <returns>\n    /// <c>true</c> if the position is greater than or equal to Start and strictly less\n    /// than End, otherwise <c>false</c>.\n    /// </returns>\n    public bool Contains(int position)\n    {\n        return unchecked((uint)(position - Start) < (uint)Length);\n    }\n\n    /// <summary>\n    /// Determines whether <paramref name=\"span\"/> falls completely within this span.\n    /// </summary>\n    /// <param name=\"span\">\n    /// The span to check.\n    /// </param>\n    /// <returns>\n    /// <c>true</c> if the specified span falls completely within this span, otherwise <c>false</c>.\n    /// </returns>\n    public bool Contains(TextSpan span)\n    {\n        return span.Start >= Start && span.End <= End;\n    }\n\n    /// <summary>\n    /// Determines whether <paramref name=\"span\"/> overlaps this span. Two spans are considered to overlap\n    /// if they have positions in common and neither is empty. Empty spans do not overlap with any\n    /// other span.\n    /// </summary>\n    /// <param name=\"span\">\n    /// The span to check.\n    /// </param>\n    /// <returns>\n    /// <c>true</c> if the spans overlap, otherwise <c>false</c>.\n    /// </returns>\n    public bool OverlapsWith(TextSpan span)\n    {\n        var overlapStart = Math.Max(Start, span.Start);\n        var overlapEnd = Math.Min(End, span.End);\n\n        return overlapStart < overlapEnd;\n    }\n\n    /// <summary>\n    /// Returns the overlap with the given span, or null if there is no overlap.\n    /// </summary>\n    /// <param name=\"span\">\n    /// The span to check.\n    /// </param>\n    /// <returns>\n    /// The overlap of the spans, or null if the overlap is empty.\n    /// </returns>\n    public TextSpan? Overlap(TextSpan span)\n    {\n        var overlapStart = Math.Max(Start, span.Start);\n        var overlapEnd = Math.Min(End, span.End);\n\n        return overlapStart < overlapEnd ? FromBounds(overlapStart, overlapEnd) : null;\n    }\n\n    /// <summary>\n    /// Determines whether <paramref name=\"span\"/> intersects this span. Two spans are considered to\n    /// intersect if they have positions in common or the end of one span\n    /// coincides with the start of the other span.\n    /// </summary>\n    /// <param name=\"span\">\n    /// The span to check.\n    /// </param>\n    /// <returns>\n    /// <c>true</c> if the spans intersect, otherwise <c>false</c>.\n    /// </returns>\n    public bool IntersectsWith(TextSpan span)\n    {\n        return span.Start <= End && span.End >= Start;\n    }\n\n    /// <summary>\n    /// Determines whether <paramref name=\"position\"/> intersects this span.\n    /// A position is considered to intersect if it is between the start and\n    /// end positions (inclusive) of this span.\n    /// </summary>\n    /// <param name=\"position\">\n    /// The position to check.\n    /// </param>\n    /// <returns>\n    /// <c>true</c> if the position intersects, otherwise <c>false</c>.\n    /// </returns>\n    public bool IntersectsWith(int position)\n    {\n        return unchecked((uint)(position - Start) <= (uint)Length);\n    }\n\n    /// <summary>\n    /// Returns the intersection with the given span, or null if there is no intersection.\n    /// </summary>\n    /// <param name=\"span\">\n    /// The span to check.\n    /// </param>\n    /// <returns>\n    /// The intersection of the spans, or null if the intersection is empty.\n    /// </returns>\n    public TextSpan? Intersection(TextSpan span)\n    {\n        var intersectStart = Math.Max(Start, span.Start);\n        var intersectEnd = Math.Min(End, span.End);\n\n        return intersectStart <= intersectEnd ? FromBounds(intersectStart, intersectEnd) : null;\n    }\n\n    /// <summary>\n    /// Creates a new <see cref=\"TextSpan\"/> from <paramref name=\"start\" /> and <paramref\n    /// name=\"end\"/> positions as opposed to a position and length.\n    ///\n    /// The returned TextSpan contains the range with <paramref name=\"start\"/> inclusive,\n    /// and <paramref name=\"end\"/> exclusive.\n    /// </summary>\n    public static TextSpan FromBounds(int start, int end)\n    {\n        ArgumentOutOfRangeException.ThrowIfNegative(start, nameof(start));\n        ArgumentOutOfRangeException.ThrowIfLessThan(end, start, nameof(end));\n\n        return new TextSpan(start, end - start);\n    }\n\n    /// <summary>\n    /// Determines if two instances of <see cref=\"TextSpan\"/> are the same.\n    /// </summary>\n    public static bool operator ==(TextSpan left, TextSpan right)\n    {\n        return left.Equals(right);\n    }\n\n    /// <summary>\n    /// Determines if two instances of <see cref=\"TextSpan\"/> are different.\n    /// </summary>\n    public static bool operator !=(TextSpan left, TextSpan right)\n    {\n        return !left.Equals(right);\n    }\n\n    /// <summary>\n    /// Determines if current instance of <see cref=\"TextSpan\"/> is equal to another.\n    /// </summary>\n    public bool Equals(TextSpan other)\n    {\n        return Start == other.Start && Length == other.Length;\n    }\n\n    /// <summary>\n    /// Determines if current instance of <see cref=\"TextSpan\"/> is equal to another.\n    /// </summary>\n    public override bool Equals(object? obj) => obj is TextSpan span && Equals(span);\n\n    /// <summary>\n    /// Produces a hash code for <see cref=\"TextSpan\"/>.\n    /// </summary>\n    public override int GetHashCode()\n    {\n        return HashCode.Combine(Start, Length);\n    }\n\n    /// <summary>\n    /// Provides a string representation for <see cref=\"TextSpan\"/>.\n    /// This representation uses \"half-open interval\" notation, indicating the endpoint character is not included.\n    /// Example: <c>[10..20)</c>, indicating the text starts at position 10 and ends at position 20 not included.\n    /// </summary>\n    public override string ToString()\n    {\n        return $\"[{Start}..{End})\";\n    }\n\n    /// <summary>\n    /// Compares current instance of <see cref=\"TextSpan\"/> with another.\n    /// </summary>\n    public int CompareTo(TextSpan other)\n    {\n        var diff = Start - other.Start;\n        if (diff != 0)\n        {\n            return diff;\n        }\n\n        return Length - other.Length;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/RelayPropertyChangedEventArgs.cs",
    "content": "﻿using System.ComponentModel;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class RelayPropertyChangedEventArgs : PropertyChangedEventArgs\n{\n    public bool IsRelay { get; }\n\n    /// <inheritdoc />\n    public RelayPropertyChangedEventArgs(string? propertyName, bool isRelay = false)\n        : base(propertyName)\n    {\n        IsRelay = isRelay;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/RemoteResource.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\n/// <summary>\n/// Defines a remote downloadable resource.\n/// </summary>\npublic readonly record struct RemoteResource\n{\n    public required Uri Url { get; init; }\n\n    public Uri[]? FallbackUrls { get; init; }\n\n    public string? FileNameOverride { get; init; }\n\n    public string FileName => FileNameOverride ?? Path.GetFileName(Url.ToString());\n\n    /// <summary>\n    /// Optional relative subdirectory to download the file to.\n    /// </summary>\n    public string? RelativeDirectory { get; init; }\n\n    /// <summary>\n    /// Relative path to download the file to.\n    /// This is <see cref=\"RelativeDirectory\"/> combined with <see cref=\"FileName\"/> if <see cref=\"RelativeDirectory\"/> is not null.\n    /// Otherwise, it is just <see cref=\"FileName\"/>.\n    /// </summary>\n    public string RelativePath =>\n        !string.IsNullOrEmpty(RelativeDirectory) ? Path.Combine(RelativeDirectory, FileName) : FileName;\n\n    public string? HashSha256 { get; init; }\n\n    /// <summary>\n    /// Type info, for remote models this is <see cref=\"SharedFolderType\"/> of the model.\n    /// </summary>\n    public object? ContextType { get; init; }\n\n    public Uri? InfoUrl { get; init; }\n\n    public string? LicenseType { get; init; }\n\n    public Uri? LicenseUrl { get; init; }\n\n    public string? Author { get; init; }\n\n    /// <summary>\n    /// Whether to auto-extract the archive after download\n    /// </summary>\n    public bool AutoExtractArchive { get; init; }\n\n    /// <summary>\n    /// Optional relative path to extract the archive to, if AutoExtractArchive is true\n    /// </summary>\n    public string? ExtractRelativePath { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/SafetensorMetadata.cs",
    "content": "using System.Buffers;\nusing System.Buffers.Binary;\nusing System.Text.Json;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic record SafetensorMetadata\n{\n    // public string? NetworkModule { get; init; }\n    // public string? ModelSpecArchitecture { get; init; }\n\n    public List<Tag>? TagFrequency { get; init; }\n\n    public required List<Metadata> OtherMetadata { get; init; }\n\n    /// <summary>\n    /// Tries to parse the metadata from a SafeTensor file.\n    /// </summary>\n    /// <param name=\"safetensorPath\">Path to the SafeTensor file.</param>\n    /// <returns>The parsed metadata. Can be <see langword=\"null\"/> if the file does not contain metadata.</returns>\n    public static async Task<SafetensorMetadata?> ParseAsync(string safetensorPath)\n    {\n        using var stream = new FileStream(safetensorPath, FileMode.Open, FileAccess.Read, FileShare.Read);\n        return await ParseAsync(stream);\n    }\n\n    /// <summary>\n    /// Tries to parse the metadata from a SafeTensor file.\n    /// </summary>\n    /// <param name=\"safetensorStream\">Stream to the SafeTensor file.</param>\n    /// <returns>The parsed metadata. Can be <see langword=\"null\"/> if the file does not contain metadata.</returns>\n    public static async Task<SafetensorMetadata?> ParseAsync(Stream safetensorStream)\n    {\n        // 8 bytes unsigned little-endian 64-bit integer\n        // 1 byte start of JSON object '{'\n        Memory<byte> buffer = new byte[9];\n        await safetensorStream.ReadExactlyAsync(buffer).ConfigureAwait(false);\n        var span = buffer.Span;\n\n        const ulong MAX_ALLOWED_JSON_LENGTH = 100 * 1024 * 1024; // 100 MB\n        var jsonLength = BinaryPrimitives.ReadUInt64LittleEndian(span);\n        if (jsonLength > MAX_ALLOWED_JSON_LENGTH)\n        {\n            throw new InvalidDataException(\"JSON length exceeds the maximum allowed size.\");\n        }\n        if (span[8] != '{')\n        {\n            throw new InvalidDataException(\"JSON does not start with '{'.\");\n        }\n\n        // Unfornately Utf8JsonReader does not support reading from a stream directly.\n        // Usually the size of the entire JSON object is less than 500KB,\n        // using a pooled buffer should reduce the number of large allocations.\n        var jsonBytes = ArrayPool<byte>.Shared.Rent((int)jsonLength);\n        try\n        {\n            // Important: the length of the rented buffer can be larger than jsonLength\n            // and there can be additional junk data at the end.\n\n            // we already read {, so start from index 1\n            jsonBytes[0] = (byte)'{';\n            await safetensorStream\n                .ReadExactlyAsync(jsonBytes, 1, (int)(jsonLength - 1))\n                .ConfigureAwait(false);\n\n            // read the JSON with Utf8JsonReader, then only deserialize what we need\n            // saves us from allocating a bunch of strings then throwing them away\n            var reader = new Utf8JsonReader(jsonBytes.AsSpan(0, (int)jsonLength));\n\n            reader.Read();\n            if (reader.TokenType != JsonTokenType.StartObject)\n            {\n                // expecting a JSON object\n                throw new InvalidDataException(\"JSON does not start with '{'.\");\n            }\n\n            while (reader.Read())\n            {\n                // for each property in the object\n                if (reader.TokenType == JsonTokenType.EndObject)\n                {\n                    // end of the object, no \"__metadata__\" found\n                    // return true to indicate that we successfully read the JSON\n                    // but it does not contain metadata\n                    return null;\n                }\n\n                if (reader.TokenType != JsonTokenType.PropertyName)\n                {\n                    // expecting a property name\n                    throw new InvalidDataException(\n                        $\"Invalid metadata JSON, expected property name but got {reader.TokenType}.\"\n                    );\n                }\n\n                if (reader.ValueTextEquals(\"__metadata__\"))\n                {\n                    if (JsonSerializer.Deserialize<Dictionary<string, string>>(ref reader) is { } dict)\n                    {\n                        return FromDictionary(dict);\n                    }\n\n                    // got null from Deserialize\n                    throw new InvalidDataException(\"Failed to deserialize metadata.\");\n                }\n                else\n                {\n                    // skip the property value\n                    reader.Skip();\n                }\n            }\n            // should not reach here, json is malformed\n            throw new InvalidDataException(\"Invalid metadata JSON.\");\n        }\n        finally\n        {\n            ArrayPool<byte>.Shared.Return(jsonBytes);\n        }\n    }\n\n    private static readonly HashSet<string> MetadataKeys =\n    [\n        // \"ss_network_module\",\n        // \"modelspec.architecture\",\n        \"ss_tag_frequency\",\n    ];\n\n    internal static SafetensorMetadata FromDictionary(Dictionary<string, string> metadataDict)\n    {\n        // equivalent to the following code, rewitten manually for performance\n        // otherMetadata = metadataDict\n        //     .Where(kv => !MetadataKeys.Contains(kv.Key))\n        //     .Select(kv => new Metadata(kv.Key, kv.Value))\n        //     .OrderBy(x => x.Name)\n        //     .ToList();\n        var otherMetadata = new List<Metadata>(metadataDict.Count);\n        foreach (var kv in metadataDict)\n        {\n            if (MetadataKeys.Contains(kv.Key))\n            {\n                continue;\n            }\n\n            otherMetadata.Add(new Metadata(kv.Key, kv.Value));\n        }\n        otherMetadata.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal));\n\n        var metadata = new SafetensorMetadata\n        {\n            // NetworkModule = metadataDict.GetValueOrDefault(\"ss_network_module\"),\n            // ModelSpecArchitecture = metadataDict.GetValueOrDefault(\"modelspec.architecture\"),\n            OtherMetadata = otherMetadata\n        };\n\n        if (metadataDict.TryGetValue(\"ss_tag_frequency\", out var tagFrequencyJson))\n        {\n            try\n            {\n                // ss_tag_frequency example:\n                // { \"some_name\": {\"tag1\": 5, \"tag2\": 10}, \"another_name\": {\"tag1\": 3, \"tag3\": 1} }\n                // we flatten the dictionary of dictionaries into a single dictionary\n\n                var tagFrequencyDict = new Dictionary<string, int>();\n\n                var doc = JsonDocument.Parse(tagFrequencyJson);\n                var root = doc.RootElement;\n                if (root.ValueKind == JsonValueKind.Object)\n                {\n                    foreach (var property in root.EnumerateObject())\n                    {\n                        var tags = property.Value;\n                        if (tags.ValueKind != JsonValueKind.Object)\n                        {\n                            continue;\n                        }\n\n                        foreach (var tagProperty in tags.EnumerateObject())\n                        {\n                            var tagName = tagProperty.Name;\n\n                            if (\n                                string.IsNullOrEmpty(tagName)\n                                || tagProperty.Value.ValueKind != JsonValueKind.Number\n                            )\n                            {\n                                continue;\n                            }\n\n                            var count = tagProperty.Value.GetInt32();\n                            if (!tagFrequencyDict.TryAdd(tagName, count))\n                            {\n                                // tag already exists, increment the count\n                                tagFrequencyDict[tagName] += count;\n                            }\n                        }\n                    }\n                }\n\n                // equivalent to the following code, rewitten manually for performance\n                // tagFrequency = tagFrequencyDict\n                //     .Select(kv => new Tag(kv.Key, kv.Value))\n                //     .OrderByDescending(x => x.Frequency)\n                //     .ToList();\n                var tagFrequency = new List<Tag>(tagFrequencyDict.Count);\n                foreach (var kv in tagFrequencyDict)\n                {\n                    tagFrequency.Add(new Tag(kv.Key, kv.Value));\n                }\n                tagFrequency.Sort((x, y) => y.Frequency.CompareTo(x.Frequency));\n\n                metadata = metadata with { TagFrequency = tagFrequency };\n            }\n            catch (Exception)\n            {\n                // ignore\n            }\n        }\n\n        return metadata;\n    }\n\n    public readonly record struct Tag(string Name, int Frequency);\n\n    public readonly record struct Metadata(string Name, string Value);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Secrets.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Api.CivitTRPC;\nusing StabilityMatrix.Core.Models.Api.Lykos;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic readonly record struct Secrets\n{\n    [Obsolete(\"Use LykosAccountV2 instead\")]\n    public LykosAccountV1Tokens? LykosAccount { get; init; }\n\n    public CivitApiTokens? CivitApi { get; init; }\n\n    public LykosAccountV2Tokens? LykosAccountV2 { get; init; }\n\n    public string? HuggingFaceToken { get; init; }\n}\n\npublic static class SecretsExtensions\n{\n    public static bool HasLegacyLykosAccount(this Secrets secrets)\n    {\n#pragma warning disable CS0618 // Type or member is obsolete\n        return secrets.LykosAccount is not null;\n#pragma warning restore CS0618 // Type or member is obsolete\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/AnalyticsSettings.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing Semver;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Core.Models.Settings;\n\npublic class AnalyticsSettings\n{\n    [JsonIgnore]\n    public static TimeSpan DefaultLaunchDataSendInterval { get; set; } = TimeSpan.FromDays(1);\n\n    [JsonConverter(typeof(SemVersionJsonConverter))]\n    public SemVersion? LastSeenConsentVersion { get; set; }\n\n    public bool? LastSeenConsentAccepted { get; set; }\n\n    public bool IsUsageDataEnabled { get; set; }\n\n    public DateTimeOffset? LaunchDataLastSentAt { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/GlobalSettings.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Settings;\n\npublic record GlobalSettings\n{\n    public bool EulaAccepted { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/HolidayMode.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Settings;\n\n[JsonConverter(typeof(JsonStringEnumConverter<HolidayMode>))]\npublic enum HolidayMode\n{\n    Automatic,\n    Enabled,\n    Disabled\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/LastDownloadLocationInfo.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Settings;\n\npublic class LastDownloadLocationInfo\n{\n    public string? SelectedInstallLocation { get; set; }\n    public string? CustomInstallLocation { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/LibrarySettings.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Settings;\n\npublic class LibrarySettings\n{\n    public string? LibraryPath { get; set; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/ModelSearchOptions.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Api;\n\nnamespace StabilityMatrix.Core.Models.Settings;\n\npublic record ModelSearchOptions(CivitPeriod SelectedPeriod, CivitSortMode SortMode, CivitModelType SelectedModelType, string SelectedBaseModelType);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/NotificationKey.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\nusing StabilityMatrix.Core.Helper;\n\nnamespace StabilityMatrix.Core.Models.Settings;\n\n/// <summary>\n/// Notification Names\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n[JsonConverter(typeof(ParsableStringValueJsonConverter<NotificationKey>))]\npublic record NotificationKey(string Value) : StringValue(Value), IParsable<NotificationKey>\n{\n    public NotificationOption DefaultOption { get; init; }\n\n    public NotificationLevel Level { get; init; }\n\n    public string? DisplayName { get; init; }\n\n    public static NotificationKey Inference_PromptCompleted =>\n        new(\"Inference_PromptCompleted\")\n        {\n            DefaultOption = Compat.IsLinux ? NotificationOption.AppToast : NotificationOption.NativePush,\n            Level = NotificationLevel.Success,\n            DisplayName = \"Inference Prompt Completed\"\n        };\n\n    public static NotificationKey Download_Completed =>\n        new(\"Download_Completed\")\n        {\n            DefaultOption = Compat.IsLinux ? NotificationOption.AppToast : NotificationOption.NativePush,\n            Level = NotificationLevel.Success,\n            DisplayName = \"Download Completed\"\n        };\n\n    public static NotificationKey Download_Failed =>\n        new(\"Download_Failed\")\n        {\n            DefaultOption = Compat.IsLinux ? NotificationOption.AppToast : NotificationOption.NativePush,\n            Level = NotificationLevel.Error,\n            DisplayName = \"Download Failed\"\n        };\n\n    public static NotificationKey Download_Canceled =>\n        new(\"Download_Canceled\")\n        {\n            DefaultOption = Compat.IsLinux ? NotificationOption.AppToast : NotificationOption.NativePush,\n            Level = NotificationLevel.Warning,\n            DisplayName = \"Download Canceled\"\n        };\n\n    public static NotificationKey Package_Install_Completed =>\n        new(\"Package_Install_Completed\")\n        {\n            DefaultOption = Compat.IsLinux ? NotificationOption.AppToast : NotificationOption.NativePush,\n            Level = NotificationLevel.Success,\n            DisplayName = \"Package Install Completed\"\n        };\n\n    public static NotificationKey Package_Install_Failed =>\n        new(\"Package_Install_Failed\")\n        {\n            DefaultOption = Compat.IsLinux ? NotificationOption.AppToast : NotificationOption.NativePush,\n            Level = NotificationLevel.Error,\n            DisplayName = \"Package Install Failed\"\n        };\n\n    public static Dictionary<string, NotificationKey> All { get; } = GetValues<NotificationKey>();\n\n    /// <inheritdoc />\n    public override string ToString() => base.ToString();\n\n    /// <inheritdoc />\n    public static NotificationKey Parse(string s, IFormatProvider? provider)\n    {\n        return All[s];\n    }\n\n    /// <inheritdoc />\n    public static bool TryParse(\n        string? s,\n        IFormatProvider? provider,\n        [MaybeNullWhen(false)] out NotificationKey result\n    )\n    {\n        return All.TryGetValue(s ?? \"\", out result);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/NotificationLevel.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Settings;\n\npublic enum NotificationLevel\n{\n    Information,\n    Success,\n    Warning,\n    Error\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/NotificationOption.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace StabilityMatrix.Core.Models.Settings;\n\npublic enum NotificationOption\n{\n    [Display(Name = \"None\", Description = \"No notification\")]\n    None,\n\n    [Display(Name = \"In-App\", Description = \"Show a toast in the app\")]\n    AppToast,\n\n    [Display(Name = \"Desktop\", Description = \"Native desktop push notification\")]\n    NativePush\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/NumberFormatMode.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace StabilityMatrix.Core.Models.Settings;\n\npublic enum NumberFormatMode\n{\n    /// <summary>\n    /// Use the default number format\n    /// </summary>\n    [Display(Name = \"Default\")]\n    Default,\n\n    /// <summary>\n    /// Use the number format from the current culture\n    /// </summary>\n    [Display(Name = \"Locale Specific\")]\n    CurrentCulture,\n\n    /// <summary>\n    /// Use the number format from the invariant culture\n    /// </summary>\n    [Display(Name = \"Invariant\")]\n    InvariantCulture\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/Settings.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Globalization;\nusing System.Text.Json.Serialization;\nusing Semver;\nusing StabilityMatrix.Core.Converters.Json;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.Update;\n\nnamespace StabilityMatrix.Core.Models.Settings;\n\npublic class Settings\n{\n    public int? Version { get; set; } = 1;\n    public bool FirstLaunchSetupComplete { get; set; }\n    public string? Theme { get; set; } = \"Dark\";\n    public string? Language { get; set; } = GetDefaultCulture().Name;\n\n    public NumberFormatMode NumberFormatMode { get; set; } = NumberFormatMode.CurrentCulture;\n\n    public List<InstalledPackage> InstalledPackages { get; set; } = new();\n\n    [JsonPropertyName(\"ActiveInstalledPackage\")]\n    public Guid? ActiveInstalledPackageId { get; set; }\n\n    /// <summary>\n    /// The first installed package matching the <see cref=\"ActiveInstalledPackageId\"/>\n    /// or null if no matching package\n    /// </summary>\n    [JsonIgnore]\n    public InstalledPackage? ActiveInstalledPackage\n    {\n        get =>\n            ActiveInstalledPackageId == null\n                ? null\n                : InstalledPackages.FirstOrDefault(x => x.Id == ActiveInstalledPackageId);\n        set => ActiveInstalledPackageId = value?.Id;\n    }\n\n    [JsonPropertyName(\"PreferredWorkflowPackage\")]\n    public Guid? PreferredWorkflowPackageId { get; set; }\n\n    [JsonIgnore]\n    public InstalledPackage? PreferredWorkflowPackage\n    {\n        get =>\n            PreferredWorkflowPackageId == null\n                ? null\n                : InstalledPackages.FirstOrDefault(x => x.Id == PreferredWorkflowPackageId);\n        set => PreferredWorkflowPackageId = value?.Id;\n    }\n\n    public bool HasSeenWelcomeNotification { get; set; }\n    public List<string>? PathExtensions { get; set; }\n    public string? WebApiHost { get; set; }\n    public string? WebApiPort { get; set; }\n\n    /// <summary>\n    /// Preferred update channel\n    /// </summary>\n    public UpdateChannel PreferredUpdateChannel { get; set; } = UpdateChannel.Stable;\n\n    /// <summary>\n    /// Whether to check for updates\n    /// </summary>\n    public bool CheckForUpdates { get; set; } = true;\n\n    /// <summary>\n    /// The last auto-update version that had a notification dismissed by the user\n    /// </summary>\n    [JsonConverter(typeof(SemVersionJsonConverter))]\n    public SemVersion? LastSeenUpdateVersion { get; set; }\n\n    /// <summary>\n    /// Set to the version the user is updating from when updating\n    /// </summary>\n    [JsonConverter(typeof(SemVersionJsonConverter))]\n    public SemVersion? UpdatingFromVersion { get; set; }\n\n    // UI states\n    public bool ModelBrowserNsfwEnabled { get; set; }\n    public bool IsNavExpanded { get; set; }\n    public bool IsImportAsConnected { get; set; }\n    public bool ShowConnectedModelImages { get; set; }\n    public WindowSettings? WindowSettings { get; set; }\n\n    public ModelSearchOptions? ModelSearchOptions { get; set; }\n\n    /// <summary>\n    /// Whether prompt auto completion is enabled\n    /// </summary>\n    public bool IsPromptCompletionEnabled { get; set; } = true;\n\n    /// <summary>\n    /// Relative path to the tag completion CSV file from 'LibraryDir/Tags'\n    /// </summary>\n    public string? TagCompletionCsv { get; set; }\n\n    /// <summary>\n    /// Whether to remove underscores from completions\n    /// </summary>\n    public bool IsCompletionRemoveUnderscoresEnabled { get; set; } = true;\n\n    /// <summary>\n    /// Format for Inference output image file names\n    /// </summary>\n    public string? InferenceOutputImageFileNameFormat { get; set; }\n\n    /// <summary>\n    /// Whether the Inference Image Viewer shows pixel grids at high zoom levels\n    /// </summary>\n    public bool IsImageViewerPixelGridEnabled { get; set; } = true;\n\n    /// <summary>\n    /// Whether Inference Image Browser delete action uses recycle bin if available\n    /// </summary>\n    public bool IsInferenceImageBrowserUseRecycleBinForDelete { get; set; } = true;\n\n    public bool RemoveFolderLinksOnShutdown { get; set; }\n\n    public bool IsDiscordRichPresenceEnabled { get; set; }\n\n    public HashSet<string> DisabledBaseModelTypes { get; set; } = [];\n\n    public HashSet<string> SavedInferenceDimensions { get; set; } =\n        [\n            \"1024 x 1024\",\n            \"1152 x 896\",\n            \"1216 x 832\",\n            \"1280 x 720\",\n            \"1344 x 768\",\n            \"1536 x 640\",\n            \"768 x 768\",\n            \"512 x 512\",\n            \"640 x 1536\",\n            \"768 x 1344\",\n            \"720 x 1280\",\n            \"832 x 1216\",\n            \"896 x 1152\",\n        ];\n\n    [JsonIgnore]\n    public Dictionary<string, string> DefaultEnvironmentVariables { get; } =\n        new()\n        {\n            // Fixes potential setuptools error on Portable Windows Python\n            // [\"SETUPTOOLS_USE_DISTUTILS\"] = \"stdlib\",\n            // Suppresses 'A new release of pip is available' messages\n            [\"PIP_DISABLE_PIP_VERSION_CHECK\"] = \"1\",\n        };\n\n    [JsonPropertyName(\"EnvironmentVariables\")]\n    public Dictionary<string, string>? UserEnvironmentVariables { get; set; }\n\n    [JsonIgnore]\n    public IReadOnlyDictionary<string, string> EnvironmentVariables\n    {\n        get\n        {\n            // add here when can use GlobalConfig\n            DefaultEnvironmentVariables[\"UV_CACHE_DIR\"] = Path.Combine(\n                GlobalConfig.LibraryDir,\n                \"Assets\",\n                \"uv\",\n                \"cache\"\n            );\n\n            if (UserEnvironmentVariables is null || UserEnvironmentVariables.Count == 0)\n            {\n                return DefaultEnvironmentVariables;\n            }\n\n            return DefaultEnvironmentVariables\n                .Concat(UserEnvironmentVariables)\n                .GroupBy(pair => pair.Key)\n                // User variables override default variables with the same key\n                .ToDictionary(grouping => grouping.Key, grouping => grouping.Last().Value);\n        }\n    }\n\n    public float AnimationScale { get; set; } = 1.0f;\n\n    public bool AutoScrollLaunchConsoleToEnd { get; set; } = true;\n\n    public int ConsoleLogHistorySize { get; set; } = 9001;\n\n    public HashSet<int> FavoriteModels { get; set; } = new();\n\n    public HashSet<TeachingTip> SeenTeachingTips { get; set; } = new();\n\n    public Dictionary<NotificationKey, NotificationOption> NotificationOptions { get; set; } = new();\n\n    public List<string> SelectedBaseModels { get; set; } = [];\n    public List<string> SelectedCivitBaseModels { get; set; } = [];\n\n    public Size InferenceImageSize { get; set; } = new(150, 190);\n\n    [Obsolete(\"Use OutputsPageResizeFactor instead\")]\n    public Size OutputsImageSize { get; set; } = new(300, 300);\n    public HolidayMode HolidayModeSetting { get; set; } = HolidayMode.Automatic;\n    public bool IsWorkflowInfiniteScrollEnabled { get; set; } = true;\n    public bool IsOutputsTreeViewEnabled { get; set; } = true;\n    public CheckpointSortMode CheckpointSortMode { get; set; } = CheckpointSortMode.SharedFolderType;\n    public ListSortDirection CheckpointSortDirection { get; set; } = ListSortDirection.Descending;\n    public bool ShowModelsInSubfolders { get; set; } = true;\n    public bool SortConnectedModelsFirst { get; set; } = true;\n    public int ConsoleFontSize { get; set; } = 14;\n    public bool AutoLoadCivitModels { get; set; } = true;\n\n    /// <summary>\n    /// When false, will copy files when drag/drop import happens\n    /// Otherwise, it will move, as it states\n    /// </summary>\n    public bool MoveFilesOnImport { get; set; } = true;\n\n    public bool DragMovesAllSelected { get; set; } = true;\n\n    public bool HideEmptyRootCategories { get; set; }\n\n    public bool HideInstalledModelsInModelBrowser { get; set; }\n\n    public bool ShowNsfwInCheckpointsPage { get; set; }\n\n    // public bool OptedInToInstallTelemetry { get; set; }\n\n    public AnalyticsSettings Analytics { get; set; } = new();\n\n    public double CheckpointsPageResizeFactor { get; set; } = 1.0d;\n\n    public double OutputsPageResizeFactor { get; set; } = 1.0d;\n\n    public double CivitBrowserResizeFactor { get; set; } = 1.0d;\n\n    public bool HideEarlyAccessModels { get; set; }\n\n    public bool CivitUseDiscoveryApi { get; set; }\n\n    public string? ModelDirectoryOverride { get; set; } = null;\n\n    public GpuInfo? PreferredGpu { get; set; }\n\n    public int MaxConcurrentDownloads { get; set; }\n\n    public bool FilterExtraNetworksByBaseModel { get; set; } = true;\n\n    public bool ShowAllAvailablePythonVersions { get; set; }\n\n    public bool IsMainWindowSidebarOpen { get; set; }\n\n    public Dictionary<string, LastDownloadLocationInfo> ModelTypeDownloadPreferences { get; set; } = new();\n\n    public bool ShowTrainingDataInModelBrowser { get; set; }\n\n    public string? CivitModelBrowserFileNamePattern { get; set; }\n\n    public int InferenceDimensionStepChange { get; set; } = 128;\n\n    [JsonIgnore]\n    public bool IsHolidayModeActive =>\n        HolidayModeSetting == HolidayMode.Automatic\n            ? DateTimeOffset.Now.Month == 12\n            : HolidayModeSetting == HolidayMode.Enabled;\n\n    public void RemoveInstalledPackageAndUpdateActive(InstalledPackage package)\n    {\n        RemoveInstalledPackageAndUpdateActive(package.Id);\n    }\n\n    public void RemoveInstalledPackageAndUpdateActive(Guid id)\n    {\n        InstalledPackages.RemoveAll(x => x.Id == id);\n        UpdateActiveInstalledPackage();\n    }\n\n    /// <summary>\n    /// Update ActiveInstalledPackage if not valid\n    /// uses first package or null if no packages\n    /// </summary>\n    public void UpdateActiveInstalledPackage()\n    {\n        // Empty packages - set to null\n        if (InstalledPackages.Count == 0)\n        {\n            ActiveInstalledPackageId = null;\n        }\n        // Active package is not in package - set to first package\n        else if (InstalledPackages.All(x => x.Id != ActiveInstalledPackageId))\n        {\n            ActiveInstalledPackageId = InstalledPackages[0].Id;\n        }\n    }\n\n    public void SetUpdateCheckDisabledForPackage(InstalledPackage package, bool disabled)\n    {\n        var installedPackage = InstalledPackages.FirstOrDefault(p => p.Id == package.Id);\n        if (installedPackage != null)\n        {\n            installedPackage.DontCheckForUpdates = disabled;\n        }\n    }\n\n    /// <summary>\n    /// Return either the system default culture, if supported, or en-US\n    /// </summary>\n    /// <returns></returns>\n    public static CultureInfo GetDefaultCulture()\n    {\n        var supportedCultures = new[] { \"en-US\", \"ja-JP\", \"zh-Hans\", \"zh-Hant\" };\n\n        var systemCulture = CultureInfo.InstalledUICulture;\n\n        if (systemCulture.Name.StartsWith(\"zh-Hans\", StringComparison.OrdinalIgnoreCase))\n        {\n            return new CultureInfo(\"zh-Hans\");\n        }\n\n        if (systemCulture.Name.StartsWith(\"zh-Hant\"))\n        {\n            return new CultureInfo(\"zh-Hant\");\n        }\n\n        return supportedCultures.Contains(systemCulture.Name) ? systemCulture : new CultureInfo(\"en-US\");\n    }\n}\n\n[JsonSourceGenerationOptions(\n    WriteIndented = true,\n    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull\n)]\n[JsonSerializable(typeof(Settings))]\n[JsonSerializable(typeof(bool))]\n[JsonSerializable(typeof(int))]\n[JsonSerializable(typeof(string))]\n[JsonSerializable(typeof(LastDownloadLocationInfo))]\n[JsonSerializable(typeof(Dictionary<string, LastDownloadLocationInfo>))]\ninternal partial class SettingsSerializerContext : JsonSerializerContext;\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/SettingsTransaction.cs",
    "content": "﻿using AsyncAwaitBestPractices;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models.Settings;\n\n/// <summary>\n/// Transaction object which saves settings manager changes when disposed.\n/// </summary>\npublic class SettingsTransaction(ISettingsManager settingsManager, Action onCommit, Func<Task> onCommitAsync)\n    : IDisposable,\n        IAsyncDisposable\n{\n    public Settings Settings => settingsManager.Settings;\n\n    public void Dispose()\n    {\n        onCommit();\n        GC.SuppressFinalize(this);\n    }\n\n    public async ValueTask DisposeAsync()\n    {\n        await onCommitAsync().ConfigureAwait(false);\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/Size.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Settings;\n\npublic record struct Size(double Width, double Height)\n{\n    public static Size operator +(Size current, Size other) =>\n        new(current.Width + other.Width, current.Height + other.Height);\n\n    public static Size operator -(Size current, Size other) =>\n        new(current.Width - other.Width, current.Height - other.Height);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/TeachingTip.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Core.Models.Settings;\n\n/// <summary>\n/// Teaching tip names\n/// </summary>\n[JsonConverter(typeof(StringJsonConverter<TeachingTip>))]\npublic record TeachingTip(string Value) : StringValue(Value)\n{\n    public static TeachingTip AccountsCredentialsStorageNotice => new(\"AccountsCredentialsStorageNotice\");\n    public static TeachingTip CheckpointCategoriesTip => new(\"CheckpointCategoriesTip\");\n    public static TeachingTip PackageExtensionsInstallNotice => new(\"PackageExtensionsInstallNotice\");\n    public static TeachingTip DownloadsTip => new(\"DownloadsTip\");\n    public static TeachingTip WebUiButtonMovedTip => new(\"WebUiButtonMovedTip\");\n    public static TeachingTip InferencePromptHelpButtonTip => new(\"InferencePromptHelpButtonTip\");\n    public static TeachingTip LykosAccountMigrateTip => new(\"LykosAccountMigrateTip\");\n    public static TeachingTip SharedFolderMigrationTip => new(\"SharedFolderMigrationTip\");\n    public static TeachingTip FolderMapTip => new(\"FolderMapTip\");\n    public static TeachingTip InferencePromptAmplifyTip => new(\"InferencePromptAmplifyTip\");\n    public static TeachingTip PromptAmplifyDisclaimer => new(\"PromptAmplifyDisclaimer\");\n\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        return base.ToString();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Settings/WindowSettings.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Settings;\n\npublic record WindowSettings(double Width, double Height, int X, int Y, bool IsMaximized);\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/SharedFolderMethod.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic enum SharedFolderMethod\n{\n    Symlink,\n    Configuration,\n    None\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/SharedFolderType.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\n\nnamespace StabilityMatrix.Core.Models;\n\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n[SuppressMessage(\"ReSharper\", \"IdentifierTypo\")]\n[Flags]\npublic enum SharedFolderType : ulong\n{\n    Unknown = 0,\n\n    [Extensions.Description(\"Checkpoints\")]\n    StableDiffusion = 1 << 0,\n    Lora = 1 << 1,\n    LyCORIS = 1 << 2,\n\n    [Extensions.Description(\"Upscalers (ESRGAN)\")]\n    ESRGAN = 1 << 3,\n    GFPGAN = 1 << 4,\n    BSRGAN = 1 << 5,\n    Codeformer = 1 << 6,\n    Diffusers = 1 << 7,\n    RealESRGAN = 1 << 8,\n    SwinIR = 1 << 9,\n    VAE = 1 << 10,\n    ApproxVAE = 1 << 11,\n    Karlo = 1 << 12,\n    DeepDanbooru = 1 << 13,\n    Embeddings = 1 << 14,\n    Hypernetwork = 1 << 15,\n    ControlNet = 1 << 16,\n    LDSR = 1 << 17,\n    TextEncoders = 1 << 18,\n    ScuNET = 1 << 19,\n    GLIGEN = 1 << 20,\n    AfterDetailer = 1 << 21,\n    IpAdapter = 1 << 22,\n    T2IAdapter = 1 << 23,\n    IpAdapters15 = 1 << 24,\n    IpAdaptersXl = 1 << 25,\n    ClipVision = 1 << 26,\n    SVD = 1 << 27,\n    Ultralytics = 1 << 28,\n    Sams = 1 << 29,\n    PromptExpansion = 1 << 30,\n\n    [Extensions.Description(\"Diffusion Models (UNet-only)\")]\n    DiffusionModels = 1ul << 31,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/SharedOutputType.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic enum SharedOutputType\n{\n    All,\n    Text2Img,\n    Text2Vid,\n    Img2Img,\n    Img2Vid,\n    Extras,\n    Text2ImgGrids,\n    Img2ImgGrids,\n    SVD,\n    Saved,\n    Consolidated,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/StringValue.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Reflection;\nusing System.Runtime.Serialization;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic abstract record StringValue(string Value) : IFormattable\n{\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        return Value;\n    }\n\n    /// <inheritdoc />\n    public string ToString(string? format, IFormatProvider? formatProvider)\n    {\n        return Value;\n    }\n\n    /// <summary>\n    /// Get all values of type <typeparamref name=\"T\"/> as a dictionary.\n    /// Includes all public static properties.\n    /// </summary>\n    protected static Dictionary<string, T> GetValues<\n        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T\n    >()\n        where T : StringValue\n    {\n        var values = new Dictionary<string, T>();\n\n        foreach (var field in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Static))\n        {\n            if (field.GetValue(null) is T value)\n            {\n                // Exclude if IgnoreDataMember\n                if (field.GetCustomAttribute<IgnoreDataMemberAttribute>() is not null)\n                    continue;\n\n                values.Add(value.Value, value);\n            }\n        }\n\n        return values;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/TaskResult.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic readonly record struct TaskResult<T>\n{\n    public readonly T? Result;\n    public readonly Exception? Exception;\n\n    [MemberNotNullWhen(true, nameof(Result))]\n    public bool IsSuccessful => Exception is null && Result != null;\n    \n    public TaskResult(T? result, Exception? exception)\n    {\n        Result = result;\n        Exception = exception;\n    }\n    \n    public TaskResult(T result)\n    {\n        Result = result;\n    }\n\n    public static TaskResult<T> FromException(Exception exception) => new(default, exception);\n\n    public void Deconstruct(out T? result, out Exception? exception)\n    {\n        result = Result;\n        exception = Exception;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Tokens/PromptExtraNetwork.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Tokens;\n\n/// <summary>\n/// Represents an extra network token in a prompt.\n/// In format \n/// </summary>\npublic record PromptExtraNetwork\n{\n    public required PromptExtraNetworkType Type { get; init; }\n    public required string Name { get; init; }\n    public double? ModelWeight { get; init; }\n    public double? ClipWeight { get; init; }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Tokens/PromptExtraNetworkType.cs",
    "content": "﻿using StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Core.Models.Tokens;\n\n[Flags]\npublic enum PromptExtraNetworkType\n{\n    [ConvertTo<SharedFolderType>(SharedFolderType.Lora)]\n    Lora = 1 << 0,\n\n    [ConvertTo<SharedFolderType>(SharedFolderType.LyCORIS)]\n    LyCORIS = 1 << 1,\n\n    [ConvertTo<SharedFolderType>(SharedFolderType.Embeddings)]\n    Embedding = 1 << 2\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/TorchIndex.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models;\n\npublic enum TorchIndex\n{\n    Cuda,\n    Rocm,\n    DirectMl,\n    Cpu,\n    Mps,\n    Zluda,\n    Ipex,\n    None\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/TrackedDownload.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text.Json.Serialization;\nusing AsyncAwaitBestPractices;\nusing NLog;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class TrackedDownload\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    [JsonIgnore]\n    private IDownloadService? downloadService;\n\n    [JsonIgnore]\n    private Task? downloadTask;\n\n    [JsonIgnore]\n    private CancellationTokenSource? downloadCancellationTokenSource;\n\n    [JsonIgnore]\n    private CancellationTokenSource? downloadPauseTokenSource;\n\n    [JsonIgnore]\n    private CancellationTokenSource AggregateCancellationTokenSource =>\n        CancellationTokenSource.CreateLinkedTokenSource(\n            downloadCancellationTokenSource?.Token ?? CancellationToken.None,\n            downloadPauseTokenSource?.Token ?? CancellationToken.None\n        );\n\n    public required Guid Id { get; init; }\n\n    public required Uri SourceUrl { get; init; }\n\n    public Uri? RedirectedUrl { get; init; }\n\n    public required DirectoryPath DownloadDirectory { get; init; }\n\n    public required string FileName { get; init; }\n\n    public required string TempFileName { get; init; }\n\n    public string? ExpectedHashSha256 { get; set; }\n\n    /// <summary>\n    /// Whether to auto-extract the archive after download\n    /// </summary>\n    public bool AutoExtractArchive { get; set; }\n\n    /// <summary>\n    /// Optional relative path to extract the archive to, if AutoExtractArchive is true\n    /// </summary>\n    public string? ExtractRelativePath { get; set; }\n\n    [JsonIgnore]\n    [MemberNotNullWhen(true, nameof(ExpectedHashSha256))]\n    public bool ValidateHash => ExpectedHashSha256 is not null;\n\n    [JsonConverter(typeof(JsonStringEnumConverter<ProgressState>))]\n    public ProgressState ProgressState { get; set; } = ProgressState.Inactive;\n\n    public List<string> ExtraCleanupFileNames { get; init; } = new();\n\n    // Used for restoring progress on load\n    public long DownloadedBytes { get; set; }\n    public long TotalBytes { get; set; }\n\n    /// <summary>\n    /// Optional context action to be invoked on completion\n    /// </summary>\n    public IContextAction? ContextAction { get; set; }\n\n    [JsonIgnore]\n    public Exception? Exception { get; private set; }\n\n    private int attempts;\n\n    #region Events\n    public event EventHandler<ProgressReport>? ProgressUpdate;\n\n    private void OnProgressUpdate(ProgressReport e)\n    {\n        // Update downloaded and total bytes\n        DownloadedBytes = Convert.ToInt64(e.Current);\n        TotalBytes = Convert.ToInt64(e.Total);\n\n        ProgressUpdate?.Invoke(this, e);\n    }\n\n    public event EventHandler<ProgressState>? ProgressStateChanging;\n\n    private void OnProgressStateChanging(ProgressState e)\n    {\n        Logger.Debug(\"Download {Download}: State changing to {State}\", FileName, e);\n\n        ProgressStateChanging?.Invoke(this, e);\n    }\n\n    public event EventHandler<ProgressState>? ProgressStateChanged;\n\n    private void OnProgressStateChanged(ProgressState e)\n    {\n        Logger.Debug(\"Download {Download}: State changed to {State}\", FileName, e);\n\n        ProgressStateChanged?.Invoke(this, e);\n    }\n    #endregion\n\n    [MemberNotNull(nameof(downloadService))]\n    private void EnsureDownloadService()\n    {\n        if (downloadService == null)\n        {\n            throw new InvalidOperationException(\"Download service is not set.\");\n        }\n    }\n\n    private async Task StartDownloadTask(long resumeFromByte, CancellationToken cancellationToken)\n    {\n        var progress = new Progress<ProgressReport>(OnProgressUpdate);\n\n        DownloadDirectory.Create();\n\n        await downloadService!\n            .ResumeDownloadToFileAsync(\n                SourceUrl.ToString(),\n                DownloadDirectory.JoinFile(TempFileName),\n                resumeFromByte,\n                progress,\n                cancellationToken: cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        // If hash validation is enabled, validate the hash\n        if (ValidateHash)\n        {\n            OnProgressUpdate(new ProgressReport(0, isIndeterminate: true, type: ProgressType.Hashing));\n            var hash = await FileHash\n                .GetSha256Async(DownloadDirectory.JoinFile(TempFileName), progress)\n                .ConfigureAwait(false);\n            if (hash != ExpectedHashSha256?.ToLowerInvariant())\n            {\n                throw new Exception(\n                    $\"Hash validation for {FileName} failed, expected {ExpectedHashSha256} but got {hash}\"\n                );\n            }\n        }\n\n        // Rename the temp file to the final file\n        var tempFile = DownloadDirectory.JoinFile(TempFileName);\n        var finalFile = tempFile.Rename(FileName);\n\n        // If auto-extract is enabled, extract the archive\n        if (AutoExtractArchive)\n        {\n            OnProgressUpdate(new ProgressReport(0, isIndeterminate: true, type: ProgressType.Extract));\n\n            var extractDirectory = string.IsNullOrWhiteSpace(ExtractRelativePath)\n                ? DownloadDirectory\n                : DownloadDirectory.JoinDir(ExtractRelativePath);\n\n            extractDirectory.Create();\n\n            await ArchiveHelper\n                .Extract7Z(finalFile, extractDirectory, new Progress<ProgressReport>(OnProgressUpdate))\n                .ConfigureAwait(false);\n        }\n    }\n\n    /// <summary>\n    /// This is only intended for use by the download service.\n    /// Please use <see cref=\"TrackedDownloadService\"/>.TryStartDownload instead.\n    /// </summary>\n    /// <exception cref=\"InvalidOperationException\"></exception>\n    internal void Start()\n    {\n        if (ProgressState != ProgressState.Inactive && ProgressState != ProgressState.Pending)\n        {\n            throw new InvalidOperationException(\n                $\"Download state must be inactive or pending to start, not {ProgressState}\"\n            );\n        }\n        Logger.Debug(\"Starting download {Download}\", FileName);\n\n        EnsureDownloadService();\n\n        downloadCancellationTokenSource = new CancellationTokenSource();\n        downloadPauseTokenSource = new CancellationTokenSource();\n\n        downloadTask = StartDownloadTask(0, AggregateCancellationTokenSource.Token)\n            .ContinueWith(OnDownloadTaskCompleted);\n\n        OnProgressStateChanging(ProgressState.Working);\n        ProgressState = ProgressState.Working;\n        OnProgressStateChanged(ProgressState);\n    }\n\n    internal void Resume()\n    {\n        if (ProgressState != ProgressState.Inactive && ProgressState != ProgressState.Paused)\n        {\n            Logger.Warn(\n                \"Attempted to resume download {Download} but it is not paused ({State})\",\n                FileName,\n                ProgressState\n            );\n        }\n        Logger.Debug(\"Resuming download {Download}\", FileName);\n\n        // Read the temp file to get the current size\n        var tempSize = 0L;\n\n        var tempFile = DownloadDirectory.JoinFile(TempFileName);\n        if (tempFile.Exists)\n        {\n            tempSize = tempFile.Info.Length;\n        }\n\n        EnsureDownloadService();\n\n        downloadCancellationTokenSource = new CancellationTokenSource();\n        downloadPauseTokenSource = new CancellationTokenSource();\n\n        downloadTask = StartDownloadTask(tempSize, AggregateCancellationTokenSource.Token)\n            .ContinueWith(OnDownloadTaskCompleted);\n\n        OnProgressStateChanging(ProgressState.Working);\n        ProgressState = ProgressState.Working;\n        OnProgressStateChanged(ProgressState);\n    }\n\n    public void Pause()\n    {\n        if (ProgressState != ProgressState.Working)\n        {\n            Logger.Warn(\n                \"Attempted to pause download {Download} but it is not in progress ({State})\",\n                FileName,\n                ProgressState\n            );\n            return;\n        }\n\n        Logger.Debug(\"Pausing download {Download}\", FileName);\n        downloadPauseTokenSource?.Cancel();\n        OnProgressStateChanging(ProgressState.Paused);\n        ProgressState = ProgressState.Paused;\n        OnProgressStateChanged(ProgressState);\n    }\n\n    public void Cancel()\n    {\n        if (ProgressState is not (ProgressState.Working or ProgressState.Inactive))\n        {\n            Logger.Warn(\n                \"Attempted to cancel download {Download} but it is not in progress ({State})\",\n                FileName,\n                ProgressState\n            );\n            return;\n        }\n\n        Logger.Debug(\"Cancelling download {Download}\", FileName);\n\n        // Cancel token if it exists\n        if (downloadCancellationTokenSource is { } token)\n        {\n            token.Cancel();\n        }\n        // Otherwise handle it manually\n        else\n        {\n            DoCleanup();\n\n            OnProgressStateChanging(ProgressState.Cancelled);\n            ProgressState = ProgressState.Cancelled;\n            OnProgressStateChanged(ProgressState);\n        }\n    }\n\n    public void SetPending()\n    {\n        OnProgressStateChanging(ProgressState.Pending);\n        ProgressState = ProgressState.Pending;\n        OnProgressStateChanged(ProgressState);\n    }\n\n    /// <summary>\n    /// Deletes the temp file and any extra cleanup files\n    /// </summary>\n    private void DoCleanup()\n    {\n        try\n        {\n            DownloadDirectory.JoinFile(TempFileName).Delete();\n        }\n        catch (IOException)\n        {\n            Logger.Warn(\"Failed to delete temp file {TempFile}\", TempFileName);\n        }\n\n        foreach (var extraFile in ExtraCleanupFileNames)\n        {\n            try\n            {\n                DownloadDirectory.JoinFile(extraFile).Delete();\n            }\n            catch (IOException)\n            {\n                Logger.Warn(\"Failed to delete extra cleanup file {ExtraFile}\", extraFile);\n            }\n        }\n    }\n\n    /// <summary>\n    /// Invoked by the task's completion callback\n    /// </summary>\n    private void OnDownloadTaskCompleted(Task task)\n    {\n        // For cancelled, check if it was actually cancelled or paused\n        if (task.IsCanceled)\n        {\n            // If the task was cancelled, set the state to cancelled\n            if (downloadCancellationTokenSource?.IsCancellationRequested == true)\n            {\n                OnProgressStateChanging(ProgressState.Cancelled);\n                ProgressState = ProgressState.Cancelled;\n            }\n            // If the task was not cancelled, set the state to paused\n            else if (downloadPauseTokenSource?.IsCancellationRequested == true)\n            {\n                OnProgressStateChanging(ProgressState.Inactive);\n                ProgressState = ProgressState.Inactive;\n            }\n            else\n            {\n                throw new InvalidOperationException(\n                    \"Download task was cancelled but neither cancellation token was cancelled.\"\n                );\n            }\n        }\n        // For faulted\n        else if (task.IsFaulted)\n        {\n            // Set the exception\n            Exception = task.Exception;\n\n            if ((Exception is IOException || Exception?.InnerException is IOException) && attempts < 3)\n            {\n                attempts++;\n                Logger.Warn(\n                    \"Download {Download} failed with {Exception}, retrying ({Attempt})\",\n                    FileName,\n                    Exception,\n                    attempts\n                );\n\n                OnProgressStateChanging(ProgressState.Inactive);\n                ProgressState = ProgressState.Inactive;\n                Resume();\n                return;\n            }\n\n            Logger.Warn(Exception, \"Download {Download} failed\", FileName);\n\n            OnProgressStateChanging(ProgressState.Failed);\n            ProgressState = ProgressState.Failed;\n        }\n        // Otherwise success\n        else\n        {\n            OnProgressStateChanging(ProgressState.Success);\n            ProgressState = ProgressState.Success;\n        }\n\n        // For failed or cancelled, delete the temp files\n        if (ProgressState is ProgressState.Failed or ProgressState.Cancelled)\n        {\n            DoCleanup();\n        }\n        // For pause, just do nothing\n\n        OnProgressStateChanged(ProgressState);\n\n        // Dispose of the task and cancellation token\n        downloadTask = null;\n        downloadCancellationTokenSource = null;\n        downloadPauseTokenSource = null;\n    }\n\n    public void SetDownloadService(IDownloadService service)\n    {\n        downloadService = service;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/UnknownInstalledPackage.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Core.Models;\n\npublic class UnknownInstalledPackage : InstalledPackage\n{\n    public static UnknownInstalledPackage FromDirectoryName(string name)\n    {\n        return new UnknownInstalledPackage\n        {\n            Id = Guid.NewGuid(),\n            PackageName = UnknownPackage.Key,\n            DisplayName = name,\n            PythonVersion = PyInstallationManager.Python_3_10_17.StringValue,\n            LibraryPath = $\"Packages{System.IO.Path.DirectorySeparatorChar}{name}\",\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Update/UpdateChannel.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Core.Models.Update;\n\n[JsonConverter(typeof(DefaultUnknownEnumConverter<UpdateChannel>))]\npublic enum UpdateChannel\n{\n    Unknown,\n    Stable,\n    Preview,\n    Development\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Update/UpdateInfo.cs",
    "content": "﻿using System.Globalization;\nusing System.Text.Json.Serialization;\nusing Semver;\nusing StabilityMatrix.Core.Converters.Json;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Core.Models.Update;\n\npublic record UpdateInfo\n{\n    [JsonConverter(typeof(SemVersionJsonConverter))]\n    public required SemVersion Version { get; init; }\n\n    public required DateTimeOffset ReleaseDate { get; init; }\n\n    public UpdateChannel Channel { get; init; }\n\n    public UpdateType Type { get; init; }\n\n    public required Uri Url { get; init; }\n\n    public required Uri Changelog { get; init; }\n\n    /// <summary>\n    /// Blake3 hash of the file\n    /// </summary>\n    public required string HashBlake3 { get; init; }\n\n    /// <summary>\n    /// ED25519 signature of the semicolon seperated string:\n    /// \"version + releaseDate + channel + type + url + changelog + hash_blake3\"\n    /// verifiable using our stored public key\n    /// </summary>\n    public required string Signature { get; init; }\n\n    /// <summary>\n    /// Data for use in signature verification.\n    /// Semicolon separated string of fields:\n    /// \"version, releaseDate, channel, type, url, changelog, hashBlake3\"\n    /// </summary>\n    public string GetSignedData()\n    {\n        var channel = Channel.GetStringValue().ToLowerInvariant();\n        var date = FormatDateTimeOffsetInvariant(ReleaseDate);\n        return $\"{Version};{date};{channel};\" + $\"{(int)Type};{Url};{Changelog};\" + $\"{HashBlake3}\";\n    }\n\n    /// <summary>\n    /// Format a DatetimeOffset to a culture invariant string for use in signature verification.\n    /// </summary>\n    private static string FormatDateTimeOffsetInvariant(DateTimeOffset dateTimeOffset)\n    {\n        return dateTimeOffset.ToString(\n            @\"yyyy-MM-ddTHH\\:mm\\:ss.ffffffzzz\",\n            CultureInfo.InvariantCulture\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Update/UpdateManifest.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Models.Update;\n\n[JsonSerializable(typeof(UpdateManifest))]\npublic record UpdateManifest\n{\n    public required Dictionary<UpdateChannel, UpdatePlatforms> Updates { get; init; }\n}\n\n\n// TODO: Bugged in .NET 7 but we can use in 8 https://github.com/dotnet/runtime/pull/79828\n/*[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]\n[JsonSerializable(typeof(UpdateManifest))]\npublic partial class UpdateManifestContext : JsonSerializerContext\n{\n}*/\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Update/UpdatePlatforms.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Helper;\n\nnamespace StabilityMatrix.Core.Models.Update;\n\npublic record UpdatePlatforms\n{\n    [JsonPropertyName(\"win-x64\")]\n    public UpdateInfo? WindowsX64 { get; init; }\n\n    [JsonPropertyName(\"linux-x64\")]\n    public UpdateInfo? LinuxX64 { get; init; }\n\n    [JsonPropertyName(\"macos-arm64\")]\n    public UpdateInfo? MacOsArm64 { get; init; }\n\n    public UpdateInfo? GetInfoForCurrentPlatform()\n    {\n        if (Compat.IsWindows)\n        {\n            return WindowsX64;\n        }\n\n        if (Compat.IsLinux)\n        {\n            return LinuxX64;\n        }\n\n        if (Compat.IsMacOS && Compat.IsArm)\n        {\n            return MacOsArm64;\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Models/Update/UpdateType.cs",
    "content": "﻿namespace StabilityMatrix.Core.Models.Update;\n\n[Flags]\npublic enum UpdateType\n{\n    Normal = 1 << 0,\n    Critical = 1 << 1,\n    Mandatory = 1 << 2,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/AnsiCommand.cs",
    "content": "﻿namespace StabilityMatrix.Core.Processes;\n\n[Flags]\npublic enum AnsiCommand\n{\n    /// <summary>\n    /// Default value\n    /// </summary>\n    None = 0,\n    \n    // Erase commands\n    \n    /// <summary>\n    /// Erase from cursor to end of line\n    /// ESC[K or ESC[0K\n    /// </summary>\n    EraseToEndOfLine = 1 << 0,\n    \n    /// <summary>\n    /// Erase from start of line to cursor\n    /// ESC[1K\n    /// </summary>\n    EraseFromStartOfLine = 1 << 1,\n   \n    /// <summary>\n    /// Erase entire line\n    /// ESC[2K\n    /// </summary>\n    EraseLine = 1 << 2,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/AnsiParser.cs",
    "content": "﻿using System.Text.RegularExpressions;\n\nnamespace StabilityMatrix.Core.Processes;\n\npublic static partial class AnsiParser\n{\n    /// <summary>\n    /// From https://github.com/chalk/ansi-regex\n    /// </summary>\n    /// <returns></returns>\n    [GeneratedRegex(@\"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))\")]\n    public static partial Regex AnsiEscapeSequenceRegex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/AnsiProcess.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Text;\n\nnamespace StabilityMatrix.Core.Processes;\n\n/// <summary>\n/// Process supporting parsing of ANSI escape sequences\n/// </summary>\npublic class AnsiProcess : Process\n{\n    private AsyncStreamReader? stdoutReader;\n    private AsyncStreamReader? stderrReader;\n\n    public AnsiProcess(ProcessStartInfo startInfo)\n    {\n        StartInfo = startInfo;\n        EnableRaisingEvents = false;\n\n        StartInfo.UseShellExecute = false;\n        StartInfo.CreateNoWindow = true;\n        StartInfo.RedirectStandardOutput = true;\n        StartInfo.RedirectStandardInput = true;\n        StartInfo.RedirectStandardError = true;\n\n        // Need this to parse ANSI escape sequences correctly\n        StartInfo.StandardOutputEncoding = new UTF8Encoding(false);\n        StartInfo.StandardErrorEncoding = new UTF8Encoding(false);\n        StartInfo.StandardInputEncoding = new UTF8Encoding(false);\n    }\n\n    /// <summary>\n    /// Start asynchronous reading of stdout and stderr\n    /// </summary>\n    /// <param name=\"callback\">Called on each new line</param>\n    public void BeginAnsiRead(Action<ProcessOutput> callback)\n    {\n        var stdoutStream = StandardOutput.BaseStream;\n        stdoutReader = new AsyncStreamReader(\n            stdoutStream,\n            s =>\n            {\n                if (s == null)\n                    return;\n                callback(ProcessOutput.FromStdOutLine(s));\n            },\n            StandardOutput.CurrentEncoding\n        );\n\n        var stderrStream = StandardError.BaseStream;\n        stderrReader = new AsyncStreamReader(\n            stderrStream,\n            s =>\n            {\n                if (s == null)\n                    return;\n                callback(ProcessOutput.FromStdErrLine(s));\n            },\n            StandardError.CurrentEncoding\n        );\n\n        stdoutReader.BeginReadLine();\n        stderrReader.BeginReadLine();\n    }\n\n    /// <summary>\n    /// Waits for output readers to finish\n    /// </summary>\n    public async Task WaitUntilOutputEOF(CancellationToken ct = default)\n    {\n        if (stdoutReader is not null)\n        {\n            await stdoutReader.EOF.WaitAsync(ct).ConfigureAwait(false);\n        }\n        if (stderrReader is not null)\n        {\n            await stderrReader.EOF.WaitAsync(ct).ConfigureAwait(false);\n        }\n    }\n\n    /// <summary>\n    /// Cancels stream readers, no effect if already cancelled\n    /// </summary>\n    public void CancelStreamReaders()\n    {\n        stdoutReader?.CancelOperation();\n        stderrReader?.CancelOperation();\n    }\n\n    protected override void Dispose(bool disposing)\n    {\n        CancelStreamReaders();\n        stdoutReader?.Dispose();\n        stdoutReader = null;\n        stderrReader?.Dispose();\n        stderrReader = null;\n        base.Dispose(disposing);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/ApcMessage.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing NLog;\n\nnamespace StabilityMatrix.Core.Processes;\n\n/// <summary>\n/// Defines a custom APC message, embeddable in a subprocess output stream.\n/// Format is as such:\n/// <code>\"{APC}{CustomPrefix}(JsonSerialized ApcMessage){StChar}\"</code>\n/// <example>\"\\u009f[SM;{\"type\":\"input\",\"data\":\"hello\"}\\u009c\"</example>\n/// </summary>\npublic readonly record struct ApcMessage\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); \n    \n    public const char ApcChar = (char) 0x9F;\n    public const char StChar = (char) 0x9C;\n    public const string CustomPrefix = \"[SM;\";\n    \n    [JsonPropertyName(\"type\")]\n    public required ApcType Type { get; init; }\n    \n    [JsonPropertyName(\"data\")]\n    public required string Data { get; init; }\n    \n    /// <summary>\n    /// Attempts to extract an APC message from the given text\n    /// </summary>\n    /// <returns>ApcMessage struct</returns>\n    public static bool TryParse(string value, [NotNullWhen(true)] out ApcMessage? message)\n    {\n        message = null;\n        var startIndex = value.IndexOf(ApcChar);\n        if (startIndex == -1) return false;\n        \n        // Check the IdPrefix follows the ApcEscape\n        var idIndex = value.IndexOf(CustomPrefix, startIndex + 1, StringComparison.Ordinal);\n        if (idIndex == -1) return false;\n        \n        // Get the end index (ST escape)\n        var stIndex = value.IndexOf(StChar, idIndex + CustomPrefix.Length);\n        if (stIndex == -1) return false;\n\n        // Extract the json string (between idIndex and stIndex)\n        var json = value.Substring(idIndex + CustomPrefix.Length, stIndex - idIndex - CustomPrefix.Length);\n        \n        try\n        {\n            message = JsonSerializer.Deserialize<ApcMessage>(json);\n            return true;\n        }\n        catch (Exception e)\n        {\n            Logger.Warn($\"Failed to deserialize APC message: {e.Message}\");\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/ApcParser.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Text.Json;\n\nnamespace StabilityMatrix.Core.Processes;\n\n/// <summary>\n/// Parse escaped messages from subprocess\n/// The message standard:\n///   - Message events are prefixed with char 'APC' (9F)\n///   - Followed by '[SM;'\n///   - Json dict string of 2 strings, 'type' and 'data'\n///   - Ends with char 'ST' (9C)\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\ninternal static class ApcParser\n{\n    public const char ApcEscape = (char) 0x9F;\n    public const string IdPrefix = \"[SM;\";\n    public const char StEscape = (char) 0x9C;\n    \n    /// <summary>\n    /// Attempts to extract an APC message from the given text\n    /// </summary>\n    /// <returns>ApcMessage struct</returns>\n    public static bool TryParse(string text, out ApcMessage? message)\n    {\n        message = null;\n        var startIndex = text.IndexOf(ApcEscape);\n        if (startIndex == -1) return false;\n        \n        // Check the IdPrefix follows the ApcEscape\n        var idIndex = text.IndexOf(IdPrefix, startIndex + 1, StringComparison.Ordinal);\n        if (idIndex == -1) return false;\n        \n        // Get the end index (ST escape)\n        var stIndex = text.IndexOf(StEscape, idIndex + IdPrefix.Length);\n        if (stIndex == -1) return false;\n\n        // Extract the json string (between idIndex and stIndex)\n        var json = text.Substring(idIndex + IdPrefix.Length, stIndex - idIndex - IdPrefix.Length);\n        \n        try\n        {\n            message = JsonSerializer.Deserialize<ApcMessage>(json);\n            return true;\n        }\n        catch (Exception e)\n        {\n            Debug.WriteLine($\"Failed to parse APC message: {e.Message}\");\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/ApcType.cs",
    "content": "﻿using System.Runtime.Serialization;\nusing System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Processes;\n\n[JsonConverter(typeof(JsonStringEnumConverter<ApcType>))]\npublic enum ApcType\n{\n    [EnumMember(Value = \"input\")]\n    Input = 1,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/Argument.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\n\nnamespace StabilityMatrix.Core.Processes;\n\n/// <summary>\n/// Represents a command line argument.\n/// </summary>\npublic readonly record struct Argument\n{\n    /// <summary>\n    /// The value of the argument.\n    /// </summary>\n    public string Value { get; init; } = string.Empty;\n\n    /// <summary>\n    /// Optional key for the argument.\n    /// </summary>\n    public string? Key { get; init; }\n\n    /// <summary>\n    /// Whether the <see cref=\"Key\"/> property is set and not empty.\n    /// </summary>\n    [MemberNotNullWhen(true, nameof(Key))]\n    public bool HasKey => !string.IsNullOrEmpty(Key);\n\n    /// <summary>\n    /// Whether the argument value is already quoted for command line usage.\n    /// </summary>\n    public bool IsQuoted { get; init; }\n\n    /// <summary>\n    /// Gets the value with quoting if necessary.\n    /// Is equal to <see cref=\"Value\"/> if <see cref=\"IsQuoted\"/> is <see langword=\"true\"/>.\n    /// </summary>\n    /// <returns></returns>\n    public string GetQuotedValue() => IsQuoted ? Value : ProcessRunner.Quote(Value);\n\n    /// <summary>\n    /// Create a new argument with the given pre-quoted value.\n    /// </summary>\n    public static Argument Quoted(string value) => new(value) { IsQuoted = true };\n\n    /// <summary>\n    /// Create a new keyed argument with the given pre-quoted value.\n    /// </summary>\n    public static Argument Quoted(string key, string value) => new(key, value) { IsQuoted = true };\n\n    public Argument() { }\n\n    public Argument(string value)\n    {\n        Value = value;\n    }\n\n    public Argument(string key, string value)\n    {\n        Value = value;\n        Key = key;\n    }\n\n    // Implicit (string -> Argument)\n    public static implicit operator Argument(string _) => new(_);\n\n    // Explicit (Argument -> string)\n    public static explicit operator string(Argument _) => _.Value;\n\n    // Implicit ((string, string) -> Argument)\n    public static implicit operator Argument((string Key, string Value) _) => new(_.Key, _.Value);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/AsyncStreamReader.cs",
    "content": "﻿// Based on System.Diagnostics.AsyncStreamReader\n// Licensed to the .NET Foundation under one or more agreements.\n// The .NET Foundation licenses this file to you under the MIT license.\n\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.ExceptionServices;\nusing System.Text;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Core.Processes;\n\n/// <summary>\n/// Modified from System.Diagnostics.AsyncStreamReader to support terminal processing.\n///\n/// Currently has these modifications:\n/// - Carriage returns do not count as newlines '\\r'.\n/// - APC messages are sent immediately without needing a newline.\n///\n/// <seealso cref=\"ApcParser\"/>\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\ninternal sealed class AsyncStreamReader : IDisposable\n{\n    private const int DefaultBufferSize = 1024; // Byte buffer size\n\n    private readonly Stream _stream;\n    private readonly Decoder _decoder;\n    private readonly byte[] _byteBuffer;\n    private readonly char[] _charBuffer;\n\n    // Delegate to call user function.\n    private readonly Action<string?> _userCallBack;\n\n    private readonly CancellationTokenSource _cts;\n    private Task? _readToBufferTask;\n    private readonly Queue<string?> _messageQueue;\n    private StringBuilder? _sb;\n    private bool _bLastCarriageReturn;\n    private bool _cancelOperation;\n\n    // Cache the last position scanned in sb when searching for lines.\n    private int _currentLinePos;\n\n    // (new) Flag to send next buffer immediately\n    private bool _sendNextBufferImmediately;\n\n    // Creates a new AsyncStreamReader for the given stream. The\n    // character encoding is set by encoding and the buffer size,\n    // in number of 16-bit characters, is set by bufferSize.\n    internal AsyncStreamReader(Stream stream, Action<string?> callback, Encoding encoding)\n    {\n        Debug.Assert(stream != null && encoding != null && callback != null, \"Invalid arguments!\");\n        Debug.Assert(stream.CanRead, \"Stream must be readable!\");\n\n        _stream = stream;\n        _userCallBack = callback;\n        _decoder = encoding.GetDecoder();\n        _byteBuffer = new byte[DefaultBufferSize];\n\n        // This is the maximum number of chars we can get from one iteration in loop inside ReadBuffer.\n        // Used so ReadBuffer can tell when to copy data into a user's char[] directly, instead of our internal char[].\n        var maxCharsPerBuffer = encoding.GetMaxCharCount(DefaultBufferSize);\n        _charBuffer = new char[maxCharsPerBuffer];\n\n        _cts = new CancellationTokenSource();\n        _messageQueue = new Queue<string?>();\n    }\n\n    // User calls BeginRead to start the asynchronous read\n    internal void BeginReadLine()\n    {\n        _cancelOperation = false;\n\n        if (_sb == null)\n        {\n            _sb = new StringBuilder(DefaultBufferSize);\n            _readToBufferTask = Task.Run((Func<Task>)ReadBufferAsync);\n        }\n        else\n        {\n            FlushMessageQueue(rethrowInNewThread: false);\n        }\n    }\n\n    internal void CancelOperation()\n    {\n        _cancelOperation = true;\n    }\n\n    // This is the async callback function. Only one thread could/should call this.\n    private async Task ReadBufferAsync()\n    {\n        while (true)\n        {\n            try\n            {\n                var bytesRead = await _stream\n                    .ReadAsync(new Memory<byte>(_byteBuffer), _cts.Token)\n                    .ConfigureAwait(false);\n                if (bytesRead == 0)\n                    break;\n\n                var charLen = _decoder.GetChars(_byteBuffer, 0, bytesRead, _charBuffer, 0);\n\n                Debug.WriteLine(\n                    $\"AsyncStreamReader - Read {charLen} chars: \"\n                        + $\"{new string(_charBuffer, 0, charLen).ToRepr()}\"\n                );\n\n                _sb!.Append(_charBuffer, 0, charLen);\n                MoveLinesFromStringBuilderToMessageQueue();\n            }\n            catch (IOException)\n            {\n                // We should ideally consume errors from operations getting cancelled\n                // so that we don't crash the unsuspecting parent with an unhandled exc.\n                // This seems to come in 2 forms of exceptions (depending on platform and scenario),\n                // namely OperationCanceledException and IOException (for errorcode that we don't\n                // map explicitly).\n                break; // Treat this as EOF\n            }\n            catch (OperationCanceledException)\n            {\n                // We should consume any OperationCanceledException from child read here\n                // so that we don't crash the parent with an unhandled exc\n                break; // Treat this as EOF\n            }\n\n            // If user's delegate throws exception we treat this as EOF and\n            // completing without processing current buffer content\n            if (FlushMessageQueue(rethrowInNewThread: true))\n            {\n                return;\n            }\n        }\n\n        // We're at EOF, process current buffer content and flush message queue.\n        lock (_messageQueue)\n        {\n            if (_sb!.Length != 0)\n            {\n                var remaining = _sb.ToString();\n                _messageQueue.Enqueue(remaining);\n                _sb.Length = 0;\n                Debug.WriteLine(\n                    $\"AsyncStreamReader - Reached EOF, sent remaining buffer: {remaining}\"\n                );\n            }\n            _messageQueue.Enqueue(null);\n        }\n\n        FlushMessageQueue(rethrowInNewThread: true);\n    }\n\n    // Send remaining buffer\n    [MethodImpl(MethodImplOptions.AggressiveInlining)]\n    private void SendRemainingBuffer()\n    {\n        lock (_messageQueue)\n        {\n            if (_sb!.Length == 0)\n                return;\n\n            _messageQueue.Enqueue(_sb.ToString());\n            _sb.Length = 0;\n        }\n    }\n\n    // Send remaining buffer from index\n    [MethodImpl(MethodImplOptions.AggressiveInlining)]\n    private void SendRemainingBuffer(int startIndex)\n    {\n        lock (_messageQueue)\n        {\n            if (_sb!.Length == 0)\n                return;\n\n            _messageQueue.Enqueue(_sb.ToString(startIndex, _sb.Length - startIndex));\n            _sb.Length = 0;\n        }\n    }\n\n    // Sends a message to the queue if not null or empty\n    private void SendToQueue(string? message)\n    {\n        if (string.IsNullOrEmpty(message))\n            return;\n        lock (_messageQueue)\n        {\n            _messageQueue.Enqueue(message);\n        }\n    }\n\n    // Read lines stored in StringBuilder and the buffer we just read into.\n    // A line is defined as a sequence of characters followed by\n    // a carriage return ('\\r'), a line feed ('\\n'), or a carriage return\n    // immediately followed by a line feed. The resulting string does not\n    // contain the terminating carriage return and/or line feed. The returned\n    // value is null if the end of the input stream has been reached.\n    private void MoveLinesFromStringBuilderToMessageQueue()\n    {\n        var currentIndex = _currentLinePos;\n        var lineStart = 0;\n        var len = _sb!.Length;\n\n        // skip a beginning '\\n' character of new block if last block ended\n        // with '\\r'\n        if (_bLastCarriageReturn && len > 0 && _sb[0] == '\\n')\n        {\n            currentIndex = 1;\n            lineStart = 1;\n            _bLastCarriageReturn = false;\n        }\n\n        while (currentIndex < len)\n        {\n            var ch = _sb[currentIndex];\n            // Note the following common line feed chars:\n            // \\n - UNIX   \\r\\n - DOS\n            switch (ch)\n            {\n                case '\\n':\n                {\n                    // Include the '\\n' as part of line.\n                    var line = _sb.ToString(lineStart, currentIndex - lineStart + 1);\n                    lineStart = currentIndex + 1;\n\n                    lock (_messageQueue)\n                    {\n                        _messageQueue.Enqueue(line);\n                    }\n\n                    break;\n                }\n                // \\r\\n - Windows\n                // \\r alone is parsed as carriage return\n                case '\\r':\n                {\n                    // when next char is \\n, we found \\r\\n - linebreak\n                    if (currentIndex + 1 < len && _sb[currentIndex + 1] == '\\n')\n                    {\n                        // Include the '\\r\\n' as part of line.\n                        var line = _sb.ToString(lineStart, currentIndex - lineStart + 2);\n                        lock (_messageQueue)\n                        {\n                            _messageQueue.Enqueue(line);\n                        }\n                        // Advance 2 chars for \\r\\n\n                        lineStart = currentIndex + 2;\n                        // Increment one extra plus the end of the loop increment\n                        currentIndex++;\n                    }\n                    else\n                    {\n                        // Send buffer up to this point, not including \\r\n                        // But skip if there's no content\n                        if (currentIndex == lineStart)\n                            break;\n\n                        var line = _sb.ToString(lineStart, currentIndex - lineStart);\n                        lock (_messageQueue)\n                        {\n                            _messageQueue.Enqueue(line);\n                        }\n                        // Set line start to current index\n                        lineStart = currentIndex;\n                    }\n                    break;\n                }\n                // Additional handling for Apc escape messages\n                case ApcParser.ApcEscape:\n                {\n                    // Unconditionally consume until StEscape\n                    // Look for index of StEscape\n                    var searchIndex = currentIndex;\n                    while (searchIndex < len && _sb[searchIndex] != ApcParser.StEscape)\n                    {\n                        searchIndex++;\n                    }\n\n                    // If we found StEscape, we have a complete APC message\n                    if (searchIndex < len)\n                    {\n                        // Include the StEscape as part of line.\n                        var line = _sb.ToString(lineStart, searchIndex - lineStart + 1);\n                        lock (_messageQueue)\n                        {\n                            _messageQueue.Enqueue(line);\n                        }\n                        Debug.WriteLine($\"AsyncStreamReader - Sent Apc: '{line}'\");\n                        // Flag to send the next buffer immediately\n                        _sendNextBufferImmediately = true;\n                        // Advance currentIndex and lineStart to StEscape\n                        // lineStart = searchIndex + 1;\n                        currentIndex = searchIndex;\n                        var remainingStart = currentIndex + 1;\n                        var remainingStr = _sb.ToString(\n                            remainingStart,\n                            _sb.Length - remainingStart\n                        );\n                        Debug.WriteLine(\n                            $\"AsyncStreamReader - Sending remaining buffer: '{remainingStr}'\"\n                        );\n                        // Send the rest of the buffer immediately\n                        SendRemainingBuffer(currentIndex + 1);\n                        return;\n                    }\n                    // Otherwise continue without any other changes\n                    break;\n                }\n                // If we receive an Ansi escape, send the existing buffer immediately\n                // Kind of behaves like newlines\n                case '\\u001b':\n                {\n                    // Unlike '\\n', this char is not included in the line\n                    var line = _sb.ToString(lineStart, currentIndex - lineStart);\n                    SendToQueue(line);\n                    // Set line start to current index\n                    lineStart = currentIndex;\n\n                    // Look ahead and match the escape sequence\n                    var remaining = _sb.ToString(currentIndex, len - currentIndex);\n                    var result = AnsiParser.AnsiEscapeSequenceRegex().Match(remaining);\n\n                    // If we found a match, send the escape sequence match, and move forward\n                    if (result.Success)\n                    {\n                        var escapeSequence = result.Value;\n                        SendToQueue(escapeSequence);\n\n                        Debug.WriteLine(\n                            $\"AsyncStreamReader - Sent Ansi escape sequence: {escapeSequence.ToRepr()}\"\n                        );\n\n                        // Advance currentIndex and lineStart to end of escape sequence\n                        // minus 1 since we will increment currentIndex at the end of the loop\n                        lineStart = currentIndex + escapeSequence.Length;\n                        currentIndex = lineStart - 1;\n                    }\n                    else\n                    {\n                        Debug.WriteLine(\n                            $\"AsyncStreamReader - No match for Ansi escape sequence: {remaining.ToRepr()}\"\n                        );\n                    }\n\n                    break;\n                }\n            }\n            currentIndex++;\n        }\n        if (len > 0 && _sb[len - 1] == '\\r')\n        {\n            _bLastCarriageReturn = true;\n        }\n\n        // If flagged, send remaining buffer immediately\n        if (_sendNextBufferImmediately)\n        {\n            SendRemainingBuffer();\n            _sendNextBufferImmediately = false;\n            return;\n        }\n\n        // Keep the rest characters which can't form a new line in string builder.\n        if (lineStart < len)\n        {\n            if (lineStart == 0)\n            {\n                // we found no linebreaks, in this case we cache the position\n                // so next time we don't have to restart from the beginning\n                _currentLinePos = currentIndex;\n            }\n            else\n            {\n                _sb.Remove(0, lineStart);\n                _currentLinePos = 0;\n            }\n        }\n        else\n        {\n            _sb.Length = 0;\n            _currentLinePos = 0;\n        }\n    }\n\n    // If everything runs without exception, returns false.\n    // If an exception occurs and rethrowInNewThread is true, returns true.\n    // If an exception occurs and rethrowInNewThread is false, the exception propagates.\n    private bool FlushMessageQueue(bool rethrowInNewThread)\n    {\n        try\n        {\n            // Keep going until we're out of data to process.\n            while (true)\n            {\n                // Get the next line (if there isn't one, we're done) and\n                // invoke the user's callback with it.\n                string? line;\n                lock (_messageQueue)\n                {\n                    if (_messageQueue.Count == 0)\n                    {\n                        break;\n                    }\n                    line = _messageQueue.Dequeue();\n                }\n\n                if (!_cancelOperation)\n                {\n                    _userCallBack(line); // invoked outside of the lock\n                }\n            }\n            return false;\n        }\n        catch (Exception e)\n        {\n            // If rethrowInNewThread is true, we can't let the exception propagate synchronously on this thread,\n            // so propagate it in a thread pool thread and return true to indicate to the caller that this failed.\n            // Otherwise, let the exception propagate.\n            if (rethrowInNewThread)\n            {\n                ThreadPool.QueueUserWorkItem(\n                    edi => ((ExceptionDispatchInfo)edi!).Throw(),\n                    ExceptionDispatchInfo.Capture(e)\n                );\n                return true;\n            }\n            throw;\n        }\n    }\n\n    internal Task EOF => _readToBufferTask ?? Task.CompletedTask;\n\n    public void Dispose()\n    {\n        _cts.Cancel();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/ProcessArgs.cs",
    "content": "﻿using System.Collections;\nusing System.Collections.Immutable;\nusing System.ComponentModel;\nusing System.Diagnostics.Contracts;\nusing System.Runtime.CompilerServices;\nusing System.Text.RegularExpressions;\nusing OneOf;\n\nnamespace StabilityMatrix.Core.Processes;\n\n/// <summary>\n/// Parameter type for command line arguments\n/// Implicitly converts between string and string[],\n/// with no parsing if the input and output types are the same.\n/// </summary>\n[Localizable(false)]\n[CollectionBuilder(typeof(ProcessArgsCollectionBuilder), \"Create\")]\npublic partial class ProcessArgs : OneOfBase<string, ImmutableArray<Argument>>, IEnumerable<Argument>\n{\n    public static ProcessArgs Empty { get; } = new(ImmutableArray<Argument>.Empty);\n\n    /// <summary>\n    /// Create a new <see cref=\"ProcessArgs\"/> from pre-quoted argument parts,\n    /// which may contain spaces or multiple arguments.\n    /// </summary>\n    /// <param name=\"inputs\">Quoted string arguments</param>\n    /// <returns>A new <see cref=\"ProcessArgs\"/> instance</returns>\n    public static ProcessArgs FromQuoted(IEnumerable<string> inputs)\n    {\n        var args = inputs.Select(Argument.Quoted).ToImmutableArray();\n        return new ProcessArgs(args);\n    }\n\n    /*public ProcessArgs(string arguments)\n        : base(arguments) { }\n    \n    public ProcessArgs(IEnumerable<Argument> arguments)\n        : base(arguments.ToImmutableArray()) { }*/\n\n    public ProcessArgs(OneOf<string, ImmutableArray<Argument>> input)\n        : base(input) { }\n\n    /// <summary>\n    /// Whether the argument string contains the given substring,\n    /// or any of the given arguments if the input is an array.\n    /// </summary>\n    public bool Contains(string argument) =>\n        Match(\n            str => str.Contains(argument),\n            arr => arr.Any(arg => arg.Value == argument || arg.Key == argument)\n        );\n\n    [Pure]\n    public ProcessArgs Concat(ProcessArgs other) =>\n        Match(\n            str => new ProcessArgs(string.Join(' ', str, other.ToString())),\n            argsArray => new ProcessArgs(argsArray.AddRange(other.ToArgumentArray()))\n        );\n\n    [Pure]\n    public ProcessArgs Prepend(ProcessArgs other) =>\n        Match(\n            str => new ProcessArgs(string.Join(' ', other.ToString(), str)),\n            argsArray => new ProcessArgs(other.ToArgumentArray().AddRange(argsArray))\n        );\n\n    /// <summary>\n    /// Gets a process string representation for command line execution.\n    /// </summary>\n    [Pure]\n    public override string ToString()\n    {\n        return Match(\n            str => str,\n            argsArray => string.Join(' ', argsArray.Select(arg => arg.GetQuotedValue()))\n        );\n    }\n\n    /// <summary>\n    /// Gets an immutable array of <see cref=\"Argument\"/> instances.\n    /// </summary>\n    [Pure]\n    public ImmutableArray<Argument> ToArgumentArray() =>\n        Match(str => [..ParseArguments(str)], argsArray => argsArray);\n\n    IEnumerator IEnumerable.GetEnumerator()\n    {\n        return GetEnumerator();\n    }\n\n    public IEnumerator<Argument> GetEnumerator()\n    {\n        return ToArgumentArray().AsEnumerable().GetEnumerator();\n    }\n\n    /// <summary>\n    /// Parses the input string into <see cref=\"Argument\"/> instances.\n    /// </summary>\n    private static IEnumerable<Argument> ParseArguments(string input) =>\n        ArgumentsRegex().Matches(input).Select(match => new Argument(match.Value.Trim('\"')));\n\n    [GeneratedRegex(\"\"\"[\\\"].+?[\\\"]|[^ ]+\"\"\", RegexOptions.IgnoreCase)]\n    private static partial Regex ArgumentsRegex();\n\n    // Implicit (string -> ProcessArgs)\n    public static implicit operator ProcessArgs(string input) => new(input);\n\n    // Implicit (string[] -> Argument[] -> ProcessArgs)\n    public static implicit operator ProcessArgs(string[] input) =>\n        new(input.Select(x => new Argument(x)).ToImmutableArray());\n\n    // Implicit (Argument[] -> ProcessArgs)\n    public static implicit operator ProcessArgs(Argument[] input) => new(input.ToImmutableArray());\n\n    // Implicit (ProcessArgs -> string)\n    public static implicit operator string(ProcessArgs input) => input.ToString();\n}\n\n[Localizable(false)]\npublic static class ProcessArgsCollectionBuilder\n{\n    public static ProcessArgs Create(ReadOnlySpan<Argument> values) => new(values.ToImmutableArray());\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/ProcessArgsBuilder.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Diagnostics.Contracts;\n\nnamespace StabilityMatrix.Core.Processes;\n\n/// <summary>\n/// Builder for <see cref=\"ProcessArgs\"/>.\n/// </summary>\npublic record ProcessArgsBuilder\n{\n    public ImmutableList<Argument> Arguments { get; init; } = ImmutableList<Argument>.Empty;\n\n    public ProcessArgsBuilder(params Argument[] arguments)\n    {\n        Arguments = arguments.ToImmutableList();\n    }\n\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        return ToProcessArgs().ToString();\n    }\n\n    public ProcessArgs ToProcessArgs()\n    {\n        return new ProcessArgs(Arguments.ToImmutableArray());\n    }\n\n    public static implicit operator ProcessArgs(ProcessArgsBuilder builder) => builder.ToProcessArgs();\n}\n\npublic static class ProcessArgBuilderExtensions\n{\n    [Pure]\n    public static T AddArg<T>(this T builder, Argument argument)\n        where T : ProcessArgsBuilder\n    {\n        return builder with { Arguments = builder.Arguments.Add(argument) };\n    }\n\n    [Pure]\n    public static T AddArgs<T>(this T builder, params Argument[] argument)\n        where T : ProcessArgsBuilder\n    {\n        return builder with { Arguments = builder.Arguments.AddRange(argument) };\n    }\n\n    /// <summary>\n    /// Add arguments from strings using the given key.\n    /// </summary>\n    [Pure]\n    public static T AddKeyedArgs<T>(this T builder, string key, IEnumerable<string> arguments)\n        where T : ProcessArgsBuilder\n    {\n        return builder with\n        {\n            Arguments = builder.Arguments.AddRange(arguments.Select(arg => new Argument(key, arg)))\n        };\n    }\n\n    [Pure]\n    public static T UpdateArg<T>(this T builder, string key, Argument argument)\n        where T : ProcessArgsBuilder\n    {\n        foreach (var arg in builder.Arguments)\n        {\n            if ((arg.Key ?? arg.Value) == key)\n            {\n                return builder with { Arguments = builder.Arguments.Replace(arg, argument) };\n            }\n        }\n\n        // No match, add the new argument\n        return builder.AddArg(argument);\n    }\n\n    [Pure]\n    public static T RemoveArgKey<T>(this T builder, string argumentKey)\n        where T : ProcessArgsBuilder\n    {\n        return builder with\n        {\n            Arguments = builder\n                .Arguments.Where(arg => (arg.Key ?? arg.Value) != argumentKey)\n                .ToImmutableList()\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/ProcessOutput.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Text.RegularExpressions;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Core.Processes;\n\npublic readonly record struct ProcessOutput\n{\n    /// <summary>\n    /// Parsed text with escape sequences and line endings removed\n    /// </summary>\n    public required string Text { get; init; }\n\n    /// <summary>\n    /// Optional Raw output,\n    /// mainly for debug and logging.\n    /// </summary>\n    public string? RawText { get; init; }\n    \n    /// <summary>\n    /// True if output from stderr, false for stdout.\n    /// </summary>\n    public bool IsStdErr { get; init; }\n    \n    /// <summary>\n    /// Count of newlines to append to the output.\n    /// (Currently not used)\n    /// </summary>\n    public int NewLines { get; init; }\n    \n    /// <summary>\n    /// Instruction to clear last n lines\n    /// From carriage return '\\r'\n    /// </summary>\n    public int CarriageReturn { get; init; }\n    \n    /// <summary>\n    /// Instruction to move write cursor up n lines\n    /// From Ansi sequence ESC[#A where # is count of lines\n    /// </summary>\n    public int CursorUp { get; init; }\n    \n    /// <summary>\n    /// Flag-type Ansi commands\n    /// </summary>\n    public AnsiCommand AnsiCommand { get; init; }\n    \n    /// <summary>\n    /// Apc message sent from the subprocess\n    /// </summary>\n    public ApcMessage? ApcMessage { get; init; }\n\n    public static ProcessOutput FromStdOutLine(string text)\n    {\n        return FromLine(text, false);\n    }\n    \n    public static ProcessOutput FromStdErrLine(string text)\n    {\n        return FromLine(text, true);\n    }\n\n    private static ProcessOutput FromLine(string text, bool isStdErr)\n    {\n        // Parse APC message\n        if (ApcParser.TryParse(text, out var message))\n        {\n            // Override and return\n            return new ProcessOutput\n            {\n                RawText = text,\n                Text = text,\n                IsStdErr = isStdErr,\n                ApcMessage = message\n            };\n        }\n        \n        // Normal parsing\n        var originalText = text;\n        \n        // Remove \\r from the beginning of the line, and add them to count\n        var clearLines = 0;\n        \n        // Strip leading carriage return until newline\n        while (!text.StartsWith(Environment.NewLine) && text.StartsWith('\\r'))\n        {\n            text = text[1..];\n            clearLines++;\n        }\n        \n        // Detect ansi escape sequences\n        var ansiCommands = AnsiCommand.None;\n        var cursorUp = 0;\n        \n        if (text.Contains('\\u001b'))\n        {\n            // Cursor up sequence - ESC[#A\n            // Where # is count of lines to move up, if not specified, default to 1\n            if (Regex.Match(text, @\"\\x1B\\[(\\d+)?A\") is {Success: true} match)\n            {\n                // Default to 1 if no count\n                cursorUp = int.TryParse(match.Groups[1].Value, out var n) ? n : 1;\n                \n                // Remove the sequence from the text\n                text = text[..match.Index] + text[(match.Index + match.Length)..];\n            }\n            // Erase line sequence - ESC[#K\n            // (For erasing we don't move the cursor)\n            // Omitted - defaults to 0\n            // 0 - clear from cursor to end of line\n            // 1 - clear from start of line to cursor\n            // 2 - clear entire line\n            if (Regex.Match(text, @\"\\x1B\\[(0|1|2)?K\") is {Success: true} match2)\n            {\n                // Default to 0 if no count\n                var eraseLineMode = int.TryParse(match2.Groups[1].Value, out var n) ? n : 0;\n                \n                ansiCommands |= eraseLineMode switch\n                {\n                    0 => AnsiCommand.EraseToEndOfLine,\n                    1 => AnsiCommand.EraseFromStartOfLine,\n                    2 => AnsiCommand.EraseLine,\n                    _ => AnsiCommand.None\n                };\n                \n                // Remove the sequence from the text\n                text = text[..match2.Index] + text[(match2.Index + match2.Length)..];\n            }\n            // Private modes, all of these can be safely ignored\n            if (Regex.Match(text, @\"\\x1B\\[?(25l|25h|47l|47h|1049h|1049l)\") is\n                     {Success: true} match3)\n            {\n                // Remove the sequence from the text\n                text = text[..match3.Index] + text[(match3.Index + match3.Length)..];\n            }\n        }\n        \n        // If text still contains escape sequences, remove them\n        if (text.Contains('\\u001b'))\n        {\n            Debug.WriteLine($\"Removing unhandled escape sequences: {text.ToRepr()}\");\n            text = AnsiParser.AnsiEscapeSequenceRegex().Replace(text, \"\");\n        }\n        \n        var output = new ProcessOutput\n        {\n            RawText = originalText,\n            Text = text,\n            IsStdErr = isStdErr,\n            CarriageReturn = clearLines,\n            CursorUp = cursorUp,\n            AnsiCommand = ansiCommands,\n        };\n        return output;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/ProcessResult.cs",
    "content": "﻿using StabilityMatrix.Core.Exceptions;\n\nnamespace StabilityMatrix.Core.Processes;\n\npublic readonly record struct ProcessResult\n{\n    public required int ExitCode { get; init; }\n    public string? StandardOutput { get; init; }\n    public string? StandardError { get; init; }\n\n    public string? ProcessName { get; init; }\n\n    public TimeSpan Elapsed { get; init; }\n\n    public bool IsSuccessExitCode => ExitCode == 0;\n\n    public void EnsureSuccessExitCode()\n    {\n        if (!IsSuccessExitCode)\n        {\n            throw new ProcessException(this);\n        }\n    }\n}\n\npublic static class ProcessResultTaskExtensions\n{\n    public static async Task<ProcessResult> EnsureSuccessExitCode(this Task<ProcessResult> task)\n    {\n        var result = await task.ConfigureAwait(false);\n        result.EnsureSuccessExitCode();\n        return result;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Processes/ProcessRunner.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Text;\nusing NLog;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Helper;\n\nnamespace StabilityMatrix.Core.Processes;\n\npublic static class ProcessRunner\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    /// <summary>\n    /// Opens the given URL in the default browser.\n    /// </summary>\n    /// <param name=\"url\">URL as string</param>\n    public static void OpenUrl(string url)\n    {\n        Logger.Debug($\"Opening URL '{url}'\");\n        Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });\n    }\n\n    /// <summary>\n    /// Opens the given URL in the default browser.\n    /// </summary>\n    /// <param name=\"url\">URI, using AbsoluteUri component</param>\n    public static void OpenUrl(Uri url)\n    {\n        OpenUrl(url.AbsoluteUri);\n    }\n\n    /// <summary>\n    /// Start an executable or .app on macOS.\n    /// </summary>\n    public static Process StartApp(string path, ProcessArgs args)\n    {\n        var startInfo = new ProcessStartInfo();\n\n        if (Compat.IsMacOS)\n        {\n            startInfo.FileName = \"open\";\n            startInfo.Arguments = args.Prepend([path, \"--args\"]).ToString();\n            startInfo.UseShellExecute = true;\n        }\n        else\n        {\n            startInfo.FileName = path;\n            startInfo.Arguments = args;\n        }\n\n        return Process.Start(startInfo) ?? throw new NullReferenceException(\"Process.Start returned null\");\n    }\n\n    /// <summary>\n    /// Opens the given folder in the system file explorer.\n    /// </summary>\n    public static async Task OpenFolderBrowser(string directoryPath)\n    {\n        if (Compat.IsWindows)\n        {\n            // Ensure path ends in DirectorySeparatorChar to unambiguously point to a directory\n            if (!directoryPath.EndsWith(Path.DirectorySeparatorChar))\n            {\n                directoryPath += Path.DirectorySeparatorChar;\n            }\n\n            using var process = new Process();\n\n            process.StartInfo.FileName = Quote(directoryPath);\n            process.StartInfo.UseShellExecute = true;\n            process.StartInfo.Verb = \"open\";\n\n            process.Start();\n\n            // Apparently using verb open detaches the process object, so we can't wait for process exit here\n        }\n        else if (Compat.IsLinux)\n        {\n            using var process = new Process();\n            process.StartInfo.FileName = directoryPath;\n            process.StartInfo.UseShellExecute = true;\n            process.Start();\n            await process.WaitForExitAsync().ConfigureAwait(false);\n        }\n        else if (Compat.IsMacOS)\n        {\n            using var process = new Process();\n            process.StartInfo.FileName = \"open\";\n            process.StartInfo.Arguments = Quote(directoryPath);\n            process.Start();\n            await process.WaitForExitAsync().ConfigureAwait(false);\n        }\n        else\n        {\n            throw new PlatformNotSupportedException();\n        }\n    }\n\n    /// <summary>\n    /// Opens the given file within its folder in the system file explorer.\n    /// </summary>\n    public static async Task OpenFileBrowser(string filePath)\n    {\n        if (Compat.IsWindows)\n        {\n            using var process = new Process();\n            process.StartInfo.FileName = \"explorer.exe\";\n            process.StartInfo.Arguments = $\"/select, {Quote(filePath)}\";\n            process.Start();\n            await process.WaitForExitAsync().ConfigureAwait(false);\n        }\n        else if (Compat.IsLinux)\n        {\n            using var process = new Process();\n            process.StartInfo.FileName = \"dbus-send\";\n            process.StartInfo.Arguments =\n                \"--print-reply --dest=org.freedesktop.FileManager1 \"\n                + \"/org/freedesktop/FileManager1 org.freedesktop.FileManager1.ShowItems \"\n                + $\"array:string:\\\"file://{filePath}\\\" string:\\\"\\\"\";\n            process.StartInfo.UseShellExecute = true;\n            process.Start();\n            await process.WaitForExitAsync().ConfigureAwait(false);\n        }\n        else if (Compat.IsMacOS)\n        {\n            using var process = new Process();\n            process.StartInfo.FileName = \"open\";\n            process.StartInfo.Arguments = $\"-R {Quote(filePath)}\";\n            process.Start();\n            await process.WaitForExitAsync().ConfigureAwait(false);\n        }\n        else\n        {\n            throw new PlatformNotSupportedException();\n        }\n    }\n\n    /// <summary>\n    /// Starts and tracks a process.\n    /// </summary>\n    private static Process StartTrackedProcess(Process process)\n    {\n        process.Start();\n\n        // Currently only supported on Windows\n        if (Compat.IsWindows)\n        {\n            ProcessTracker.AddProcess(process);\n        }\n\n        return process;\n    }\n\n    public static async Task<string> GetProcessOutputAsync(\n        string fileName,\n        string arguments,\n        string? workingDirectory = null,\n        Dictionary<string, string>? environmentVariables = null\n    )\n    {\n        Logger.Debug($\"Starting process '{fileName}' with arguments '{arguments}'\");\n\n        var info = new ProcessStartInfo\n        {\n            FileName = fileName,\n            Arguments = arguments,\n            UseShellExecute = false,\n            RedirectStandardOutput = true,\n            CreateNoWindow = true,\n        };\n\n        if (environmentVariables != null)\n        {\n            foreach (var (key, value) in environmentVariables)\n            {\n                info.EnvironmentVariables[key] = value;\n            }\n        }\n\n        if (workingDirectory != null)\n        {\n            info.WorkingDirectory = workingDirectory;\n        }\n\n        using var process = new Process();\n        process.StartInfo = info;\n        StartTrackedProcess(process);\n\n        var output = await process.StandardOutput.ReadToEndAsync();\n        await process.WaitForExitAsync();\n\n        return output;\n    }\n\n    public static async Task<ProcessResult> GetProcessResultAsync(\n        string fileName,\n        ProcessArgs arguments,\n        string? workingDirectory = null,\n        IReadOnlyDictionary<string, string>? environmentVariables = null,\n        bool useUtf8Encoding = false\n    )\n    {\n        Logger.Debug($\"Starting process '{fileName}' with arguments '{arguments}'\");\n\n        var info = new ProcessStartInfo\n        {\n            FileName = fileName,\n            Arguments = arguments,\n            UseShellExecute = false,\n            RedirectStandardOutput = true,\n            RedirectStandardError = true,\n            CreateNoWindow = true,\n        };\n\n        if (useUtf8Encoding)\n        {\n            info.StandardOutputEncoding = Encoding.UTF8;\n            info.StandardErrorEncoding = Encoding.UTF8;\n        }\n\n        if (environmentVariables != null)\n        {\n            foreach (var (key, value) in environmentVariables)\n            {\n                info.EnvironmentVariables[key] = value;\n            }\n        }\n\n        if (workingDirectory != null)\n        {\n            info.WorkingDirectory = workingDirectory;\n        }\n\n        using var process = new Process();\n        process.StartInfo = info;\n        StartTrackedProcess(process);\n\n        var stdout = await process.StandardOutput.ReadToEndAsync().ConfigureAwait(false);\n        var stderr = await process.StandardError.ReadToEndAsync().ConfigureAwait(false);\n\n        await process.WaitForExitAsync().ConfigureAwait(false);\n\n        string? processName = null;\n        TimeSpan elapsed = default;\n\n        // Accessing these properties may throw an exception if the process has already exited\n        try\n        {\n            processName = process.ProcessName;\n        }\n        catch (SystemException) { }\n\n        try\n        {\n            elapsed = process.ExitTime - process.StartTime;\n        }\n        catch (SystemException) { }\n\n        return new ProcessResult\n        {\n            ExitCode = process.ExitCode,\n            StandardOutput = stdout,\n            StandardError = stderr,\n            ProcessName = processName,\n            Elapsed = elapsed,\n        };\n    }\n\n    /// <summary>\n    /// Starts a process, captures its output in real-time via a callback, and returns the final process result.\n    /// </summary>\n    /// <param name=\"fileName\">The name of the file to execute.</param>\n    /// <param name=\"arguments\">The command-line arguments to pass to the executable.</param>\n    /// <param name=\"workingDirectory\">The working directory for the process.</param>\n    /// <param name=\"outputDataReceived\">Callback that receives process output in real-time.</param>\n    /// <param name=\"environmentVariables\">Environment variables to set for the process.</param>\n    /// <param name=\"cancellationToken\">Cancellation token to cancel waiting for process exit and close the process.</param>\n    /// <returns>A ProcessResult containing the exit code and combined output.</returns>\n    public static async Task<ProcessResult> GetAnsiProcessResultAsync(\n        string fileName,\n        ProcessArgs arguments,\n        string? workingDirectory = null,\n        Action<ProcessOutput>? outputDataReceived = null,\n        IReadOnlyDictionary<string, string>? environmentVariables = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        Logger.Debug($\"Starting process '{fileName}' with arguments '{arguments}'\");\n\n        var info = new ProcessStartInfo\n        {\n            FileName = fileName,\n            Arguments = arguments,\n            UseShellExecute = false,\n            RedirectStandardOutput = true,\n            RedirectStandardError = true,\n            CreateNoWindow = true,\n        };\n\n        if (environmentVariables != null)\n        {\n            foreach (var (key, value) in environmentVariables)\n            {\n                info.EnvironmentVariables[key] = value;\n            }\n        }\n\n        if (workingDirectory != null)\n        {\n            info.WorkingDirectory = workingDirectory;\n        }\n\n        var stdoutBuilder = new StringBuilder();\n        var stderrBuilder = new StringBuilder();\n\n        using var process = new AnsiProcess(info);\n        StartTrackedProcess(process);\n\n        try\n        {\n            if (outputDataReceived != null)\n            {\n                process.BeginAnsiRead(output =>\n                {\n                    // Call the user's callback\n                    outputDataReceived(output);\n\n                    // Also capture the output for the final result\n                    if (output.IsStdErr)\n                    {\n                        stderrBuilder.AppendLine(output.Text);\n                    }\n                    else\n                    {\n                        stdoutBuilder.AppendLine(output.Text);\n                    }\n                });\n            }\n\n            await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);\n\n            // Ensure we've processed all output\n            if (outputDataReceived != null)\n            {\n                await process.WaitUntilOutputEOF(cancellationToken).ConfigureAwait(false);\n            }\n\n            string? processName = null;\n            var elapsed = TimeSpan.Zero;\n\n            // Accessing these properties may throw an exception if the process has already exited\n            try\n            {\n                processName = process.ProcessName;\n            }\n            catch (SystemException) { }\n\n            try\n            {\n                elapsed = process.ExitTime - process.StartTime;\n            }\n            catch (SystemException) { }\n\n            return new ProcessResult\n            {\n                ExitCode = process.ExitCode,\n                StandardOutput = stdoutBuilder.ToString(),\n                StandardError = stderrBuilder.ToString(),\n                ProcessName = processName,\n                Elapsed = elapsed,\n            };\n        }\n        catch (OperationCanceledException e)\n        {\n            // Handle cancellation\n            Logger.Info($\"Process '{fileName}' was cancelled. Killing the process.\");\n            process.CancelStreamReaders();\n            process.Kill(true);\n\n            var result = new ProcessResult\n            {\n                ExitCode = process.ExitCode,\n                StandardOutput = stdoutBuilder.ToString(),\n                StandardError = stderrBuilder.ToString(),\n            };\n\n            // Accessing these properties may throw an exception if the process has already exited\n            try\n            {\n                result = result with { ProcessName = process.ProcessName };\n            }\n            catch (SystemException) { }\n\n            try\n            {\n                result = result with { Elapsed = process.ExitTime - process.StartTime };\n            }\n            catch (SystemException) { }\n\n            throw new OperationCanceledException(e.Message, new ProcessException(result), cancellationToken);\n        }\n    }\n\n    public static Process StartProcess(\n        string fileName,\n        string arguments,\n        string? workingDirectory = null,\n        Action<string?>? outputDataReceived = null,\n        IReadOnlyDictionary<string, string>? environmentVariables = null\n    )\n    {\n        Logger.Debug($\"Starting process '{fileName}' with arguments '{arguments}'\");\n        var info = new ProcessStartInfo\n        {\n            FileName = fileName,\n            Arguments = arguments,\n            UseShellExecute = false,\n            RedirectStandardOutput = true,\n            RedirectStandardError = true,\n            CreateNoWindow = true,\n        };\n\n        if (environmentVariables != null)\n        {\n            foreach (var (key, value) in environmentVariables)\n            {\n                info.EnvironmentVariables[key] = value;\n            }\n        }\n\n        if (workingDirectory != null)\n        {\n            info.WorkingDirectory = workingDirectory;\n        }\n\n        var process = new Process { StartInfo = info };\n        StartTrackedProcess(process);\n\n        if (outputDataReceived == null)\n            return process;\n\n        process.OutputDataReceived += (sender, args) => outputDataReceived(args.Data);\n        process.ErrorDataReceived += (sender, args) => outputDataReceived(args.Data);\n        process.BeginOutputReadLine();\n        process.BeginErrorReadLine();\n\n        return process;\n    }\n\n    public static AnsiProcess StartAnsiProcess(\n        string fileName,\n        string arguments,\n        string? workingDirectory = null,\n        Action<ProcessOutput>? outputDataReceived = null,\n        IReadOnlyDictionary<string, string>? environmentVariables = null\n    )\n    {\n        Logger.Debug(\n            $\"Starting process '{fileName}' with arguments '{arguments}' in working directory '{workingDirectory}'\"\n        );\n        var info = new ProcessStartInfo\n        {\n            FileName = fileName,\n            Arguments = arguments,\n            UseShellExecute = false,\n            RedirectStandardOutput = true,\n            RedirectStandardError = true,\n            CreateNoWindow = true,\n        };\n\n        if (environmentVariables != null)\n        {\n            foreach (var (key, value) in environmentVariables)\n            {\n                info.EnvironmentVariables[key] = value;\n            }\n        }\n\n        if (workingDirectory != null)\n        {\n            info.WorkingDirectory = workingDirectory;\n        }\n\n        var process = new AnsiProcess(info);\n        StartTrackedProcess(process);\n\n        if (outputDataReceived != null)\n        {\n            process.BeginAnsiRead(outputDataReceived);\n        }\n\n        return process;\n    }\n\n    public static AnsiProcess StartAnsiProcess(\n        string fileName,\n        IEnumerable<string> arguments,\n        string? workingDirectory = null,\n        Action<ProcessOutput>? outputDataReceived = null,\n        Dictionary<string, string>? environmentVariables = null\n    )\n    {\n        // Quote arguments containing spaces\n        var args = string.Join(\" \", arguments.Where(s => !string.IsNullOrEmpty(s)).Select(Quote));\n        return StartAnsiProcess(fileName, args, workingDirectory, outputDataReceived, environmentVariables);\n    }\n\n    public static async Task<ProcessResult> RunBashCommand(\n        string command,\n        string workingDirectory = \"\",\n        IReadOnlyDictionary<string, string>? environmentVariables = null\n    )\n    {\n        // Escape any single quotes in the command\n        var escapedCommand = command.Replace(\"\\\"\", \"\\\\\\\"\");\n        var arguments = $\"-c \\\"{escapedCommand}\\\"\";\n\n        Logger.Info($\"Running bash command [bash {arguments}]\");\n\n        var processInfo = new ProcessStartInfo(\"bash\", arguments)\n        {\n            UseShellExecute = false,\n            RedirectStandardOutput = true,\n            RedirectStandardError = true,\n            WorkingDirectory = workingDirectory,\n        };\n\n        if (environmentVariables != null)\n        {\n            foreach (var (key, value) in environmentVariables)\n            {\n                processInfo.EnvironmentVariables[key] = value;\n            }\n        }\n\n        using var process = new Process();\n        process.StartInfo = processInfo;\n\n        var stdout = new StringBuilder();\n        var stderr = new StringBuilder();\n        process.OutputDataReceived += (_, args) => stdout.Append(args.Data);\n        process.ErrorDataReceived += (_, args) => stderr.Append(args.Data);\n\n        StartTrackedProcess(process);\n        process.BeginOutputReadLine();\n        process.BeginErrorReadLine();\n\n        await process.WaitForExitAsync().ConfigureAwait(false);\n\n        return new ProcessResult\n        {\n            ExitCode = process.ExitCode,\n            StandardOutput = stdout.ToString(),\n            StandardError = stderr.ToString(),\n        };\n    }\n\n    public static Task<ProcessResult> RunBashCommand(\n        IEnumerable<string> commands,\n        string workingDirectory = \"\",\n        IReadOnlyDictionary<string, string>? environmentVariables = null\n    )\n    {\n        // Quote arguments containing spaces\n        var args = string.Join(\" \", commands.Select(Quote));\n        return RunBashCommand(args, workingDirectory, environmentVariables);\n    }\n\n    /// <summary>\n    /// Quotes argument with double quotes if it contains spaces,\n    /// and does not already start and end with double quotes.\n    /// </summary>\n    public static string Quote(string argument)\n    {\n        var inner = argument.Trim('\"');\n        return inner.Contains(' ') ? $\"\\\"{inner}\\\"\" : argument;\n    }\n\n    /// <summary>\n    /// Waits for process to exit, then validates exit code.\n    /// </summary>\n    /// <param name=\"process\">Process to check.</param>\n    /// <param name=\"expectedExitCode\">Expected exit code.</param>\n    /// <param name=\"cancelToken\">Cancellation token.</param>\n    /// <exception cref=\"ProcessException\">Thrown if exit code does not match expected value.</exception>\n    public static async Task WaitForExitConditionAsync(\n        Process process,\n        int expectedExitCode = 0,\n        CancellationToken cancelToken = default\n    )\n    {\n        if (!process.HasExited)\n        {\n            await process.WaitForExitAsync(cancelToken).ConfigureAwait(false);\n        }\n\n        if (process.ExitCode == expectedExitCode)\n        {\n            return;\n        }\n\n        // Accessing ProcessName may error on some platforms\n        string? processName = null;\n        try\n        {\n            processName = process.ProcessName;\n        }\n        catch (SystemException) { }\n\n        throw new ProcessException(\n            \"Process \"\n                + (processName == null ? \"\" : processName + \" \")\n                + $\"failed with exit-code {process.ExitCode}.\"\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/ArgParser.cs",
    "content": "﻿using NLog;\nusing Python.Runtime;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Core.Python;\n\n/// <summary>\n/// Extracts command arguments from Python source file.\n/// </summary>\npublic class ArgParser\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n    private readonly IPyRunner pyRunner;\n    private string rootPath;\n    private string moduleName;\n\n    public ArgParser(IPyRunner pyRunner, string rootPath, string moduleName)\n    {\n        this.pyRunner = pyRunner;\n        this.rootPath = rootPath;\n        this.moduleName = moduleName;\n    }\n    \n    /// <summary>\n    /// Convert a PyObject to a value that can be used as a LaunchOption value.\n    /// </summary>\n    public static object PyObjectToOptionValue(PyObject obj)\n    {\n        var type = obj.GetPythonType().Name;\n        return type switch\n        {\n            \"bool\" => obj.As<bool>(),\n            \"int\" => obj.As<int>(),\n            \"str\" => obj.As<string>(),\n            _ => throw new ArgumentException($\"Unknown option type {type}\")\n        };\n    }\n\n    /// <summary>\n    /// Convert a PyObject to a LaunchOptionType enum.\n    /// </summary>\n    public static LaunchOptionType? PyObjectToOptionType(PyObject typeObj)\n    {\n        var typeName = typeObj.GetAttr(\"__name__\").As<string>();\n        return typeName switch\n        {\n            \"bool\" => LaunchOptionType.Bool,\n            \"int\" => LaunchOptionType.Int,\n            \"str\" => LaunchOptionType.String,\n            _ => null\n        };\n    }\n    \n    public async Task<List<LaunchOptionDefinition>> GetArgsAsync()\n    {\n        await pyRunner.Initialize();\n\n        return await pyRunner.RunInThreadWithLock(() =>\n        {\n            using var scope = Py.CreateScope();\n            dynamic sys = scope.Import(\"sys\");\n            dynamic argparse = scope.Import(\"argparse\");\n            // Add root path to sys.path\n            sys.path.insert(0, rootPath);\n            // Import module\n            var argsModule = scope.Import(moduleName);\n            var argsDict = argsModule.GetAttr(\"__dict__\").As<PyDict>();\n            // Find ArgumentParser object in module\n            dynamic? argParser = null;\n            var argParserType = argparse.ArgumentParser;\n            foreach (var obj in argsDict.Values())\n            {\n                if (obj.IsInstance(argParserType))\n                {\n                    argParser = obj;\n                    break;\n                }\n            }\n            if (argParser == null)\n            {\n                throw new ArgumentException($\"Could not find ArgumentParser object in module '{moduleName}'\");\n            }\n            // Loop through arguments\n            var definitions = new List<LaunchOptionDefinition>();\n            \n            foreach (var action in argParser._actions)\n            {\n                var name = (action.dest as PyObject)?.As<string>();\n                if (name == null)\n                {\n                    throw new Exception(\"Argument option did not have a `dest` value\");\n                }\n                var optionStrings = ((PyObject) action.option_strings).As<string[]>();\n                var dest = (action.dest as PyObject)?.As<string>();\n                // var nArgs = (action.nargs as PyObject)?.As<int>();\n                var isConst = (action.@const as PyObject)?.IsTrue() ?? false;\n                var isRequired = (action.required as PyObject)?.IsTrue() ?? false;\n                var type = action.type as PyObject;\n                // Bool types will have a type of None (null)\n                var optionType = type == null ? LaunchOptionType.Bool : PyObjectToOptionType(type);\n                if (optionType == null)\n                {\n                    Logger.Warn(\"Skipping option {Dest} with type {Name}\", dest, type);\n                    continue;\n                }\n                // Parse default\n                var @default = action.@default as PyObject;\n                var defaultValue = @default != null ? PyObjectToOptionValue(@default) : null;\n\n                var help = (action.help as PyObject)?.As<string>();\n\n                definitions.Add(new LaunchOptionDefinition\n                {\n                    Name = help ?? name,\n                    Description = help,\n                    Options = new List<string> { optionStrings[0] },\n                    // ReSharper disable once ConstantNullCoalescingCondition\n                    Type = optionType ?? LaunchOptionType.Bool,\n                    DefaultValue = defaultValue,\n                    MinSelectedOptions = isRequired ? 1 : 0,\n                });\n            }\n\n            return definitions;\n        });\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/IPyInstallationManager.cs",
    "content": "namespace StabilityMatrix.Core.Python;\n\n/// <summary>\n/// Interface for managing Python installations\n/// </summary>\npublic interface IPyInstallationManager\n{\n    /// <summary>\n    /// Gets all discoverable Python installations (legacy and UV-managed).\n    /// This is now an async method.\n    /// </summary>\n    Task<IEnumerable<PyInstallation>> GetAllInstallationsAsync();\n\n    /// <summary>\n    /// Gets an installation for a specific version.\n    /// If not found, and UV is configured, it may attempt to install it using UV.\n    /// This is now an async method.\n    /// </summary>\n    Task<PyInstallation> GetInstallationAsync(PyVersion version);\n\n    /// <summary>\n    /// Gets the default installation.\n    /// This is now an async method.\n    /// </summary>\n    Task<PyInstallation> GetDefaultInstallationAsync();\n\n    Task<IReadOnlyList<UvPythonInfo>> GetAllAvailablePythonsAsync();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/IPyRunner.cs",
    "content": "﻿using Python.Runtime;\nusing StabilityMatrix.Core.Python.Interop;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic interface IPyRunner\n{\n    PyIOStream? StdOutStream { get; }\n    PyIOStream? StdErrStream { get; }\n\n    /// <summary>\n    /// Initializes the Python runtime using the embedded dll.\n    /// </summary>\n    Task Initialize();\n\n    /// <summary>\n    /// Switch to a specific Python installation\n    /// </summary>\n    Task SwitchToInstallation(PyVersion version);\n\n    /// <summary>\n    /// One-time setup for get-pip\n    /// </summary>\n    Task SetupPip(PyVersion? version = null);\n\n    /// <summary>\n    /// Install a Python package with pip\n    /// </summary>\n    Task InstallPackage(string package, PyVersion? version = null);\n\n    /// <summary>\n    /// Run a Function with PyRunning lock as a Task with GIL.\n    /// </summary>\n    Task<T> RunInThreadWithLock<T>(\n        Func<T> func,\n        TimeSpan? waitTimeout = null,\n        CancellationToken cancelToken = default\n    );\n\n    /// <summary>\n    /// Run an Action with PyRunning lock as a Task with GIL.\n    /// </summary>\n    Task RunInThreadWithLock(\n        Action action,\n        TimeSpan? waitTimeout = null,\n        CancellationToken cancelToken = default\n    );\n\n    /// <summary>\n    /// Evaluate Python expression and return its value as a string\n    /// </summary>\n    Task<string> Eval(string expression);\n\n    /// <summary>\n    /// Evaluate Python expression and return its value\n    /// </summary>\n    Task<T> Eval<T>(string expression);\n\n    /// <summary>\n    /// Execute Python code without returning a value\n    /// </summary>\n    Task Exec(string code);\n\n    /// <summary>\n    /// Return the Python version as a PyVersionInfo struct\n    /// </summary>\n    Task<PyVersionInfo> GetVersionInfo();\n\n    /// <summary>\n    /// Get Python directory name for the given version\n    /// </summary>\n    string GetPythonDirName(PyVersion? version = null);\n\n    /// <summary>\n    /// Get Python directory for the given version\n    /// </summary>\n    string GetPythonDir(PyVersion? version = null);\n\n    /// <summary>\n    /// Get Python DLL path for the given version\n    /// </summary>\n    string GetPythonDllPath(PyVersion? version = null);\n\n    /// <summary>\n    /// Get Python executable path for the given version\n    /// </summary>\n    string GetPythonExePath(PyVersion? version = null);\n\n    /// <summary>\n    /// Get Pip executable path for the given version\n    /// </summary>\n    string GetPipExePath(PyVersion? version = null);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/IPyVenvRunner.cs",
    "content": "﻿using System.Collections.Immutable;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic interface IPyVenvRunner\n{\n    PyBaseInstall BaseInstall { get; }\n\n    /// <summary>\n    /// The process running the python executable.\n    /// </summary>\n    AnsiProcess? Process { get; }\n\n    /// <summary>\n    /// The path to the venv root directory.\n    /// </summary>\n    DirectoryPath RootPath { get; }\n\n    /// <summary>\n    /// Optional working directory for the python process.\n    /// </summary>\n    DirectoryPath? WorkingDirectory { get; set; }\n\n    /// <summary>\n    /// Optional environment variables for the python process.\n    /// </summary>\n    ImmutableDictionary<string, string> EnvironmentVariables { get; set; }\n\n    /// <summary>\n    /// The full path to the python executable.\n    /// </summary>\n    FilePath PythonPath { get; }\n\n    /// <summary>\n    /// The full path to the pip executable.\n    /// </summary>\n    FilePath PipPath { get; }\n\n    /// <summary>\n    /// The Python version of this venv\n    /// </summary>\n    PyVersion Version { get; }\n\n    /// <summary>\n    /// List of substrings to suppress from the output.\n    /// When a line contains any of these substrings, it will not be forwarded to callbacks.\n    /// A corresponding Info log will be written instead.\n    /// </summary>\n    List<string> SuppressOutput { get; }\n\n    void UpdateEnvironmentVariables(\n        Func<ImmutableDictionary<string, string>, ImmutableDictionary<string, string>> env\n    );\n\n    /// <returns>True if the venv has a Scripts\\python.exe file</returns>\n    bool Exists();\n\n    /// <summary>\n    /// Creates a venv at the configured path.\n    /// </summary>\n    Task Setup(\n        bool existsOk = false,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    );\n\n    /// <summary>\n    /// Run a pip install command. Waits for the process to exit.\n    /// workingDirectory defaults to RootPath.\n    /// </summary>\n    Task PipInstall(ProcessArgs args, Action<ProcessOutput>? outputDataReceived = null);\n\n    /// <summary>\n    /// Run a pip uninstall command. Waits for the process to exit.\n    /// workingDirectory defaults to RootPath.\n    /// </summary>\n    Task PipUninstall(ProcessArgs args, Action<ProcessOutput>? outputDataReceived = null);\n\n    /// <summary>\n    /// Run a pip list command, return results as PipPackageInfo objects.\n    /// </summary>\n    Task<IReadOnlyList<PipPackageInfo>> PipList();\n\n    /// <summary>\n    /// Run a pip show command, return results as PipPackageInfo objects.\n    /// </summary>\n    Task<PipShowResult?> PipShow(string packageName);\n\n    /// <summary>\n    /// Run a pip index command, return result as PipIndexResult.\n    /// </summary>\n    Task<PipIndexResult?> PipIndex(string packageName, string? indexUrl = null);\n\n    /// <summary>\n    /// Run a custom install command. Waits for the process to exit.\n    /// workingDirectory defaults to RootPath.\n    /// </summary>\n    Task CustomInstall(ProcessArgs args, Action<ProcessOutput>? outputDataReceived = null);\n\n    /// <summary>\n    /// Run a command using the venv Python executable and return the result.\n    /// </summary>\n    /// <param name=\"arguments\">Arguments to pass to the Python executable.</param>\n    Task<ProcessResult> Run(ProcessArgs arguments);\n\n    void RunDetached(\n        ProcessArgs args,\n        Action<ProcessOutput>? outputDataReceived,\n        Action<int>? onExit = null,\n        bool unbuffered = true\n    );\n\n    /// <summary>\n    /// Get entry points for a package.\n    /// https://packaging.python.org/en/latest/specifications/entry-points/#entry-points\n    /// </summary>\n    Task<string?> GetEntryPoint(string entryPointName);\n\n    /// <summary>\n    /// Kills the running process and cancels stream readers, does not wait for exit.\n    /// </summary>\n    void Dispose();\n\n    /// <summary>\n    /// Kills the running process, waits for exit.\n    /// </summary>\n    ValueTask DisposeAsync();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/IUvManager.cs",
    "content": "﻿using StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic interface IUvManager\n{\n    Task<bool> IsUvAvailableAsync(CancellationToken cancellationToken = default);\n\n    /// <summary>\n    /// Lists Python distributions known to UV.\n    /// </summary>\n    /// <param name=\"installedOnly\">If true, only lists Pythons UV reports as installed.</param>\n    /// <param name=\"onConsoleOutput\">Optional callback for console output.</param>\n    /// <param name=\"cancellationToken\">Cancellation token.</param>\n    /// <returns>A list of UvPythonInfo objects.</returns>\n    Task<IReadOnlyList<UvPythonInfo>> ListAvailablePythonsAsync(\n        bool installedOnly = false,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    );\n\n    /// <summary>\n    /// Gets information about a specific installed Python version managed by UV.\n    /// </summary>\n    Task<UvPythonInfo?> GetInstalledPythonAsync(\n        PyVersion version,\n        CancellationToken cancellationToken = default\n    );\n\n    /// <summary>\n    /// Installs a specific Python version using UV.\n    /// </summary>\n    /// <param name=\"version\">Python version to install (e.g., \"3.10\" or \"3.10.13\").</param>\n    /// <param name=\"targetInstallDirectory\">Optional. If provided, UV_PYTHON_INSTALL_DIR will be set for the uv process.</param>\n    /// <param name=\"onConsoleOutput\">Optional callback for console output.</param>\n    /// <param name=\"cancellationToken\">Cancellation token.</param>\n    /// <returns>UvPythonInfo for the installed Python, or null if installation failed or info couldn't be retrieved.</returns>\n    Task<UvPythonInfo?> InstallPythonVersionAsync(\n        PyVersion version,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/Interop/PyIOStream.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing System.Text;\n\nnamespace StabilityMatrix.Core.Python.Interop;\n\n/// <summary>\n/// Implement the interface of the sys.stdout redirection\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\npublic class PyIOStream\n{\n    private readonly StringBuilder TextBuilder;\n    private readonly StringWriter TextWriter;\n\n    public PyIOStream(StringBuilder? builder = null)\n    {\n        TextBuilder = builder ?? new StringBuilder();\n        TextWriter = new StringWriter(TextBuilder);\n    }\n    \n    public event EventHandler<string>? OnWriteUpdate;\n\n    public void ClearBuffer()\n    {\n        TextBuilder.Clear();\n    }\n\n    public string GetBuffer()\n    {\n        return TextBuilder.ToString();\n    }\n\n    [SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\n    public void write(string str)\n    {\n        TextWriter.Write(str);\n        OnWriteUpdate?.Invoke(this, str);\n    }\n\n    [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n    public void writelines(IEnumerable<string> str)\n    {\n        foreach (var line in str)\n        {\n            write(line);\n        }\n    }\n\n    [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n    public void flush()\n    {\n        TextWriter.Flush();\n    }\n\n    [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n    public void close()\n    {\n        TextWriter?.Close();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/MajorMinorVersion.cs",
    "content": "﻿namespace StabilityMatrix.Core.Python;\n\npublic readonly record struct MajorMinorVersion(int Major, int Minor);\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PipIndexResult.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Text.RegularExpressions;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic partial record PipIndexResult\n{\n    public required IReadOnlyList<string> AvailableVersions { get; init; }\n\n    public static PipIndexResult Parse(string output)\n    {\n        var match = AvailableVersionsRegex().Matches(output);\n\n        var versions = output\n            .SplitLines()\n            .Select(line => AvailableVersionsRegex().Match(line))\n            .First(m => m.Success)\n            .Groups[\"versions\"].Value\n            .Split(\n                new[] { ',' },\n                StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries\n            )\n            .ToImmutableArray();\n\n        return new PipIndexResult { AvailableVersions = versions };\n    }\n\n    // Regex, capture the line starting with \"Available versions:\"\n    [GeneratedRegex(@\"^Available versions:\\s*(?<versions>.*)$\")]\n    private static partial Regex AvailableVersionsRegex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PipInstallArgs.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Diagnostics.Contracts;\nusing System.Text.RegularExpressions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Python;\n\n[SuppressMessage(\"ReSharper\", \"StringLiteralTypo\")]\npublic partial record PipInstallArgs : ProcessArgsBuilder\n{\n    public PipInstallArgs(params Argument[] arguments)\n        : base(arguments) { }\n\n    public PipInstallArgs WithTorch(string version = \"\") =>\n        this.AddArg(new Argument(\"torch\", $\"torch{version}\"));\n\n    public PipInstallArgs WithTorchDirectML(string version = \"\") =>\n        this.AddArg(new Argument(\"torch-directml\", $\"torch-directml{version}\"));\n\n    public PipInstallArgs WithTorchVision(string version = \"\") =>\n        this.AddArg(new Argument(\"torchvision\", $\"torchvision{version}\"));\n\n    public PipInstallArgs WithTorchAudio(string version = \"\") =>\n        this.AddArg(new Argument(\"torchaudio\", $\"torchaudio{version}\"));\n\n    public PipInstallArgs WithXFormers(string version = \"\") =>\n        this.AddArg(new Argument(\"xformers\", $\"xformers{version}\"));\n\n    public PipInstallArgs WithExtraIndex(string indexUrl) =>\n        this.AddKeyedArgs(\"--extra-index-url\", [\"--extra-index-url\", indexUrl]);\n\n    public PipInstallArgs WithTorchExtraIndex(string index) =>\n        WithExtraIndex($\"https://download.pytorch.org/whl/{index}\");\n\n    public PipInstallArgs WithParsedFromRequirementsTxt(\n        string requirements,\n        [StringSyntax(StringSyntaxAttribute.Regex)] string? excludePattern = null\n    )\n    {\n        var requirementsEntries = requirements\n            .SplitLines(StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)\n            .Where(s => !s.StartsWith('#'))\n            .Select(s => s.Contains('#') ? s.Substring(0, s.IndexOf('#')) : s)\n            .Select(s => s.Trim())\n            .Where(s => !string.IsNullOrWhiteSpace(s))\n            .Select(NormalizePackageSpecifier);\n\n        if (excludePattern is not null)\n        {\n            var excludeRegex = new Regex($\"^{excludePattern}$\");\n\n            requirementsEntries = requirementsEntries.Where(s => !excludeRegex.IsMatch(s));\n        }\n\n        return this.AddArgs(requirementsEntries.Select(Argument.Quoted).ToArray());\n    }\n\n    /// <summary>\n    /// Normalizes a package specifier by removing spaces around version constraint operators.\n    /// </summary>\n    /// <param name=\"specifier\">The package specifier to normalize.</param>\n    /// <returns>The normalized package specifier.</returns>\n    private static string NormalizePackageSpecifier(string specifier)\n    {\n        // Skip normalization for special pip commands that start with a hyphen\n        if (specifier.StartsWith('-'))\n            return specifier;\n\n        // Regex to match common version constraint patterns with spaces\n        // Matches: package >= 1.0.0, package <= 1.0.0, package == 1.0.0, etc.\n        var versionConstraintPattern = PackageSpecifierRegex();\n\n        var match = versionConstraintPattern.Match(specifier);\n        if (match.Success)\n        {\n            var packageName = match.Groups[1].Value;\n            var versionOperator = match.Groups[2].Value;\n            var version = match.Groups[3].Value;\n\n            return $\"{packageName}{versionOperator}{version}\";\n        }\n\n        return specifier;\n    }\n\n    public PipInstallArgs WithUserOverrides(List<PipPackageSpecifierOverride> overrides)\n    {\n        var newArgs = this;\n\n        foreach (var pipOverride in overrides)\n        {\n            if (string.IsNullOrWhiteSpace(pipOverride.Name))\n                continue;\n\n            if (pipOverride.Name is \"--extra-index-url\" or \"--index-url\")\n            {\n                pipOverride.Constraint = \"=\";\n            }\n\n            var pipOverrideArg = pipOverride.ToArgument();\n\n            if (pipOverride.Action is PipPackageSpecifierOverrideAction.Update)\n            {\n                newArgs = newArgs.RemovePipArgKey(pipOverrideArg.Key ?? pipOverrideArg.Value);\n                newArgs = newArgs.AddArg(pipOverrideArg);\n            }\n            else if (pipOverride.Action is PipPackageSpecifierOverrideAction.Remove)\n            {\n                newArgs = newArgs.RemovePipArgKey(pipOverrideArg.Key ?? pipOverrideArg.Value);\n            }\n        }\n\n        return newArgs;\n    }\n\n    [Pure]\n    public PipInstallArgs RemovePipArgKey(string argumentKey)\n    {\n        return this with\n        {\n            Arguments = Arguments\n                .Where(\n                    arg =>\n                        arg.HasKey\n                            ? (arg.Key != argumentKey)\n                            : (arg.Value != argumentKey && !arg.Value.Contains($\"{argumentKey}==\"))\n                )\n                .ToImmutableList()\n        };\n    }\n\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        return base.ToString();\n    }\n\n    [GeneratedRegex(@\"^([a-zA-Z0-9\\-_.]+)\\s*(>=|<=|==|>|<|!=|~=)\\s*(.+)$\")]\n    private static partial Regex PackageSpecifierRegex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PipPackageInfo.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic readonly record struct PipPackageInfo(\n    string Name,\n    string Version,\n    string? EditableProjectLocation = null\n);\n\n[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)]\n[JsonSerializable(typeof(PipPackageInfo))]\n[JsonSerializable(typeof(List<PipPackageInfo>))]\ninternal partial class PipPackageInfoSerializerContext : JsonSerializerContext;\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PipPackageSpecifier.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Text.Json.Serialization;\nusing System.Text.RegularExpressions;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic partial record PipPackageSpecifier\n{\n    [JsonIgnore]\n    public static IReadOnlyList<string> ConstraintOptions => [\"\", \"==\", \"~=\", \">=\", \"<=\", \">\", \"<\"];\n\n    public string? Name { get; set; }\n\n    public string? Constraint { get; set; }\n\n    public string? Version { get; set; }\n\n    public string? VersionConstraint => Constraint is null || Version is null ? null : Constraint + Version;\n\n    public static PipPackageSpecifier Parse(string value)\n    {\n        var result = TryParse(value, true, out var packageSpecifier);\n\n        Debug.Assert(result);\n\n        return packageSpecifier!;\n    }\n\n    public static bool TryParse(string value, [NotNullWhen(true)] out PipPackageSpecifier? packageSpecifier)\n    {\n        return TryParse(value, false, out packageSpecifier);\n    }\n\n    private static bool TryParse(\n        string value,\n        bool throwOnFailure,\n        [NotNullWhen(true)] out PipPackageSpecifier? packageSpecifier\n    )\n    {\n        var match = PackageSpecifierRegex().Match(value);\n        if (!match.Success)\n        {\n            if (throwOnFailure)\n            {\n                throw new ArgumentException($\"Invalid package specifier: {value}\");\n            }\n\n            packageSpecifier = null;\n            return false;\n        }\n\n        packageSpecifier = new PipPackageSpecifier\n        {\n            Name = match.Groups[\"package_name\"].Value,\n            Constraint = match.Groups[\"version_constraint\"].Value,\n            Version = match.Groups[\"version\"].Value\n        };\n\n        return true;\n    }\n\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        return Name + VersionConstraint;\n    }\n\n    public Argument ToArgument()\n    {\n        if (Name is null)\n        {\n            return new Argument(\"\");\n        }\n\n        // Normal package specifier with version constraint\n        if (VersionConstraint is not null)\n        {\n            // Use Name as key\n            return new Argument(key: Name, value: ToString());\n        }\n\n        // Possible multi arg (e.g. '--extra-index-url ...')\n        if (Name.Trim().StartsWith('-'))\n        {\n            var parts = Name.Split(' ', StringSplitOptions.RemoveEmptyEntries);\n            if (parts.Length > 1)\n            {\n                var key = parts[0];\n                var quotedParts = string.Join(' ', parts.Select(ProcessRunner.Quote));\n                return Argument.Quoted(key, quotedParts);\n            }\n        }\n\n        return new Argument(ToString());\n    }\n\n    public static implicit operator Argument(PipPackageSpecifier specifier)\n    {\n        return specifier.ToArgument();\n    }\n\n    public static implicit operator PipPackageSpecifier(string specifier)\n    {\n        return Parse(specifier);\n    }\n\n    /// <summary>\n    /// Regex to match a pip package specifier.\n    /// </summary>\n    [GeneratedRegex(\n        \"(?<package_name>[a-zA-Z0-9_]+)(?<version_specifier>(?<version_constraint>==|>=|<=|>|<|~=|!=)(<version>[a-zA-Z0-9_.]+))?\",\n        RegexOptions.CultureInvariant,\n        1000\n    )]\n    private static partial Regex PackageSpecifierRegex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PipPackageSpecifierOverride.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic record PipPackageSpecifierOverride : PipPackageSpecifier\n{\n    public PipPackageSpecifierOverrideAction Action { get; init; } = PipPackageSpecifierOverrideAction.Update;\n\n    [JsonIgnore]\n    public bool IsUpdate => Action is PipPackageSpecifierOverrideAction.Update;\n\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        return base.ToString();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PipPackageSpecifierOverrideAction.cs",
    "content": "﻿namespace StabilityMatrix.Core.Python;\n\npublic enum PipPackageSpecifierOverrideAction\n{\n    None,\n    Update,\n    Remove\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PipShowResult.cs",
    "content": "﻿using System.Diagnostics;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic record PipShowResult\n{\n    public required string Name { get; init; }\n\n    public required string Version { get; init; }\n\n    public string? Summary { get; init; }\n\n    public string? HomePage { get; init; }\n\n    public string? Author { get; init; }\n\n    public string? AuthorEmail { get; init; }\n\n    public string? License { get; init; }\n\n    public string? Location { get; init; }\n\n    public List<string>? Requires { get; init; }\n\n    public List<string>? RequiredBy { get; init; }\n\n    public static PipShowResult Parse(string output)\n    {\n        // Decode each line by splitting on first \":\" to key and value\n        var lines = output\n            .SplitLines(StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)\n            // Filter warning lines\n            .Where(line => !line.StartsWith(\"WARNING\", StringComparison.OrdinalIgnoreCase))\n            .ToList();\n\n        var indexOfLicense = GetIndexBySubstring(lines, \"License:\");\n        var indexOfLocation = GetIndexBySubstring(lines, \"Location:\");\n\n        var licenseText =\n            indexOfLicense == -1 ? null : string.Join('\\n', lines[indexOfLicense..indexOfLocation]);\n\n        if (indexOfLicense != -1)\n        {\n            lines.RemoveRange(indexOfLicense, indexOfLocation - indexOfLicense);\n        }\n\n        var linesDict = new Dictionary<string, string>();\n        foreach (var line in lines)\n        {\n            var split = line.Split(':', 2);\n            if (split.Length != 2)\n                continue;\n\n            var key = split[0].Trim();\n            var value = split[1].Trim();\n\n            if (key == \"Name\" && linesDict.ContainsKey(\"Name\"))\n            {\n                // We've hit a new package, so stop parsing\n                break;\n            }\n\n            linesDict.TryAdd(key, value);\n        }\n\n        if (!linesDict.TryGetValue(\"Name\", out var name))\n        {\n            throw new FormatException(\"The 'Name' key was not found in the pip show output.\");\n        }\n\n        if (!linesDict.TryGetValue(\"Version\", out var version))\n        {\n            throw new FormatException(\"The 'Version' key was not found in the pip show output.\");\n        }\n\n        return new PipShowResult\n        {\n            Name = name,\n            Version = version,\n            Summary = linesDict.GetValueOrDefault(\"Summary\"),\n            HomePage = linesDict.GetValueOrDefault(\"Home-page\"),\n            Author = linesDict.GetValueOrDefault(\"Author\"),\n            AuthorEmail = linesDict.GetValueOrDefault(\"Author-email\"),\n            License = licenseText,\n            Location = linesDict.GetValueOrDefault(\"Location\"),\n            Requires = linesDict\n                .GetValueOrDefault(\"Requires\")\n                ?.Split(',', StringSplitOptions.TrimEntries)\n                .ToList(),\n            RequiredBy = linesDict\n                .GetValueOrDefault(\"Required-by\")\n                ?.Split(',', StringSplitOptions.TrimEntries)\n                .ToList(),\n        };\n    }\n\n    private static int GetIndexBySubstring(List<string> lines, string searchString)\n    {\n        var index = -1;\n        for (var i = 0; i < lines.Count; i++)\n        {\n            if (!lines[i].StartsWith(searchString, StringComparison.OrdinalIgnoreCase))\n                continue;\n\n            index = i;\n            break;\n        }\n\n        return index;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PyBaseInstall.cs",
    "content": "﻿using NLog;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Core.Python;\n\n/// <summary>\n/// Represents a base Python installation that can be used by PyVenvRunner\n/// </summary>\npublic class PyBaseInstall(PyInstallation installation)\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    /// <summary>\n    /// Gets a PyBaseInstall instance for the default Python installation.\n    /// This uses the default Python 3.10.16 installation.\n    /// </summary>\n    public static PyBaseInstall Default => new(new PyInstallation(PyInstallationManager.DefaultVersion));\n\n    /// <summary>\n    /// The Python installation\n    /// </summary>\n    public PyInstallation Installation { get; } = installation;\n\n    /// <summary>\n    /// Root path of the Python installation\n    /// </summary>\n    public string RootPath => Installation.InstallPath;\n\n    /// <summary>\n    /// Python executable path\n    /// </summary>\n    public string PythonExePath => Installation.PythonExePath;\n\n    /// <summary>\n    /// Pip executable path\n    /// </summary>\n    public string PipExePath => Installation.PipExePath;\n\n    /// <summary>\n    /// Version of the Python installation\n    /// </summary>\n    public PyVersion Version => Installation.Version;\n\n    public bool UsesUv => Installation.UsesUv;\n\n    /// <summary>\n    /// Create a virtual environment with this Python installation as the base and\n    /// configure it with the specified parameters.\n    /// </summary>\n    /// <param name=\"venvPath\">Path where the virtual environment will be created</param>\n    /// <param name=\"workingDirectory\">Optional working directory for the Python process</param>\n    /// <param name=\"environmentVariables\">Optional environment variables for the Python process</param>\n    /// <param name=\"withDefaultTclTkEnv\">Whether to set up the default Tkinter environment variables (Windows)</param>\n    /// <param name=\"withQueriedTclTkEnv\">Whether to query and set up Tkinter environment variables (Unix)</param>\n    /// <returns>A configured PyVenvRunner instance</returns>\n    public IPyVenvRunner CreateVenvRunner(\n        DirectoryPath venvPath,\n        DirectoryPath? workingDirectory = null,\n        IReadOnlyDictionary<string, string>? environmentVariables = null,\n        bool withDefaultTclTkEnv = false,\n        bool withQueriedTclTkEnv = false\n    )\n    {\n        IPyVenvRunner venvRunner = new UvVenvRunner(this, venvPath);\n\n        // Set working directory if provided\n        if (workingDirectory != null)\n        {\n            venvRunner.WorkingDirectory = workingDirectory;\n        }\n\n        // Set environment variables if provided\n        if (environmentVariables != null)\n        {\n            var envVarDict = venvRunner.EnvironmentVariables;\n            foreach (var (key, value) in environmentVariables)\n            {\n                envVarDict = envVarDict.SetItem(key, value);\n            }\n\n            if (\n                Version == PyInstallationManager.Python_3_10_11\n                && !envVarDict.ContainsKey(\"SETUPTOOLS_USE_DISTUTILS\")\n            )\n            {\n                // Fixes potential setuptools error on Portable Windows Python\n                envVarDict = envVarDict.SetItem(\"SETUPTOOLS_USE_DISTUTILS\", \"stdlib\");\n            }\n\n            venvRunner.EnvironmentVariables = envVarDict;\n        }\n\n        // Configure Tkinter environment variables if requested\n        if (withDefaultTclTkEnv && Compat.IsWindows)\n        {\n            // Set up default TCL/TK environment variables for Windows\n            var envVarDict = venvRunner.EnvironmentVariables;\n            envVarDict = envVarDict.SetItem(\"TCL_LIBRARY\", Path.Combine(RootPath, \"tcl\", \"tcl8.6\"));\n            envVarDict = envVarDict.SetItem(\"TK_LIBRARY\", Path.Combine(RootPath, \"tcl\", \"tk8.6\"));\n            venvRunner.EnvironmentVariables = envVarDict;\n        }\n        else if (withQueriedTclTkEnv && Compat.IsUnix)\n        {\n            // For Unix, we might need to query the system for TCL/TK locations\n            try\n            {\n                // Implementation would depend on how your system detects TCL/TK on Unix\n                Logger.Debug(\"Setting up TCL/TK environment for Unix\");\n                // This would be implemented based on your system's requirements\n            }\n            catch (Exception ex)\n            {\n                Logger.Warn(ex, \"Failed to set up TCL/TK environment for Unix\");\n            }\n        }\n\n        return venvRunner;\n    }\n\n    /// <summary>\n    /// Asynchronously create a virtual environment with this Python installation as the base and\n    /// configure it with the specified parameters.\n    /// </summary>\n    /// <param name=\"venvPath\">Path where the virtual environment will be created</param>\n    /// <param name=\"workingDirectory\">Optional working directory for the Python process</param>\n    /// <param name=\"environmentVariables\">Optional environment variables for the Python process</param>\n    /// <param name=\"withDefaultTclTkEnv\">Whether to set up the default Tkinter environment variables (Windows)</param>\n    /// <param name=\"withQueriedTclTkEnv\">Whether to query and set up Tkinter environment variables (Unix)</param>\n    /// <returns>A configured PyVenvRunner instance</returns>\n    public async Task<IPyVenvRunner> CreateVenvRunnerAsync(\n        string venvPath,\n        string? workingDirectory = null,\n        IReadOnlyDictionary<string, string>? environmentVariables = null,\n        bool withDefaultTclTkEnv = false,\n        bool withQueriedTclTkEnv = false\n    )\n    {\n        var dirPath = new DirectoryPath(venvPath);\n        var workingDir = workingDirectory != null ? new DirectoryPath(workingDirectory) : null;\n\n        // Use the synchronous version and just return with a completed task\n        var venvRunner = CreateVenvRunner(\n            dirPath,\n            workingDir,\n            environmentVariables,\n            withDefaultTclTkEnv,\n            withQueriedTclTkEnv\n        );\n\n        return venvRunner;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PyInstallation.cs",
    "content": "using StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Core.Python;\n\n/// <summary>\n/// Represents a specific Python installation\n/// </summary>\npublic class PyInstallation\n{\n    /// <summary>\n    /// The version of this Python installation\n    /// </summary>\n    public PyVersion Version { get; }\n\n    /// <summary>\n    /// The root directory of this Python installation.\n    /// This is the primary source of truth for the installation's location.\n    /// </summary>\n    public DirectoryPath RootDir { get; }\n\n    /// <summary>\n    /// Path to the Python installation directory.\n    /// Derived from RootDir.\n    /// </summary>\n    public string InstallPath => RootDir.FullPath;\n\n    /// <summary>\n    /// The name of the Python directory (e.g., \"Python310\", \"Python31011\")\n    /// This is more of a convention for legacy paths or naming.\n    /// If RootDir is arbitrary (e.g., from UV default), this might just be RootDir.Name.\n    /// </summary>\n    public string DirectoryName\n    {\n        get\n        {\n            // If the RootDir seems to follow our old convention, use the old logic.\n            // Otherwise, just use the directory name from RootDir.\n            var expectedLegacyDirName = GetDirectoryNameForVersion(Version);\n            if (Version == PyInstallationManager.Python_3_10_11) // Special case from original\n            {\n                expectedLegacyDirName = \"Python310\";\n            }\n\n            if (\n                RootDir.Name.Equals(expectedLegacyDirName, StringComparison.OrdinalIgnoreCase)\n                || (\n                    Version == PyInstallationManager.Python_3_10_11\n                    && RootDir.Name.Equals(\"Python310\", StringComparison.OrdinalIgnoreCase)\n                )\n            )\n            {\n                return RootDir.Name; // It matches a known pattern or is the direct name\n            }\n            // If InstallPath was calculated by the old logic, RootDir.Name would be the DirectoryName.\n            // If InstallPath was provided directly (e.g. UV default path), then RootDir.Name is just the last segment of that path.\n            return RootDir.Name;\n        }\n    }\n\n    /// <summary>\n    /// Path to the Python linked library relative from the Python directory\n    /// </summary>\n    public string RelativePythonDllPath =>\n        Compat.Switch(\n            (PlatformKind.Windows, $\"python{Version.Major}{Version.Minor}.dll\"),\n            (PlatformKind.Linux, Path.Combine(\"lib\", $\"libpython{Version.Major}.{Version.Minor}.so\")),\n            (PlatformKind.MacOS, Path.Combine(\"lib\", $\"libpython{Version.Major}.{Version.Minor}.dylib\"))\n        );\n\n    /// <summary>\n    /// Full path to the Python linked library\n    /// </summary>\n    public string PythonDllPath => Path.Combine(InstallPath, RelativePythonDllPath);\n\n    /// <summary>\n    /// Path to the Python executable\n    /// </summary>\n    public string PythonExePath =>\n        Compat.Switch(\n            (PlatformKind.Windows, Path.Combine(InstallPath, \"python.exe\")),\n            (PlatformKind.Linux, Path.Combine(InstallPath, \"bin\", \"python3\")), // Could also be 'python' if uv installs it that way or it's a system python\n            (PlatformKind.MacOS, Path.Combine(InstallPath, \"bin\", \"python3\")) // Same as Linux\n        );\n\n    /// <summary>\n    /// Path to the pip executable\n    /// </summary>\n    public string PipExePath =>\n        Compat.Switch(\n            (PlatformKind.Windows, Path.Combine(InstallPath, \"Scripts\", \"pip.exe\")),\n            (PlatformKind.Linux, Path.Combine(InstallPath, \"bin\", \"pip3\")),\n            (PlatformKind.MacOS, Path.Combine(InstallPath, \"bin\", \"pip3\"))\n        );\n\n    // These might become less relevant if UV handles venv creation and pip directly for venvs\n    // but the base Python installation will still have them.\n    /// <summary>\n    /// Path to the get-pip script (less relevant with UV)\n    /// </summary>\n    public string GetPipPath => Path.Combine(InstallPath, \"get-pip.pyc\"); // This path is specific, might not exist in UV installs\n\n    /// <summary>\n    /// Path to the virtualenv executable (less relevant with UV)\n    /// </summary>\n    public string VenvPath => Path.Combine(InstallPath, \"Scripts\", \"virtualenv\" + Compat.ExeExtension);\n\n    /// <summary>\n    /// Check if pip is installed in this base Python.\n    /// </summary>\n    public bool PipInstalled => File.Exists(PipExePath);\n\n    /// <summary>\n    /// Check if virtualenv is installed (less relevant with UV).\n    /// </summary>\n    public bool VenvInstalled => File.Exists(VenvPath);\n\n    public bool UsesUv => Version != PyInstallationManager.Python_3_10_11;\n\n    /// <summary>\n    /// Primary constructor for when the installation path is known.\n    /// This should be used by PyInstallationManager when it discovers an installation (legacy or UV-managed).\n    /// </summary>\n    /// <param name=\"version\">The Python version.</param>\n    /// <param name=\"installPath\">The full path to the root of the Python installation.</param>\n    public PyInstallation(PyVersion version, string installPath)\n    {\n        Version = version;\n        RootDir = new DirectoryPath(installPath); // Set RootDir directly\n\n        // Basic validation: ensure the path is not empty. More checks could be added.\n        if (string.IsNullOrWhiteSpace(installPath))\n        {\n            throw new ArgumentException(\"Installation path cannot be null or empty.\", nameof(installPath));\n        }\n    }\n\n    /// <summary>\n    /// Constructor for legacy/default Python installations where the path is derived.\n    /// This calculates InstallPath based on GlobalConfig and version.\n    /// </summary>\n    /// <param name=\"version\">The Python version.</param>\n    public PyInstallation(PyVersion version)\n        : this(version, CalculateDefaultInstallPath(version)) // Delegate to the primary constructor\n    { }\n\n    /// <summary>\n    /// Constructor for legacy/default Python installations with explicit major, minor, micro.\n    /// </summary>\n    public PyInstallation(int major, int minor, int micro = 0)\n        : this(new PyVersion(major, minor, micro)) { }\n\n    /// <summary>\n    /// Calculates the default installation path based on the version.\n    /// Used by the legacy constructor.\n    /// </summary>\n    private static string CalculateDefaultInstallPath(PyVersion version)\n    {\n        return Path.Combine(GlobalConfig.LibraryDir, \"Assets\", GetDirectoryNameForVersion(version));\n    }\n\n    /// <summary>\n    /// Gets the conventional directory name for a given Python version.\n    /// This is mainly for deriving legacy paths or for when UV is instructed\n    /// to install into a directory with this naming scheme.\n    /// </summary>\n    /// <param name=\"version\">The Python version.</param>\n    /// <param name=\"precision\">How precise the directory name should be (Major.Minor or Major.Minor.Patch).</param>\n    /// <returns>The directory name string.</returns>\n    public static string GetDirectoryNameForVersion(\n        PyVersion version,\n        VersionEqualityPrecision precision = VersionEqualityPrecision.MajorMinorPatch\n    )\n    {\n        // Handle the special case for 3.10.11 which was previously just \"Python310\"\n        if (version is { Major: 3, Minor: 10, Micro: 11 } && precision != VersionEqualityPrecision.MajorMinor)\n        {\n            // If we're checking against the specific 3.10.11 from PyInstallationManager, and precision allows for micro\n            if (version == PyInstallationManager.Python_3_10_11)\n                return \"Python310\";\n        }\n\n        return precision switch\n        {\n            VersionEqualityPrecision.MajorMinor => $\"Python{version.Major}{version.Minor}\",\n            _ => $\"Python{version.Major}{version.Minor}{version.Micro}\",\n        };\n    }\n\n    public enum VersionEqualityPrecision\n    {\n        MajorMinor,\n        MajorMinorPatch,\n    }\n\n    /// <summary>\n    /// Check if this Python installation appears to be valid by checking for essential files.\n    /// (e.g., Python DLL or executable).\n    /// </summary>\n    public bool Exists()\n    {\n        if (!Directory.Exists(InstallPath))\n            return false;\n\n        // A more robust check might be needed. PythonExePath and PythonDllPath depend on OS.\n        // For now, let's check for the DLL on Windows and Exe on others as a primary indicator.\n        // Or just check PythonExePath as it should always exist.\n        return File.Exists(PythonExePath) || File.Exists(PythonDllPath);\n    }\n\n    /// <summary>\n    /// Creates a unique identifier for this Python installation\n    /// </summary>\n    public override string ToString() => $\"Python {Version} (at {InstallPath})\";\n\n    public override bool Equals(object? obj)\n    {\n        if (obj is PyInstallation other)\n        {\n            // Consider installations equal if version and path are the same.\n            return Version.Equals(other.Version)\n                && StringComparer.OrdinalIgnoreCase.Equals(InstallPath, other.InstallPath);\n        }\n        return false;\n    }\n\n    public override int GetHashCode()\n    {\n        return HashCode.Combine(Version, InstallPath.ToLowerInvariant());\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PyInstallationManager.cs",
    "content": "using Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Python;\n\n/// <summary>\n/// Manages multiple Python installations, potentially leveraging UV.\n/// </summary>\n[RegisterSingleton<IPyInstallationManager, PyInstallationManager>]\npublic class PyInstallationManager(IUvManager uvManager, ISettingsManager settingsManager)\n    : IPyInstallationManager\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    // Default Python versions - these are TARGET versions SM knows about\n    public static readonly PyVersion Python_3_10_11 = new(3, 10, 11);\n    public static readonly PyVersion Python_3_10_17 = new(3, 10, 17);\n    public static readonly PyVersion Python_3_11_13 = new(3, 11, 13);\n    public static readonly PyVersion Python_3_12_10 = new(3, 12, 10);\n    public static readonly PyVersion Python_3_13_12 = new(3, 13, 12);\n\n    /// <summary>\n    /// List of preferred/target Python versions StabilityMatrix officially supports.\n    /// UV can be used to fetch these if not present.\n    /// </summary>\n    public static readonly IReadOnlyList<PyVersion> OldVersions = new List<PyVersion>\n    {\n        Python_3_10_11,\n    }.AsReadOnly();\n\n    /// <summary>\n    /// The default Python version to use if none is specified.\n    /// </summary>\n    public static readonly PyVersion DefaultVersion = Python_3_10_11;\n\n    /// <summary>\n    /// Gets all discoverable Python installations (legacy and UV-managed).\n    /// This is now an async method.\n    /// </summary>\n    public async Task<IEnumerable<PyInstallation>> GetAllInstallationsAsync()\n    {\n        var allInstallations = new List<PyInstallation>();\n        var discoveredInstallPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase); // To avoid duplicates by path\n\n        // 1. Legacy/Bundled Installations (based on TargetVersions and expected paths)\n        Logger.Debug(\"Discovering legacy/bundled Python installations...\");\n        foreach (var version in OldVersions)\n        {\n            // The PyInstallation constructor (PyVersion version) now calculates the default path.\n            var legacyPyInstall = new PyInstallation(version);\n            if (legacyPyInstall.Exists() && discoveredInstallPaths.Add(legacyPyInstall.InstallPath))\n            {\n                allInstallations.Add(legacyPyInstall);\n                Logger.Debug($\"Found legacy Python: {legacyPyInstall}\");\n            }\n        }\n\n        // 2. UV-Managed Installations\n        if (await uvManager.IsUvAvailableAsync().ConfigureAwait(false))\n        {\n            Logger.Debug(\"Discovering UV-managed Python installations...\");\n            try\n            {\n                var uvPythons = await uvManager\n                    .ListAvailablePythonsAsync(installedOnly: true)\n                    .ConfigureAwait(false);\n                foreach (var uvPythonInfo in uvPythons)\n                {\n                    if (string.IsNullOrWhiteSpace(uvPythonInfo.InstallPath))\n                        continue;\n\n                    if (discoveredInstallPaths.Add(uvPythonInfo.InstallPath)) // Check if we haven't already added this path (e.g., UV installed to a legacy spot)\n                    {\n                        var uvPyInstall = new PyInstallation(uvPythonInfo.Version, uvPythonInfo.InstallPath);\n                        if (uvPyInstall.Exists()) // Double check, UV said it's installed\n                        {\n                            allInstallations.Add(uvPyInstall);\n                            Logger.Debug($\"Found UV-managed Python: {uvPyInstall}\");\n                        }\n                        else\n                        {\n                            Logger.Warn(\n                                $\"UV listed Python at {uvPythonInfo.InstallPath} as installed, but PyInstallation.Exists() check failed.\"\n                            );\n                        }\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                Logger.Error(ex, \"Failed to list UV-managed Python installations.\");\n            }\n        }\n        else\n        {\n            Logger.Debug(\"UV management of base Pythons is enabled, but UV is not available/detected.\");\n        }\n\n        // Return distinct by version, prioritizing (if necessary, though path check helps)\n        // For now, just distinct by the PyInstallation object itself (which considers version and path)\n        return allInstallations.Distinct().OrderBy(p => p.Version).ToList();\n    }\n\n    public async Task<IReadOnlyList<UvPythonInfo>> GetAllAvailablePythonsAsync()\n    {\n        var allPythons = await uvManager.ListAvailablePythonsAsync().ConfigureAwait(false);\n        Func<UvPythonInfo, bool> isSupportedVersion = settingsManager.Settings.ShowAllAvailablePythonVersions\n            ? p => p is { Source: \"cpython\", Version.Minor: >= 10 }\n            : p => p is { Source: \"cpython\", Version.Minor: >= 10 and <= 13, Variant: not \"freethreaded\" };\n\n        var filteredPythons = allPythons\n            .Where(isSupportedVersion)\n            .GroupBy(p => p.Key)\n            .Select(g => g.OrderByDescending(p => p.IsInstalled).First())\n            .OrderBy(p => p.Version)\n            .ToList();\n        var legacyPythonPath = Path.Combine(settingsManager.LibraryDir, \"Assets\", \"Python310\");\n\n        if (\n            filteredPythons.Any(x => x.Version == Python_3_10_11 && x.InstallPath == legacyPythonPath)\n            is false\n        )\n        {\n            var legacyPythonKey =\n                Compat.IsWindows ? \"python-3.10.11-embed-amd64\"\n                : Compat.IsMacOS ? \"cpython-3.10.11-macos-arm64\"\n                : \"cpython-3.10.11-x86_64-unknown-linux-gnu\";\n\n            filteredPythons.Insert(\n                0,\n                new UvPythonInfo(\n                    Python_3_10_11,\n                    legacyPythonPath,\n                    true,\n                    \"cpython\",\n                    null,\n                    null,\n                    legacyPythonKey,\n                    null,\n                    null\n                )\n            );\n        }\n\n        return filteredPythons;\n    }\n\n    /// <summary>\n    /// Gets an installation for a specific version.\n    /// If not found, and UV is configured, it may attempt to install it using UV.\n    /// This is now an async method.\n    /// </summary>\n    public async Task<PyInstallation> GetInstallationAsync(PyVersion version)\n    {\n        // 1. Try to find an already existing installation (legacy or UV-managed)\n        var existingInstallations = await GetAllInstallationsAsync().ConfigureAwait(false);\n\n        // Try exact match first\n        var exactMatch = existingInstallations.FirstOrDefault(p => p.Version == version);\n        if (exactMatch != null)\n        {\n            Logger.Debug($\"Found existing exact match for Python {version}: {exactMatch.InstallPath}\");\n            return exactMatch;\n        }\n\n        // 2. If not found, and UV is allowed to install missing base Pythons, try to install it with UV\n        if (await uvManager.IsUvAvailableAsync().ConfigureAwait(false))\n        {\n            Logger.Info($\"Python {version} not found. Attempting to install with UV.\");\n            try\n            {\n                var installedUvPython = await uvManager\n                    .InstallPythonVersionAsync(version)\n                    .ConfigureAwait(false);\n                if (\n                    installedUvPython.HasValue\n                    && !string.IsNullOrWhiteSpace(installedUvPython.Value.InstallPath)\n                )\n                {\n                    var newPyInstall = new PyInstallation(\n                        installedUvPython.Value.Version,\n                        installedUvPython.Value.InstallPath\n                    );\n                    if (newPyInstall.Exists())\n                    {\n                        Logger.Info(\n                            $\"Successfully installed Python {installedUvPython.Value.Version} with UV at {newPyInstall.InstallPath}\"\n                        );\n                        return newPyInstall;\n                    }\n\n                    Logger.Error(\n                        $\"UV reported successful install of Python {installedUvPython.Value.Version} at {newPyInstall.InstallPath}, but PyInstallation.Exists() check failed.\"\n                    );\n                }\n                else\n                {\n                    Logger.Warn(\n                        $\"UV failed to install Python {version}. Result from UV manager was null or had no path.\"\n                    );\n                }\n            }\n            catch (Exception ex)\n            {\n                Logger.Error(ex, $\"Error attempting to install Python {version} with UV.\");\n            }\n        }\n\n        // 3. Fallback: Return a PyInstallation object representing the *expected* legacy path.\n        //    The caller can then check .Exists() on it.\n        //    This maintains compatibility with code that might expect a PyInstallation object even if the files aren't there.\n        Logger.Warn(\n            $\"Python {version} not found and UV installation was not attempted or failed. Returning prospective legacy PyInstallation object.\"\n        );\n        return new PyInstallation(version); // This constructor uses the default/legacy path.\n    }\n\n    public async Task<PyInstallation> GetDefaultInstallationAsync()\n    {\n        return await GetInstallationAsync(DefaultVersion).ConfigureAwait(false);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PyRunner.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing Injectio.Attributes;\nusing NLog;\nusing Python.Runtime;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python.Interop;\n\nnamespace StabilityMatrix.Core.Python;\n\n[SuppressMessage(\"ReSharper\", \"NotAccessedPositionalProperty.Global\")]\npublic record struct PyVersionInfo(int Major, int Minor, int Micro, string ReleaseLevel, int Serial);\n\n[SuppressMessage(\"ReSharper\", \"MemberCanBePrivate.Global\")]\n[RegisterSingleton<IPyRunner, PyRunner>]\npublic class PyRunner : IPyRunner\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    // Set by ISettingsManager.TryFindLibrary()\n    public static DirectoryPath HomeDir { get; set; } = string.Empty;\n\n    // The installation manager for handling different Python versions\n    private readonly IPyInstallationManager installationManager;\n\n    // The current Python installation being used\n    private PyInstallation? currentInstallation;\n\n    /// <summary>\n    /// Get the Python directory name for the given version, or the default version if none specified\n    /// </summary>\n    public string GetPythonDirName(PyVersion? version = null) =>\n        version != null\n            ? $\"Python{version.Value.Major}{version.Value.Minor}{version.Value.Micro}\"\n            : \"Python310\"; // Default to 3.10.11 for compatibility\n\n    /// <summary>\n    /// Get the Python directory for the given version, or the default version if none specified\n    /// </summary>\n    public string GetPythonDir(PyVersion? version = null) =>\n        Path.Combine(GlobalConfig.LibraryDir, \"Assets\", GetPythonDirName(version));\n\n    /// <summary>\n    /// Get the Python DLL path for the given version, or the default version if none specified\n    /// </summary>\n    public string GetPythonDllPath(PyVersion? version = null)\n    {\n        var pythonDir = GetPythonDir(version);\n        var relativePath =\n            version != null\n                ? Compat.Switch(\n                    (PlatformKind.Windows, $\"python{version.Value.Major}{version.Value.Minor}.dll\"),\n                    (\n                        PlatformKind.Linux,\n                        Path.Combine(\"lib\", $\"libpython{version.Value.Major}.{version.Value.Minor}.so\")\n                    ),\n                    (\n                        PlatformKind.MacOS,\n                        Path.Combine(\"lib\", $\"libpython{version.Value.Major}.{version.Value.Minor}.dylib\")\n                    )\n                )\n                : RelativePythonDllPath;\n\n        return Path.Combine(pythonDir, relativePath);\n    }\n\n    /// <summary>\n    /// Get the Python executable path for the given version, or the default version if none specified\n    /// </summary>\n    public string GetPythonExePath(PyVersion? version = null)\n    {\n        var pythonDir = GetPythonDir(version);\n        return Compat.Switch(\n            (PlatformKind.Windows, Path.Combine(pythonDir, \"python.exe\")),\n            (PlatformKind.Linux, Path.Combine(pythonDir, \"bin\", \"python3\")),\n            (PlatformKind.MacOS, Path.Combine(pythonDir, \"bin\", \"python3\"))\n        );\n    }\n\n    /// <summary>\n    /// Get the pip executable path for the given version, or the default version if none specified\n    /// </summary>\n    public string GetPipExePath(PyVersion? version = null)\n    {\n        var pythonDir = GetPythonDir(version);\n        return Compat.Switch(\n            (PlatformKind.Windows, Path.Combine(pythonDir, \"Scripts\", \"pip.exe\")),\n            (PlatformKind.Linux, Path.Combine(pythonDir, \"bin\", \"pip3\")),\n            (PlatformKind.MacOS, Path.Combine(pythonDir, \"bin\", \"pip3\"))\n        );\n    }\n\n    // Legacy properties for compatibility - these use the default Python version\n    public const string PythonDirName = \"Python310\";\n    public static string PythonDir => Path.Combine(GlobalConfig.LibraryDir, \"Assets\", PythonDirName);\n\n    /// <summary>\n    /// Path to the Python Linked library relative from the Python directory.\n    /// </summary>\n    public static string RelativePythonDllPath =>\n        Compat.Switch(\n            (PlatformKind.Windows, \"python310.dll\"),\n            (PlatformKind.Linux, Path.Combine(\"lib\", \"libpython3.10.so\")),\n            (PlatformKind.MacOS, Path.Combine(\"lib\", \"libpython3.10.dylib\"))\n        );\n\n    public static string PythonDllPath => Path.Combine(PythonDir, RelativePythonDllPath);\n    public static string PythonExePath =>\n        Compat.Switch(\n            (PlatformKind.Windows, Path.Combine(PythonDir, \"python.exe\")),\n            (PlatformKind.Linux, Path.Combine(PythonDir, \"bin\", \"python3\")),\n            (PlatformKind.MacOS, Path.Combine(PythonDir, \"bin\", \"python3\"))\n        );\n    public static string PipExePath =>\n        Compat.Switch(\n            (PlatformKind.Windows, Path.Combine(PythonDir, \"Scripts\", \"pip.exe\")),\n            (PlatformKind.Linux, Path.Combine(PythonDir, \"bin\", \"pip3\")),\n            (PlatformKind.MacOS, Path.Combine(PythonDir, \"bin\", \"pip3\"))\n        );\n\n    public static string GetPipPath => Path.Combine(PythonDir, \"get-pip.pyc\");\n\n    public static string VenvPath => Path.Combine(PythonDir, \"Scripts\", \"virtualenv\" + Compat.ExeExtension);\n\n    public static bool PipInstalled => File.Exists(PipExePath);\n    public static bool VenvInstalled => File.Exists(VenvPath);\n\n    private static readonly SemaphoreSlim PyRunning = new(1, 1);\n\n    public PyIOStream? StdOutStream { get; private set; }\n    public PyIOStream? StdErrStream { get; private set; }\n\n    public PyRunner(IPyInstallationManager installationManager)\n    {\n        this.installationManager = installationManager;\n    }\n\n    /// <summary>\n    /// Switch to a specific Python installation\n    /// </summary>\n    public async Task SwitchToInstallation(PyVersion version)\n    {\n        // If Python is already initialized with a different version, we need to shutdown first\n        if (PythonEngine.IsInitialized && currentInstallation?.Version != version)\n        {\n            // hacky stuff until Python.NET stops using BinaryFormatter\n            AppContext.SetSwitch(\n                \"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization\",\n                true\n            );\n            Logger.Info(\"Shutting down previous Python runtime for version switch\");\n            PythonEngine.Shutdown();\n            AppContext.SetSwitch(\n                \"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization\",\n                false\n            );\n        }\n\n        // If not initialized or we had to shutdown, initialize with the new version\n        if (!PythonEngine.IsInitialized)\n        {\n            // Get the installation for this version\n            var installation = await installationManager.GetInstallationAsync(version).ConfigureAwait(false);\n            if (!installation.Exists())\n            {\n                throw new FileNotFoundException(\n                    $\"Python {version} installation not found at {installation.InstallPath}\"\n                );\n            }\n\n            currentInstallation = installation;\n\n            // Initialize with this installation\n            await InitializeWithInstallation(installation).ConfigureAwait(false);\n        }\n    }\n\n    /// <summary>\n    /// Initialize Python runtime with a specific installation\n    /// </summary>\n    private async Task InitializeWithInstallation(PyInstallation installation)\n    {\n        if (PythonEngine.IsInitialized)\n            return;\n\n        Logger.Info(\"Setting PYTHONHOME={PythonDir}\", installation.InstallPath.ToRepr());\n\n        // Append Python path to PATH\n        var newEnvPath = Compat.GetEnvPathWithExtensions(installation.InstallPath);\n        Logger.Debug(\"Setting PATH={NewEnvPath}\", newEnvPath.ToRepr());\n        Environment.SetEnvironmentVariable(\"PATH\", newEnvPath, EnvironmentVariableTarget.Process);\n\n        Logger.Info(\"Initializing Python runtime with DLL: {DllPath}\", installation.PythonDllPath);\n        // Check PythonDLL exists\n        if (!File.Exists(installation.PythonDllPath))\n        {\n            throw new FileNotFoundException(\"Python linked library not found\", installation.PythonDllPath);\n        }\n\n        Runtime.PythonDLL = installation.PythonDllPath;\n        PythonEngine.PythonHome = installation.InstallPath;\n        PythonEngine.Initialize();\n        PythonEngine.BeginAllowThreads();\n\n        // Redirect stdout and stderr\n        StdOutStream = new PyIOStream();\n        StdErrStream = new PyIOStream();\n        await RunInThreadWithLock(() =>\n            {\n                var sys =\n                    Py.Import(\"sys\") as PyModule ?? throw new NullReferenceException(\"sys module not found\");\n                sys.Set(\"stdout\", StdOutStream);\n                sys.Set(\"stderr\", StdErrStream);\n            })\n            .ConfigureAwait(false);\n    }\n\n    /// <summary>$\n    /// Initializes the Python runtime using the embedded dll.\n    /// Can be called with no effect after initialization.\n    /// </summary>\n    /// <exception cref=\"FileNotFoundException\">Thrown if Python DLL not found.</exception>\n    public async Task Initialize()\n    {\n        if (PythonEngine.IsInitialized)\n            return;\n\n        // Get the default installation\n        var defaultInstallation = await installationManager\n            .GetDefaultInstallationAsync()\n            .ConfigureAwait(false);\n        if (!defaultInstallation.Exists())\n        {\n            throw new FileNotFoundException(\n                $\"Default Python installation not found at {defaultInstallation.InstallPath}\"\n            );\n        }\n\n        currentInstallation = defaultInstallation;\n        await InitializeWithInstallation(defaultInstallation).ConfigureAwait(false);\n    }\n\n    /// <summary>\n    /// One-time setup for get-pip\n    /// </summary>\n    public async Task SetupPip(PyVersion? version = null)\n    {\n        // Use either the specified version or the current installation\n        var installation =\n            version != null\n                ? await installationManager.GetInstallationAsync(version.Value).ConfigureAwait(false)\n                : currentInstallation\n                    ?? await installationManager.GetDefaultInstallationAsync().ConfigureAwait(false);\n\n        var getPipPath = Path.Combine(installation.InstallPath, \"get-pip.pyc\");\n\n        if (!File.Exists(getPipPath))\n        {\n            throw new FileNotFoundException(\"get-pip not found\", getPipPath);\n        }\n\n        await ProcessRunner\n            .GetProcessResultAsync(installation.PythonExePath, [\"-m\", \"get-pip\"])\n            .EnsureSuccessExitCode()\n            .ConfigureAwait(false);\n\n        // Pip version 24.1 deprecated numpy star requirement spec used by some packages\n        // So make the base pip less than that for compatibility, venvs can upgrade themselves if needed\n        await ProcessRunner\n            .GetProcessResultAsync(\n                installation.PythonExePath,\n                [\"-m\", \"pip\", \"install\", \"pip==23.3.2\", \"setuptools==69.5.1\"]\n            )\n            .EnsureSuccessExitCode()\n            .ConfigureAwait(false);\n    }\n\n    /// <summary>\n    /// Install a Python package with pip\n    /// </summary>\n    public async Task InstallPackage(string package, PyVersion? version = null)\n    {\n        // Use either the specified version or the current installation\n        var installation =\n            version != null\n                ? await installationManager.GetInstallationAsync(version.Value).ConfigureAwait(false)\n                : currentInstallation\n                    ?? await installationManager.GetDefaultInstallationAsync().ConfigureAwait(false);\n\n        if (!File.Exists(installation.PipExePath))\n        {\n            throw new FileNotFoundException(\"pip not found\", installation.PipExePath);\n        }\n        var result = await ProcessRunner\n            .GetProcessResultAsync(installation.PythonExePath, $\"-m pip install {package}\")\n            .ConfigureAwait(false);\n        result.EnsureSuccessExitCode();\n    }\n\n    /// <summary>\n    /// Run a Function with PyRunning lock as a Task with GIL.\n    /// </summary>\n    /// <param name=\"func\">Function to run.</param>\n    /// <param name=\"waitTimeout\">Time limit for waiting on PyRunning lock.</param>\n    /// <param name=\"cancelToken\">Cancellation token.</param>\n    /// <exception cref=\"OperationCanceledException\">cancelToken was canceled, or waitTimeout expired.</exception>\n    public async Task<T> RunInThreadWithLock<T>(\n        Func<T> func,\n        TimeSpan? waitTimeout = null,\n        CancellationToken cancelToken = default\n    )\n    {\n        // Wait to acquire PyRunning lock\n        await PyRunning.WaitAsync(cancelToken).ConfigureAwait(false);\n        try\n        {\n            return await Task.Run(\n                    () =>\n                    {\n                        using (Py.GIL())\n                        {\n                            return func();\n                        }\n                    },\n                    cancelToken\n                )\n                .ConfigureAwait(false);\n        }\n        finally\n        {\n            PyRunning.Release();\n        }\n    }\n\n    /// <summary>\n    /// Run an Action with PyRunning lock as a Task with GIL.\n    /// </summary>\n    /// <param name=\"action\">Action to run.</param>\n    /// <param name=\"waitTimeout\">Time limit for waiting on PyRunning lock.</param>\n    /// <param name=\"cancelToken\">Cancellation token.</param>\n    /// <exception cref=\"OperationCanceledException\">cancelToken was canceled, or waitTimeout expired.</exception>\n    public async Task RunInThreadWithLock(\n        Action action,\n        TimeSpan? waitTimeout = null,\n        CancellationToken cancelToken = default\n    )\n    {\n        // Wait to acquire PyRunning lock\n        await PyRunning.WaitAsync(cancelToken).ConfigureAwait(false);\n        try\n        {\n            await Task.Run(\n                    () =>\n                    {\n                        using (Py.GIL())\n                        {\n                            action();\n                        }\n                    },\n                    cancelToken\n                )\n                .ConfigureAwait(false);\n        }\n        finally\n        {\n            PyRunning.Release();\n        }\n    }\n\n    /// <summary>\n    /// Evaluate Python expression and return its value as a string\n    /// </summary>\n    /// <param name=\"expression\"></param>\n    public async Task<string> Eval(string expression)\n    {\n        return await Eval<string>(expression);\n    }\n\n    /// <summary>\n    /// Evaluate Python expression and return its value\n    /// </summary>\n    /// <param name=\"expression\"></param>\n    public Task<T> Eval<T>(string expression)\n    {\n        return RunInThreadWithLock(() =>\n        {\n            using var scope = Py.CreateScope();\n            var result = scope.Eval(expression);\n\n            // For string, cast with __str__()\n            if (typeof(T) == typeof(string))\n            {\n                return result.GetAttr(\"__str__\").Invoke().As<T>();\n            }\n            return result.As<T>();\n        });\n    }\n\n    /// <summary>\n    /// Execute Python code without returning a value\n    /// </summary>\n    /// <param name=\"code\"></param>\n    public Task Exec(string code)\n    {\n        return RunInThreadWithLock(() =>\n        {\n            using var scope = Py.CreateScope();\n            scope.Exec(code);\n        });\n    }\n\n    /// <summary>\n    /// Return the Python version as a PyVersionInfo struct\n    /// </summary>\n    public async Task<PyVersionInfo> GetVersionInfo()\n    {\n        var info = await Eval<PyObject[]>(\"tuple(__import__('sys').version_info)\");\n        return new PyVersionInfo(\n            info[0].As<int>(),\n            info[1].As<int>(),\n            info[2].As<int>(),\n            info[3].As<string>(),\n            info[4].As<int>()\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PyVenvRunner.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Text;\nusing System.Text.Json;\nusing NLog;\nusing Salaros.Configuration;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Python;\n\n/// <summary>\n/// Python runner using a subprocess, mainly for venv support.\n/// </summary>\npublic class PyVenvRunner : IDisposable, IAsyncDisposable, IPyVenvRunner\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private string? lastSetPyvenvCfgPath;\n\n    /// <summary>\n    /// Relative path to the site-packages folder from the venv root.\n    /// This is platform specific.\n    /// </summary>\n    public static string GetRelativeSitePackagesPath(PyVersion? version = null)\n    {\n        var minorVersion = version?.Minor ?? 10;\n        return Compat.Switch(\n            (PlatformKind.Windows, \"Lib/site-packages\"),\n            (PlatformKind.Unix, $\"lib/python3.{minorVersion}/site-packages\")\n        );\n    }\n\n    /// <summary>\n    /// Legacy path for compatibility\n    /// </summary>\n    public static string RelativeSitePackagesPath =>\n        Compat.Switch(\n            (PlatformKind.Windows, \"Lib/site-packages\"),\n            (PlatformKind.Unix, \"lib/python3.10/site-packages\")\n        );\n\n    public PyBaseInstall BaseInstall { get; }\n\n    /// <summary>\n    /// The process running the python executable.\n    /// </summary>\n    public AnsiProcess? Process { get; private set; }\n\n    /// <summary>\n    /// The path to the venv root directory.\n    /// </summary>\n    public DirectoryPath RootPath { get; }\n\n    /// <summary>\n    /// Optional working directory for the python process.\n    /// </summary>\n    public DirectoryPath? WorkingDirectory { get; set; }\n\n    /// <summary>\n    /// Optional environment variables for the python process.\n    /// </summary>\n    public ImmutableDictionary<string, string> EnvironmentVariables { get; set; } =\n        ImmutableDictionary<string, string>.Empty;\n\n    /// <summary>\n    /// Name of the python binary folder.\n    /// 'Scripts' on Windows, 'bin' on Unix.\n    /// </summary>\n    public static string RelativeBinPath =>\n        Compat.Switch((PlatformKind.Windows, \"Scripts\"), (PlatformKind.Unix, \"bin\"));\n\n    /// <summary>\n    /// The relative path to the python executable.\n    /// </summary>\n    public static string RelativePythonPath =>\n        Compat.Switch(\n            (PlatformKind.Windows, Path.Combine(\"Scripts\", \"python.exe\")),\n            (PlatformKind.Unix, Path.Combine(\"bin\", \"python3\"))\n        );\n\n    /// <summary>\n    /// The full path to the python executable.\n    /// </summary>\n    public FilePath PythonPath => RootPath.JoinFile(RelativePythonPath);\n\n    /// <summary>\n    /// The relative path to the pip executable.\n    /// </summary>\n    public static string RelativePipPath =>\n        Compat.Switch(\n            (PlatformKind.Windows, Path.Combine(\"Scripts\", \"pip.exe\")),\n            (PlatformKind.Unix, Path.Combine(\"bin\", \"pip3\"))\n        );\n\n    /// <summary>\n    /// The full path to the pip executable.\n    /// </summary>\n    public FilePath PipPath => RootPath.JoinFile(RelativePipPath);\n\n    /// <summary>\n    /// The Python version of this venv\n    /// </summary>\n    public PyVersion Version => BaseInstall.Version;\n\n    /// <summary>\n    /// List of substrings to suppress from the output.\n    /// When a line contains any of these substrings, it will not be forwarded to callbacks.\n    /// A corresponding Info log will be written instead.\n    /// </summary>\n    public List<string> SuppressOutput { get; } = new() { \"fatal: not a git repository\" };\n\n    internal PyVenvRunner(PyBaseInstall baseInstall, DirectoryPath rootPath)\n    {\n        BaseInstall = baseInstall;\n        RootPath = rootPath;\n        EnvironmentVariables = EnvironmentVariables.SetItem(\"VIRTUAL_ENV\", rootPath.FullPath);\n    }\n\n    public void UpdateEnvironmentVariables(\n        Func<ImmutableDictionary<string, string>, ImmutableDictionary<string, string>> env\n    )\n    {\n        EnvironmentVariables = env(EnvironmentVariables);\n    }\n\n    /// <returns>True if the venv has a Scripts\\python.exe file</returns>\n    public bool Exists() => PythonPath.Exists;\n\n    /// <summary>\n    /// Creates a venv at the configured path.\n    /// </summary>\n    public async Task Setup(\n        bool existsOk = false,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (!existsOk && Exists())\n        {\n            throw new InvalidOperationException(\"Venv already exists\");\n        }\n\n        // Create RootPath if it doesn't exist\n        RootPath.Create();\n\n        // Create venv (copy mode if windows)\n        var args = new string[] { \"-m\", \"virtualenv\", Compat.IsWindows ? \"--always-copy\" : \"\", RootPath };\n\n        var venvProc = ProcessRunner.StartAnsiProcess(\n            BaseInstall.PythonExePath,\n            args,\n            WorkingDirectory?.FullPath,\n            onConsoleOutput\n        );\n\n        try\n        {\n            await venvProc.WaitForExitAsync(cancellationToken).ConfigureAwait(false);\n\n            // Check return code\n            if (venvProc.ExitCode != 0)\n            {\n                throw new ProcessException($\"Venv creation failed with code {venvProc.ExitCode}\");\n            }\n        }\n        catch (OperationCanceledException)\n        {\n            venvProc.CancelStreamReaders();\n        }\n        finally\n        {\n            venvProc.Kill();\n            venvProc.Dispose();\n        }\n    }\n\n    /// <summary>\n    /// Set current python path to pyvenv.cfg\n    /// This should be called before using the venv, in case user moves the venv directory.\n    /// </summary>\n    private void SetPyvenvCfg(string pythonDirectory, bool force = false)\n    {\n        // Skip if we are not created yet\n        if (!Exists())\n            return;\n\n        // Skip if already set to same value\n        if (lastSetPyvenvCfgPath == pythonDirectory && !force)\n            return;\n\n        // Path to pyvenv.cfg\n        var cfgPath = Path.Combine(RootPath, \"pyvenv.cfg\");\n        if (!File.Exists(cfgPath))\n        {\n            throw new FileNotFoundException(\"pyvenv.cfg not found\", cfgPath);\n        }\n\n        Logger.Info(\"Updating pyvenv.cfg with embedded Python directory {PyDir}\", pythonDirectory);\n\n        // Insert a top section\n        var topSection = \"[top]\" + Environment.NewLine;\n        var cfg = new ConfigParser(topSection + File.ReadAllText(cfgPath));\n\n        // Need to set all path keys - home, base-prefix, base-exec-prefix, base-executable\n        cfg.SetValue(\"top\", \"home\", pythonDirectory);\n        cfg.SetValue(\"top\", \"base-prefix\", pythonDirectory);\n\n        cfg.SetValue(\"top\", \"base-exec-prefix\", pythonDirectory);\n\n        cfg.SetValue(\n            \"top\",\n            \"base-executable\",\n            Path.Combine(pythonDirectory, Compat.IsWindows ? \"python.exe\" : RelativePythonPath)\n        );\n\n        // Convert to string for writing, strip the top section\n        var cfgString = cfg.ToString()!.Replace(topSection, \"\");\n        File.WriteAllText(cfgPath, cfgString);\n\n        // Update last set path\n        lastSetPyvenvCfgPath = pythonDirectory;\n    }\n\n    /// <summary>\n    /// Run a pip install command. Waits for the process to exit.\n    /// workingDirectory defaults to RootPath.\n    /// </summary>\n    public async Task PipInstall(ProcessArgs args, Action<ProcessOutput>? outputDataReceived = null)\n    {\n        if (!File.Exists(PipPath))\n        {\n            throw new FileNotFoundException(\"pip not found\", PipPath);\n        }\n\n        // Record output for errors\n        var output = new StringBuilder();\n\n        var outputAction = new Action<ProcessOutput>(s =>\n        {\n            Logger.Debug($\"Pip output: {s.Text}\");\n            // Record to output\n            output.Append(s.Text);\n            // Forward to callback\n            outputDataReceived?.Invoke(s);\n        });\n\n        RunDetached(args.Prepend(\"-m pip install\").Concat(\"--exists-action s\"), outputAction);\n        await Process.WaitForExitAsync().ConfigureAwait(false);\n\n        // Check return code\n        if (Process.ExitCode != 0)\n        {\n            throw new ProcessException(\n                $\"pip install failed with code {Process.ExitCode}: {output.ToString().ToRepr()}\"\n            );\n        }\n    }\n\n    /// <summary>\n    /// Run a pip uninstall command. Waits for the process to exit.\n    /// workingDirectory defaults to RootPath.\n    /// </summary>\n    public async Task PipUninstall(ProcessArgs args, Action<ProcessOutput>? outputDataReceived = null)\n    {\n        if (!File.Exists(PipPath))\n        {\n            throw new FileNotFoundException(\"pip not found\", PipPath);\n        }\n\n        // Record output for errors\n        var output = new StringBuilder();\n\n        var outputAction = new Action<ProcessOutput>(s =>\n        {\n            Logger.Debug($\"Pip output: {s.Text}\");\n            // Record to output\n            output.Append(s.Text);\n            // Forward to callback\n            outputDataReceived?.Invoke(s);\n        });\n\n        RunDetached($\"-m pip uninstall -y {args}\", outputAction);\n        await Process.WaitForExitAsync().ConfigureAwait(false);\n\n        // Check return code\n        if (Process.ExitCode != 0)\n        {\n            throw new ProcessException(\n                $\"pip install failed with code {Process.ExitCode}: {output.ToString().ToRepr()}\"\n            );\n        }\n    }\n\n    /// <summary>\n    /// Run a pip list command, return results as PipPackageInfo objects.\n    /// </summary>\n    public async Task<IReadOnlyList<PipPackageInfo>> PipList()\n    {\n        if (!File.Exists(PipPath))\n        {\n            throw new FileNotFoundException(\"pip not found\", PipPath);\n        }\n\n        SetPyvenvCfg(BaseInstall.RootPath);\n\n        var result = await ProcessRunner\n            .GetProcessResultAsync(\n                PythonPath,\n                \"-m pip list --format=json\",\n                WorkingDirectory?.FullPath,\n                EnvironmentVariables\n            )\n            .ConfigureAwait(false);\n\n        // Check return code\n        if (result.ExitCode != 0)\n        {\n            throw new ProcessException(\n                $\"pip list failed with code {result.ExitCode}: {result.StandardOutput}, {result.StandardError}\"\n            );\n        }\n\n        // There may be warning lines before the Json line, or update messages after\n        // Filter to find the first line that starts with [\n        var jsonLine = result\n            .StandardOutput?.SplitLines(\n                StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries\n            )\n            .Select(line => line.Trim())\n            .FirstOrDefault(\n                line =>\n                    line.StartsWith(\"[\", StringComparison.OrdinalIgnoreCase)\n                    && line.EndsWith(\"]\", StringComparison.OrdinalIgnoreCase)\n            );\n\n        if (jsonLine is null)\n        {\n            return [];\n        }\n\n        return JsonSerializer.Deserialize<List<PipPackageInfo>>(\n                jsonLine,\n                PipPackageInfoSerializerContext.Default.Options\n            ) ?? [];\n    }\n\n    /// <summary>\n    /// Run a pip show command, return results as PipPackageInfo objects.\n    /// </summary>\n    public async Task<PipShowResult?> PipShow(string packageName)\n    {\n        if (!File.Exists(PipPath))\n        {\n            throw new FileNotFoundException(\"pip not found\", PipPath);\n        }\n\n        SetPyvenvCfg(BaseInstall.RootPath);\n\n        var result = await ProcessRunner\n            .GetProcessResultAsync(\n                PythonPath,\n                new[] { \"-m\", \"pip\", \"show\", packageName },\n                WorkingDirectory?.FullPath,\n                EnvironmentVariables\n            )\n            .ConfigureAwait(false);\n\n        // Check return code\n        if (result.ExitCode != 0)\n        {\n            throw new ProcessException(\n                $\"pip show failed with code {result.ExitCode}: {result.StandardOutput}, {result.StandardError}\"\n            );\n        }\n\n        if (result.StandardOutput!.StartsWith(\"WARNING: Package(s) not found:\"))\n        {\n            return null;\n        }\n\n        return PipShowResult.Parse(result.StandardOutput);\n    }\n\n    /// <summary>\n    /// Run a pip index command, return result as PipIndexResult.\n    /// </summary>\n    public async Task<PipIndexResult?> PipIndex(string packageName, string? indexUrl = null)\n    {\n        if (!File.Exists(PipPath))\n        {\n            throw new FileNotFoundException(\"pip not found\", PipPath);\n        }\n\n        SetPyvenvCfg(BaseInstall.RootPath);\n\n        var args = new ProcessArgsBuilder(\n            \"-m\",\n            \"pip\",\n            \"index\",\n            \"versions\",\n            packageName,\n            \"--no-color\",\n            \"--disable-pip-version-check\"\n        );\n\n        if (indexUrl is not null)\n        {\n            args = args.AddKeyedArgs(\"--index-url\", [\"--index-url\", indexUrl]);\n        }\n\n        var result = await ProcessRunner\n            .GetProcessResultAsync(PythonPath, args, WorkingDirectory?.FullPath, EnvironmentVariables)\n            .ConfigureAwait(false);\n\n        // Check return code\n        if (result.ExitCode != 0)\n        {\n            throw new ProcessException(\n                $\"pip index failed with code {result.ExitCode}: {result.StandardOutput}, {result.StandardError}\"\n            );\n        }\n\n        if (\n            string.IsNullOrEmpty(result.StandardOutput)\n            || result\n                .StandardOutput!.SplitLines()\n                .Any(l => l.StartsWith(\"ERROR: No matching distribution found\"))\n        )\n        {\n            return null;\n        }\n\n        return PipIndexResult.Parse(result.StandardOutput);\n    }\n\n    /// <summary>\n    /// Run a custom install command. Waits for the process to exit.\n    /// workingDirectory defaults to RootPath.\n    /// </summary>\n    public async Task CustomInstall(ProcessArgs args, Action<ProcessOutput>? outputDataReceived = null)\n    {\n        // Record output for errors\n        var output = new StringBuilder();\n\n        var outputAction =\n            outputDataReceived == null\n                ? null\n                : new Action<ProcessOutput>(s =>\n                {\n                    Logger.Debug($\"Install output: {s.Text}\");\n                    // Record to output\n                    output.Append(s.Text);\n                    // Forward to callback\n                    outputDataReceived(s);\n                });\n\n        RunDetached(args, outputAction);\n        await Process.WaitForExitAsync().ConfigureAwait(false);\n\n        // Check return code\n        if (Process.ExitCode != 0)\n        {\n            throw new ProcessException(\n                $\"install script failed with code {Process.ExitCode}: {output.ToString().ToRepr()}\"\n            );\n        }\n    }\n\n    /// <summary>\n    /// Run a command using the venv Python executable and return the result.\n    /// </summary>\n    /// <param name=\"arguments\">Arguments to pass to the Python executable.</param>\n    public async Task<ProcessResult> Run(ProcessArgs arguments)\n    {\n        // Record output for errors\n        var output = new StringBuilder();\n\n        var outputAction = new Action<string?>(s =>\n        {\n            if (s == null)\n                return;\n            Logger.Debug(\"Pip output: {Text}\", s);\n            output.Append(s);\n        });\n\n        SetPyvenvCfg(BaseInstall.RootPath);\n        using var process = ProcessRunner.StartProcess(\n            PythonPath,\n            arguments,\n            WorkingDirectory?.FullPath,\n            outputAction,\n            EnvironmentVariables\n        );\n        await process.WaitForExitAsync().ConfigureAwait(false);\n\n        return new ProcessResult { ExitCode = process.ExitCode, StandardOutput = output.ToString() };\n    }\n\n    [MemberNotNull(nameof(Process))]\n    public void RunDetached(\n        ProcessArgs args,\n        Action<ProcessOutput>? outputDataReceived,\n        Action<int>? onExit = null,\n        bool unbuffered = true\n    )\n    {\n        var arguments = args.ToString();\n\n        if (!PythonPath.Exists)\n        {\n            throw new FileNotFoundException(\"Venv python not found\", PythonPath);\n        }\n        SetPyvenvCfg(BaseInstall.RootPath);\n\n        Logger.Info(\n            \"Launching venv process [{PythonPath}] \"\n                + \"in working directory [{WorkingDirectory}] with args {Arguments}\",\n            PythonPath,\n            WorkingDirectory?.ToString(),\n            arguments\n        );\n\n        var filteredOutput =\n            outputDataReceived == null\n                ? null\n                : new Action<ProcessOutput>(s =>\n                {\n                    if (SuppressOutput.Any(s.Text.Contains))\n                    {\n                        Logger.Info(\"Filtered output: {S}\", s);\n                        return;\n                    }\n                    outputDataReceived.Invoke(s);\n                });\n\n        var env = EnvironmentVariables;\n\n        // Disable pip caching - uses significant memory for large packages like torch\n        // env[\"PIP_NO_CACHE_DIR\"] = \"true\";\n\n        // On windows, add portable git to PATH and binary as GIT\n        if (Compat.IsWindows)\n        {\n            var portableGitBin = GlobalConfig.LibraryDir.JoinDir(\"PortableGit\", \"bin\");\n            var venvBin = RootPath.JoinDir(RelativeBinPath);\n            if (env.TryGetValue(\"PATH\", out var pathValue))\n            {\n                env = env.SetItem(\n                    \"PATH\",\n                    Compat.GetEnvPathWithExtensions(portableGitBin, venvBin, pathValue)\n                );\n            }\n            else\n            {\n                env = env.SetItem(\"PATH\", Compat.GetEnvPathWithExtensions(portableGitBin, venvBin));\n            }\n            env = env.SetItem(\"GIT\", portableGitBin.JoinFile(\"git.exe\"));\n        }\n        else\n        {\n            if (env.TryGetValue(\"PATH\", out var pathValue))\n            {\n                env = env.SetItem(\"PATH\", Compat.GetEnvPathWithExtensions(pathValue));\n            }\n            else\n            {\n                env = env.SetItem(\"PATH\", Compat.GetEnvPathWithExtensions());\n            }\n        }\n\n        if (unbuffered)\n        {\n            env = env.SetItem(\"PYTHONUNBUFFERED\", \"1\");\n\n            // If arguments starts with -, it's a flag, insert `u` after it for unbuffered mode\n            if (arguments.StartsWith('-'))\n            {\n                arguments = arguments.Insert(1, \"u\");\n            }\n            // Otherwise insert -u at the beginning\n            else\n            {\n                arguments = \"-u \" + arguments;\n            }\n        }\n\n        Logger.Info(\"PATH: {Path}\", env[\"PATH\"]);\n\n        Process = ProcessRunner.StartAnsiProcess(\n            PythonPath,\n            arguments,\n            workingDirectory: WorkingDirectory?.FullPath,\n            outputDataReceived: filteredOutput,\n            environmentVariables: env\n        );\n\n        if (onExit != null)\n        {\n            Process.EnableRaisingEvents = true;\n            Process.Exited += (sender, _) =>\n            {\n                onExit((sender as AnsiProcess)?.ExitCode ?? -1);\n            };\n        }\n    }\n\n    /// <summary>\n    /// Get entry points for a package.\n    /// https://packaging.python.org/en/latest/specifications/entry-points/#entry-points\n    /// </summary>\n    public async Task<string?> GetEntryPoint(string entryPointName)\n    {\n        // ReSharper disable once StringLiteralTypo\n        var code = $\"\"\"\n                   from importlib.metadata import entry_points\n                   \n                   results = entry_points(group='console_scripts', name='{entryPointName}')\n                   print(tuple(results)[0].value, end='')\n                   \"\"\";\n\n        var result = await Run($\"-c \\\"{code}\\\"\").ConfigureAwait(false);\n        if (result.ExitCode == 0 && !string.IsNullOrWhiteSpace(result.StandardOutput))\n        {\n            return result.StandardOutput;\n        }\n\n        return null;\n    }\n\n    /// <summary>\n    /// Kills the running process and cancels stream readers, does not wait for exit.\n    /// </summary>\n    public void Dispose()\n    {\n        if (Process is not null)\n        {\n            Process.CancelStreamReaders();\n            Process.Kill(true);\n            Process.Dispose();\n        }\n\n        Process = null;\n        GC.SuppressFinalize(this);\n    }\n\n    /// <summary>\n    /// Kills the running process, waits for exit.\n    /// </summary>\n    public async ValueTask DisposeAsync()\n    {\n        if (Process is { HasExited: false })\n        {\n            Process.Kill(true);\n            try\n            {\n                await Process.WaitForExitAsync(new CancellationTokenSource(5000).Token).ConfigureAwait(false);\n            }\n            catch (OperationCanceledException e)\n            {\n                Logger.Warn(e, \"Venv Process did not exit in time in DisposeAsync\");\n\n                Process.CancelStreamReaders();\n            }\n        }\n\n        Process = null;\n        GC.SuppressFinalize(this);\n    }\n\n    ~PyVenvRunner()\n    {\n        Dispose();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/PyVersion.cs",
    "content": "using System;\nusing System.Text.RegularExpressions;\n\nnamespace StabilityMatrix.Core.Python;\n\n/// <summary>\n/// Represents a Python version\n/// </summary>\npublic readonly struct PyVersion : IEquatable<PyVersion>, IComparable<PyVersion>\n{\n    /// <summary>\n    /// Major version number\n    /// </summary>\n    public int Major { get; }\n\n    /// <summary>\n    /// Minor version number\n    /// </summary>\n    public int Minor { get; }\n\n    /// <summary>\n    /// Micro/patch version number\n    /// </summary>\n    public int Micro { get; }\n\n    /// <summary>\n    /// Creates a new PyVersion\n    /// </summary>\n    public PyVersion(int major, int minor, int micro)\n    {\n        Major = major;\n        Minor = minor;\n        Micro = micro;\n    }\n\n    /// <summary>\n    /// Parses a version string in the format \"major.minor.micro\"\n    /// </summary>\n    public static PyVersion Parse(string versionString)\n    {\n        var parts = versionString.Split('.');\n        if (parts.Length is < 2 or > 3)\n        {\n            throw new ArgumentException($\"Invalid version format: {versionString}\", nameof(versionString));\n        }\n\n        if (!int.TryParse(parts[0], out var major) || !int.TryParse(parts[1], out var minor))\n        {\n            throw new ArgumentException($\"Invalid version format: {versionString}\", nameof(versionString));\n        }\n\n        var micro = 0;\n        if (parts.Length <= 2)\n            return new PyVersion(major, minor, micro);\n\n        if (!int.TryParse(parts[2], out micro))\n        {\n            throw new ArgumentException($\"Invalid version format: {versionString}\", nameof(versionString));\n        }\n\n        return new PyVersion(major, minor, micro);\n    }\n\n    /// <summary>\n    /// Tries to parse a version string\n    /// </summary>\n    public static bool TryParse(string versionString, out PyVersion version)\n    {\n        try\n        {\n            version = Parse(versionString);\n            return true;\n        }\n        catch\n        {\n            version = default;\n            return false;\n        }\n    }\n\n    // Inside PyVersion.cs (or a new PyVersionParser.cs utility class)\n\n    public static bool TryParseFromComplexString(string versionString, out PyVersion version)\n    {\n        version = default;\n        if (string.IsNullOrWhiteSpace(versionString))\n            return false;\n\n        // Regex to capture major.minor.micro and optional pre-release (e.g., a6, rc1)\n        // It tries to be greedy on the numeric part.\n        var match = Regex.Match(\n            versionString,\n            @\"^(?<major>\\d+)(?:\\.(?<minor>\\d+))?(?:\\.(?<micro>\\d+))?(?:[a-zA-Z]+\\d*)?$\"\n        );\n\n        if (!match.Success)\n            return false;\n\n        if (!int.TryParse(match.Groups[\"major\"].Value, out var major))\n            return false;\n\n        var minor = 0;\n        if (match.Groups[\"minor\"].Success && !string.IsNullOrEmpty(match.Groups[\"minor\"].Value))\n        {\n            if (!int.TryParse(match.Groups[\"minor\"].Value, out minor))\n                return false;\n        }\n\n        var micro = 0;\n        if (match.Groups[\"micro\"].Success && !string.IsNullOrEmpty(match.Groups[\"micro\"].Value))\n        {\n            if (!int.TryParse(match.Groups[\"micro\"].Value, out micro))\n                return false;\n        }\n\n        version = new PyVersion(major, minor, micro);\n        return true;\n    }\n\n    /// <summary>\n    /// Returns the version as a string in the format \"major.minor.micro\"\n    /// </summary>\n    public override string ToString() => $\"{Major}.{Minor}.{Micro}\";\n\n    /// <summary>\n    /// Checks if this version equals another version\n    /// </summary>\n    public bool Equals(PyVersion other) =>\n        Major == other.Major && Minor == other.Minor && Micro == other.Micro;\n\n    /// <summary>\n    /// Compares this version to another version\n    /// </summary>\n    public int CompareTo(PyVersion other)\n    {\n        var majorComparison = Major.CompareTo(other.Major);\n        if (majorComparison != 0)\n            return majorComparison;\n\n        var minorComparison = Minor.CompareTo(other.Minor);\n        if (minorComparison != 0)\n            return minorComparison;\n\n        return Micro.CompareTo(other.Micro);\n    }\n\n    /// <summary>\n    /// Checks if this version equals another object\n    /// </summary>\n    public override bool Equals(object? obj) => obj is PyVersion other && Equals(other);\n\n    /// <summary>\n    /// Gets a hash code for this version\n    /// </summary>\n    public override int GetHashCode() => HashCode.Combine(Major, Minor, Micro);\n\n    public static bool operator ==(PyVersion left, PyVersion right) => left.Equals(right);\n\n    public static bool operator !=(PyVersion left, PyVersion right) => !left.Equals(right);\n\n    public static bool operator <(PyVersion left, PyVersion right) => left.CompareTo(right) < 0;\n\n    public static bool operator <=(PyVersion left, PyVersion right) => left.CompareTo(right) <= 0;\n\n    public static bool operator >(PyVersion left, PyVersion right) => left.CompareTo(right) > 0;\n\n    public static bool operator >=(PyVersion left, PyVersion right) => left.CompareTo(right) >= 0;\n\n    public string StringValue => $\"{Major}.{Minor}.{Micro}\";\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/QueryTclTkLibraryResult.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic record QueryTclTkLibraryResult(string? TclLibrary, string? TkLibrary);\n\n[JsonSerializable(typeof(QueryTclTkLibraryResult))]\ninternal partial class QueryTclTkLibraryResultJsonContext : JsonSerializerContext;\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/UvInstallArgs.cs",
    "content": "using System.Collections.Immutable;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Diagnostics.Contracts;\nusing System.Text.RegularExpressions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Python;\n\n/// <summary>\n/// Builds arguments for 'uv pip install' commands.\n/// </summary>\n[SuppressMessage(\"ReSharper\", \"StringLiteralTypo\")]\npublic record UvInstallArgs : ProcessArgsBuilder\n{\n    public UvInstallArgs(params Argument[] arguments)\n        : base(arguments) { }\n\n    /// <summary>\n    /// Adds the Torch package.\n    /// </summary>\n    /// <param name=\"versionSpecifier\">Optional version specifier (e.g., \"==2.1.0+cu118\", \">=2.0\").</param>\n    public UvInstallArgs WithTorch(string versionSpecifier = \"\") =>\n        this.AddArg(UvPackageSpecifier.Parse($\"torch{versionSpecifier}\"));\n\n    /// <summary>\n    /// Adds the Torch-DirectML package.\n    /// </summary>\n    /// <param name=\"versionSpecifier\">Optional version specifier.</param>\n    public UvInstallArgs WithTorchDirectML(string versionSpecifier = \"\") =>\n        this.AddArg(UvPackageSpecifier.Parse($\"torch-directml{versionSpecifier}\"));\n\n    /// <summary>\n    /// Adds the TorchVision package.\n    /// </summary>\n    /// <param name=\"versionSpecifier\">Optional version specifier.</param>\n    public UvInstallArgs WithTorchVision(string versionSpecifier = \"\") =>\n        this.AddArg(UvPackageSpecifier.Parse($\"torchvision{versionSpecifier}\"));\n\n    /// <summary>\n    /// Adds the TorchAudio package.\n    /// </summary>\n    /// <param name=\"versionSpecifier\">Optional version specifier.</param>\n    public UvInstallArgs WithTorchAudio(string versionSpecifier = \"\") =>\n        this.AddArg(UvPackageSpecifier.Parse($\"torchaudio{versionSpecifier}\"));\n\n    /// <summary>\n    /// Adds the xFormers package.\n    /// </summary>\n    /// <param name=\"versionSpecifier\">Optional version specifier.</param>\n    public UvInstallArgs WithXFormers(string versionSpecifier = \"\") =>\n        this.AddArg(UvPackageSpecifier.Parse($\"xformers{versionSpecifier}\"));\n\n    /// <summary>\n    /// Adds an extra index URL.\n    /// uv equivalent: --extra-index-url &lt;URL&gt;\n    /// </summary>\n    /// <param name=\"indexUrl\">The URL of the extra index.</param>\n    public UvInstallArgs WithExtraIndex(string indexUrl) =>\n        this.AddKeyedArgs(\"--extra-index-url\", [\"--extra-index-url\", indexUrl]);\n\n    /// <summary>\n    /// Adds the PyTorch specific extra index URL.\n    /// </summary>\n    /// <param name=\"torchIndexVariant\">The PyTorch index variant (e.g., \"cu118\", \"cu121\", \"cpu\").</param>\n    public UvInstallArgs WithTorchExtraIndex(string torchIndexVariant) =>\n        WithExtraIndex($\"https://download.pytorch.org/whl/{torchIndexVariant}\");\n\n    /// <summary>\n    /// Parses package specifiers from a requirements.txt-formatted string.\n    /// Lines starting with '#' are ignored. Inline comments are removed.\n    /// </summary>\n    /// <param name=\"requirements\">The string content of a requirements.txt file.</param>\n    /// <param name=\"excludePattern\">Optional regex pattern to exclude packages by name.</param>\n    public UvInstallArgs WithParsedFromRequirementsTxt(\n        string requirements,\n        [StringSyntax(StringSyntaxAttribute.Regex)] string? excludePattern = null\n    )\n    {\n        var lines = requirements\n            .SplitLines(StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)\n            .Where(s => !s.StartsWith('#'))\n            .Select(s => s.Contains('#') ? s.Substring(0, s.IndexOf('#')).Trim() : s.Trim())\n            .Where(s => !string.IsNullOrWhiteSpace(s));\n\n        var argumentsToAdd = new List<Argument>();\n        Regex? excludeRegex = null;\n        if (excludePattern is not null)\n        {\n            excludeRegex = new Regex($\"^{excludePattern}$\", RegexOptions.Compiled);\n        }\n\n        foreach (var line in lines)\n        {\n            try\n            {\n                var specifier = UvPackageSpecifier.Parse(line);\n                if (\n                    excludeRegex is not null\n                    && specifier.Name is not null\n                    && excludeRegex.IsMatch(specifier.Name)\n                )\n                {\n                    continue;\n                }\n                argumentsToAdd.Add(specifier); // Implicit conversion to Argument\n            }\n            catch (ArgumentException ex)\n            {\n                // Line is not a valid UvPackageSpecifier according to UvPackageSpecifier.Parse.\n                // This could be a pip command/option (e.g., flags like --no-cache-dir, -r other.txt, -e path).\n                // If the line starts with a hyphen, treat it as a command-line option directly.\n                var trimmedLine = line.Trim();\n                if (trimmedLine.StartsWith(\"-\"))\n                {\n                    // Add as a raw argument. ProcessArgsBuilder usually handles splitting if it's like \"--key value\".\n                    // Or it could be a simple flag like \"--no-deps\".\n                    argumentsToAdd.Add(new Argument(trimmedLine));\n                }\n                else\n                {\n                    // Log or handle other unparseable lines if necessary. For now, skipping non-flag unparseable lines.\n                    // Logger.Warn($\"Skipping unparseable line in requirements: {line}. Exception: {ex.Message}\");\n                }\n            }\n        }\n\n        return this.AddArgs(argumentsToAdd.ToArray());\n    }\n\n    /// <summary>\n    /// Applies user-defined overrides to the package specifiers.\n    /// </summary>\n    /// <param name=\"overrides\">A list of package specifier overrides.</param>\n    public UvInstallArgs WithUserOverrides(List<UvPackageSpecifierOverride> overrides)\n    {\n        var newArgs = this;\n\n        foreach (var uvOverride in overrides)\n        {\n            if (string.IsNullOrWhiteSpace(uvOverride.Name))\n                continue;\n\n            // Special handling for index URLs, ensuring constraint is treated as assignment\n            if (uvOverride.Name is \"--extra-index-url\" or \"--index-url\")\n            {\n                uvOverride.Constraint = \"=\"; // Or ensure ToArgument() for these produces correct format.\n            }\n\n            var uvOverrideArg = uvOverride.ToArgument();\n\n            if (uvOverride.Action is UvPackageSpecifierOverrideAction.Update)\n            {\n                newArgs = newArgs.RemoveUvArgKey(uvOverrideArg.Key ?? uvOverrideArg.Value);\n                newArgs = newArgs.AddArg(uvOverrideArg);\n            }\n            else if (uvOverride.Action is UvPackageSpecifierOverrideAction.Remove)\n            {\n                newArgs = newArgs.RemoveUvArgKey(uvOverrideArg.Key ?? uvOverrideArg.Value);\n            }\n        }\n        return newArgs;\n    }\n\n    public UvInstallArgs WithUserOverrides(List<PipPackageSpecifierOverride> overrides)\n    {\n        var newArgs = this;\n\n        foreach (var uvOverride in overrides)\n        {\n            if (string.IsNullOrWhiteSpace(uvOverride.Name))\n                continue;\n\n            // Special handling for index URLs, ensuring constraint is treated as assignment\n            if (uvOverride.Name is \"--extra-index-url\" or \"--index-url\")\n            {\n                uvOverride.Constraint = \"=\";\n            }\n\n            var uvOverrideArg = uvOverride.ToArgument();\n\n            if (uvOverride.Action is PipPackageSpecifierOverrideAction.Update)\n            {\n                newArgs = newArgs.RemoveUvArgKey(uvOverrideArg.Key ?? uvOverrideArg.Value);\n                newArgs = newArgs.AddArg(uvOverrideArg);\n            }\n            else if (uvOverride.Action is PipPackageSpecifierOverrideAction.Remove)\n            {\n                newArgs = newArgs.RemoveUvArgKey(uvOverrideArg.Key ?? uvOverrideArg.Value);\n            }\n        }\n        return newArgs;\n    }\n\n    /// <summary>\n    /// Removes an argument or package specifier by its key.\n    /// For packages, the key is typically the package name.\n    /// </summary>\n    [Pure]\n    public UvInstallArgs RemoveUvArgKey(string argumentKey)\n    {\n        return this with\n        {\n            Arguments = Arguments\n                .Where(\n                    arg =>\n                        arg.HasKey\n                            ? (arg.Key != argumentKey)\n                            : (\n                                arg.Value != argumentKey\n                                && !(\n                                    arg.Value.StartsWith($\"{argumentKey}==\")\n                                    || arg.Value.StartsWith($\"{argumentKey}~=\")\n                                    || arg.Value.StartsWith($\"{argumentKey}>=\")\n                                    || arg.Value.StartsWith($\"{argumentKey}<=\")\n                                    || arg.Value.StartsWith($\"{argumentKey}!=\")\n                                    || arg.Value.StartsWith($\"{argumentKey}>\")\n                                    || arg.Value.StartsWith($\"{argumentKey}<\")\n                                )\n                            )\n                )\n                .ToImmutableList()\n        };\n    }\n\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        // Prepends \"pip install\" to the arguments for clarity if used directly as a command string.\n        // However, UvManager will call \"uv\" with \"pip install\" and then these arguments.\n        // So, the base.ToString() which just joins arguments is usually what's needed by UvManager.\n        return base.ToString();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/UvManager.cs",
    "content": "using System.Text.Encodings.Web;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing System.Text.RegularExpressions;\nusing System.Text.Unicode;\nusing Injectio.Attributes;\nusing NLog;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Python;\n\n[RegisterSingleton<IUvManager, UvManager>]\npublic partial class UvManager : IUvManager\n{\n    private readonly ISettingsManager settingsManager;\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private static readonly JsonSerializerOptions JsonSettings = new()\n    {\n        Converters = { new JsonStringEnumConverter() },\n        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,\n        Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),\n    };\n    private string? uvExecutablePath;\n    private DirectoryPath? uvPythonInstallPath;\n\n    // Regex to parse lines from 'uv python list'\n    // Example lines:\n    //   cpython@3.10.13 (installed at /home/user/.local/share/uv/python/cpython-3.10.13-x86_64-unknown-linux-gnu)\n    //   cpython@3.11.7\n    //   pypy@3.9.18\n    // More complex if it includes source/arch/os:\n    //   cpython@3.12.2           x86_64-unknown-linux-gnu (installed at /path)\n    // We need a flexible regex. Let's assume a structure like:\n    // <key/name_with_version> [optional_arch] [optional_os] [(installed at <path>)]\n    // Or simpler from newer uv versions:\n    // 3.10.13           cpython  x86_64-unknown-linux-gnu (installed at /path)\n    // 3.11.7            cpython  x86_64-unknown-linux-gnu\n    private static readonly Regex UvPythonListRegex = UvListRegex();\n\n    public UvManager(ISettingsManager settingsManager)\n    {\n        this.settingsManager = settingsManager;\n        if (!settingsManager.IsLibraryDirSet)\n            return;\n\n        uvPythonInstallPath = new DirectoryPath(settingsManager.LibraryDir, \"Assets\", \"Python\");\n        uvExecutablePath = Path.Combine(\n            settingsManager.LibraryDir,\n            \"Assets\",\n            \"uv\",\n            Compat.IsWindows ? \"uv.exe\" : \"uv\"\n        );\n        Logger.Debug($\"UvManager initialized with uv executable path: {uvExecutablePath}\");\n    }\n\n    public async Task<bool> IsUvAvailableAsync(CancellationToken cancellationToken = default)\n    {\n        try\n        {\n            uvExecutablePath ??= Path.Combine(\n                settingsManager.LibraryDir,\n                \"Assets\",\n                \"uv\",\n                Compat.IsWindows ? \"uv.exe\" : \"uv\"\n            );\n\n            var result = await ProcessRunner\n                .GetAnsiProcessResultAsync(\n                    uvExecutablePath,\n                    [\"--version\"],\n                    cancellationToken: cancellationToken\n                )\n                .ConfigureAwait(false);\n            return result.IsSuccessExitCode;\n        }\n        catch (Exception ex)\n        {\n            Logger.Warn(\n                ex,\n                $\"UV availability check failed for path '{uvExecutablePath}'. UV might not be installed or accessible.\"\n            );\n            return false;\n        }\n    }\n\n    /// <summary>\n    /// Lists Python distributions known to UV.\n    /// </summary>\n    /// <param name=\"installedOnly\">If true, only lists Pythons UV reports as installed.</param>\n    /// <param name=\"onConsoleOutput\">Optional callback for console output.</param>\n    /// <param name=\"cancellationToken\">Cancellation token.</param>\n    /// <returns>A list of UvPythonInfo objects.</returns>\n    public async Task<IReadOnlyList<UvPythonInfo>> ListAvailablePythonsAsync(\n        bool installedOnly = false,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        uvPythonInstallPath ??= new DirectoryPath(settingsManager.LibraryDir, \"Assets\", \"Python\");\n        uvExecutablePath ??= Path.Combine(\n            settingsManager.LibraryDir,\n            \"Assets\",\n            \"uv\",\n            Compat.IsWindows ? \"uv.exe\" : \"uv\"\n        );\n\n        var args = new ProcessArgsBuilder(\"python\", \"list\", \"--managed-python\", \"--output-format\", \"json\");\n        if (settingsManager.Settings.ShowAllAvailablePythonVersions)\n        {\n            args = args.AddArg(\"--all-versions\");\n        }\n\n        var envVars = new Dictionary<string, string>\n        {\n            // Always use the centrally configured path\n            [\"UV_PYTHON_INSTALL_DIR\"] = uvPythonInstallPath,\n        };\n\n        var uvDirectory = Path.GetDirectoryName(uvExecutablePath);\n\n        var result = await ProcessRunner\n            .GetProcessResultAsync(uvExecutablePath, args, uvDirectory, envVars, useUtf8Encoding: true)\n            .ConfigureAwait(false);\n\n        if (!result.IsSuccessExitCode)\n        {\n            Logger.Error(\n                $\"Failed to list UV Python versions. Exit Code: {result.ExitCode}. Error: {result.StandardError}\"\n            );\n            return [];\n        }\n\n        var pythons = new List<UvPythonInfo>();\n        var json = result.StandardOutput;\n        if (string.IsNullOrWhiteSpace(json))\n        {\n            Logger.Warn(\"UV Python list output is empty or null.\");\n            return pythons.AsReadOnly();\n        }\n\n        var uvPythonListEntries = JsonSerializer.Deserialize<List<UvPythonListEntry>>(json, JsonSettings);\n        if (uvPythonListEntries == null)\n        {\n            Logger.Warn(\"Failed to deserialize UV Python list output.\");\n            return pythons.AsReadOnly();\n        }\n\n        var filteredPythons = uvPythonListEntries\n            .Where(e => e.Path == null || e.Path.StartsWith(uvPythonInstallPath))\n            .Where(e =>\n                settingsManager.Settings.ShowAllAvailablePythonVersions\n                || (!e.Version.Contains(\"a\") && !e.Version.Contains(\"b\"))\n            )\n            .Select(e => new UvPythonInfo\n            {\n                InstallPath = Path.GetDirectoryName(e.Path) ?? string.Empty,\n                Version = e.VersionParts,\n                Architecture = e.Arch,\n                IsInstalled = e.Path != null,\n                Key = e.Key,\n                Os = e.Os.ToLowerInvariant(),\n                Source = e.Implementation.ToLowerInvariant(),\n                Libc = e.Libc,\n                Variant = e.Variant,\n            });\n\n        pythons.AddRange(filteredPythons);\n\n        return pythons.AsReadOnly();\n    }\n\n    /// <summary>\n    /// Gets information about a specific installed Python version managed by UV.\n    /// </summary>\n    public async Task<UvPythonInfo?> GetInstalledPythonAsync(\n        PyVersion version,\n        CancellationToken cancellationToken = default\n    )\n    {\n        var installedPythons = await ListAvailablePythonsAsync(\n                installedOnly: true,\n                cancellationToken: cancellationToken\n            )\n            .ConfigureAwait(false);\n        // Find best match (exact or major.minor with highest patch)\n        var exactMatch = installedPythons.FirstOrDefault(p => p.IsInstalled && p.Version == version);\n        if (exactMatch is { IsInstalled: true })\n            return exactMatch; // Struct default is not null\n\n        return installedPythons\n            .Where(p => p.IsInstalled && p.Version.Major == version.Major && p.Version.Minor == version.Minor)\n            .OrderByDescending(p => p.Version.Micro)\n            .FirstOrDefault();\n    }\n\n    /// <summary>\n    /// Installs a specific Python version using UV.\n    /// </summary>\n    /// <param name=\"version\">Python version to install (e.g., \"3.10\" or \"3.10.13\").</param>\n    /// <param name=\"onConsoleOutput\">Optional callback for console output.</param>\n    /// <param name=\"cancellationToken\">Cancellation token.</param>\n    /// <returns>UvPythonInfo for the installed Python, or null if installation failed or info couldn't be retrieved.</returns>\n    public async Task<UvPythonInfo?> InstallPythonVersionAsync(\n        PyVersion version,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        uvPythonInstallPath ??= new DirectoryPath(settingsManager.LibraryDir, \"Assets\", \"Python\");\n        uvExecutablePath ??= Path.Combine(\n            settingsManager.LibraryDir,\n            \"Assets\",\n            \"uv\",\n            Compat.IsWindows ? \"uv.exe\" : \"uv\"\n        );\n\n        var versionString = $\"{version.Major}.{version.Minor}.{version.Micro}\";\n        if (version.Micro == 0)\n        {\n            versionString = $\"{version.Major}.{version.Minor}\";\n        }\n\n        var args = new ProcessArgsBuilder(\"python\", \"install\", versionString);\n        var envVars = new Dictionary<string, string>\n        {\n            // Always use the centrally configured path\n            [\"UV_PYTHON_INSTALL_DIR\"] = uvPythonInstallPath,\n        };\n\n        Logger.Debug(\n            $\"Setting UV_PYTHON_INSTALL_DIR to central path '{uvPythonInstallPath}' for Python {versionString} installation.\"\n        );\n        Directory.CreateDirectory(uvPythonInstallPath);\n\n        var processResult = await ProcessRunner\n            .GetAnsiProcessResultAsync(\n                uvExecutablePath,\n                args,\n                environmentVariables: envVars,\n                outputDataReceived: onConsoleOutput,\n                cancellationToken: cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        if (!processResult.IsSuccessExitCode)\n        { /* Log error */\n            return null;\n        }\n\n        Logger.Info($\"UV install command completed for Python {versionString}. Verifying...\");\n\n        // Verification Strategy 1: Use GetInstalledPythonAsync\n        var installedPythonInfo = await GetInstalledPythonAsync(version, cancellationToken)\n            .ConfigureAwait(false);\n        if (\n            installedPythonInfo is { IsInstalled: true }\n            && !string.IsNullOrWhiteSpace(installedPythonInfo.Value.InstallPath)\n        )\n        {\n            var verifiedInstall = new PyInstallation(\n                installedPythonInfo.Value.Version,\n                installedPythonInfo.Value.InstallPath\n            );\n            if (verifiedInstall.Exists())\n            {\n                Logger.Info(\n                    $\"Verified install via GetInstalledPythonAsync: {installedPythonInfo.Value.Version} at {installedPythonInfo.Value.InstallPath}\"\n                );\n                return installedPythonInfo.Value;\n            }\n            Logger.Warn(\n                $\"GetInstalledPythonAsync found path {installedPythonInfo.Value.InstallPath} but PyInstallation.Exists() failed.\"\n            );\n        }\n        else\n        {\n            Logger.Warn(\n                $\"Could not find Python {version} via GetInstalledPythonAsync after install command.\"\n            );\n        }\n\n        // Verification Strategy 2 (Fallback): Look inside the known parent directory\n        Logger.Debug($\"Attempting fallback path discovery in central directory: {uvPythonInstallPath}\");\n        try\n        {\n            var subdirectories = Directory.GetDirectories(uvPythonInstallPath);\n            var potentialDirs = subdirectories\n                .Select(dir => new { Path = dir, DirInfo = new DirectoryInfo(dir) })\n                .Where(x =>\n                    x.DirInfo.Name.StartsWith(\"cpython-\", StringComparison.OrdinalIgnoreCase)\n                    || x.DirInfo.Name.StartsWith(\"pypy-\", StringComparison.OrdinalIgnoreCase)\n                )\n                .Where(x => x.DirInfo.Name.Contains($\"{version.Major}.{version.Minor}\"))\n                .OrderByDescending(x => x.DirInfo.CreationTimeUtc)\n                .ToList();\n\n            foreach (var potentialDir in potentialDirs)\n            {\n                var actualInstallPath = potentialDir.Path;\n                var pyInstallCheck = new PyInstallation(version, actualInstallPath);\n                if (!pyInstallCheck.Exists())\n                    continue;\n\n                Logger.Info($\"Fallback discovery found likely installation at: {actualInstallPath}\");\n                var inferredKey = Path.GetFileName(actualInstallPath);\n                var inferredSource = inferredKey.Split('-')[0];\n                return new UvPythonInfo(\n                    version,\n                    actualInstallPath,\n                    true,\n                    inferredSource,\n                    null,\n                    null,\n                    inferredKey,\n                    null,\n                    null\n                );\n            }\n        }\n        catch (Exception ex)\n        {\n            Logger.Error(ex, $\"Error during fallback path discovery in {uvPythonInstallPath}\");\n        }\n\n        Logger.Error($\"Failed to verify and locate Python {version} after UV install command.\");\n        return null;\n    }\n\n    [GeneratedRegex(\n        @\"^\\s*(?<key>[a-zA-Z0-9_.-]+(?:[\\+\\-][a-zA-Z0-9_.-]+)?)\\s+(?<status_or_path>.+)\\s*$\",\n        RegexOptions.IgnoreCase | RegexOptions.Compiled,\n        \"en-US\"\n    )]\n    private static partial Regex UvListRegex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/UvPackageSpecifier.cs",
    "content": "using System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Text.Json.Serialization;\nusing System.Text.RegularExpressions;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic partial record UvPackageSpecifier\n{\n    [JsonIgnore]\n    public static IReadOnlyList<string> ConstraintOptions => [\"\", \"==\", \"~=\", \">=\", \"<=\", \">\", \"<\"];\n\n    public string? Name { get; set; }\n\n    public string? Constraint { get; set; }\n\n    public string? Version { get; set; }\n\n    public string? VersionConstraint => Constraint is null || Version is null ? null : Constraint + Version;\n\n    public static UvPackageSpecifier Parse(string value)\n    {\n        var result = TryParse(value, true, out var packageSpecifier);\n\n        Debug.Assert(result);\n\n        return packageSpecifier!;\n    }\n\n    public static bool TryParse(string value, [NotNullWhen(true)] out UvPackageSpecifier? packageSpecifier)\n    {\n        return TryParse(value, false, out packageSpecifier);\n    }\n\n    private static bool TryParse(\n        string value,\n        bool throwOnFailure,\n        [NotNullWhen(true)] out UvPackageSpecifier? packageSpecifier\n    )\n    {\n        // uv allows for more complex specifiers, including URLs and path specifiers directly.\n        // For now, this regex focuses on PyPI-style name and version specifiers.\n        // Enhancements could be made here to support git URLs, local paths, etc. if needed.\n        var match = PackageSpecifierRegex().Match(value);\n        if (!match.Success)\n        {\n            // Check if it's a URL or path-like specifier (basic check)\n            // uv supports these directly. For simplicity, we'll treat them as a Name-only specifier for now.\n            if (\n                Uri.IsWellFormedUriString(value, UriKind.Absolute)\n                || value.Contains(Path.DirectorySeparatorChar)\n                || value.Contains(Path.AltDirectorySeparatorChar)\n            )\n            {\n                packageSpecifier = new UvPackageSpecifier { Name = value };\n                return true;\n            }\n\n            if (throwOnFailure)\n            {\n                throw new ArgumentException($\"Invalid or unsupported package specifier for uv: {value}\");\n            }\n\n            packageSpecifier = null;\n            return false;\n        }\n\n        packageSpecifier = new UvPackageSpecifier\n        {\n            Name = match.Groups[\"package_name\"].Value,\n            Constraint = match.Groups[\"version_constraint\"].Value, // Will be empty string if no constraint\n            Version = match.Groups[\"version\"].Value // Will be empty string if no version\n        };\n\n        // Ensure Constraint and Version are null if they were empty strings from regex.\n        if (string.IsNullOrEmpty(packageSpecifier.Constraint))\n            packageSpecifier.Constraint = null;\n        if (string.IsNullOrEmpty(packageSpecifier.Version))\n            packageSpecifier.Version = null;\n\n        return true;\n    }\n\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        if (Name is null)\n            return string.Empty;\n        return Name + (VersionConstraint ?? string.Empty);\n    }\n\n    public Argument ToArgument()\n    {\n        if (Name is null)\n        {\n            return new Argument(\"\");\n        }\n\n        // Handle URL or path specifiers - they are typically just the value itself.\n        if (\n            Uri.IsWellFormedUriString(Name, UriKind.Absolute)\n            || Name.Contains(Path.DirectorySeparatorChar)\n            || Name.Contains(Path.AltDirectorySeparatorChar)\n        )\n        {\n            return new Argument(ProcessRunner.Quote(Name)); // Ensure paths with spaces are quoted\n        }\n\n        // Normal package specifier with optional version constraint\n        if (VersionConstraint is not null)\n        {\n            // Use Name as key to allow for potential overrides if the builder uses keys\n            // Otherwise, it's just value. For uv install, it's usually just the full string \"package==version\".\n            return new Argument(key: Name, value: ToString());\n        }\n\n        // Handles cases like \"--extra-index-url <url>\" or other flags passed as package names.\n        // This logic might be more relevant for a generic ArgsBuilder than for a package specifier directly,\n        // unless these are passed in a requirements file and parsed this way.\n        if (Name.Trim().StartsWith('-'))\n        {\n            var parts = Name.Split(' ', StringSplitOptions.RemoveEmptyEntries);\n            if (parts.Length > 1)\n            {\n                var key = parts[0];\n                // Re-join parts, quoting each if necessary (though Name should be the first part here)\n                // This specific case might be better handled by the ArgsBuilder itself.\n                // For a UvPackageSpecifier, if Name starts with '-', it's usually a single argument value (e.g. from req.txt).\n                return Argument.Quoted(key, Name); // Or simply new Argument(Name) if it's a single directive\n            }\n        }\n\n        return new Argument(ToString());\n    }\n\n    public static implicit operator Argument(UvPackageSpecifier specifier)\n    {\n        return specifier.ToArgument();\n    }\n\n    public static implicit operator UvPackageSpecifier(string specifier)\n    {\n        return Parse(specifier);\n    }\n\n    /// <summary>\n    /// Regex to match a pip/uv package specifier with name and optional version.\n    /// Does not explicitly match URLs or file paths, those are handled as a fallback.\n    /// (?i) for case-insensitive package names, though PyPI is case-insensitive in practice.\n    /// </summary>\n    [GeneratedRegex(\n        @\"^(?<package_name>[a-zA-Z0-9_.-]+)(?:(?<version_constraint>[~><=!]=?|[><])\\s*(?<version>[a-zA-Z0-9_.*+-]+))?$\",\n        RegexOptions.CultureInvariant | RegexOptions.Compiled\n    )]\n    private static partial Regex PackageSpecifierRegex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/UvPackageSpecifierOverride.cs",
    "content": "using System.Text.Json.Serialization;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic record UvPackageSpecifierOverride : UvPackageSpecifier\n{\n    public UvPackageSpecifierOverrideAction Action { get; init; } = UvPackageSpecifierOverrideAction.Update;\n\n    [JsonIgnore]\n    public bool IsUpdate => Action is UvPackageSpecifierOverrideAction.Update;\n\n    /// <inheritdoc />\n    public override string ToString()\n    {\n        // The base ToString() from UvPackageSpecifier should be sufficient as it already formats\n        // the package name and version constraint (e.g., \"package_name==1.0.0\").\n        // The Action property influences how this specifier is used by an ArgsBuilder,\n        // rather than its string representation as a package.\n        return base.ToString();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/UvPackageSpecifierOverrideAction.cs",
    "content": "namespace StabilityMatrix.Core.Python;\n\npublic enum UvPackageSpecifierOverrideAction\n{\n    None,\n    Update,\n    Remove\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/UvPythonInfo.cs",
    "content": "﻿namespace StabilityMatrix.Core.Python;\n\n/// <summary>\n/// Represents information about a Python installation as discovered or managed by UV.\n/// </summary>\npublic readonly record struct UvPythonInfo(\n    PyVersion Version,\n    string InstallPath, // Full path to the root of the Python installation\n    bool IsInstalled, // True if UV reports it as installed\n    string? Source, // e.g., \"cpython\", \"pypy\" - from 'uv python list', aka implementation\n    string? Architecture, // e.g., \"x86_64\" - from 'uv python list'\n    string? Os, // e.g., \"unknown-linux-gnu\" - from 'uv python list'\n    string? Key, // The unique key/name uv uses, e.g., \"cpython@3.10.13\" or \"3.10.13\",\n    string? Variant, // default/freethreaded\n    string? Libc // gnu/musl/gnueabi/gnueabihf/musl/none\n);\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/UvPythonListEntry.cs",
    "content": "﻿using System.Text.Json.Serialization;\nusing NLog;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic class UvPythonListEntry\n{\n    public required string Key { get; set; }\n    public required string Version { get; set; }\n    public string? Path { get; set; }\n    public string? Symlink { get; set; }\n    public Uri? Url { get; set; }\n    public string Os { get; set; }\n    public string Variant { get; set; }\n    public string Implementation { get; set; }\n    public string Arch { get; set; }\n    public string Libc { get; set; }\n\n    [JsonIgnore]\n    public PyVersion VersionParts\n    {\n        get\n        {\n            if (string.IsNullOrWhiteSpace(Version))\n                return new PyVersion(0, 0, 0);\n\n            if (Version.Contains(\"a\"))\n            {\n                // substring to exclude everything after the first \"a\" (including the first \"a\")\n                var version = Version.Substring(0, Version.IndexOf(\"a\", StringComparison.OrdinalIgnoreCase));\n                return PyVersion.Parse(version);\n            }\n\n            if (Version.Contains(\"b\"))\n            {\n                // substring to exclude everything after the first \"b\" (including the first \"b\")\n                var version = Version.Substring(0, Version.IndexOf(\"b\", StringComparison.OrdinalIgnoreCase));\n                return PyVersion.Parse(version);\n            }\n\n            if (Version.Contains(\"rc\"))\n            {\n                // substring to exclude everything after the first \"rc\" (including the first \"rc\")\n                var version = Version.Substring(0, Version.IndexOf(\"rc\", StringComparison.OrdinalIgnoreCase));\n                return PyVersion.Parse(version);\n            }\n\n            try\n            {\n                return PyVersion.Parse(Version);\n            }\n            catch (Exception e)\n            {\n                LogManager\n                    .GetCurrentClassLogger()\n                    .Error(e, \"Failed to parse Python version: {Version}\", Version);\n                return new PyVersion(0, 0, 0);\n            }\n        }\n    }\n\n    [JsonIgnore]\n    public bool IsPrerelease => Version.Contains(\"a\") || Version.Contains(\"b\") || Version.Contains(\"rc\");\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Python/UvVenvRunner.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Text;\nusing System.Text.Json;\nusing NLog;\nusing Salaros.Configuration;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Core.Python;\n\npublic class UvVenvRunner : IPyVenvRunner\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    private string? lastSetPyvenvCfgPath;\n\n    /// <summary>\n    /// Relative path to the site-packages folder from the venv root.\n    /// This is platform specific.\n    /// </summary>\n    public static string GetRelativeSitePackagesPath(PyVersion? version = null)\n    {\n        var minorVersion = version?.Minor ?? 10;\n        return Compat.Switch(\n            (PlatformKind.Windows, \"Lib/site-packages\"),\n            (PlatformKind.Unix, $\"lib/python3.{minorVersion}/site-packages\")\n        );\n    }\n\n    /// <summary>\n    /// Legacy path for compatibility\n    /// </summary>\n    public static string RelativeSitePackagesPath =>\n        Compat.Switch(\n            (PlatformKind.Windows, \"Lib/site-packages\"),\n            (PlatformKind.Unix, \"lib/python3.10/site-packages\")\n        );\n\n    public PyBaseInstall BaseInstall { get; }\n\n    /// <summary>\n    /// The process running the python executable.\n    /// </summary>\n    public AnsiProcess? Process { get; private set; }\n\n    /// <summary>\n    /// The path to the venv root directory.\n    /// </summary>\n    public DirectoryPath RootPath { get; }\n\n    /// <summary>\n    /// Optional working directory for the python process.\n    /// </summary>\n    public DirectoryPath? WorkingDirectory { get; set; }\n\n    /// <summary>\n    /// Optional environment variables for the python process.\n    /// </summary>\n    public ImmutableDictionary<string, string> EnvironmentVariables { get; set; } =\n        ImmutableDictionary<string, string>.Empty;\n\n    /// <summary>\n    /// Name of the python binary folder.\n    /// 'Scripts' on Windows, 'bin' on Unix.\n    /// </summary>\n    public static string RelativeBinPath =>\n        Compat.Switch((PlatformKind.Windows, \"Scripts\"), (PlatformKind.Unix, \"bin\"));\n\n    /// <summary>\n    /// The relative path to the python executable.\n    /// </summary>\n    public static string RelativePythonPath =>\n        Compat.Switch(\n            (PlatformKind.Windows, Path.Combine(\"Scripts\", \"python.exe\")),\n            (PlatformKind.Unix, Path.Combine(\"bin\", \"python3\"))\n        );\n\n    /// <summary>\n    /// The full path to the python executable.\n    /// </summary>\n    public FilePath PythonPath => RootPath.JoinFile(RelativePythonPath);\n\n    /// <summary>\n    /// The relative path to the pip executable.\n    /// </summary>\n    public static string RelativePipPath =>\n        Compat.Switch(\n            (PlatformKind.Windows, Path.Combine(\"Scripts\", \"pip.exe\")),\n            (PlatformKind.Unix, Path.Combine(\"bin\", \"pip3\"))\n        );\n\n    /// <summary>\n    /// The full path to the pip executable.\n    /// </summary>\n    public FilePath PipPath => RootPath.JoinFile(RelativePipPath);\n\n    /// <summary>\n    /// The Python version of this venv\n    /// </summary>\n    public PyVersion Version => BaseInstall.Version;\n\n    /// <summary>\n    /// List of substrings to suppress from the output.\n    /// When a line contains any of these substrings, it will not be forwarded to callbacks.\n    /// A corresponding Info log will be written instead.\n    /// </summary>\n    public List<string> SuppressOutput { get; } = new() { \"fatal: not a git repository\" };\n\n    internal UvVenvRunner(PyBaseInstall baseInstall, DirectoryPath rootPath)\n    {\n        BaseInstall = baseInstall;\n        RootPath = rootPath;\n        EnvironmentVariables = EnvironmentVariables.SetItem(\"VIRTUAL_ENV\", rootPath.FullPath);\n    }\n\n    public void UpdateEnvironmentVariables(\n        Func<ImmutableDictionary<string, string>, ImmutableDictionary<string, string>> env\n    )\n    {\n        EnvironmentVariables = env(EnvironmentVariables);\n    }\n\n    /// <returns>True if the venv has a Scripts\\python.exe file</returns>\n    public bool Exists() => PythonPath.Exists;\n\n    private FilePath UvExecutablePath =>\n        new(GlobalConfig.LibraryDir, \"Assets\", \"uv\", Compat.IsWindows ? \"uv.exe\" : \"uv\");\n\n    /// <summary>\n    /// Creates a venv at the configured path.\n    /// </summary>\n    public async Task Setup(\n        bool existsOk = false,\n        Action<ProcessOutput>? onConsoleOutput = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (!existsOk && Exists())\n        {\n            throw new InvalidOperationException(\"Venv already exists\");\n        }\n\n        // Create RootPath if it doesn't exist\n        RootPath.Create();\n\n        // Create venv (copy mode if windows)\n        var args = new ProcessArgsBuilder(\n            \"venv\",\n            RootPath.ToString(),\n            \"--allow-existing\",\n            \"--python\",\n            BaseInstall.PythonExePath\n        );\n\n        var venvProc = ProcessRunner.StartAnsiProcess(\n            UvExecutablePath,\n            args.ToProcessArgs(),\n            WorkingDirectory?.FullPath,\n            onConsoleOutput\n        );\n\n        try\n        {\n            await venvProc.WaitForExitAsync(cancellationToken).ConfigureAwait(false);\n\n            // Check return code\n            if (venvProc.ExitCode != 0)\n            {\n                throw new ProcessException($\"Venv creation failed with code {venvProc.ExitCode}\");\n            }\n        }\n        catch (OperationCanceledException)\n        {\n            venvProc.CancelStreamReaders();\n        }\n        finally\n        {\n            venvProc.Kill();\n            venvProc.Dispose();\n        }\n    }\n\n    /// <summary>\n    /// Set current python path to pyvenv.cfg\n    /// This should be called before using the venv, in case user moves the venv directory.\n    /// </summary>\n    private void SetPyvenvCfg(string pythonDirectory, bool force = false)\n    {\n        // Skip if we are not created yet\n        if (!Exists())\n            return;\n\n        // Skip if already set to same value\n        if (lastSetPyvenvCfgPath == pythonDirectory && !force)\n            return;\n\n        // Path to pyvenv.cfg\n        var cfgPath = Path.Combine(RootPath, \"pyvenv.cfg\");\n        if (!File.Exists(cfgPath))\n        {\n            throw new FileNotFoundException(\"pyvenv.cfg not found\", cfgPath);\n        }\n\n        Logger.Info(\"Updating pyvenv.cfg with embedded Python directory {PyDir}\", pythonDirectory);\n\n        // Insert a top section\n        var topSection = \"[top]\" + Environment.NewLine;\n        var cfg = new ConfigParser(topSection + File.ReadAllText(cfgPath));\n\n        // Need to set all path keys - home, base-prefix, base-exec-prefix, base-executable\n        cfg.SetValue(\"top\", \"home\", pythonDirectory);\n        cfg.SetValue(\"top\", \"base-prefix\", pythonDirectory);\n\n        cfg.SetValue(\"top\", \"base-exec-prefix\", pythonDirectory);\n\n        cfg.SetValue(\n            \"top\",\n            \"base-executable\",\n            Path.Combine(pythonDirectory, Compat.IsWindows ? \"python.exe\" : RelativePythonPath)\n        );\n\n        // Convert to string for writing, strip the top section\n        var cfgString = cfg.ToString()!.Replace(topSection, \"\");\n        File.WriteAllText(cfgPath, cfgString);\n\n        // Update last set path\n        lastSetPyvenvCfgPath = pythonDirectory;\n    }\n\n    /// <summary>\n    /// Run a pip install command. Waits for the process to exit.\n    /// workingDirectory defaults to RootPath.\n    /// </summary>\n    public async Task PipInstall(ProcessArgs args, Action<ProcessOutput>? outputDataReceived = null)\n    {\n        if (!File.Exists(UvExecutablePath))\n        {\n            throw new FileNotFoundException(\"uv not found\", UvExecutablePath);\n        }\n\n        SetPyvenvCfg(BaseInstall.RootPath);\n\n        // Record output for errors\n        var output = new StringBuilder();\n\n        var outputAction = new Action<ProcessOutput>(s =>\n        {\n            Logger.Debug($\"Pip output: {s.Text}\");\n            // Record to output\n            output.Append(s.Text);\n            // Forward to callback\n            outputDataReceived?.Invoke(s);\n        });\n\n        RunUvDetached(\n            args.Prepend([\"pip\", \"install\"])\n                .Concat([\"--index-strategy\", \"unsafe-first-match\", \"--python\", PythonPath.ToString()]),\n            outputAction\n        );\n        await Process.WaitForExitAsync().ConfigureAwait(false);\n\n        // Check return code\n        if (Process.ExitCode != 0)\n        {\n            throw new ProcessException(\n                $\"pip install failed with code {Process.ExitCode}: {output.ToString().ToRepr()}\"\n            );\n        }\n    }\n\n    /// <summary>\n    /// Run a pip uninstall command. Waits for the process to exit.\n    /// workingDirectory defaults to RootPath.\n    /// </summary>\n    public async Task PipUninstall(ProcessArgs args, Action<ProcessOutput>? outputDataReceived = null)\n    {\n        if (!File.Exists(UvExecutablePath))\n        {\n            throw new FileNotFoundException(\"uv not found\", UvExecutablePath);\n        }\n\n        SetPyvenvCfg(BaseInstall.RootPath);\n\n        // Record output for errors\n        var output = new StringBuilder();\n\n        var outputAction = new Action<ProcessOutput>(s =>\n        {\n            Logger.Debug($\"Pip output: {s.Text}\");\n            // Record to output\n            output.Append(s.Text);\n            // Forward to callback\n            outputDataReceived?.Invoke(s);\n        });\n\n        RunUvDetached(\n            args.Prepend([\"pip\", \"uninstall\"]).Concat([\"--python\", PythonPath.ToString()]),\n            outputAction\n        );\n        await Process.WaitForExitAsync().ConfigureAwait(false);\n\n        // Check return code\n        if (Process.ExitCode != 0)\n        {\n            throw new ProcessException(\n                $\"pip install failed with code {Process.ExitCode}: {output.ToString().ToRepr()}\"\n            );\n        }\n    }\n\n    /// <summary>\n    /// Run a pip list command, return results as PipPackageInfo objects.\n    /// </summary>\n    public async Task<IReadOnlyList<PipPackageInfo>> PipList()\n    {\n        if (!File.Exists(UvExecutablePath))\n        {\n            throw new FileNotFoundException(\"uv not found\", UvExecutablePath);\n        }\n\n        SetPyvenvCfg(BaseInstall.RootPath);\n\n        var result = await ProcessRunner\n            .GetProcessResultAsync(\n                UvExecutablePath,\n                [\"pip\", \"list\", \"--format=json\", \"--python\", PythonPath.ToString()],\n                WorkingDirectory?.FullPath,\n                EnvironmentVariables\n            )\n            .ConfigureAwait(false);\n\n        // Check return code\n        if (result.ExitCode != 0)\n        {\n            throw new ProcessException(\n                $\"pip list failed with code {result.ExitCode}: {result.StandardOutput}, {result.StandardError}\"\n            );\n        }\n\n        // There may be warning lines before the Json line, or update messages after\n        // Filter to find the first line that starts with [\n        var jsonLine = result\n            .StandardOutput?.SplitLines(\n                StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries\n            )\n            .Select(line => line.Trim())\n            .FirstOrDefault(line =>\n                line.StartsWith(\"[\", StringComparison.OrdinalIgnoreCase)\n                && line.EndsWith(\"]\", StringComparison.OrdinalIgnoreCase)\n            );\n\n        if (jsonLine is null)\n        {\n            return [];\n        }\n\n        return JsonSerializer.Deserialize<List<PipPackageInfo>>(\n                jsonLine,\n                PipPackageInfoSerializerContext.Default.Options\n            ) ?? [];\n    }\n\n    /// <summary>\n    /// Run a pip show command, return results as PipPackageInfo objects.\n    /// </summary>\n    public async Task<PipShowResult?> PipShow(string packageName)\n    {\n        if (!File.Exists(UvExecutablePath))\n        {\n            throw new FileNotFoundException(\"uv not found\", UvExecutablePath);\n        }\n\n        SetPyvenvCfg(BaseInstall.RootPath);\n\n        var result = await ProcessRunner\n            .GetProcessResultAsync(\n                UvExecutablePath,\n                [\"pip\", \"show\", packageName, \"--python\", PythonPath.ToString()],\n                WorkingDirectory?.FullPath,\n                EnvironmentVariables\n            )\n            .ConfigureAwait(false);\n\n        // Check return code\n        if (result.ExitCode != 0)\n        {\n            throw new ProcessException(\n                $\"pip show failed with code {result.ExitCode}: {result.StandardOutput}, {result.StandardError}\"\n            );\n        }\n\n        if (result.StandardOutput!.StartsWith(\"WARNING: Package(s) not found:\"))\n        {\n            return null;\n        }\n\n        return PipShowResult.Parse(result.StandardOutput);\n    }\n\n    /// <summary>\n    /// Run a pip index command, return result as PipIndexResult.\n    /// </summary>\n    public async Task<PipIndexResult?> PipIndex(string packageName, string? indexUrl = null)\n    {\n        if (!File.Exists(PipPath))\n        {\n            throw new FileNotFoundException(\"pip not found\", PipPath);\n        }\n\n        SetPyvenvCfg(BaseInstall.RootPath);\n\n        var args = new ProcessArgsBuilder(\n            \"-m\",\n            \"pip\",\n            \"index\",\n            \"versions\",\n            packageName,\n            \"--no-color\",\n            \"--disable-pip-version-check\"\n        );\n\n        if (indexUrl is not null)\n        {\n            args = args.AddKeyedArgs(\"--index-url\", [\"--index-url\", indexUrl]);\n        }\n\n        var result = await ProcessRunner\n            .GetProcessResultAsync(PythonPath, args, WorkingDirectory?.FullPath, EnvironmentVariables)\n            .ConfigureAwait(false);\n\n        // Check return code\n        if (result.ExitCode != 0)\n        {\n            throw new ProcessException(\n                $\"pip index failed with code {result.ExitCode}: {result.StandardOutput}, {result.StandardError}\"\n            );\n        }\n\n        if (\n            string.IsNullOrEmpty(result.StandardOutput)\n            || result\n                .StandardOutput!.SplitLines()\n                .Any(l => l.StartsWith(\"ERROR: No matching distribution found\"))\n        )\n        {\n            return null;\n        }\n\n        return PipIndexResult.Parse(result.StandardOutput);\n    }\n\n    /// <summary>\n    /// Run a custom install command. Waits for the process to exit.\n    /// workingDirectory defaults to RootPath.\n    /// </summary>\n    public async Task CustomInstall(ProcessArgs args, Action<ProcessOutput>? outputDataReceived = null)\n    {\n        // Record output for errors\n        var output = new StringBuilder();\n\n        var outputAction =\n            outputDataReceived == null\n                ? null\n                : new Action<ProcessOutput>(s =>\n                {\n                    Logger.Debug($\"Install output: {s.Text}\");\n                    // Record to output\n                    output.Append(s.Text);\n                    // Forward to callback\n                    outputDataReceived(s);\n                });\n\n        RunDetached(args, outputAction);\n        await Process.WaitForExitAsync().ConfigureAwait(false);\n\n        // Check return code\n        if (Process.ExitCode != 0)\n        {\n            throw new ProcessException(\n                $\"install script failed with code {Process.ExitCode}: {output.ToString().ToRepr()}\"\n            );\n        }\n    }\n\n    /// <summary>\n    /// Run a command using the venv Python executable and return the result.\n    /// </summary>\n    /// <param name=\"arguments\">Arguments to pass to the Python executable.</param>\n    public async Task<ProcessResult> Run(ProcessArgs arguments)\n    {\n        // Record output for errors\n        var output = new StringBuilder();\n\n        var outputAction = new Action<string?>(s =>\n        {\n            if (s == null)\n                return;\n            Logger.Debug(\"Pip output: {Text}\", s);\n            output.Append(s);\n        });\n\n        SetPyvenvCfg(BaseInstall.RootPath);\n        using var process = ProcessRunner.StartProcess(\n            PythonPath,\n            arguments,\n            WorkingDirectory?.FullPath,\n            outputAction,\n            EnvironmentVariables\n        );\n        await process.WaitForExitAsync().ConfigureAwait(false);\n\n        return new ProcessResult { ExitCode = process.ExitCode, StandardOutput = output.ToString() };\n    }\n\n    [MemberNotNull(nameof(Process))]\n    public void RunDetached(\n        ProcessArgs args,\n        Action<ProcessOutput>? outputDataReceived,\n        Action<int>? onExit = null,\n        bool unbuffered = true\n    )\n    {\n        var arguments = args.ToString();\n\n        if (!PythonPath.Exists)\n        {\n            throw new FileNotFoundException(\"Venv python not found\", PythonPath);\n        }\n        SetPyvenvCfg(BaseInstall.RootPath);\n\n        Logger.Info(\n            \"Launching venv process [{PythonPath}] \"\n                + \"in working directory [{WorkingDirectory}] with args {Arguments}\",\n            PythonPath,\n            WorkingDirectory?.ToString(),\n            arguments\n        );\n\n        var filteredOutput =\n            outputDataReceived == null\n                ? null\n                : new Action<ProcessOutput>(s =>\n                {\n                    if (SuppressOutput.Any(s.Text.Contains))\n                    {\n                        Logger.Info(\"Filtered output: {S}\", s);\n                        return;\n                    }\n                    outputDataReceived.Invoke(s);\n                });\n\n        var env = EnvironmentVariables;\n\n        // Disable pip caching - uses significant memory for large packages like torch\n        // env[\"PIP_NO_CACHE_DIR\"] = \"true\";\n\n        // On windows, add portable git to PATH and binary as GIT\n        if (Compat.IsWindows)\n        {\n            var portableGitBin = GlobalConfig.LibraryDir.JoinDir(\"PortableGit\", \"bin\");\n            var venvBin = RootPath.JoinDir(RelativeBinPath);\n            var uvFolder = GlobalConfig.LibraryDir.JoinDir(\"Assets\", \"uv\");\n            if (env.TryGetValue(\"PATH\", out var pathValue))\n            {\n                env = env.SetItem(\n                    \"PATH\",\n                    Compat.GetEnvPathWithExtensions(portableGitBin, venvBin, uvFolder, pathValue)\n                );\n            }\n            else\n            {\n                env = env.SetItem(\"PATH\", Compat.GetEnvPathWithExtensions(portableGitBin, uvFolder, venvBin));\n            }\n            env = env.SetItem(\"GIT\", portableGitBin.JoinFile(\"git.exe\"));\n        }\n        else\n        {\n            if (env.TryGetValue(\"PATH\", out var pathValue))\n            {\n                env = env.SetItem(\"PATH\", Compat.GetEnvPathWithExtensions(pathValue));\n            }\n            else\n            {\n                env = env.SetItem(\"PATH\", Compat.GetEnvPathWithExtensions());\n            }\n        }\n\n        if (unbuffered)\n        {\n            env = env.SetItem(\"PYTHONUNBUFFERED\", \"1\");\n\n            // If arguments starts with -, it's a flag, insert `u` after it for unbuffered mode\n            if (arguments.StartsWith('-'))\n            {\n                arguments = arguments.Insert(1, \"u\");\n            }\n            // Otherwise insert -u at the beginning\n            else\n            {\n                arguments = \"-u \" + arguments;\n            }\n        }\n\n        Logger.Info(\"PATH: {Path}\", env[\"PATH\"]);\n\n        Process = ProcessRunner.StartAnsiProcess(\n            PythonPath,\n            arguments,\n            workingDirectory: WorkingDirectory?.FullPath,\n            outputDataReceived: filteredOutput,\n            environmentVariables: env\n        );\n\n        if (onExit != null)\n        {\n            Process.EnableRaisingEvents = true;\n            Process.Exited += (sender, _) =>\n            {\n                onExit((sender as AnsiProcess)?.ExitCode ?? -1);\n            };\n        }\n    }\n\n    [MemberNotNull(nameof(Process))]\n    private void RunUvDetached(\n        ProcessArgs args,\n        Action<ProcessOutput>? outputDataReceived,\n        Action<int>? onExit = null\n    )\n    {\n        var arguments = args.ToString();\n\n        if (!UvExecutablePath.Exists)\n        {\n            throw new FileNotFoundException(\"uv not found\", PythonPath);\n        }\n\n        Logger.Info(\n            \"Launching uv process [{UvExecutablePath}] \"\n                + \"in working directory [{WorkingDirectory}] with args {Arguments}\",\n            UvExecutablePath,\n            WorkingDirectory?.ToString(),\n            arguments\n        );\n\n        var filteredOutput =\n            outputDataReceived == null\n                ? null\n                : new Action<ProcessOutput>(s =>\n                {\n                    if (SuppressOutput.Any(s.Text.Contains))\n                    {\n                        Logger.Info(\"Filtered output: {S}\", s);\n                        return;\n                    }\n                    outputDataReceived.Invoke(s);\n                });\n\n        var env = EnvironmentVariables;\n\n        // Disable pip caching - uses significant memory for large packages like torch\n        // env[\"PIP_NO_CACHE_DIR\"] = \"true\";\n\n        // On windows, add portable git to PATH and binary as GIT\n        if (Compat.IsWindows)\n        {\n            var portableGitBin = GlobalConfig.LibraryDir.JoinDir(\"PortableGit\", \"bin\");\n            var venvBin = RootPath.JoinDir(RelativeBinPath);\n            if (env.TryGetValue(\"PATH\", out var pathValue))\n            {\n                env = env.SetItem(\n                    \"PATH\",\n                    Compat.GetEnvPathWithExtensions(portableGitBin, venvBin, pathValue)\n                );\n            }\n            else\n            {\n                env = env.SetItem(\"PATH\", Compat.GetEnvPathWithExtensions(portableGitBin, venvBin));\n            }\n            env = env.SetItem(\"GIT\", portableGitBin.JoinFile(\"git.exe\"));\n        }\n        else\n        {\n            if (env.TryGetValue(\"PATH\", out var pathValue))\n            {\n                env = env.SetItem(\"PATH\", Compat.GetEnvPathWithExtensions(pathValue));\n            }\n            else\n            {\n                env = env.SetItem(\"PATH\", Compat.GetEnvPathWithExtensions());\n            }\n        }\n\n        Logger.Info(\"PATH: {Path}\", env[\"PATH\"]);\n\n        Process = ProcessRunner.StartAnsiProcess(\n            UvExecutablePath,\n            arguments,\n            workingDirectory: WorkingDirectory?.FullPath,\n            outputDataReceived: filteredOutput,\n            environmentVariables: env\n        );\n\n        if (onExit != null)\n        {\n            Process.EnableRaisingEvents = true;\n            Process.Exited += (sender, _) =>\n            {\n                onExit((sender as AnsiProcess)?.ExitCode ?? -1);\n            };\n        }\n    }\n\n    /// <summary>\n    /// Get entry points for a package.\n    /// https://packaging.python.org/en/latest/specifications/entry-points/#entry-points\n    /// </summary>\n    public async Task<string?> GetEntryPoint(string entryPointName)\n    {\n        // ReSharper disable once StringLiteralTypo\n        var code = $\"\"\"\n            from importlib.metadata import entry_points\n\n            results = entry_points(group='console_scripts', name='{entryPointName}')\n            print(tuple(results)[0].value, end='')\n            \"\"\";\n\n        var result = await Run($\"-c \\\"{code}\\\"\").ConfigureAwait(false);\n        if (result.ExitCode == 0 && !string.IsNullOrWhiteSpace(result.StandardOutput))\n        {\n            return result.StandardOutput;\n        }\n\n        return null;\n    }\n\n    /// <summary>\n    /// Kills the running process and cancels stream readers, does not wait for exit.\n    /// </summary>\n    public void Dispose()\n    {\n        if (Process is not null)\n        {\n            Process.CancelStreamReaders();\n            Process.Kill(true);\n            Process.Dispose();\n        }\n\n        Process = null;\n        GC.SuppressFinalize(this);\n    }\n\n    /// <summary>\n    /// Kills the running process, waits for exit.\n    /// </summary>\n    public async ValueTask DisposeAsync()\n    {\n        if (Process is { HasExited: false })\n        {\n            Process.Kill(true);\n            try\n            {\n                await Process.WaitForExitAsync(new CancellationTokenSource(5000).Token).ConfigureAwait(false);\n            }\n            catch (OperationCanceledException e)\n            {\n                Logger.Warn(e, \"Venv Process did not exit in time in DisposeAsync\");\n\n                Process.CancelStreamReaders();\n            }\n        }\n\n        Process = null;\n        GC.SuppressFinalize(this);\n    }\n\n    ~UvVenvRunner()\n    {\n        Dispose();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/ReparsePoints/DeviceIoControlCode.cs",
    "content": "﻿namespace StabilityMatrix.Core.ReparsePoints;\n\ninternal enum DeviceIoControlCode : uint\n{\n    /// <summary>\n    /// FSCTL_SET_REPARSE_POINT\n    /// Command to set the reparse point data block.\n    /// </summary>\n    SetReparsePoint = 0x000900A4,\n\n    /// <summary>\n    /// FSCTL_GET_REPARSE_POINT\n    /// Command to get the reparse point data block.\n    /// </summary>\n    GetReparsePoint = 0x000900A8,\n\n    /// <summary>\n    /// FSCTL_DELETE_REPARSE_POINT\n    /// Command to delete the reparse point data base.\n    /// </summary>\n    DeleteReparsePoint = 0x000900AC,\n\n    /// <summary>\n    /// IO_REPARSE_TAG_MOUNT_POINT\n    /// Reparse point tag used to identify mount points and junction points.\n    /// </summary>\n    ReparseTagMountPoint = 0xA0000003,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/ReparsePoints/Junction.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Runtime.InteropServices;\nusing System.Runtime.Versioning;\nusing System.Text;\nusing Microsoft.Win32.SafeHandles;\n\nnamespace StabilityMatrix.Core.ReparsePoints;\n\n[SupportedOSPlatform(\"windows\")]\npublic static class Junction\n{\n    /// <summary>\n    /// This prefix indicates to NTFS that the path is to be treated as a non-interpreted\n    /// path in the virtual file system.\n    /// </summary>\n    private const string NonInterpretedPathPrefix = @\"\\??\\\";\n\n    [DllImport(\"kernel32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n    private static extern IntPtr CreateFile(\n        [MarshalAs(UnmanagedType.LPWStr)] string lpFileName,\n        [MarshalAs(UnmanagedType.U4)] Win32FileAccess dwDesiredAccess,\n        [MarshalAs(UnmanagedType.U4)] Win32FileShare dwShareMode,\n        IntPtr lpSecurityAttributes,\n        [MarshalAs(UnmanagedType.U4)] Win32CreationDisposition dwCreationDisposition,\n        [MarshalAs(UnmanagedType.U4)] Win32FileAttribute dwFlagsAndAttributes,\n        IntPtr hTemplateFile);\n\n    [DllImport(\"kernel32.dll\", SetLastError = true, CharSet = CharSet.Auto)]\n    private static extern bool DeviceIoControl(SafeFileHandle hDevice, uint dwIoControlCode,\n        [In] IntPtr lpInBuffer, uint nInBufferSize,\n        IntPtr lpOutBuffer, uint nOutBufferSize,\n        [Out] out uint lpBytesReturned, IntPtr lpOverlapped);\n    \n    /// <summary>\n    /// Creates a junction point from the specified directory to the specified target directory.\n    /// </summary>\n    /// <param name=\"junctionPoint\">The junction point path</param>\n    /// <param name=\"targetDir\">The target directory (Must already exist)</param>\n    /// <param name=\"overwrite\">If true overwrites an existing reparse point or empty directory</param>\n    /// <exception cref=\"IOException\">Thrown when the junction point could not be created or when\n    /// an existing directory was found and <paramref name=\"overwrite\" /> if false</exception>\n    public static void Create(string junctionPoint, string targetDir, bool overwrite)\n    {\n        targetDir = Path.GetFullPath(targetDir);\n\n        if (!Directory.Exists(targetDir))\n        {\n            throw new IOException(\"Target path does not exist or is not a directory\");\n        }\n\n        if (Directory.Exists(junctionPoint))\n        {\n            if (!overwrite)\n                throw new IOException(\"Directory already exists and overwrite parameter is false.\");\n        }\n        else\n        {\n            Directory.CreateDirectory(junctionPoint);\n        }\n\n        using var fileHandle = OpenReparsePoint(junctionPoint, Win32FileAccess.GenericWrite);\n        var targetDirBytes = Encoding.Unicode.GetBytes(\n            NonInterpretedPathPrefix + Path.GetFullPath(targetDir));\n\n        var reparseDataBuffer = new ReparseDataBuffer\n        {\n            ReparseTag = (uint) DeviceIoControlCode.ReparseTagMountPoint,\n            ReparseDataLength = Convert.ToUInt16(targetDirBytes.Length + 12),\n            SubstituteNameOffset = 0,\n            SubstituteNameLength = Convert.ToUInt16(targetDirBytes.Length),\n            PrintNameOffset = Convert.ToUInt16(targetDirBytes.Length + 2),\n            PrintNameLength = 0,\n            PathBuffer = new byte[0x3ff0]\n        };\n\n        Array.Copy(targetDirBytes, reparseDataBuffer.PathBuffer, targetDirBytes.Length);\n\n        var inBufferSize = Marshal.SizeOf(reparseDataBuffer);\n        var inBuffer = Marshal.AllocHGlobal(inBufferSize);\n\n        try\n        {\n            Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false);\n            \n            var result = DeviceIoControl(\n                fileHandle, (uint) DeviceIoControlCode.SetReparsePoint,\n                inBuffer, Convert.ToUInt32(targetDirBytes.Length + 20), \n                IntPtr.Zero, 0, \n                out var bytesReturned, IntPtr.Zero);\n            \n            Debug.WriteLine($\"bytesReturned: {bytesReturned}\");\n\n            if (!result)\n            {\n                ThrowLastWin32Error($\"Unable to create junction point\" +\n                                    $\" {junctionPoint} -> {targetDir}\");\n            }\n        }\n        finally\n        {\n            Marshal.FreeHGlobal(inBuffer);\n        }\n    }\n    \n    /// <summary>\n    /// Deletes a junction point at the specified source directory along with the directory itself.\n    /// Does nothing if the junction point does not exist.\n    /// </summary>\n    /// <param name=\"junctionPoint\">The junction point path</param>\n    public static void Delete(string junctionPoint)\n    {\n        if (!Directory.Exists(junctionPoint))\n        {\n            if (File.Exists(junctionPoint))\n                throw new IOException(\"Path is not a junction point.\");\n\n            return;\n        }\n\n        using var fileHandle = OpenReparsePoint(junctionPoint, Win32FileAccess.GenericWrite);\n        \n        var reparseDataBuffer = new ReparseDataBuffer\n        {\n            ReparseTag = (uint) DeviceIoControlCode.ReparseTagMountPoint,\n            ReparseDataLength = 0,\n            PathBuffer = new byte[0x3ff0]\n        };\n\n        var inBufferSize = Marshal.SizeOf(reparseDataBuffer);\n        var inBuffer = Marshal.AllocHGlobal(inBufferSize);\n        try\n        {\n            Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false);\n                \n            var result = DeviceIoControl(fileHandle, \n                (uint) DeviceIoControlCode.DeleteReparsePoint,\n                inBuffer, 8, \n                IntPtr.Zero, 0, \n                out var bytesReturned, IntPtr.Zero);\n\n            Debug.WriteLine($\"bytesReturned: {bytesReturned}\");\n            \n            if (!result)\n            {\n                ThrowLastWin32Error($\"Unable to delete junction point {junctionPoint}\");\n            }\n        }\n        finally\n        {\n            Marshal.FreeHGlobal(inBuffer);\n        }\n\n        try\n        {\n            Directory.Delete(junctionPoint);\n        }\n        catch (IOException ex)\n        {\n            throw new IOException(\"Unable to delete junction point.\", ex);\n        }\n    }\n    \n    /// <summary>\n    /// Determines whether the specified path exists and refers to a junction point.\n    /// </summary>\n    /// <param name=\"path\">The junction point path</param>\n    /// <returns>True if the specified path represents a junction point</returns>\n    /// <exception cref=\"IOException\">Thrown if the specified path is invalid\n    /// or some other error occurs</exception>\n    public static bool Exists(string path)\n    {\n        if (!Directory.Exists(path)) return false;\n\n        using var handle = OpenReparsePoint(path, Win32FileAccess.GenericRead);\n        var target = InternalGetTarget(handle);\n        return target != null;\n    }\n\n    /// <summary>\n    /// Gets the target of the specified junction point.\n    /// </summary>\n    /// <param name=\"junctionPoint\">The junction point path</param>\n    /// <returns>The target of the junction point</returns>\n    /// <exception cref=\"IOException\">Thrown when the specified path does not\n    /// exist, is invalid, is not a junction point, or some other error occurs</exception>\n    public static string GetTarget(string junctionPoint)\n    {\n        using var handle = OpenReparsePoint(junctionPoint, Win32FileAccess.GenericRead);\n        var target = InternalGetTarget(handle);\n        if (target == null)\n        {\n            throw new IOException(\"Path is not a junction point.\");\n        }\n        return target;\n    }\n    \n    private static string? InternalGetTarget(SafeFileHandle handle)\n    {\n        var outBufferSize = Marshal.SizeOf(typeof(ReparseDataBuffer));\n        var outBuffer = Marshal.AllocHGlobal(outBufferSize);\n\n        try\n        {\n            var result = DeviceIoControl(\n                handle, \n                (uint) DeviceIoControlCode.GetReparsePoint,\n                IntPtr.Zero, \n                0, \n                outBuffer, \n                (uint) outBufferSize, \n                out var bytesReturned, \n                IntPtr.Zero);\n\n            Debug.WriteLine($\"bytesReturned: {bytesReturned}\");\n            \n            // Errors\n            if (!result)\n            {\n                var error = Marshal.GetLastWin32Error();\n                if (error == (int) Win32ErrorCode.NotAReparsePoint)\n                {\n                    return null;\n                }\n                else\n                {\n                    ThrowLastWin32Error(\"Unable to get information about junction point.\");\n                }\n            }\n\n            // Check output\n            if (outBuffer == IntPtr.Zero) return null;\n            // Safe interpret as ReparseDataBuffer type\n            if (Marshal.PtrToStructure(outBuffer, typeof(ReparseDataBuffer))\n                is not ReparseDataBuffer reparseDataBuffer)\n            {\n                return null;\n            }\n\n            // Check if it's a mount point\n            if (reparseDataBuffer.ReparseTag != (uint) DeviceIoControlCode.ReparseTagMountPoint)\n            {\n                return null;\n            }\n\n            // Get the target dir string\n            var targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer,\n                reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength);\n\n            if (targetDir.StartsWith(NonInterpretedPathPrefix))\n            {\n                targetDir = targetDir[NonInterpretedPathPrefix.Length..];\n            }\n\n            return targetDir;\n        }\n        finally\n        {\n            Marshal.FreeHGlobal(outBuffer);\n        }\n    }\n\n    private static SafeFileHandle OpenReparsePoint(string reparsePoint, Win32FileAccess accessMode)\n    {\n        var filePtr = CreateFile(\n            reparsePoint,\n            accessMode,\n            Win32FileShare.Read | Win32FileShare.Write | Win32FileShare.Delete,\n            IntPtr.Zero,\n            Win32CreationDisposition.OpenExisting,\n            Win32FileAttribute.FlagBackupSemantics | Win32FileAttribute.FlagOpenReparsePoint,\n            IntPtr.Zero);\n\n        var handle = new SafeFileHandle(filePtr, true);\n\n        if (Marshal.GetLastWin32Error() != 0)\n        {\n            ThrowLastWin32Error($\"Unable to open reparse point {reparsePoint}\");\n        }\n        \n        return handle;\n    }\n    \n    [DoesNotReturn]\n    private static void ThrowLastWin32Error(string message)\n    {\n        throw new IOException(message, Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/ReparsePoints/ReparseDataBuffer.cs",
    "content": "﻿using System.Runtime.InteropServices;\n\nnamespace StabilityMatrix.Core.ReparsePoints;\n\n/// <summary>\n/// Because the tag we're using is IO_REPARSE_TAG_MOUNT_POINT,\n/// we use the MountPointReparseBuffer struct in the DUMMYUNIONNAME union.\n/// </summary>\n[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]\npublic struct ReparseDataBuffer\n{\n    /// <summary>\n    /// Reparse point tag. Must be a Microsoft reparse point tag.\n    /// </summary>\n    public uint ReparseTag;\n    \n    /// <summary>\n    /// Size, in bytes, of the reparse data in the buffer that <see cref=\"PathBuffer\"/> points to.\n    /// This can be calculated by:\n    /// (4 * sizeof(ushort)) + SubstituteNameLength + PrintNameLength + \n    /// (namesAreNullTerminated ? 2 * sizeof(char) : 0);\n    /// </summary>\n    public ushort ReparseDataLength;\n    \n    /// <summary>\n    /// Reserved; do not use.\n    /// </summary>\n#pragma warning disable CS0169 // Field is never used\n    private ushort Reserved;\n#pragma warning restore CS0169 // Field is never used\n    \n    /// <summary>\n    /// Offset, in bytes, of the substitute name string in the <see cref=\"PathBuffer\"/> array.\n    /// </summary>\n    public ushort SubstituteNameOffset;\n    \n    /// <summary>\n    /// Length, in bytes, of the substitute name string. If this string is null-terminated,\n    /// <see cref=\"SubstituteNameLength\"/> does not include space for the null character.\n    /// </summary>\n    public ushort SubstituteNameLength;\n    \n    /// <summary>\n    /// Offset, in bytes, of the print name string in the <see cref=\"PathBuffer\"/> array.\n    /// </summary>\n    public ushort PrintNameOffset;\n    \n    /// <summary>\n    /// Length, in bytes, of the print name string. If this string is null-terminated,\n    /// <see cref=\"PrintNameLength\"/> does not include space for the null character.\n    /// </summary>\n    public ushort PrintNameLength;\n    \n    /// <summary>\n    /// A buffer containing the unicode-encoded path string. The path string contains\n    /// the substitute name string and print name string.\n    /// </summary>\n    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)]\n    public byte[] PathBuffer;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/ReparsePoints/Win32CreationDisposition.cs",
    "content": "﻿namespace StabilityMatrix.Core.ReparsePoints;\n\ninternal enum Win32CreationDisposition : uint\n{\n    New = 1,\n    CreateAlways = 2,\n    OpenExisting = 3,\n    OpenAlways = 4,\n    TruncateExisting = 5,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/ReparsePoints/Win32ErrorCode.cs",
    "content": "﻿namespace StabilityMatrix.Core.ReparsePoints;\n\ninternal enum Win32ErrorCode\n{\n    /// <summary>\n    /// The file or directory is not a reparse point.\n    /// ERROR_NOT_A_REPARSE_POINT\n    /// </summary>\n    NotAReparsePoint = 4390,\n\n    /// <summary>\n    /// The reparse point attribute cannot be set because it conflicts with an existing attribute.\n    /// ERROR_REPARSE_ATTRIBUTE_CONFLICT\n    /// </summary>\n    ReparseAttributeConflict = 4391,\n\n    /// <summary>\n    /// The data present in the reparse point buffer is invalid.\n    /// ERROR_INVALID_REPARSE_DATA\n    /// </summary>\n    InvalidReparseData = 4392,\n\n    /// <summary>\n    /// The tag present in the reparse point buffer is invalid.\n    /// ERROR_REPARSE_TAG_INVALID\n    /// </summary>\n    ReparseTagInvalid = 4393,\n\n    /// <summary>\n    /// There is a mismatch between the tag specified in the request and the tag present in the reparse point.\n    /// ERROR_REPARSE_TAG_MISMATCH\n    /// </summary>\n    ReparseTagMismatch = 4394,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/ReparsePoints/Win32FileAccess.cs",
    "content": "﻿namespace StabilityMatrix.Core.ReparsePoints;\n\n[Flags]\ninternal enum Win32FileAccess : uint\n{\n    GenericRead = 0x80000000U,\n    GenericWrite = 0x40000000U,\n    GenericExecute = 0x20000000U,\n    GenericAll = 0x10000000U\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/ReparsePoints/Win32FileAttribute.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\n\nnamespace StabilityMatrix.Core.ReparsePoints;\n\n[Flags]\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\ninternal enum Win32FileAttribute : uint\n{\n    AttributeReadOnly = 0x1U,\n    AttributeHidden = 0x2U,\n    AttributeSystem = 0x4U,\n    AttributeDirectory = 0x10U,\n    AttributeArchive = 0x20U,\n    AttributeDevice = 0x40U,\n    AttributeNormal = 0x80U,\n    AttributeTemporary = 0x100U,\n    AttributeSparseFile = 0x200U,\n    AttributeReparsePoint = 0x400U,\n    AttributeCompressed = 0x800U,\n    AttributeOffline = 0x1000U,\n    AttributeNotContentIndexed = 0x2000U,\n    AttributeEncrypted = 0x4000U,\n    AttributeIntegrityStream = 0x8000U,\n    AttributeVirtual = 0x10000U,\n    AttributeNoScrubData = 0x20000U,\n    AttributeEA = 0x40000U,\n    AttributeRecallOnOpen = 0x40000U,\n    AttributePinned = 0x80000U,\n    AttributeUnpinned = 0x100000U,\n    AttributeRecallOnDataAccess = 0x400000U,\n    FlagOpenNoRecall = 0x100000U,\n    /// <summary>\n    /// Normal reparse point processing will not occur; CreateFile will attempt to open the reparse point. When a file is opened, a file handle is returned,\n    /// whether or not the filter that controls the reparse point is operational.\n    /// <br />This flag cannot be used with the <see cref=\"FileMode.Create\"/> flag.\n    /// <br />If the file is not a reparse point, then this flag is ignored.\n    /// </summary>\n    FlagOpenReparsePoint = 0x200000U,\n    FlagSessionAware = 0x800000U,\n    FlagPosixSemantics = 0x1000000U,\n    /// <summary>\n    /// You must set this flag to obtain a handle to a directory. A directory handle can be passed to some functions instead of a file handle.\n    /// </summary>\n    FlagBackupSemantics = 0x2000000U,\n    FlagDeleteOnClose = 0x4000000U,\n    FlagSequentialScan = 0x8000000U,\n    FlagRandomAccess = 0x10000000U,\n    FlagNoBuffering = 0x20000000U,\n    FlagOverlapped = 0x40000000U,\n    FlagWriteThrough = 0x80000000U\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/ReparsePoints/Win32FileShare.cs",
    "content": "﻿namespace StabilityMatrix.Core.ReparsePoints;\n\n[Flags]\ninternal enum Win32FileShare : uint\n{\n    None = 0x00000000,\n    Read = 0x00000001,\n    Write = 0x00000002,\n    Delete = 0x00000004,\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/DownloadService.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Net;\nusing System.Net.Http.Headers;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing Polly.Contrib.WaitAndRetry;\nusing StabilityMatrix.Core.Exceptions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Services;\n\n[RegisterSingleton<IDownloadService, DownloadService>]\npublic class DownloadService : IDownloadService\n{\n    private readonly ILogger<DownloadService> logger;\n    private readonly IHttpClientFactory httpClientFactory;\n    private readonly ISecretsManager secretsManager;\n    private const int BufferSize = ushort.MaxValue;\n\n    public DownloadService(\n        ILogger<DownloadService> logger,\n        IHttpClientFactory httpClientFactory,\n        ISecretsManager secretsManager\n    )\n    {\n        this.logger = logger;\n        this.httpClientFactory = httpClientFactory;\n        this.secretsManager = secretsManager;\n    }\n\n    public async Task DownloadToFileAsync(\n        string downloadUrl,\n        string downloadPath,\n        IProgress<ProgressReport>? progress = null,\n        string? httpClientName = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        using var client = string.IsNullOrWhiteSpace(httpClientName)\n            ? httpClientFactory.CreateClient()\n            : httpClientFactory.CreateClient(httpClientName);\n\n        client.Timeout = TimeSpan.FromMinutes(10);\n        client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(\"StabilityMatrix\", \"2.0\"));\n\n        await AddConditionalHeaders(client, new Uri(downloadUrl)).ConfigureAwait(false);\n\n        await using var file = new FileStream(\n            downloadPath,\n            FileMode.Create,\n            FileAccess.Write,\n            FileShare.None\n        );\n\n        long contentLength = 0;\n\n        var response = await client\n            .GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken)\n            .ConfigureAwait(false);\n        contentLength = response.Content.Headers.ContentLength ?? 0;\n\n        var delays = Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromMilliseconds(50), retryCount: 3);\n\n        foreach (var delay in delays)\n        {\n            if (contentLength > 0)\n                break;\n            logger.LogDebug(\"Retrying get-headers for content-length\");\n            await Task.Delay(delay, cancellationToken).ConfigureAwait(false);\n            response = await client\n                .GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken)\n                .ConfigureAwait(false);\n            contentLength = response.Content.Headers.ContentLength ?? 0;\n        }\n        var isIndeterminate = contentLength == 0;\n\n        if (contentLength > 0)\n        {\n            // check free space\n            if (\n                SystemInfo.GetDiskFreeSpaceBytes(Path.GetDirectoryName(downloadPath)) is { } freeSpace\n                && freeSpace < contentLength\n            )\n            {\n                throw new ApplicationException(\n                    $\"Not enough free space to download file. Free: {freeSpace} bytes, Required: {contentLength} bytes\"\n                );\n            }\n        }\n\n        await using var stream = await response\n            .Content.ReadAsStreamAsync(cancellationToken)\n            .ConfigureAwait(false);\n        var stopwatch = Stopwatch.StartNew();\n        var totalBytesRead = 0L;\n        var buffer = new byte[BufferSize];\n        while (true)\n        {\n            var bytesRead = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);\n            if (bytesRead == 0)\n                break;\n            await file.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);\n\n            totalBytesRead += bytesRead;\n\n            var elapsedSeconds = stopwatch.Elapsed.TotalSeconds;\n            var speedInMBps = (totalBytesRead / elapsedSeconds) / (1024 * 1024);\n\n            if (isIndeterminate)\n            {\n                progress?.Report(new ProgressReport(-1, isIndeterminate: true));\n            }\n            else\n            {\n                progress?.Report(\n                    new ProgressReport(\n                        current: Convert.ToUInt64(totalBytesRead),\n                        total: Convert.ToUInt64(contentLength),\n                        message: \"Downloading...\",\n                        printToConsole: false,\n                        speedInMBps: speedInMBps\n                    )\n                );\n            }\n        }\n\n        await file.FlushAsync(cancellationToken).ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(1f, message: \"Download complete!\"));\n    }\n\n    /// <inheritdoc />\n    public async Task ResumeDownloadToFileAsync(\n        string downloadUrl,\n        string downloadPath,\n        long existingFileSize,\n        IProgress<ProgressReport>? progress = null,\n        string? httpClientName = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        using var client = string.IsNullOrWhiteSpace(httpClientName)\n            ? httpClientFactory.CreateClient()\n            : httpClientFactory.CreateClient(httpClientName);\n\n        using var noRedirectClient = httpClientFactory.CreateClient(\"DontFollowRedirects\");\n\n        client.Timeout = TimeSpan.FromMinutes(10);\n        client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(\"StabilityMatrix\", \"2.0\"));\n\n        await AddConditionalHeaders(client, new Uri(downloadUrl)).ConfigureAwait(false);\n        await AddConditionalHeaders(noRedirectClient, new Uri(downloadUrl)).ConfigureAwait(false);\n\n        // Create file if it doesn't exist\n        if (!File.Exists(downloadPath))\n        {\n            logger.LogInformation(\"Resume file doesn't exist, creating file {DownloadPath}\", downloadPath);\n            File.Create(downloadPath).Close();\n        }\n\n        await using var file = new FileStream(\n            downloadPath,\n            FileMode.Append,\n            FileAccess.Write,\n            FileShare.None\n        );\n\n        // Remaining content length\n        long remainingContentLength = 0;\n        // Total of the original content\n        long originalContentLength = 0;\n\n        using var noRedirectRequest = new HttpRequestMessage();\n        noRedirectRequest.Method = HttpMethod.Get;\n        noRedirectRequest.RequestUri = new Uri(downloadUrl);\n        noRedirectRequest.Headers.Range = new RangeHeaderValue(existingFileSize, null);\n\n        HttpResponseMessage? response = null;\n        foreach (\n            var delay in Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromMilliseconds(50), retryCount: 4)\n        )\n        {\n            var noRedirectResponse = await noRedirectClient\n                .SendAsync(noRedirectRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken)\n                .ConfigureAwait(false);\n\n            if ((int)noRedirectResponse.StatusCode > 299 && (int)noRedirectResponse.StatusCode < 400)\n            {\n                var redirectUrl = noRedirectResponse.Headers.Location?.ToString();\n                if (redirectUrl != null && redirectUrl.Contains(\"reason=download-auth\"))\n                {\n                    throw new UnauthorizedAccessException();\n                }\n            }\n            else if (noRedirectResponse.StatusCode == HttpStatusCode.Unauthorized)\n            {\n                if (\n                    noRedirectRequest.RequestUri?.Host.Equals(\n                        \"huggingface.co\",\n                        StringComparison.OrdinalIgnoreCase\n                    ) == true\n                )\n                {\n                    throw new HuggingFaceLoginRequiredException();\n                }\n                if (\n                    noRedirectRequest.RequestUri?.Host.Equals(\n                        \"civitai.com\",\n                        StringComparison.OrdinalIgnoreCase\n                    ) == true\n                )\n                {\n                    var responseContent = await noRedirectResponse\n                        .Content.ReadAsStringAsync(cancellationToken)\n                        .ConfigureAwait(false);\n                    if (responseContent.Contains(\"The creator of this asset has disabled downloads\"))\n                    {\n                        throw new CivitDownloadDisabledException();\n                    }\n\n                    throw new CivitLoginRequiredException();\n                }\n\n                throw new UnauthorizedAccessException();\n            }\n            else if (noRedirectResponse.StatusCode == HttpStatusCode.Forbidden)\n            {\n                throw new EarlyAccessException();\n            }\n\n            using var redirectRequest = new HttpRequestMessage();\n            redirectRequest.Method = HttpMethod.Get;\n            redirectRequest.RequestUri = new Uri(downloadUrl);\n            redirectRequest.Headers.Range = new RangeHeaderValue(existingFileSize, null);\n\n            response = await client\n                .SendAsync(redirectRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken)\n                .ConfigureAwait(false);\n\n            remainingContentLength = response.Content.Headers.ContentLength ?? 0;\n            originalContentLength = response.Content.Headers.ContentRange?.Length.GetValueOrDefault() ?? 0;\n\n            if (remainingContentLength > 0)\n                break;\n\n            logger.LogDebug(\"Retrying get-headers for content-length\");\n            await Task.Delay(delay, cancellationToken).ConfigureAwait(false);\n        }\n\n        if (response == null)\n        {\n            throw new ApplicationException(\"Response is null\");\n        }\n\n        var isIndeterminate = remainingContentLength == 0;\n\n        await using var stream = await response\n            .Content.ReadAsStreamAsync(cancellationToken)\n            .ConfigureAwait(false);\n        var totalBytesRead = 0L;\n        var stopwatch = Stopwatch.StartNew();\n        var buffer = new byte[BufferSize];\n        while (true)\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            var bytesRead = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);\n            if (bytesRead == 0)\n                break;\n            await file.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);\n\n            totalBytesRead += bytesRead;\n\n            var elapsedSeconds = stopwatch.Elapsed.TotalSeconds;\n            var speedInMBps = (totalBytesRead / elapsedSeconds) / (1024 * 1024);\n\n            if (isIndeterminate)\n            {\n                progress?.Report(new ProgressReport(-1, isIndeterminate: true));\n            }\n            else\n            {\n                progress?.Report(\n                    new ProgressReport(\n                        // Report the current as session current + original start size\n                        current: Convert.ToUInt64(totalBytesRead + existingFileSize),\n                        // Total as the original total\n                        total: Convert.ToUInt64(originalContentLength),\n                        message: \"Downloading...\",\n                        speedInMBps: speedInMBps\n                    )\n                );\n            }\n        }\n\n        await file.FlushAsync(cancellationToken).ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(1f, message: \"Download complete!\"));\n    }\n\n    /// <inheritdoc />\n    public async Task<long> GetFileSizeAsync(\n        string downloadUrl,\n        string? httpClientName = null,\n        CancellationToken cancellationToken = default\n    )\n    {\n        using var client = string.IsNullOrWhiteSpace(httpClientName)\n            ? httpClientFactory.CreateClient()\n            : httpClientFactory.CreateClient(httpClientName);\n\n        client.Timeout = TimeSpan.FromMinutes(10);\n        client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(\"StabilityMatrix\", \"2.0\"));\n\n        await AddConditionalHeaders(client, new Uri(downloadUrl)).ConfigureAwait(false);\n\n        var contentLength = 0L;\n\n        foreach (\n            var delay in Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromMilliseconds(50), retryCount: 3)\n        )\n        {\n            var response = await client\n                .GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken)\n                .ConfigureAwait(false);\n            contentLength = response.Content.Headers.ContentLength ?? -1;\n\n            if (contentLength > 0)\n                break;\n\n            logger.LogDebug(\"Retrying get-headers for content-length\");\n            await Task.Delay(delay, cancellationToken).ConfigureAwait(false);\n        }\n\n        return contentLength;\n    }\n\n    public async Task<Stream?> GetImageStreamFromUrl(string url)\n    {\n        using var client = httpClientFactory.CreateClient();\n        client.Timeout = TimeSpan.FromSeconds(10);\n        client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(\"StabilityMatrix\", \"2.0\"));\n        await AddConditionalHeaders(client, new Uri(url)).ConfigureAwait(false);\n        try\n        {\n            var response = await client.GetAsync(url).ConfigureAwait(false);\n            return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Failed to get image stream from url {Url}\", url);\n            return null;\n        }\n    }\n\n    public async Task<Stream> GetContentAsync(string url, CancellationToken cancellationToken = default)\n    {\n        using var client = httpClientFactory.CreateClient();\n        client.Timeout = TimeSpan.FromSeconds(10);\n        client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(\"StabilityMatrix\", \"2.0\"));\n\n        await AddConditionalHeaders(client, new Uri(url)).ConfigureAwait(false);\n\n        var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false);\n        return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);\n    }\n\n    /// <summary>\n    /// Adds conditional headers to the HttpClient for the given URL\n    /// </summary>\n    private async Task AddConditionalHeaders(HttpClient client, Uri url)\n    {\n        // Check if civit download\n        if (url.Host.Equals(\"civitai.com\", StringComparison.OrdinalIgnoreCase))\n        {\n            // Add auth if we have it\n            if (await secretsManager.SafeLoadAsync().ConfigureAwait(false) is { CivitApi: { } civitApi })\n            {\n                logger.LogTrace(\n                    \"Adding Civit auth header {Signature} for download {Url}\",\n                    ObjectHash.GetStringSignature(civitApi.ApiToken),\n                    url\n                );\n                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\n                    \"Bearer\",\n                    civitApi.ApiToken\n                );\n            }\n        }\n        // Check if Hugging Face download\n        else if (url.Host.Equals(\"huggingface.co\", StringComparison.OrdinalIgnoreCase))\n        {\n            var secrets = await secretsManager.SafeLoadAsync().ConfigureAwait(false);\n            if (!string.IsNullOrWhiteSpace(secrets.HuggingFaceToken))\n            {\n                logger.LogTrace(\"Adding Hugging Face auth header for download {Url}\", url);\n                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\n                    \"Bearer\",\n                    secrets.HuggingFaceToken\n                );\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/IDownloadService.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Services;\n\npublic interface IDownloadService\n{\n    Task DownloadToFileAsync(\n        string downloadUrl,\n        string downloadPath,\n        IProgress<ProgressReport>? progress = null,\n        string? httpClientName = null,\n        CancellationToken cancellationToken = default\n    );\n\n    Task ResumeDownloadToFileAsync(\n        string downloadUrl,\n        string downloadPath,\n        long existingFileSize,\n        IProgress<ProgressReport>? progress = null,\n        string? httpClientName = null,\n        CancellationToken cancellationToken = default\n    );\n\n    Task<long> GetFileSizeAsync(\n        string downloadUrl,\n        string? httpClientName = null,\n        CancellationToken cancellationToken = default\n    );\n\n    Task<Stream?> GetImageStreamFromUrl(string url);\n\n    Task<Stream> GetContentAsync(string url, CancellationToken cancellationToken = default);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/IImageIndexService.cs",
    "content": "﻿using DynamicData.Binding;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Core.Services;\n\npublic interface IImageIndexService\n{\n    IndexCollection<LocalImageFile, string> InferenceImages { get; }\n\n    /// <summary>\n    /// Refresh index for all collections\n    /// </summary>\n    Task RefreshIndexForAllCollections();\n\n    Task RefreshIndex(IndexCollection<LocalImageFile, string> indexCollection);\n\n    /// <summary>\n    /// Refreshes the index of local images in the background\n    /// </summary>\n    void BackgroundRefreshIndex();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/IMetadataImportService.cs",
    "content": "﻿using StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Services;\n\npublic interface IMetadataImportService\n{\n    Task ScanDirectoryForMissingInfo(DirectoryPath directory, IProgress<ProgressReport>? progress = null);\n\n    Task<ConnectedModelInfo?> GetMetadataForFile(\n        FilePath filePath,\n        IProgress<ProgressReport>? progress = null,\n        bool forceReimport = false\n    );\n\n    Task UpdateExistingMetadata(DirectoryPath directory, IProgress<ProgressReport>? progress = null);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/IModelIndexService.cs",
    "content": "﻿using StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Database;\n\nnamespace StabilityMatrix.Core.Services;\n\npublic interface IModelIndexService\n{\n    Dictionary<SharedFolderType, List<LocalModelFile>> ModelIndex { get; }\n\n    /// <summary>\n    /// Set of all <see cref=\"ModelIndex\"/> files Blake3 hashes.\n    /// Synchronized with internal changes to <see cref=\"ModelIndex\"/>.\n    /// </summary>\n    IReadOnlySet<string> ModelIndexBlake3Hashes { get; }\n\n    /// <summary>\n    /// Refreshes the local model file index.\n    /// </summary>\n    Task RefreshIndex();\n\n    /// <summary>\n    /// Starts a background task to refresh the local model file index.\n    /// </summary>\n    void BackgroundRefreshIndex();\n\n    /// <summary>\n    /// Get all models of the specified type from the existing (in-memory) index.\n    /// </summary>\n    IEnumerable<LocalModelFile> FindByModelType(SharedFolderType types);\n\n    /// <summary>\n    /// Gets all models in a hierarchical structure.\n    /// </summary>\n    Task<Dictionary<SharedFolderType, LocalModelFolder>> FindAllFolders();\n\n    /// <summary>\n    /// Find all models of the specified SharedFolderType.\n    /// </summary>\n    Task<IEnumerable<LocalModelFile>> FindByModelTypeAsync(SharedFolderType type);\n\n    /// <summary>\n    /// Find all models with the specified Blake3 hash.\n    /// </summary>\n    Task<IEnumerable<LocalModelFile>> FindByHashAsync(string hashBlake3);\n\n    /// <summary>\n    /// Find all models with the specified Sha256 hash\n    /// </summary>\n    Task<IEnumerable<LocalModelFile>> FindBySha256Async(string hashSha256);\n\n    /// <summary>\n    /// Remove a model from the index.\n    /// </summary>\n    Task<bool> RemoveModelAsync(LocalModelFile model);\n\n    Task<bool> RemoveModelsAsync(IEnumerable<LocalModelFile> models);\n\n    Task CheckModelsForUpdateAsync();\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/IPipWheelService.cs",
    "content": "﻿using StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Core.Services;\n\n/// <summary>\n/// Service for installing pip wheel packages from GitHub releases.\n/// All install methods are safe to call regardless of platform/GPU support -\n/// they will silently no-op if the package is not applicable.\n/// </summary>\npublic interface IPipWheelService\n{\n    /// <summary>\n    /// Installs Triton. Windows uses triton-windows, Linux uses triton.\n    /// No-ops on macOS.\n    /// </summary>\n    Task InstallTritonAsync(\n        IPyVenvRunner venv,\n        IProgress<ProgressReport>? progress = null,\n        string? version = null\n    );\n\n    /// <summary>\n    /// Installs SageAttention from pre-built wheels or source.\n    /// No-ops on macOS or non-NVIDIA GPUs.\n    /// </summary>\n    Task InstallSageAttentionAsync(\n        IPyVenvRunner venv,\n        GpuInfo? gpuInfo = null,\n        IProgress<ProgressReport>? progress = null,\n        string? version = null\n    );\n\n    /// <summary>\n    /// Installs Nunchaku from pre-built wheels.\n    /// No-ops on macOS or GPUs with compute capability &lt; 7.5.\n    /// </summary>\n    Task InstallNunchakuAsync(\n        IPyVenvRunner venv,\n        GpuInfo? gpuInfo = null,\n        IProgress<ProgressReport>? progress = null,\n        string? version = null\n    );\n\n    /// <summary>\n    /// Installs FlashAttention from pre-built wheels.\n    /// Windows only. No-ops on Linux/macOS.\n    /// </summary>\n    Task InstallFlashAttentionAsync(\n        IPyVenvRunner venv,\n        IProgress<ProgressReport>? progress = null,\n        string? version = null\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/ISecretsManager.cs",
    "content": "﻿using StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Core.Services;\n\n/// <summary>\n/// Interface for managing secure settings and tokens.\n/// </summary>\npublic interface ISecretsManager\n{\n    /// <summary>\n    /// Load and return the secrets.\n    /// </summary>\n    Task<Secrets> LoadAsync();\n\n    /// <summary>\n    /// Load and return the secrets, or save and return a new instance on error.\n    /// </summary>\n    Task<Secrets> SafeLoadAsync();\n\n    Task SaveAsync(Secrets secrets);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/ISettingsManager.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Linq.Expressions;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Settings;\n\nnamespace StabilityMatrix.Core.Services;\n\npublic interface ISettingsManager\n{\n    bool IsPortableMode { get; }\n    DirectoryPath LibraryDir { get; }\n    bool IsLibraryDirSet { get; }\n\n    string ModelsDirectory { get; }\n    string DownloadsDirectory { get; }\n    DirectoryPath TagsDirectory { get; }\n    DirectoryPath ImagesDirectory { get; }\n    DirectoryPath ImagesInferenceDirectory { get; }\n    DirectoryPath ConsolidatedImagesDirectory { get; }\n\n    Settings Settings { get; }\n\n    List<string> PackageInstallsInProgress { get; set; }\n    DirectoryPath WorkflowDirectory { get; }\n    DirectoryPath ExtensionPackDirectory { get; }\n\n    /// <summary>\n    /// Event fired when the library directory is changed\n    /// </summary>\n    event EventHandler<string>? LibraryDirChanged;\n\n    /// <summary>\n    /// Event fired when a property of Settings is changed\n    /// </summary>\n    event EventHandler<RelayPropertyChangedEventArgs>? SettingsPropertyChanged;\n\n    /// <summary>\n    /// Event fired when Settings are loaded from disk\n    /// </summary>\n    event EventHandler? Loaded;\n\n    /// <summary>\n    /// Set an override for the library directory.\n    /// </summary>\n    void SetLibraryDirOverride(DirectoryPath path);\n\n    /// <summary>\n    /// Register a handler that fires once when LibraryDir is first set.\n    /// Will fire instantly if it is already set.\n    /// </summary>\n    void RegisterOnLibraryDirSet(Action<string> handler);\n\n    /// <summary>\n    /// Return a SettingsTransaction that can be used to modify Settings\n    /// Saves on Dispose.\n    /// </summary>\n    SettingsTransaction BeginTransaction();\n\n    /// <summary>\n    /// Execute a function that modifies Settings\n    /// Commits changes after the function returns.\n    /// </summary>\n    /// <param name=\"func\">Function accepting Settings to modify</param>\n    /// <param name=\"ignoreMissingLibraryDir\">Ignore missing library dir when committing changes</param>\n    void Transaction(Action<Settings> func, bool ignoreMissingLibraryDir = false);\n\n    /// <summary>\n    /// Modify a settings property by expression and commit changes.\n    /// This will notify listeners of SettingsPropertyChanged.\n    /// </summary>\n    void Transaction<TValue>(Expression<Func<Settings, TValue>> expression, TValue value);\n\n    /// <summary>\n    /// Register a source observable object and property to be relayed to Settings\n    /// </summary>\n    IDisposable RelayPropertyFor<T, TValue>(\n        T source,\n        Expression<Func<T, TValue>> sourceProperty,\n        Expression<Func<Settings, TValue>> settingsProperty,\n        bool setInitial = false,\n        TimeSpan? delay = null\n    )\n        where T : INotifyPropertyChanged;\n\n    /// <summary>\n    /// Register an Action to be called on change of the settings property.\n    /// </summary>\n    IDisposable RegisterPropertyChangedHandler<T>(\n        Expression<Func<Settings, T>> settingsProperty,\n        Action<T> onPropertyChanged\n    );\n\n    /// <summary>\n    /// Creates an observable sequence that notifies when the specified settings property changes.\n    /// Emits the initial value upon subscription and subsequent changes.\n    /// </summary>\n    /// <typeparam name=\"T\">The type of the property value.</typeparam>\n    /// <param name=\"settingsProperty\">An expression representing the settings property to observe.</param>\n    /// <returns>An observable sequence of the property's values.</returns>\n    IObservable<T> ObservePropertyChanged<T>(Expression<Func<Settings, T>> settingsProperty);\n\n    /// <summary>\n    /// Attempts to locate and set the library path\n    /// Return true if found, false otherwise\n    /// </summary>\n    /// <param name=\"forceReload\">Force reload even if library is already set</param>\n    bool TryFindLibrary(bool forceReload = false);\n\n    /// <summary>\n    /// Save a new library path to %APPDATA%/StabilityMatrix/library.json\n    /// </summary>\n    void SetLibraryPath(string path);\n\n    /// <summary>\n    /// Enable and create settings files for portable mode\n    /// Creates the ./Data directory and the `.sm-portable` marker file\n    /// </summary>\n    void SetPortableMode();\n\n    void SaveLaunchArgs(Guid packageId, IEnumerable<LaunchOption> launchArgs);\n    bool IsEulaAccepted();\n    void SetEulaAccepted();\n\n    /// <summary>\n    /// Cancels any scheduled delayed save of settings and flushes immediately.\n    /// </summary>\n    Task FlushAsync(CancellationToken cancellationToken);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/ITrackedDownloadService.cs",
    "content": "﻿using StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Core.Services;\n\npublic interface ITrackedDownloadService\n{\n    IEnumerable<TrackedDownload> Downloads { get; }\n\n    event EventHandler<TrackedDownload>? DownloadAdded;\n\n    TrackedDownload NewDownload(Uri downloadUrl, FilePath downloadPath);\n\n    TrackedDownload NewDownload(string downloadUrl, FilePath downloadPath) =>\n        NewDownload(new Uri(downloadUrl), downloadPath);\n    Task TryStartDownload(TrackedDownload download);\n    Task TryResumeDownload(TrackedDownload download);\n    void UpdateMaxConcurrentDownloads(int newMax);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/ImageIndexService.cs",
    "content": "﻿using System.Collections.Concurrent;\nusing System.Diagnostics;\nusing AsyncAwaitBestPractices;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Core.Services;\n\n[RegisterSingleton<IImageIndexService, ImageIndexService>]\npublic class ImageIndexService : IImageIndexService\n{\n    private readonly ILogger<ImageIndexService> logger;\n    private readonly ISettingsManager settingsManager;\n\n    /// <inheritdoc />\n    public IndexCollection<LocalImageFile, string> InferenceImages { get; }\n\n    public ImageIndexService(ILogger<ImageIndexService> logger, ISettingsManager settingsManager)\n    {\n        this.logger = logger;\n        this.settingsManager = settingsManager;\n\n        InferenceImages = new IndexCollection<LocalImageFile, string>(this, file => file.AbsolutePath)\n        {\n            RelativePath = \"Inference\"\n        };\n\n        EventManager.Instance.ImageFileAdded += OnImageFileAdded;\n    }\n\n    public Task RefreshIndexForAllCollections()\n    {\n        return RefreshIndex(InferenceImages);\n    }\n\n    public async Task RefreshIndex(IndexCollection<LocalImageFile, string> indexCollection)\n    {\n        if (indexCollection.RelativePath is not { } subPath)\n            return;\n\n        var imagesDir = settingsManager.ImagesDirectory;\n        var searchDir = imagesDir.JoinDir(indexCollection.RelativePath);\n        if (!searchDir.Exists)\n        {\n            return;\n        }\n\n        // Start\n        var stopwatch = Stopwatch.StartNew();\n        logger.LogInformation(\"Refreshing images index at {SearchDir}...\", searchDir.ToString());\n\n        var errors = 0;\n        var toAdd = new ConcurrentBag<LocalImageFile>();\n\n        await Task.Run(() =>\n            {\n                var files = searchDir\n                    .EnumerateFiles(\"*\", EnumerationOptionConstants.AllDirectories)\n                    .Where(file => LocalImageFile.SupportedImageExtensions.Contains(file.Extension));\n\n                Parallel.ForEach(\n                    files,\n                    f =>\n                    {\n                        try\n                        {\n                            toAdd.Add(LocalImageFile.FromPath(f));\n                        }\n                        catch (Exception e)\n                        {\n                            Interlocked.Increment(ref errors);\n                            logger.LogWarning(\n                                e,\n                                \"Failed to add indexed image file at {Path}, skipping\",\n                                f.FullPath\n                            );\n                        }\n                    }\n                );\n            })\n            .ConfigureAwait(false);\n\n        var indexElapsed = stopwatch.Elapsed;\n\n        indexCollection.ItemsSource.EditDiff(toAdd);\n\n        // End\n        stopwatch.Stop();\n        var editElapsed = stopwatch.Elapsed - indexElapsed;\n\n        logger.LogInformation(\n            \"Image index updated for {Prefix} with ({Added}/{Total}) files, took {IndexDuration:F1}ms ({EditDuration:F1}ms edit)\",\n            subPath,\n            toAdd.Count,\n            toAdd.Count + errors,\n            indexElapsed.TotalMilliseconds,\n            editElapsed.TotalMilliseconds\n        );\n    }\n\n    private void OnImageFileAdded(object? sender, FilePath filePath)\n    {\n        var imagesFolder = settingsManager.ImagesDirectory.JoinDir(InferenceImages.RelativePath!);\n\n        if (string.IsNullOrEmpty(Path.GetRelativePath(imagesFolder, filePath)))\n        {\n            logger.LogWarning(\n                \"Image file {Path} added outside of relative directory {DirPath}, skipping\",\n                filePath,\n                imagesFolder\n            );\n            return;\n        }\n\n        try\n        {\n            InferenceImages.Add(LocalImageFile.FromPath(filePath));\n        }\n        catch (Exception e)\n        {\n            logger.LogWarning(e, \"Failed to add image file at {Path}\", filePath);\n        }\n    }\n\n    /// <inheritdoc />\n    public void BackgroundRefreshIndex()\n    {\n        RefreshIndexForAllCollections().SafeFireAndForget();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/MetadataImportService.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Text.Json;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Services;\n\n[RegisterTransient<IMetadataImportService, MetadataImportService>]\npublic class MetadataImportService(\n    ILogger<MetadataImportService> logger,\n    IDownloadService downloadService,\n    ModelFinder modelFinder\n) : IMetadataImportService\n{\n    public async Task ScanDirectoryForMissingInfo(\n        DirectoryPath directory,\n        IProgress<ProgressReport>? progress = null\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, message: \"Scanning directory...\", isIndeterminate: true));\n\n        var checkpointsWithoutMetadata = directory\n            .EnumerateFiles(\"*\", EnumerationOptionConstants.AllDirectories)\n            .Where(FileHasNoCmInfo)\n            .ToList();\n\n        var scanned = 0;\n        var success = 0;\n\n        foreach (var checkpointFilePath in checkpointsWithoutMetadata)\n        {\n            if (scanned == 0)\n            {\n                progress?.Report(\n                    new ProgressReport(\n                        current: scanned,\n                        total: checkpointsWithoutMetadata.Count,\n                        message: \"Scanning directory...\"\n                    )\n                );\n            }\n            else\n            {\n                progress?.Report(\n                    new ProgressReport(\n                        current: scanned,\n                        total: checkpointsWithoutMetadata.Count,\n                        message: $\"{success} files imported successfully\"\n                    )\n                );\n            }\n\n            var fileNameWithoutExtension = checkpointFilePath.NameWithoutExtension;\n            var cmInfoPath = checkpointFilePath.Directory?.JoinFile(\n                $\"{fileNameWithoutExtension}.cm-info.json\"\n            );\n            var cmInfoExists = File.Exists(cmInfoPath);\n            if (cmInfoExists)\n                continue;\n\n            var hashProgress = new Progress<ProgressReport>(report =>\n            {\n                progress?.Report(\n                    new ProgressReport(\n                        current: report.Current ?? 0,\n                        total: report.Total ?? 0,\n                        message: $\"Scanning file {scanned}/{checkpointsWithoutMetadata.Count} ... {report.Percentage}%\",\n                        printToConsole: false\n                    )\n                );\n            });\n\n            try\n            {\n                var blake3 = await GetBlake3Hash(cmInfoPath, checkpointFilePath, hashProgress)\n                    .ConfigureAwait(false);\n                if (string.IsNullOrWhiteSpace(blake3))\n                {\n                    logger.LogWarning($\"Blake3 hash was null for {checkpointFilePath}\");\n                    scanned++;\n                    continue;\n                }\n\n                var modelInfo = await modelFinder.RemoteFindModel(blake3).ConfigureAwait(false);\n                if (modelInfo == null)\n                {\n                    logger.LogWarning($\"Could not find model for {blake3}\");\n                    scanned++;\n                    continue;\n                }\n\n                var (model, modelVersion, modelFile) = modelInfo.Value;\n\n                var updatedCmInfo = new ConnectedModelInfo(\n                    model,\n                    modelVersion,\n                    modelFile,\n                    DateTimeOffset.UtcNow\n                );\n                await updatedCmInfo\n                    .SaveJsonToDirectory(checkpointFilePath.Directory, fileNameWithoutExtension)\n                    .ConfigureAwait(false);\n\n                var image = modelVersion.Images?.FirstOrDefault(\n                    img =>\n                        LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))\n                        && img.Type == \"image\"\n                );\n                if (image == null)\n                {\n                    scanned++;\n                    success++;\n                    continue;\n                }\n\n                await DownloadImage(image, checkpointFilePath, progress).ConfigureAwait(false);\n\n                scanned++;\n                success++;\n            }\n            catch (Exception e)\n            {\n                logger.LogError(e, \"Error while scanning {checkpointFilePath}\", checkpointFilePath);\n                scanned++;\n            }\n        }\n\n        progress?.Report(\n            new ProgressReport(\n                current: scanned,\n                total: checkpointsWithoutMetadata.Count,\n                message: $\"Metadata found for {success}/{checkpointsWithoutMetadata.Count} files\"\n            )\n        );\n    }\n\n    private static bool FileHasNoCmInfo(FilePath file)\n    {\n        return LocalModelFile.SupportedCheckpointExtensions.Contains(file.Extension)\n            && !File.Exists(file.Directory?.JoinFile($\"{file.NameWithoutExtension}.cm-info.json\"));\n    }\n\n    public async Task UpdateExistingMetadata(\n        DirectoryPath directory,\n        IProgress<ProgressReport>? progress = null\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, message: \"Scanning directory...\", isIndeterminate: true));\n\n        var cmInfoList = new Dictionary<FilePath, ConnectedModelInfo>();\n        foreach (\n            var cmInfoPath in directory.EnumerateFiles(\n                \"*.cm-info.json\",\n                EnumerationOptionConstants.AllDirectories\n            )\n        )\n        {\n            ConnectedModelInfo? cmInfo;\n            try\n            {\n                cmInfo = JsonSerializer.Deserialize<ConnectedModelInfo>(\n                    await cmInfoPath.ReadAllTextAsync().ConfigureAwait(false)\n                );\n            }\n            catch (JsonException)\n            {\n                cmInfo = null;\n            }\n            if (cmInfo == null)\n                continue;\n\n            cmInfoList.Add(cmInfoPath, cmInfo);\n        }\n\n        var success = 1;\n        foreach (var (filePath, cmInfoValue) in cmInfoList)\n        {\n            progress?.Report(\n                new ProgressReport(\n                    current: success,\n                    total: cmInfoList.Count,\n                    message: $\"Updating metadata {success}/{cmInfoList.Count}\"\n                )\n            );\n\n            try\n            {\n                var hash = cmInfoValue.Hashes.BLAKE3;\n                if (string.IsNullOrWhiteSpace(hash))\n                    continue;\n\n                var modelInfo = await modelFinder.RemoteFindModel(hash).ConfigureAwait(false);\n                if (modelInfo == null)\n                {\n                    logger.LogWarning($\"Could not find model for {hash}\");\n                    continue;\n                }\n\n                var (model, modelVersion, modelFile) = modelInfo.Value;\n\n                var updatedCmInfo = new ConnectedModelInfo(\n                    model,\n                    modelVersion,\n                    modelFile,\n                    DateTimeOffset.UtcNow\n                );\n\n                var nameWithoutCmInfo = filePath.NameWithoutExtension.Replace(\".cm-info\", string.Empty);\n                await updatedCmInfo\n                    .SaveJsonToDirectory(filePath.Directory, nameWithoutCmInfo)\n                    .ConfigureAwait(false);\n\n                var image = modelVersion.Images?.FirstOrDefault(\n                    img =>\n                        LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))\n                        && img.Type == \"image\"\n                );\n                if (image == null)\n                    continue;\n\n                await DownloadImage(image, filePath, progress).ConfigureAwait(false);\n\n                success++;\n            }\n            catch (Exception e)\n            {\n                logger.LogError(e, \"Error while updating {filePath}\", filePath);\n            }\n        }\n    }\n\n    public async Task<ConnectedModelInfo?> GetMetadataForFile(\n        FilePath filePath,\n        IProgress<ProgressReport>? progress = null,\n        bool forceReimport = false\n    )\n    {\n        progress?.Report(new ProgressReport(-1f, message: \"Getting metadata...\", isIndeterminate: true));\n\n        var fileNameWithoutExtension = filePath.NameWithoutExtension;\n        var cmInfoPath = filePath.Directory?.JoinFile($\"{fileNameWithoutExtension}.cm-info.json\");\n        var cmInfoExists = File.Exists(cmInfoPath);\n        if (cmInfoExists && !forceReimport)\n            return null;\n\n        var hashProgress = new Progress<ProgressReport>(report =>\n        {\n            progress?.Report(\n                new ProgressReport(\n                    current: report.Current ?? 0,\n                    total: report.Total ?? 0,\n                    message: $\"Getting metadata for {fileNameWithoutExtension} ... {report.Percentage}%\",\n                    printToConsole: false\n                )\n            );\n        });\n        var blake3 = await GetBlake3Hash(cmInfoPath, filePath, hashProgress).ConfigureAwait(false);\n\n        if (string.IsNullOrWhiteSpace(blake3))\n        {\n            logger.LogWarning($\"Blake3 hash was null for {filePath}\");\n            return null;\n        }\n\n        var modelInfo = await modelFinder.RemoteFindModel(blake3).ConfigureAwait(false);\n        if (modelInfo == null)\n        {\n            logger.LogWarning($\"Could not find model for {blake3}\");\n            return null;\n        }\n\n        var (model, modelVersion, modelFile) = modelInfo.Value;\n\n        var updatedCmInfo = new ConnectedModelInfo(model, modelVersion, modelFile, DateTimeOffset.UtcNow);\n        await updatedCmInfo\n            .SaveJsonToDirectory(filePath.Directory, fileNameWithoutExtension)\n            .ConfigureAwait(false);\n\n        var image = modelVersion.Images?.FirstOrDefault(\n            img =>\n                LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))\n                && img.Type == \"image\"\n        );\n\n        if (image == null)\n            return updatedCmInfo;\n\n        var imagePath = await DownloadImage(image, filePath, progress).ConfigureAwait(false);\n        updatedCmInfo.ThumbnailImageUrl = imagePath;\n\n        return updatedCmInfo;\n    }\n\n    private static async Task<string?> GetBlake3Hash(\n        FilePath? cmInfoPath,\n        FilePath checkpointFilePath,\n        IProgress<ProgressReport> hashProgress\n    )\n    {\n        if (string.IsNullOrWhiteSpace(cmInfoPath?.ToString()) || !File.Exists(cmInfoPath))\n        {\n            return await FileHash.GetBlake3Async(checkpointFilePath, hashProgress).ConfigureAwait(false);\n        }\n\n        var cmInfo = JsonSerializer.Deserialize<ConnectedModelInfo>(\n            await cmInfoPath.ReadAllTextAsync().ConfigureAwait(false)\n        );\n        return cmInfo?.Hashes.BLAKE3;\n    }\n\n    private async Task<string> DownloadImage(\n        CivitImage image,\n        FilePath modelFilePath,\n        IProgress<ProgressReport>? progress\n    )\n    {\n        var imageExt = Path.GetExtension(image.Url).TrimStart('.');\n        var nameWithoutCmInfo = modelFilePath.NameWithoutExtension.Replace(\".cm-info\", string.Empty);\n        var imageDownloadPath = Path.GetFullPath(\n            Path.Combine(modelFilePath.Directory, $\"{nameWithoutCmInfo}.preview.{imageExt}\")\n        );\n        await downloadService\n            .DownloadToFileAsync(image.Url, imageDownloadPath, progress)\n            .ConfigureAwait(false);\n\n        return imageDownloadPath;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/ModelIndexService.cs",
    "content": "﻿using System.Collections.Concurrent;\nusing System.Collections.Immutable;\nusing System.Diagnostics;\nusing System.Text;\nusing AsyncAwaitBestPractices;\nusing AutoCtor;\nusing Injectio.Attributes;\nusing KGySoft.CoreLibraries;\nusing LiteDB;\nusing LiteDB.Async;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Database;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing JsonSerializer = System.Text.Json.JsonSerializer;\n\nnamespace StabilityMatrix.Core.Services;\n\n[RegisterSingleton<IModelIndexService, ModelIndexService>]\n[AutoConstruct]\npublic partial class ModelIndexService : IModelIndexService\n{\n    private readonly ILogger<ModelIndexService> logger;\n    private readonly ISettingsManager settingsManager;\n    private readonly ILiteDbContext liteDbContext;\n    private readonly ModelFinder modelFinder;\n\n    private DateTimeOffset lastUpdateCheck = DateTimeOffset.MinValue;\n\n    private Dictionary<SharedFolderType, List<LocalModelFile>> _modelIndex = new();\n\n    private HashSet<string>? _modelIndexBlake3Hashes;\n\n    /// <summary>\n    /// Whether the database has been initially loaded.\n    /// </summary>\n    private bool IsDbLoaded { get; set; }\n\n    public Dictionary<SharedFolderType, List<LocalModelFile>> ModelIndex\n    {\n        get => _modelIndex;\n        private set\n        {\n            _modelIndex = value;\n            OnModelIndexReset();\n        }\n    }\n\n    public IReadOnlySet<string> ModelIndexBlake3Hashes =>\n        _modelIndexBlake3Hashes ??= CollectModelHashes(ModelIndex.Values.SelectMany(x => x));\n\n    [AutoPostConstruct]\n    private void Initialize()\n    {\n        // Start background index when library dir is set\n        settingsManager.RegisterOnLibraryDirSet(_ =>\n        {\n            // Skip if already loaded\n            if (IsDbLoaded)\n            {\n                return;\n            }\n\n            Task.Run(async () =>\n                {\n                    // Build db indexes\n                    await liteDbContext\n                        .LocalModelFiles.EnsureIndexAsync(m => m.HashBlake3)\n                        .ConfigureAwait(false);\n                    await liteDbContext\n                        .LocalModelFiles.EnsureIndexAsync(m => m.SharedFolderType)\n                        .ConfigureAwait(false);\n\n                    // Load models first from db, then do index refresh\n                    await EnsureLoadedAsync().ConfigureAwait(false);\n\n                    await RefreshIndex().ConfigureAwait(false);\n                })\n                .SafeFireAndForget(ex =>\n                {\n                    logger.LogError(ex, \"Error loading model index\");\n                });\n        });\n    }\n\n    // Ensure the in memory cache is loaded\n    private async Task EnsureLoadedAsync()\n    {\n        if (!IsDbLoaded)\n        {\n            await LoadFromDbAsync().ConfigureAwait(false);\n        }\n    }\n\n    /// <summary>\n    /// Populates <see cref=\"ModelIndex\"/> from the database.\n    /// </summary>\n    private async Task LoadFromDbAsync()\n    {\n        var timer = Stopwatch.StartNew();\n\n        logger.LogInformation(\"Loading models from database...\");\n\n        // Handle enum deserialize exceptions from changes\n        var allModels = await liteDbContext\n            .TryQueryWithClearOnExceptionAsync(\n                liteDbContext.LocalModelFiles,\n                liteDbContext.LocalModelFiles.IncludeAll().FindAllAsync()\n            )\n            .ConfigureAwait(false);\n\n        if (allModels is not null)\n        {\n            ModelIndex = allModels.GroupBy(m => m.SharedFolderType).ToDictionary(g => g.Key, g => g.ToList());\n        }\n        else\n        {\n            ModelIndex.Clear();\n        }\n\n        IsDbLoaded = true;\n        EventManager.Instance.OnModelIndexChanged();\n\n        timer.Stop();\n        logger.LogInformation(\n            \"Loaded {Count} models from database in {Time:F2}ms\",\n            ModelIndex.Count,\n            timer.Elapsed.TotalMilliseconds\n        );\n    }\n\n    /// <inheritdoc />\n    public async Task<Dictionary<SharedFolderType, LocalModelFolder>> FindAllFolders()\n    {\n        var modelFiles = await liteDbContext.LocalModelFiles.FindAllAsync().ConfigureAwait(false);\n\n        var rootFolders = new Dictionary<SharedFolderType, LocalModelFolder>();\n\n        foreach (var modelFile in modelFiles)\n        {\n            var pathParts = modelFile.RelativePath.Split(Path.DirectorySeparatorChar);\n            var currentFolder = rootFolders.GetOrAdd(\n                modelFile.SharedFolderType,\n                _ => new LocalModelFolder { RelativePath = pathParts[0] }\n            );\n            for (var i = 1; i < pathParts.Length - 1; i++)\n            {\n                var folderName = pathParts[i];\n                var folder = currentFolder.Folders.GetValueOrDefault(folderName);\n                if (folder == null)\n                {\n                    folder = new LocalModelFolder { RelativePath = folderName };\n                    currentFolder.Folders[folderName] = folder;\n                }\n\n                currentFolder = folder;\n            }\n\n            currentFolder.Files[modelFile.RelativePath] = modelFile;\n        }\n\n        return rootFolders;\n    }\n\n    /// <inheritdoc />\n    public IEnumerable<LocalModelFile> FindByModelType(SharedFolderType types)\n    {\n        return ModelIndex.Where(kvp => (kvp.Key & types) != 0).SelectMany(kvp => kvp.Value);\n    }\n\n    /// <inheritdoc />\n    public Task<IEnumerable<LocalModelFile>> FindByModelTypeAsync(SharedFolderType type)\n    {\n        // To list of types\n        var types = Enum.GetValues<SharedFolderType>()\n            .Where(folderType => type.HasFlag(folderType))\n            .ToArray();\n\n        return types.Length switch\n        {\n            0 => Task.FromResult(Enumerable.Empty<LocalModelFile>()),\n            1 => liteDbContext.LocalModelFiles.FindAsync(m => m.SharedFolderType == type),\n            _ => liteDbContext.LocalModelFiles.FindAsync(m => types.Contains(m.SharedFolderType)),\n        };\n    }\n\n    /// <inheritdoc />\n    public Task<IEnumerable<LocalModelFile>> FindByHashAsync(string hashBlake3)\n    {\n        return liteDbContext.LocalModelFiles.FindAsync(m => m.HashBlake3 == hashBlake3);\n    }\n\n    public Task<IEnumerable<LocalModelFile>> FindBySha256Async(string hashSha256)\n    {\n        return liteDbContext.LocalModelFiles.FindAsync(m => m.HashSha256 == hashSha256);\n    }\n\n    /// <inheritdoc />\n    public Task RefreshIndex()\n    {\n        return RefreshIndexParallelCore();\n    }\n\n    private async Task RefreshIndexCore()\n    {\n        if (!settingsManager.IsLibraryDirSet)\n        {\n            logger.LogTrace(\"Model index refresh skipped, library directory not set\");\n            return;\n        }\n\n        if (new DirectoryPath(settingsManager.ModelsDirectory) is not { Exists: true } modelsDir)\n        {\n            logger.LogTrace(\"Model index refresh skipped, model directory does not exist\");\n            return;\n        }\n\n        logger.LogInformation(\"Refreshing model index...\");\n\n        // Start\n        var stopwatch = Stopwatch.StartNew();\n\n        var newIndex = new Dictionary<SharedFolderType, List<LocalModelFile>>();\n        var newIndexFlat = new List<LocalModelFile>();\n\n        var paths = Directory\n            .EnumerateFiles(modelsDir, \"*\", EnumerationOptionConstants.AllDirectories)\n            .ToHashSet();\n\n        foreach (var path in paths)\n        {\n            // Skip if not supported extension\n            if (!LocalModelFile.SupportedCheckpointExtensions.Contains(Path.GetExtension(path)))\n            {\n                continue;\n            }\n\n            var relativePath = Path.GetRelativePath(modelsDir, path);\n\n            // Get shared folder name\n            var sharedFolderName = relativePath.Split(\n                Path.DirectorySeparatorChar,\n                StringSplitOptions.RemoveEmptyEntries\n            )[0];\n            // Try Convert to enum\n            if (!Enum.TryParse<SharedFolderType>(sharedFolderName, out var sharedFolderType))\n            {\n                continue;\n            }\n\n            // Since RelativePath is the database key, for LiteDB this is limited to 1021 bytes\n            if (Encoding.UTF8.GetByteCount(relativePath) is var byteCount and > 1021)\n            {\n                logger.LogWarning(\n                    \"Skipping model {Path} because it's path is too long ({Length} bytes)\",\n                    relativePath,\n                    byteCount\n                );\n\n                continue;\n            }\n\n            var localModel = new LocalModelFile\n            {\n                RelativePath = relativePath,\n                SharedFolderType = sharedFolderType,\n            };\n\n            // Try to find a connected model info\n            var fileDirectory = new DirectoryPath(Path.GetDirectoryName(path)!);\n            var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);\n            var jsonPath = fileDirectory.JoinFile($\"{fileNameWithoutExtension}.cm-info.json\");\n\n            if (paths.Contains(jsonPath))\n            {\n                try\n                {\n                    await using var stream = jsonPath.Info.OpenRead();\n\n                    var connectedModelInfo = await JsonSerializer\n                        .DeserializeAsync(\n                            stream,\n                            ConnectedModelInfoSerializerContext.Default.ConnectedModelInfo\n                        )\n                        .ConfigureAwait(false);\n\n                    localModel.ConnectedModelInfo = connectedModelInfo;\n                }\n                catch (Exception e)\n                {\n                    logger.LogWarning(\n                        e,\n                        \"Failed to deserialize connected model info for {Path}, skipping\",\n                        jsonPath\n                    );\n                }\n            }\n\n            // Try to find a preview image\n            var previewImagePath = LocalModelFile\n                .SupportedImageExtensions.Select(ext =>\n                    fileDirectory.JoinFile($\"{fileNameWithoutExtension}.preview{ext}\")\n                )\n                .FirstOrDefault(filePath => paths.Contains(filePath));\n\n            if (previewImagePath is not null)\n            {\n                localModel.PreviewImageRelativePath = Path.GetRelativePath(modelsDir, previewImagePath);\n            }\n\n            // Try to find a config file (same name as model file but with .yaml extension)\n            var configFile = fileDirectory.JoinFile($\"{fileNameWithoutExtension}.yaml\");\n            if (paths.Contains(configFile))\n            {\n                localModel.ConfigFullPath = configFile;\n            }\n\n            // Add to index\n            newIndexFlat.Add(localModel);\n            var list = newIndex.GetOrAdd(sharedFolderType);\n            list.Add(localModel);\n        }\n\n        ModelIndex = newIndex;\n\n        stopwatch.Stop();\n        var indexTime = stopwatch.Elapsed;\n\n        // Insert to db as transaction\n        stopwatch.Restart();\n\n        using var db = await liteDbContext.Database.BeginTransactionAsync().ConfigureAwait(false);\n\n        var localModelFiles = db.GetCollection<LocalModelFile>(\"LocalModelFiles\")!;\n\n        await localModelFiles.DeleteAllAsync().ConfigureAwait(false);\n        await localModelFiles.InsertBulkAsync(newIndexFlat).ConfigureAwait(false);\n\n        await db.CommitAsync().ConfigureAwait(false);\n\n        stopwatch.Stop();\n        var dbTime = stopwatch.Elapsed;\n\n        logger.LogInformation(\n            \"Model index refreshed with {Entries} entries, took (index: {IndexDuration}), (db: {DbDuration})\",\n            newIndexFlat.Count,\n            CodeTimer.FormatTime(indexTime),\n            CodeTimer.FormatTime(dbTime)\n        );\n\n        EventManager.Instance.OnModelIndexChanged();\n    }\n\n    private async Task RefreshIndexParallelCore()\n    {\n        if (!settingsManager.IsLibraryDirSet)\n        {\n            logger.LogTrace(\"Model index refresh skipped, library directory not set\");\n            return;\n        }\n\n        if (new DirectoryPath(settingsManager.ModelsDirectory) is not { Exists: true } modelsDir)\n        {\n            logger.LogTrace(\"Model index refresh skipped, model directory does not exist\");\n            return;\n        }\n\n        // Start\n        var stopwatch = Stopwatch.StartNew();\n        logger.LogInformation(\"Refreshing model index...\");\n\n        var newIndexFlat = new ConcurrentBag<LocalModelFile>();\n\n        var paths = Directory\n            .EnumerateFiles(modelsDir, \"*\", EnumerationOptionConstants.AllDirectories)\n            .ToHashSet();\n\n        var partitioner = Partitioner.Create(paths, EnumerablePartitionerOptions.NoBuffering);\n\n        var numThreads = Environment.ProcessorCount switch\n        {\n            >= 20 => Environment.ProcessorCount / 3 - 1,\n            > 1 => Environment.ProcessorCount,\n            _ => 1,\n        };\n\n        Parallel.ForEach(\n            partitioner,\n            new ParallelOptions { MaxDegreeOfParallelism = numThreads },\n            path =>\n            {\n                // Skip if not supported extension\n                if (!LocalModelFile.SupportedCheckpointExtensions.Contains(Path.GetExtension(path)))\n                {\n                    return;\n                }\n\n                var relativePath = Path.GetRelativePath(modelsDir, path);\n\n                // Get shared folder name\n                var sharedFolderName = relativePath.Split(\n                    Path.DirectorySeparatorChar,\n                    StringSplitOptions.RemoveEmptyEntries\n                )[0];\n                // Try Convert to enum\n                if (!Enum.TryParse<SharedFolderType>(sharedFolderName, out var sharedFolderType))\n                {\n                    sharedFolderType = SharedFolderType.Unknown;\n                }\n\n                // Since RelativePath is the database key, for LiteDB this is limited to 1021 bytes\n                if (Encoding.UTF8.GetByteCount(relativePath) is var byteCount and > 1021)\n                {\n                    logger.LogWarning(\n                        \"Skipping model {Path} because it's path is too long ({Length} bytes)\",\n                        relativePath,\n                        byteCount\n                    );\n\n                    return;\n                }\n\n                var localModel = new LocalModelFile\n                {\n                    RelativePath = relativePath,\n                    SharedFolderType = sharedFolderType,\n                };\n\n                // Try to find a connected model info\n                var fileDirectory = new DirectoryPath(Path.GetDirectoryName(path)!);\n                var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);\n                var jsonPath = fileDirectory.JoinFile($\"{fileNameWithoutExtension}.cm-info.json\");\n\n                if (paths.Contains(jsonPath))\n                {\n                    try\n                    {\n                        using var stream = jsonPath.Info.OpenRead();\n\n                        var connectedModelInfo = JsonSerializer.Deserialize(\n                            stream,\n                            ConnectedModelInfoSerializerContext.Default.ConnectedModelInfo\n                        );\n\n                        // Seems there is a limitation of LiteDB datetime resolution, so drop nanoseconds on load\n                        // Otherwise new loaded models with ns will cause mismatching equality with models loaded from db with no ns\n                        if (connectedModelInfo?.ImportedAt is { } importedAt && importedAt.Nanosecond != 0)\n                        {\n                            connectedModelInfo.ImportedAt = new DateTimeOffset(\n                                importedAt.Year,\n                                importedAt.Month,\n                                importedAt.Day,\n                                importedAt.Hour,\n                                importedAt.Minute,\n                                importedAt.Second,\n                                importedAt.Millisecond,\n                                importedAt.Offset\n                            );\n                        }\n\n                        localModel.ConnectedModelInfo = connectedModelInfo;\n                    }\n                    catch (Exception e)\n                    {\n                        logger.LogWarning(\n                            e,\n                            \"Failed to deserialize connected model info for {Path}, skipping\",\n                            jsonPath\n                        );\n                    }\n                }\n\n                // Try to find a preview image\n                var previewImagePath = LocalModelFile\n                    .SupportedImageExtensions.Select(ext =>\n                        fileDirectory.JoinFile($\"{fileNameWithoutExtension}.preview{ext}\")\n                    )\n                    .FirstOrDefault(filePath => paths.Contains(filePath));\n\n                if (previewImagePath is not null)\n                {\n                    localModel.PreviewImageRelativePath = Path.GetRelativePath(modelsDir, previewImagePath);\n                }\n\n                // Try to find a config file (same name as model file but with .yaml extension)\n                var configFile = fileDirectory.JoinFile($\"{fileNameWithoutExtension}.yaml\");\n                if (paths.Contains(configFile))\n                {\n                    localModel.ConfigFullPath = configFile;\n                }\n\n                // Add to index\n                newIndexFlat.Add(localModel);\n            }\n        );\n\n        var newIndexComplete = newIndexFlat.ToArray();\n\n        var modelsDict = ModelIndex\n            .Values.SelectMany(x => x)\n            .DistinctBy(f => f.RelativePath)\n            .ToDictionary(f => f.RelativePath, file => file);\n\n        var newIndex = new Dictionary<SharedFolderType, List<LocalModelFile>>();\n        foreach (var model in newIndexComplete)\n        {\n            if (modelsDict.TryGetValue(model.RelativePath, out var dbModel))\n            {\n                model.HasUpdate = dbModel.HasUpdate;\n                model.HasEarlyAccessUpdateOnly = dbModel.HasEarlyAccessUpdateOnly;\n                model.LastUpdateCheck = dbModel.LastUpdateCheck;\n                model.LatestModelInfo = dbModel.LatestModelInfo;\n            }\n\n            if (model.LatestModelInfo == null && model.HasCivitMetadata)\n            {\n                // Handle enum deserialize exceptions from changes\n                if (\n                    await liteDbContext\n                        .TryQueryWithClearOnExceptionAsync(\n                            liteDbContext.CivitModels,\n                            liteDbContext\n                                .CivitModels.Include(m => m.ModelVersions)\n                                .FindByIdAsync(model.ConnectedModelInfo.ModelId)\n                        )\n                        .ConfigureAwait(false) is\n                    { } latestModel\n                )\n                {\n                    model.LatestModelInfo = latestModel;\n                }\n            }\n            var list = newIndex.GetOrAdd(model.SharedFolderType);\n            list.Add(model);\n        }\n\n        ModelIndex = newIndex;\n\n        stopwatch.Stop();\n        var indexTime = stopwatch.Elapsed;\n\n        // Insert to db as transaction\n        stopwatch.Restart();\n\n        using var db = await liteDbContext.Database.BeginTransactionAsync().ConfigureAwait(false);\n        var localModelFiles = db.GetCollection<LocalModelFile>(\"LocalModelFiles\")!;\n\n        await localModelFiles.DeleteAllAsync().ConfigureAwait(false);\n        await localModelFiles.InsertBulkAsync(newIndexComplete).ConfigureAwait(false);\n\n        await db.CommitAsync().ConfigureAwait(false);\n\n        stopwatch.Stop();\n        var dbTime = stopwatch.Elapsed;\n\n        logger.LogInformation(\n            \"Model index refreshed with {Entries} entries, took {IndexDuration} ({DbDuration} db)\",\n            newIndexFlat.Count,\n            CodeTimer.FormatTime(indexTime),\n            CodeTimer.FormatTime(dbTime)\n        );\n\n        EventManager.Instance.OnModelIndexChanged();\n    }\n\n    /// <inheritdoc />\n    public void BackgroundRefreshIndex()\n    {\n        Task.Run(async () => await RefreshIndex().ConfigureAwait(false))\n            .SafeFireAndForget(ex =>\n            {\n                logger.LogError(ex, \"Error in background model indexing\");\n            });\n    }\n\n    /// <inheritdoc />\n    public async Task<bool> RemoveModelAsync(LocalModelFile model)\n    {\n        // Remove from database\n        if (await liteDbContext.LocalModelFiles.DeleteAsync(model.RelativePath).ConfigureAwait(false))\n        {\n            // Remove from index\n            if (ModelIndex.TryGetValue(model.SharedFolderType, out var list))\n            {\n                list.RemoveAll(x => x.RelativePath == model.RelativePath);\n\n                OnModelIndexReset();\n                EventManager.Instance.OnModelIndexChanged();\n            }\n\n            return true;\n        }\n\n        return false;\n    }\n\n    public async Task<bool> RemoveModelsAsync(IEnumerable<LocalModelFile> models)\n    {\n        var modelsList = models.ToList();\n        var paths = modelsList.Select(m => m.RelativePath).ToList();\n        var result = true;\n\n        foreach (var path in paths)\n        {\n            result &= await liteDbContext.LocalModelFiles.DeleteAsync(path).ConfigureAwait(false);\n        }\n\n        foreach (var model in modelsList)\n        {\n            if (ModelIndex.TryGetValue(model.SharedFolderType, out var list))\n            {\n                list.RemoveAll(x => x.RelativePath == model.RelativePath);\n            }\n        }\n\n        OnModelIndexReset();\n        EventManager.Instance.OnModelIndexChanged();\n\n        return result;\n    }\n\n    public async Task CheckModelsForUpdateAsync()\n    {\n        if (DateTimeOffset.UtcNow < lastUpdateCheck.AddMinutes(5))\n        {\n            return;\n        }\n\n        lastUpdateCheck = DateTimeOffset.UtcNow;\n\n        var installedHashes = ModelIndexBlake3Hashes;\n        var dbModels = (\n            await liteDbContext.LocalModelFiles.FindAllAsync().ConfigureAwait(false) ?? []\n        ).ToList();\n\n        var ids = dbModels\n            .Where(x => x.ConnectedModelInfo?.ModelId != null)\n            .Select(x => x.ConnectedModelInfo!.ModelId.Value)\n            .Distinct();\n\n        var remoteModels = (await modelFinder.FindRemoteModelsById(ids).ConfigureAwait(false)).ToList();\n\n        // update the civitmodels cache with this new result\n        await liteDbContext.UpsertCivitModelAsync(remoteModels).ConfigureAwait(false);\n\n        var localModelsToUpdate = new List<LocalModelFile>();\n        foreach (var dbModel in dbModels)\n        {\n            if (dbModel.ConnectedModelInfo == null)\n                continue;\n\n            var remoteModel = remoteModels.FirstOrDefault(m => m.Id == dbModel.ConnectedModelInfo!.ModelId);\n\n            var latestVersion = remoteModel?.ModelVersions?.FirstOrDefault();\n\n            if (latestVersion?.Files is not { } latestVersionFiles)\n            {\n                continue;\n            }\n\n            var latestHashes = latestVersionFiles\n                .Where(f => f.Type == CivitFileType.Model)\n                .Select(f => f.Hashes.BLAKE3)\n                .Where(hash => hash is not null)\n                .ToList();\n\n            dbModel.HasUpdate = !latestHashes.Any(hash => installedHashes.Contains(hash!));\n            dbModel.HasEarlyAccessUpdateOnly = GetHasEarlyAccessUpdateOnly(dbModel, remoteModel);\n            dbModel.LastUpdateCheck = DateTimeOffset.UtcNow;\n            dbModel.LatestModelInfo = remoteModel;\n\n            localModelsToUpdate.Add(dbModel);\n        }\n        await liteDbContext.LocalModelFiles.UpsertAsync(localModelsToUpdate).ConfigureAwait(false);\n        await LoadFromDbAsync().ConfigureAwait(false);\n    }\n\n    public async Task UpsertModelAsync(LocalModelFile model)\n    {\n        await liteDbContext.LocalModelFiles.UpsertAsync(model).ConfigureAwait(false);\n        await LoadFromDbAsync().ConfigureAwait(false);\n    }\n\n    private void OnModelIndexReset()\n    {\n        _modelIndexBlake3Hashes = null;\n    }\n\n    private static HashSet<string> CollectModelHashes(IEnumerable<LocalModelFile> models)\n    {\n        var hashes = new HashSet<string>();\n        foreach (var model in models)\n        {\n            if (model.ConnectedModelInfo?.Hashes?.BLAKE3 is { } hashBlake3)\n            {\n                hashes.Add(hashBlake3);\n            }\n        }\n        return hashes;\n    }\n\n    private static bool GetHasEarlyAccessUpdateOnly(LocalModelFile model, CivitModel? remoteModel)\n    {\n        if (!model.HasUpdate || !model.HasCivitMetadata)\n            return false;\n\n        var versions = remoteModel?.ModelVersions;\n        if (versions == null || versions.Count == 0)\n            return false;\n\n        var installedVersionId = model.ConnectedModelInfo?.VersionId;\n        if (installedVersionId == null)\n            return false;\n\n        var installedIndex = versions.FindIndex(version => version.Id == installedVersionId.Value);\n        if (installedIndex <= 0)\n            return false;\n\n        return versions.Take(installedIndex).All(version => version.IsEarlyAccess);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/OpenModelDbManager.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing Apizr;\nusing Apizr.Caching;\nusing Apizr.Caching.Attributes;\nusing Apizr.Configuring.Manager;\nusing Apizr.Connecting;\nusing Apizr.Mapping;\nusing Fusillade;\nusing Polly.Registry;\nusing StabilityMatrix.Core.Api;\nusing StabilityMatrix.Core.Models.Api.OpenModelsDb;\n\nnamespace StabilityMatrix.Core.Services;\n\npublic class OpenModelDbManager(\n    ILazyFactory<IOpenModelDbApi> lazyWebApi,\n    IConnectivityHandler connectivityHandler,\n    ICacheHandler cacheHandler,\n    IMappingHandler mappingHandler,\n    ILazyFactory<ResiliencePipelineRegistry<string>> lazyResiliencePipelineRegistry,\n    IApizrManagerOptions<IOpenModelDbApi> apizrOptions\n)\n    : ApizrManager<IOpenModelDbApi>(\n        lazyWebApi,\n        connectivityHandler,\n        cacheHandler,\n        mappingHandler,\n        lazyResiliencePipelineRegistry,\n        apizrOptions\n    )\n{\n    public Uri UsersBaseUri => new(\"https://openmodeldb.info/users\");\n\n    public Uri ModelsBaseUri => new(\"https://openmodeldb.info/models\");\n\n    public IReadOnlyDictionary<string, OpenModelDbTag>? Tags { get; private set; }\n\n    public IReadOnlyDictionary<string, OpenModelDbArchitecture>? Architectures { get; private set; }\n\n    [MemberNotNull(nameof(Tags), nameof(Architectures))]\n    public async Task EnsureMetadataLoadedAsync(Priority priority = default)\n    {\n        if (Tags is null)\n        {\n            Tags = await ExecuteAsync(api => api.GetTags()).ConfigureAwait(false);\n        }\n        if (Architectures is null)\n        {\n            Architectures = await ExecuteAsync(api => api.GetArchitectures()).ConfigureAwait(false);\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/PipWheelService.cs",
    "content": "﻿using System.Text.RegularExpressions;\nusing Injectio.Attributes;\nusing NLog;\nusing Octokit;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Helper.HardwareInfo;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Core.Services;\n\n/// <summary>\n/// Service for installing pip wheel packages from GitHub releases.\n/// </summary>\n[RegisterSingleton<IPipWheelService, PipWheelService>]\npublic class PipWheelService(\n    IGithubApiCache githubApi,\n    IDownloadService downloadService,\n    IPrerequisiteHelper prerequisiteHelper\n) : IPipWheelService\n{\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\n\n    #region Triton\n\n    /// <inheritdoc />\n    public async Task InstallTritonAsync(\n        IPyVenvRunner venv,\n        IProgress<ProgressReport>? progress = null,\n        string? version = null\n    )\n    {\n        // No-op on macOS\n        if (Compat.IsMacOS)\n        {\n            Logger.Info(\"Skipping Triton installation - not supported on macOS\");\n            return;\n        }\n\n        var packageName = Compat.IsWindows ? \"triton-windows\" : \"triton\";\n        var versionSpec = string.IsNullOrWhiteSpace(version) ? \"\" : $\"=={version}\";\n\n        progress?.Report(new ProgressReport(-1f, $\"Installing {packageName}\", isIndeterminate: true));\n\n        await venv.PipInstall($\"{packageName}{versionSpec}\", progress.AsProcessOutputHandler())\n            .ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(1f, \"Triton installed\", isIndeterminate: false));\n    }\n\n    #endregion\n\n    #region SageAttention\n\n    /// <inheritdoc />\n    public async Task InstallSageAttentionAsync(\n        IPyVenvRunner venv,\n        GpuInfo? gpuInfo = null,\n        IProgress<ProgressReport>? progress = null,\n        string? version = null\n    )\n    {\n        // No-op on macOS\n        if (Compat.IsMacOS)\n        {\n            Logger.Info(\"Skipping SageAttention installation - not supported on macOS\");\n            return;\n        }\n\n        // No-op for non-NVIDIA GPUs (SageAttention requires CUDA)\n        if (gpuInfo is not null && !gpuInfo.IsNvidia)\n        {\n            Logger.Info(\"Skipping SageAttention installation - requires NVIDIA GPU\");\n            return;\n        }\n\n        // On Linux, can use pip directly\n        if (Compat.IsLinux)\n        {\n            var versionSpec = string.IsNullOrWhiteSpace(version) ? \"\" : $\"=={version}\";\n            progress?.Report(new ProgressReport(-1f, \"Installing SageAttention\", isIndeterminate: true));\n            await venv.PipInstall($\"sageattention{versionSpec}\", progress.AsProcessOutputHandler())\n                .ConfigureAwait(false);\n            progress?.Report(new ProgressReport(1f, \"SageAttention installed\", isIndeterminate: false));\n            return;\n        }\n\n        // Windows: find wheel from GitHub releases\n        await InstallSageAttentionWindowsAsync(venv, gpuInfo, progress, version).ConfigureAwait(false);\n    }\n\n    private async Task InstallSageAttentionWindowsAsync(\n        IPyVenvRunner venv,\n        GpuInfo? gpuInfo,\n        IProgress<ProgressReport>? progress,\n        string? version\n    )\n    {\n        var torchInfo = await venv.PipShow(\"torch\").ConfigureAwait(false);\n        if (torchInfo is null)\n        {\n            Logger.Warn(\"Cannot install SageAttention - torch not installed\");\n            return;\n        }\n\n        progress?.Report(new ProgressReport(-1f, \"Finding SageAttention wheel\", isIndeterminate: true));\n\n        // Get releases from GitHub\n        var releases = await githubApi.GetAllReleases(\"woct0rdho\", \"SageAttention\").ConfigureAwait(false);\n        var releaseList = releases\n            .Where(r => r.TagName.Contains(\"windows\"))\n            .OrderByDescending(r => r.CreatedAt)\n            .ToList();\n\n        if (releaseList.Count == 0)\n        {\n            Logger.Warn(\"No SageAttention Windows releases found\");\n            await InstallSageAttentionFromSourceAsync(venv, progress).ConfigureAwait(false);\n            return;\n        }\n\n        // Find matching wheel from release assets\n        var wheelUrl = FindMatchingWheelAsset(releaseList, torchInfo, venv.Version, version);\n\n        if (!string.IsNullOrWhiteSpace(wheelUrl))\n        {\n            progress?.Report(\n                new ProgressReport(-1f, \"Installing Triton & SageAttention\", isIndeterminate: true)\n            );\n\n            // Install triton-windows first, then sage with --no-deps to prevent torch reinstall\n            var pipArgs = new PipInstallArgs(\"triton-windows\").AddArg(\"--no-deps\").AddArg(wheelUrl);\n            await venv.PipInstall(pipArgs, progress.AsProcessOutputHandler()).ConfigureAwait(false);\n\n            progress?.Report(new ProgressReport(1f, \"SageAttention installed\", isIndeterminate: false));\n            return;\n        }\n\n        // No wheel found - fall back to building from source\n        Logger.Info(\"No matching SageAttention wheel found, building from source\");\n        await InstallSageAttentionFromSourceAsync(venv, progress).ConfigureAwait(false);\n    }\n\n    private static string? FindMatchingWheelAsset(\n        IEnumerable<Release> releases,\n        PipShowResult torchInfo,\n        PyVersion pyVersion,\n        string? targetVersion\n    )\n    {\n        // Parse torch info\n        var torchVersionStr = torchInfo.Version;\n        var plusIndex = torchVersionStr.IndexOf('+');\n        var baseTorchVersion = plusIndex >= 0 ? torchVersionStr[..plusIndex] : torchVersionStr;\n        var cudaIndex = plusIndex >= 0 ? torchVersionStr[(plusIndex + 1)..] : \"\";\n\n        // Get major.minor of torch\n        var torchParts = baseTorchVersion.Split('.');\n        var shortTorch = torchParts.Length >= 2 ? $\"{torchParts[0]}.{torchParts[1]}\" : baseTorchVersion;\n\n        // Get python version string (e.g., \"cp312\")\n        var shortPy = $\"cp3{pyVersion.Minor}\";\n\n        foreach (var release in releases)\n        {\n            // If a specific version is requested, filter releases\n            if (!string.IsNullOrWhiteSpace(targetVersion) && !release.TagName.Contains(targetVersion))\n                continue;\n\n            foreach (var asset in release.Assets)\n            {\n                var name = asset.Name;\n\n                // Must be a wheel file\n                if (!name.EndsWith(\".whl\"))\n                    continue;\n\n                // Must be for Windows\n                if (!name.Contains(\"win_amd64\"))\n                    continue;\n\n                // Check Python version compatibility (cp39-abi3 works for cp39+, or specific version)\n                var matchesPython =\n                    name.Contains($\"{shortPy}-{shortPy}\")\n                    || name.Contains(\"cp39-abi3\")\n                    || (pyVersion.Minor >= 9 && name.Contains(\"abi3\"));\n\n                if (!matchesPython)\n                    continue;\n\n                // Check torch version match\n                // Assets use patterns like: cu128torch2.9.0 or cu130torch2.9.0andhigher\n                var matchesTorch =\n                    name.Contains($\"torch{shortTorch}\")\n                    || name.Contains($\"torch{baseTorchVersion}\")\n                    || (name.Contains(\"andhigher\") && CompareTorchVersions(baseTorchVersion, name));\n\n                // Check CUDA index match\n                var matchesCuda = !string.IsNullOrEmpty(cudaIndex) && name.Contains(cudaIndex);\n\n                if (matchesTorch && matchesCuda)\n                {\n                    Logger.Info(\"Found matching SageAttention wheel: {Name}\", name);\n                    return asset.BrowserDownloadUrl;\n                }\n            }\n        }\n\n        return null;\n    }\n\n    private static bool CompareTorchVersions(string installedTorch, string assetName)\n    {\n        // Extract torch version from asset name (e.g., \"torch2.9.0andhigher\" -> \"2.9.0\")\n        var match = Regex.Match(assetName, @\"torch(\\d+\\.\\d+\\.\\d+)\");\n        if (!match.Success)\n            return false;\n\n        if (!Version.TryParse(installedTorch, out var installed))\n            return false;\n\n        if (!Version.TryParse(match.Groups[1].Value, out var required))\n            return false;\n\n        // \"andhigher\" means installed version must be >= required version\n        return installed >= required;\n    }\n\n    private async Task InstallSageAttentionFromSourceAsync(\n        IPyVenvRunner venv,\n        IProgress<ProgressReport>? progress\n    )\n    {\n        // Check prerequisites\n        if (!prerequisiteHelper.IsVcBuildToolsInstalled)\n        {\n            Logger.Warn(\"Cannot build SageAttention from source - VS Build Tools not installed\");\n            return;\n        }\n\n        var nvccPath = await Utilities.WhichAsync(\"nvcc\").ConfigureAwait(false);\n        if (string.IsNullOrWhiteSpace(nvccPath))\n        {\n            var cuda126Path = new DirectoryPath(\n                @\"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v12.6\\bin\"\n            );\n            var cuda128Path = new DirectoryPath(\n                @\"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v12.8\\bin\"\n            );\n\n            if (!cuda126Path.Exists && !cuda128Path.Exists)\n            {\n                Logger.Warn(\"Cannot build SageAttention from source - CUDA Toolkit not found\");\n                return;\n            }\n\n            nvccPath = cuda128Path.Exists\n                ? cuda128Path.JoinFile(\"nvcc.exe\").ToString()\n                : cuda126Path.JoinFile(\"nvcc.exe\").ToString();\n        }\n\n        // Set up CUDA environment\n        var cudaBinPath = Path.GetDirectoryName(nvccPath)!;\n        var cudaHome = Path.GetDirectoryName(cudaBinPath)!;\n\n        venv.UpdateEnvironmentVariables(env =>\n        {\n            env = env.TryGetValue(\"PATH\", out var pathValue)\n                ? env.SetItem(\"PATH\", $\"{cudaBinPath}{Path.PathSeparator}{pathValue}\")\n                : env.Add(\"PATH\", cudaBinPath);\n\n            if (!env.ContainsKey(\"CUDA_HOME\"))\n            {\n                env = env.Add(\"CUDA_HOME\", cudaHome);\n            }\n\n            return env;\n        });\n\n        progress?.Report(new ProgressReport(-1f, \"Installing Triton\", isIndeterminate: true));\n        await venv.PipInstall(\"triton-windows\", progress.AsProcessOutputHandler()).ConfigureAwait(false);\n\n        venv.UpdateEnvironmentVariables(env => env.SetItem(\"SETUPTOOLS_USE_DISTUTILS\", \"setuptools\"));\n\n        // Download python libs for building\n        await AddMissingLibsToVenvAsync(venv, progress).ConfigureAwait(false);\n\n        var sageDir = venv.WorkingDirectory?.JoinDir(\"SageAttention\") ?? new DirectoryPath(\"SageAttention\");\n\n        if (!sageDir.Exists)\n        {\n            progress?.Report(new ProgressReport(-1f, \"Downloading SageAttention\", isIndeterminate: true));\n            await prerequisiteHelper\n                .RunGit(\n                    [\"clone\", \"https://github.com/thu-ml/SageAttention.git\", sageDir.ToString()],\n                    progress.AsProcessOutputHandler()\n                )\n                .ConfigureAwait(false);\n        }\n\n        progress?.Report(new ProgressReport(-1f, \"Building SageAttention\", isIndeterminate: true));\n        await venv.PipInstall([sageDir.ToString()], progress.AsProcessOutputHandler()).ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(1f, \"SageAttention built and installed\", isIndeterminate: false));\n    }\n\n    private async Task AddMissingLibsToVenvAsync(IPyVenvRunner venv, IProgress<ProgressReport>? progress)\n    {\n        var venvLibsDir = venv.RootPath.JoinDir(\"libs\");\n        var venvIncludeDir = venv.RootPath.JoinDir(\"include\");\n\n        if (\n            venvLibsDir.Exists\n            && venvIncludeDir.Exists\n            && venvLibsDir.JoinFile(\"python3.lib\").Exists\n            && venvLibsDir.JoinFile(\"python310.lib\").Exists\n        )\n        {\n            return;\n        }\n\n        const string pythonLibsUrl = \"https://cdn.lykos.ai/python_libs_for_sage.zip\";\n        var downloadPath = venv.RootPath.JoinFile(\"python_libs_for_sage.zip\");\n\n        progress?.Report(new ProgressReport(-1f, \"Downloading Python libraries\", isIndeterminate: true));\n        await downloadService\n            .DownloadToFileAsync(pythonLibsUrl, downloadPath, progress)\n            .ConfigureAwait(false);\n\n        progress?.Report(new ProgressReport(-1f, \"Extracting Python libraries\", isIndeterminate: true));\n        await ArchiveHelper.Extract7Z(downloadPath, venv.RootPath, progress).ConfigureAwait(false);\n\n        var includeFolder = venv.RootPath.JoinDir(\"include\");\n        var scriptsIncludeFolder = venv.RootPath.JoinDir(\"Scripts\", \"include\");\n        await includeFolder.CopyToAsync(scriptsIncludeFolder).ConfigureAwait(false);\n\n        await downloadPath.DeleteAsync().ConfigureAwait(false);\n    }\n\n    #endregion\n\n    #region Nunchaku\n\n    /// <inheritdoc />\n    public async Task InstallNunchakuAsync(\n        IPyVenvRunner venv,\n        GpuInfo? gpuInfo = null,\n        IProgress<ProgressReport>? progress = null,\n        string? version = null\n    )\n    {\n        // No-op on macOS\n        if (Compat.IsMacOS)\n        {\n            Logger.Info(\"Skipping Nunchaku installation - not supported on macOS\");\n            return;\n        }\n\n        // No-op for GPUs with compute capability < 7.5\n        if (gpuInfo?.ComputeCapabilityValue is < 7.5m)\n        {\n            Logger.Info(\"Skipping Nunchaku installation - GPU compute capability < 7.5\");\n            return;\n        }\n\n        var torchInfo = await venv.PipShow(\"torch\").ConfigureAwait(false);\n        if (torchInfo is null)\n        {\n            Logger.Warn(\"Cannot install Nunchaku - torch not installed\");\n            return;\n        }\n\n        progress?.Report(new ProgressReport(-1f, \"Finding Nunchaku wheel\", isIndeterminate: true));\n\n        // Get releases from GitHub\n        var releases = await githubApi.GetAllReleases(\"nunchaku-ai\", \"nunchaku\").ConfigureAwait(false);\n        var releaseList = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.CreatedAt).ToList();\n\n        if (releaseList.Count == 0)\n        {\n            Logger.Warn(\"No Nunchaku releases found\");\n            return;\n        }\n\n        var wheelUrl = FindMatchingNunchakuWheelAsset(releaseList, torchInfo, venv.Version, version);\n\n        if (string.IsNullOrWhiteSpace(wheelUrl))\n        {\n            Logger.Warn(\"No compatible Nunchaku wheel found for torch {TorchVersion}\", torchInfo.Version);\n            return;\n        }\n\n        progress?.Report(new ProgressReport(-1f, \"Installing Nunchaku\", isIndeterminate: true));\n        // Use --no-deps to prevent reinstalling torch without CUDA\n        await venv.PipInstall(\n                new PipInstallArgs(\"--no-deps\").AddArg(wheelUrl),\n                progress.AsProcessOutputHandler()\n            )\n            .ConfigureAwait(false);\n        progress?.Report(new ProgressReport(1f, \"Nunchaku installed\", isIndeterminate: false));\n    }\n\n    private static string? FindMatchingNunchakuWheelAsset(\n        IEnumerable<Release> releases,\n        PipShowResult torchInfo,\n        PyVersion pyVersion,\n        string? targetVersion\n    )\n    {\n        // Parse torch version\n        var torchVersionStr = torchInfo.Version;\n        var plusIndex = torchVersionStr.IndexOf('+');\n        var baseTorchVersion = plusIndex >= 0 ? torchVersionStr[..plusIndex] : torchVersionStr;\n        var torchParts = baseTorchVersion.Split('.');\n        var shortTorch = torchParts.Length >= 2 ? $\"{torchParts[0]}.{torchParts[1]}\" : baseTorchVersion;\n\n        // Get python version string\n        var shortPy = $\"cp3{pyVersion.Minor}\";\n\n        // Get platform\n        var platform = Compat.IsWindows ? \"win_amd64\" : \"linux_x86_64\";\n\n        Logger.Debug(\n            \"Searching for Nunchaku wheel: Python={ShortPy}, Torch={ShortTorch}, Platform={Platform}\",\n            shortPy,\n            shortTorch,\n            platform\n        );\n\n        foreach (var release in releases)\n        {\n            // If a specific version is requested, filter releases\n            if (!string.IsNullOrWhiteSpace(targetVersion) && !release.TagName.Contains(targetVersion))\n                continue;\n\n            foreach (var asset in release.Assets)\n            {\n                var name = asset.Name;\n\n                if (!name.EndsWith(\".whl\"))\n                    continue;\n\n                if (!name.Contains(platform))\n                    continue;\n\n                // Check Python version\n                if (!name.Contains($\"{shortPy}-{shortPy}\"))\n                    continue;\n\n                // Check torch version (assets use patterns like: torch2.7 or torch2.8)\n                if (!name.Contains($\"torch{shortTorch}\"))\n                    continue;\n\n                Logger.Info(\n                    \"Found matching Nunchaku wheel: {Name} (Python={ShortPy}, Torch={ShortTorch})\",\n                    name,\n                    shortPy,\n                    shortTorch\n                );\n                return asset.BrowserDownloadUrl;\n            }\n        }\n\n        return null;\n    }\n\n    #endregion\n\n    #region FlashAttention\n\n    /// <inheritdoc />\n    public async Task InstallFlashAttentionAsync(\n        IPyVenvRunner venv,\n        IProgress<ProgressReport>? progress = null,\n        string? version = null\n    )\n    {\n        // Windows only\n        if (!Compat.IsWindows)\n        {\n            Logger.Info(\"Skipping FlashAttention installation - Windows only\");\n            return;\n        }\n\n        var torchInfo = await venv.PipShow(\"torch\").ConfigureAwait(false);\n        if (torchInfo is null)\n        {\n            Logger.Warn(\"Cannot install FlashAttention - torch not installed\");\n            return;\n        }\n\n        progress?.Report(new ProgressReport(-1f, \"Finding FlashAttention wheel\", isIndeterminate: true));\n\n        // Get releases from GitHub\n        var releases = await githubApi\n            .GetAllReleases(\"mjun0812\", \"flash-attention-prebuild-wheels\")\n            .ConfigureAwait(false);\n        var releaseList = releases.OrderByDescending(r => r.CreatedAt).ToList();\n\n        if (releaseList.Count == 0)\n        {\n            Logger.Warn(\"No FlashAttention releases found\");\n            return;\n        }\n\n        var wheelUrl = FindMatchingFlashAttentionWheelAsset(releaseList, torchInfo, venv.Version, version);\n\n        if (string.IsNullOrWhiteSpace(wheelUrl))\n        {\n            Logger.Warn(\n                \"No compatible FlashAttention wheel found for torch {TorchVersion}\",\n                torchInfo.Version\n            );\n            return;\n        }\n\n        progress?.Report(new ProgressReport(-1f, \"Installing FlashAttention\", isIndeterminate: true));\n        // Use --no-deps to prevent reinstalling torch without CUDA\n        await venv.PipInstall(\n                new PipInstallArgs(\"--no-deps\").AddArg(wheelUrl),\n                progress.AsProcessOutputHandler()\n            )\n            .ConfigureAwait(false);\n        progress?.Report(new ProgressReport(1f, \"FlashAttention installed\", isIndeterminate: false));\n    }\n\n    private static string? FindMatchingFlashAttentionWheelAsset(\n        IEnumerable<Release> releases,\n        PipShowResult torchInfo,\n        PyVersion pyVersion,\n        string? targetVersion\n    )\n    {\n        // Parse torch version and CUDA index\n        var torchVersionStr = torchInfo.Version;\n        var plusIndex = torchVersionStr.IndexOf('+');\n        var baseTorchVersion = plusIndex >= 0 ? torchVersionStr[..plusIndex] : torchVersionStr;\n        var cudaIndex = plusIndex >= 0 ? torchVersionStr[(plusIndex + 1)..] : \"\";\n        var torchParts = baseTorchVersion.Split('.');\n        var shortTorch = torchParts.Length >= 2 ? $\"{torchParts[0]}.{torchParts[1]}\" : baseTorchVersion;\n\n        // Get python version string\n        var shortPy = $\"cp3{pyVersion.Minor}\";\n\n        foreach (var release in releases)\n        {\n            foreach (var asset in release.Assets)\n            {\n                var name = asset.Name;\n\n                if (!name.EndsWith(\".whl\"))\n                    continue;\n\n                if (!name.Contains(\"win_amd64\"))\n                    continue;\n\n                // Check for specific version if requested\n                if (\n                    !string.IsNullOrWhiteSpace(targetVersion) && !name.Contains($\"flash_attn-{targetVersion}\")\n                )\n                    continue;\n\n                // Check Python version\n                if (!name.Contains($\"{shortPy}-{shortPy}\"))\n                    continue;\n\n                // Check torch version\n                if (!name.Contains($\"torch{shortTorch}\"))\n                    continue;\n\n                // Check CUDA index\n                if (!string.IsNullOrEmpty(cudaIndex) && !name.Contains(cudaIndex))\n                    continue;\n\n                Logger.Info(\"Found matching FlashAttention wheel: {Name}\", name);\n                return asset.BrowserDownloadUrl;\n            }\n        }\n\n        return null;\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/SecretsManager.cs",
    "content": "﻿using System.Reactive.Concurrency;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Core.Services;\n\n/// <summary>\n/// Default implementation of <see cref=\"ISecretsManager\"/>.\n/// Data is encrypted at rest in %APPDATA%\\StabilityMatrix\\user-secrets.data\n/// </summary>\n[RegisterSingleton<ISecretsManager, SecretsManager>]\npublic class SecretsManager : ISecretsManager\n{\n    private readonly ILogger<SecretsManager> logger;\n\n    private static FilePath GlobalFile => GlobalConfig.HomeDir.JoinFile(\"user-secrets.data\");\n\n    private static SemaphoreSlim GlobalFileLock { get; } = new(1, 1);\n\n    public SecretsManager(ILogger<SecretsManager> logger)\n    {\n        this.logger = logger;\n    }\n\n    /// <inheritdoc />\n    public async Task<Secrets> LoadAsync()\n    {\n        if (!GlobalFile.Exists)\n        {\n            return new Secrets();\n        }\n\n        var fileBytes = await GlobalFile.ReadAllBytesAsync().ConfigureAwait(false);\n        return GlobalEncryptedSerializer.Deserialize<Secrets>(fileBytes);\n    }\n\n    /// <inheritdoc />\n    public async Task<Secrets> SafeLoadAsync()\n    {\n        try\n        {\n            return await LoadAsync().ConfigureAwait(false);\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Failed to load secrets ({ExcType}), saving new instance\", e.GetType().Name);\n\n            var secrets = new Secrets();\n            await SaveAsync(secrets).ConfigureAwait(false);\n\n            return secrets;\n        }\n    }\n\n    /// <inheritdoc />\n    public async Task SaveAsync(Secrets secrets)\n    {\n        await GlobalFileLock.WaitAsync().ConfigureAwait(false);\n\n        try\n        {\n            var fileBytes = GlobalEncryptedSerializer.Serialize(secrets);\n            await GlobalFile.WriteAllBytesAsync(fileBytes).ConfigureAwait(false);\n        }\n        finally\n        {\n            GlobalFileLock.Release();\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/SettingsManager.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Linq.Expressions;\nusing System.Reactive.Disposables;\nusing System.Reactive.Linq;\nusing System.Reflection;\nusing System.Text.Json;\nusing AsyncAwaitBestPractices;\nusing CompiledExpressions;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Core.Services;\n\n[RegisterSingleton<ISettingsManager, SettingsManager>]\npublic class SettingsManager(ILogger<SettingsManager> logger) : ISettingsManager\n{\n    private static string GlobalSettingsPath => Path.Combine(Compat.AppDataHome, \"global.json\");\n\n    private readonly SemaphoreSlim fileLock = new(1, 1);\n\n    private bool isLoaded;\n\n    private DirectoryPath? libraryDirOverride;\n\n    // Library properties\n    public bool IsPortableMode { get; private set; }\n\n    private DirectoryPath? libraryDir;\n    public DirectoryPath LibraryDir\n    {\n        get\n        {\n            if (libraryDir is null)\n            {\n                throw new InvalidOperationException(\"LibraryDir is not set\");\n            }\n\n            return libraryDir;\n        }\n        private set\n        {\n            var isChanged = libraryDir != value;\n\n            libraryDir = value;\n\n            // Only invoke if different\n            if (isChanged)\n            {\n                LibraryDirChanged?.Invoke(this, value);\n            }\n        }\n    }\n\n    [MemberNotNullWhen(true, nameof(libraryDir))]\n    public bool IsLibraryDirSet => libraryDir is not null;\n\n    // Dynamic paths from library\n    private FilePath SettingsFile => LibraryDir.JoinFile(\"settings.json\");\n    public string ModelsDirectory => Settings.ModelDirectoryOverride ?? Path.Combine(LibraryDir, \"Models\");\n    public string DownloadsDirectory => Path.Combine(LibraryDir, \".downloads\");\n    public DirectoryPath WorkflowDirectory => LibraryDir.JoinDir(\"Workflows\");\n    public DirectoryPath TagsDirectory => LibraryDir.JoinDir(\"Tags\");\n    public DirectoryPath ImagesDirectory => LibraryDir.JoinDir(\"Images\");\n    public DirectoryPath ImagesInferenceDirectory => ImagesDirectory.JoinDir(\"Inference\");\n    public DirectoryPath ConsolidatedImagesDirectory => ImagesDirectory.JoinDir(\"Consolidated\");\n    public DirectoryPath ExtensionPackDirectory => LibraryDir.JoinDir(\"ExtensionPacks\");\n\n    public Settings Settings { get; private set; } = new();\n\n    public List<string> PackageInstallsInProgress { get; set; } = [];\n\n    /// <inheritdoc />\n    public event EventHandler<string>? LibraryDirChanged;\n\n    /// <inheritdoc />\n    public event EventHandler<RelayPropertyChangedEventArgs>? SettingsPropertyChanged;\n\n    /// <inheritdoc />\n    public event EventHandler? Loaded;\n\n    /// <inheritdoc />\n    public void SetLibraryDirOverride(DirectoryPath path)\n    {\n        libraryDirOverride = path;\n    }\n\n    /// <inheritdoc />\n    public void RegisterOnLibraryDirSet(Action<string> handler)\n    {\n        if (IsLibraryDirSet)\n        {\n            handler(LibraryDir);\n            return;\n        }\n\n        LibraryDirChanged += Handler;\n\n        return;\n\n        void Handler(object? sender, string dir)\n        {\n            LibraryDirChanged -= Handler;\n            handler(dir);\n        }\n    }\n\n    /// <inheritdoc />\n    public SettingsTransaction BeginTransaction()\n    {\n        if (!IsLibraryDirSet)\n        {\n            throw new InvalidOperationException(\"LibraryDir not set when BeginTransaction was called\");\n        }\n        return new SettingsTransaction(this, () => SaveSettings(), () => SaveSettingsAsync());\n    }\n\n    /// <inheritdoc />\n    public void Transaction(Action<Settings> func, bool ignoreMissingLibraryDir = false)\n    {\n        if (!IsLibraryDirSet)\n        {\n            if (ignoreMissingLibraryDir)\n            {\n                func(Settings);\n                return;\n            }\n            throw new InvalidOperationException(\"LibraryDir not set when Transaction was called\");\n        }\n        using var transaction = BeginTransaction();\n        func(transaction.Settings);\n    }\n\n    /// <inheritdoc />\n    public void Transaction<TValue>(Expression<Func<Settings, TValue>> expression, TValue value)\n    {\n        var accessor = CompiledExpression.CreateAccessor(expression);\n\n        // Set value\n        using var transaction = BeginTransaction();\n        accessor.Set(transaction.Settings, value);\n\n        // Invoke property changed event\n        SettingsPropertyChanged?.Invoke(this, new RelayPropertyChangedEventArgs(accessor.FullName));\n    }\n\n    /// <inheritdoc />\n    public IDisposable RelayPropertyFor<T, TValue>(\n        T source,\n        Expression<Func<T, TValue>> sourceProperty,\n        Expression<Func<Settings, TValue>> settingsProperty,\n        bool setInitial = false,\n        TimeSpan? delay = null\n    )\n        where T : INotifyPropertyChanged\n    {\n        var sourceInstanceAccessor = CompiledExpression.CreateAccessor(sourceProperty).WithInstance(source);\n        var settingsAccessor = CompiledExpression.CreateAccessor(settingsProperty);\n\n        var sourcePropertyPath = sourceInstanceAccessor.FullName;\n        var settingsPropertyPath = settingsAccessor.FullName;\n\n        var sourceTypeName = source.GetType().Name;\n\n        // Update source when settings change\n        void OnSettingsPropertyChanged(object? sender, RelayPropertyChangedEventArgs args)\n        {\n            if (args.PropertyName != settingsPropertyPath)\n                return;\n\n            // Skip if event is relay and the sender is the source, to prevent duplicate\n            if (args.IsRelay && ReferenceEquals(sender, source))\n                return;\n\n            logger.LogTrace(\n                \"[RelayPropertyFor] \" + \"Settings.{SettingsProperty:l} -> {SourceType:l}.{SourceProperty:l}\",\n                settingsPropertyPath,\n                sourceTypeName,\n                sourcePropertyPath\n            );\n\n            sourceInstanceAccessor.Set(source, settingsAccessor.Get(Settings));\n        }\n\n        // Set and Save settings when source changes\n        void OnSourcePropertyChanged(object? sender, PropertyChangedEventArgs args)\n        {\n            if (args.PropertyName != sourcePropertyPath)\n                return;\n\n            // If TValue is a primitive type, check if there are changes first.\n            // If not, skip saving and property changed event.\n            if (typeof(TValue).IsPrimitive || typeof(TValue).IsEnum)\n            {\n                var settingsValue = settingsAccessor.Get(Settings);\n                var sourceValue = sourceInstanceAccessor.Get();\n                if (EqualityComparer<TValue>.Default.Equals(settingsValue, sourceValue))\n                {\n                    /*logger.LogTrace(\n                        \"[RelayPropertyFor] {SourceType:l}.{SourceProperty:l} -> Settings.{SettingsProperty:l} (<No Changes>)\",\n                        sourceTypeName,\n                        sourcePropertyPath,\n                        settingsPropertyPath\n                    );*/\n                    return;\n                }\n            }\n\n            logger.LogTrace(\n                \"[RelayPropertyFor] {SourceType:l}.{SourceProperty:l} -> Settings.{SettingsProperty:l}\",\n                sourceTypeName,\n                sourcePropertyPath,\n                settingsPropertyPath\n            );\n\n            settingsAccessor.Set(Settings, sourceInstanceAccessor.Get());\n\n            if (IsLibraryDirSet)\n            {\n                if (delay != null)\n                {\n                    SaveSettingsDelayed(delay.Value).SafeFireAndForget();\n                }\n                else\n                {\n                    SaveSettingsAsync().SafeFireAndForget();\n                }\n            }\n            else\n            {\n                logger.LogWarning(\n                    \"[RelayPropertyFor] LibraryDir not set when saving ({SourceType:l}.{SourceProperty:l} -> Settings.{SettingsProperty:l})\",\n                    sourceTypeName,\n                    sourcePropertyPath,\n                    settingsPropertyPath\n                );\n            }\n\n            // Invoke property changed event, passing along sender\n            SettingsPropertyChanged?.Invoke(\n                sender,\n                new RelayPropertyChangedEventArgs(settingsPropertyPath, true)\n            );\n        }\n\n        var subscription = Disposable.Create(() =>\n        {\n            source.PropertyChanged -= OnSourcePropertyChanged;\n            SettingsPropertyChanged -= OnSettingsPropertyChanged;\n        });\n\n        try\n        {\n            SettingsPropertyChanged += OnSettingsPropertyChanged;\n            source.PropertyChanged += OnSourcePropertyChanged;\n\n            // Set initial value if requested\n            if (setInitial)\n            {\n                sourceInstanceAccessor.Set(settingsAccessor.Get(Settings));\n            }\n        }\n        catch\n        {\n            subscription.Dispose();\n            throw;\n        }\n\n        return subscription;\n    }\n\n    /// <inheritdoc />\n    public IDisposable RegisterPropertyChangedHandler<T>(\n        Expression<Func<Settings, T>> settingsProperty,\n        Action<T> onPropertyChanged\n    )\n    {\n        var handlerName = onPropertyChanged.Method.Name;\n        var settingsAccessor = CompiledExpression.CreateAccessor(settingsProperty);\n\n        return Observable\n            .FromEventPattern<EventHandler<RelayPropertyChangedEventArgs>, RelayPropertyChangedEventArgs>(\n                h => SettingsPropertyChanged += h,\n                h => SettingsPropertyChanged -= h\n            )\n            .Where(args => args.EventArgs.PropertyName == settingsAccessor.FullName)\n            .Subscribe(_ =>\n            {\n                logger.LogTrace(\n                    \"[RegisterPropertyChangedHandler] Settings.{SettingsProperty:l} -> Handler ({Action})\",\n                    settingsAccessor.FullName,\n                    handlerName\n                );\n\n                onPropertyChanged(settingsAccessor.Get(Settings));\n            });\n    }\n\n    /// <inheritdoc />\n    public IObservable<T> ObservePropertyChanged<T>(Expression<Func<Settings, T>> settingsProperty)\n    {\n        var settingsAccessor = CompiledExpression.CreateAccessor(settingsProperty);\n\n        return Observable\n            .FromEventPattern<EventHandler<RelayPropertyChangedEventArgs>, RelayPropertyChangedEventArgs>(\n                h => SettingsPropertyChanged += h,\n                h => SettingsPropertyChanged -= h\n            )\n            .Where(args => args.EventArgs.PropertyName == settingsAccessor.FullName)\n            .Select(_ => settingsAccessor.Get(Settings));\n    }\n\n    /// <summary>\n    /// Attempts to locate and set the library path\n    /// Return true if found, false otherwise\n    /// </summary>\n    public bool TryFindLibrary(bool forceReload = false)\n    {\n        if (IsLibraryDirSet && !forceReload)\n            return true;\n\n        // 0. Check Override\n        if (libraryDirOverride is not null)\n        {\n            logger.LogInformation(\"Using library override path {Path}\", libraryDirOverride.FullPath);\n\n            LibraryDir = libraryDirOverride;\n            SetStaticLibraryPaths();\n            LoadSettings();\n            return true;\n        }\n\n        // 1. Check portable mode\n        var appDir = Compat.AppCurrentDir;\n        IsPortableMode = File.Exists(Path.Combine(appDir, \"Data\", \".sm-portable\"));\n        if (IsPortableMode)\n        {\n            LibraryDir = appDir + \"Data\";\n            SetStaticLibraryPaths();\n            LoadSettings();\n            return true;\n        }\n\n        // 2. Check %APPDATA%/StabilityMatrix/library.json\n        FilePath libraryJsonFile = Compat.AppDataHome + \"library.json\";\n        if (!libraryJsonFile.Exists)\n            return false;\n\n        try\n        {\n            var libraryJson = libraryJsonFile.ReadAllText();\n            var librarySettings = JsonSerializer.Deserialize<LibrarySettings>(libraryJson);\n\n            if (\n                !string.IsNullOrWhiteSpace(librarySettings?.LibraryPath)\n                && Directory.Exists(librarySettings.LibraryPath)\n            )\n            {\n                LibraryDir = librarySettings.LibraryPath;\n                SetStaticLibraryPaths();\n                LoadSettings();\n                return true;\n            }\n        }\n        catch (Exception e)\n        {\n            logger.LogWarning(\"Failed to read library.json in AppData: {Message}\", e.Message);\n        }\n        return false;\n    }\n\n    // Set static classes requiring library path\n    private void SetStaticLibraryPaths()\n    {\n        GlobalConfig.LibraryDir = LibraryDir;\n        ArchiveHelper.HomeDir = LibraryDir;\n        PyRunner.HomeDir = LibraryDir;\n        GlobalConfig.ModelsDir = ModelsDirectory;\n    }\n\n    /// <summary>\n    /// Save a new library path to %APPDATA%/StabilityMatrix/library.json\n    /// </summary>\n    public void SetLibraryPath(string path)\n    {\n        Compat.AppDataHome.Create();\n        var libraryJsonFile = Compat.AppDataHome.JoinFile(\"library.json\");\n\n        var library = new LibrarySettings { LibraryPath = path };\n        var libraryJson = JsonSerializer.Serialize(\n            library,\n            new JsonSerializerOptions { WriteIndented = true }\n        );\n        libraryJsonFile.WriteAllText(libraryJson);\n\n        // actually create the LibraryPath directory\n        Directory.CreateDirectory(path);\n    }\n\n    /// <summary>\n    /// Enable and create settings files for portable mode\n    /// Creates the ./Data directory and the `.sm-portable` marker file\n    /// </summary>\n    public void SetPortableMode()\n    {\n        // Get app directory\n        var appDir = Compat.AppCurrentDir;\n        // Create data directory\n        var dataDir = appDir.JoinDir(\"Data\");\n        dataDir.Create();\n        // Create marker file\n        dataDir.JoinFile(\".sm-portable\").Create();\n    }\n\n    public void SaveLaunchArgs(Guid packageId, IEnumerable<LaunchOption> launchArgs)\n    {\n        var packageData = Settings.InstalledPackages.FirstOrDefault(x => x.Id == packageId);\n        if (packageData == null)\n        {\n            return;\n        }\n        // Only save if not null or default\n        var toSave = launchArgs.Where(opt => !opt.IsEmptyOrDefault()).ToList();\n\n        packageData.LaunchArgs = toSave;\n        SaveSettings();\n    }\n\n    public bool IsEulaAccepted()\n    {\n        if (!File.Exists(GlobalSettingsPath))\n        {\n            Directory.CreateDirectory(Path.GetDirectoryName(GlobalSettingsPath)!);\n            File.Create(GlobalSettingsPath).Close();\n            File.WriteAllText(GlobalSettingsPath, \"{}\");\n            return false;\n        }\n\n        var json = File.ReadAllText(GlobalSettingsPath);\n        var globalSettings = JsonSerializer.Deserialize<GlobalSettings>(json);\n        return globalSettings?.EulaAccepted ?? false;\n    }\n\n    public void SetEulaAccepted()\n    {\n        var globalSettings = new GlobalSettings { EulaAccepted = true };\n        var json = JsonSerializer.Serialize(globalSettings);\n        File.WriteAllText(GlobalSettingsPath, json);\n    }\n\n    /// <summary>\n    /// Loads settings from the settings file. Continues without loading if the file does not exist or is empty.\n    /// Will set <see cref=\"isLoaded\"/> to true when finished in any case.\n    /// </summary>\n    protected virtual void LoadSettings(CancellationToken cancellationToken = default)\n    {\n        fileLock.Wait(cancellationToken);\n\n        try\n        {\n            if (!SettingsFile.Exists)\n            {\n                return;\n            }\n\n            using var fileStream = SettingsFile.Info.OpenRead();\n\n            if (fileStream.Length == 0)\n            {\n                logger.LogWarning(\"Settings file is empty, using default settings\");\n                return;\n            }\n\n            var loadedSettings = JsonSerializer.Deserialize(\n                fileStream,\n                SettingsSerializerContext.Default.Settings\n            );\n\n            if (loadedSettings is not null)\n            {\n                Settings = loadedSettings;\n            }\n        }\n        finally\n        {\n            fileLock.Release();\n\n            isLoaded = true;\n\n            Loaded?.Invoke(this, EventArgs.Empty);\n        }\n    }\n\n    /// <summary>\n    /// Loads settings from the settings file. Continues without loading if the file does not exist or is empty.\n    /// Will set <see cref=\"isLoaded\"/> to true when finished in any case.\n    /// </summary>\n    protected virtual async Task LoadSettingsAsync(CancellationToken cancellationToken = default)\n    {\n        await fileLock.WaitAsync(cancellationToken).ConfigureAwait(false);\n\n        try\n        {\n            if (!SettingsFile.Exists)\n            {\n                return;\n            }\n\n            await using var fileStream = SettingsFile.Info.OpenRead();\n\n            if (fileStream.Length == 0)\n            {\n                logger.LogWarning(\"Settings file is empty, using default settings\");\n                return;\n            }\n\n            var loadedSettings = await JsonSerializer\n                .DeserializeAsync(fileStream, SettingsSerializerContext.Default.Settings, cancellationToken)\n                .ConfigureAwait(false);\n\n            if (loadedSettings is not null)\n            {\n                Settings = loadedSettings;\n            }\n\n            Loaded?.Invoke(this, EventArgs.Empty);\n        }\n        finally\n        {\n            fileLock.Release();\n\n            isLoaded = true;\n\n            Loaded?.Invoke(this, EventArgs.Empty);\n        }\n    }\n\n    protected virtual void SaveSettings(CancellationToken cancellationToken = default)\n    {\n        // Skip saving if not loaded yet\n        if (!isLoaded)\n            return;\n\n        fileLock.Wait(cancellationToken);\n\n        try\n        {\n            // Create empty settings file if it doesn't exist\n            if (!SettingsFile.Exists)\n            {\n                SettingsFile.Directory?.Create();\n                SettingsFile.Create();\n            }\n\n            // Check disk space\n            if (SystemInfo.GetDiskFreeSpaceBytes(SettingsFile) is < 1 * SystemInfo.Mebibyte)\n            {\n                logger.LogWarning(\"Not enough disk space to save settings\");\n                return;\n            }\n\n            var jsonBytes = JsonSerializer.SerializeToUtf8Bytes(\n                Settings,\n                SettingsSerializerContext.Default.Settings\n            );\n\n            if (jsonBytes.Length == 0)\n            {\n                logger.LogError(\"JsonSerializer returned empty bytes for some reason\");\n                return;\n            }\n\n            using var fs = File.Open(SettingsFile, FileMode.Open);\n            if (fs.CanWrite)\n            {\n                fs.Write(jsonBytes, 0, jsonBytes.Length);\n                fs.Flush();\n                fs.SetLength(jsonBytes.Length);\n            }\n        }\n        finally\n        {\n            fileLock.Release();\n        }\n    }\n\n    protected virtual async Task SaveSettingsAsync(CancellationToken cancellationToken = default)\n    {\n        // Skip saving if not loaded yet\n        if (!isLoaded)\n            return;\n\n        await fileLock.WaitAsync(cancellationToken).ConfigureAwait(false);\n\n        try\n        {\n            // Create empty settings file if it doesn't exist\n            if (!SettingsFile.Exists)\n            {\n                SettingsFile.Directory?.Create();\n                SettingsFile.Create();\n            }\n\n            // Check disk space\n            if (SystemInfo.GetDiskFreeSpaceBytes(SettingsFile) is < 1 * SystemInfo.Mebibyte)\n            {\n                logger.LogWarning(\"Not enough disk space to save settings\");\n                return;\n            }\n\n            var jsonBytes = JsonSerializer.SerializeToUtf8Bytes(\n                Settings,\n                SettingsSerializerContext.Default.Settings\n            );\n\n            if (jsonBytes.Length == 0)\n            {\n                logger.LogError(\"JsonSerializer returned empty bytes for some reason\");\n                return;\n            }\n\n            await using var fs = File.Open(SettingsFile, FileMode.Open);\n            if (fs.CanWrite)\n            {\n                await fs.WriteAsync(jsonBytes, cancellationToken).ConfigureAwait(false);\n                await fs.FlushAsync(cancellationToken).ConfigureAwait(false);\n                fs.SetLength(jsonBytes.Length);\n            }\n        }\n        finally\n        {\n            fileLock.Release();\n        }\n    }\n\n    private volatile CancellationTokenSource? delayedSaveCts;\n\n    private Task SaveSettingsDelayed(TimeSpan delay)\n    {\n        var cts = new CancellationTokenSource();\n\n        var oldCancellationToken = Interlocked.Exchange(ref delayedSaveCts, cts);\n\n        try\n        {\n            oldCancellationToken?.Cancel();\n        }\n        catch (ObjectDisposedException) { }\n\n        return Task.Run(\n            async () =>\n            {\n                try\n                {\n                    await Task.Delay(delay, cts.Token).ConfigureAwait(false);\n\n                    await SaveSettingsAsync(cts.Token).ConfigureAwait(false);\n                }\n                catch (TaskCanceledException) { }\n                finally\n                {\n                    cts.Dispose();\n                }\n            },\n            CancellationToken.None\n        );\n    }\n\n    public Task FlushAsync(CancellationToken cancellationToken)\n    {\n        if (cancellationToken.IsCancellationRequested)\n        {\n            return Task.FromCanceled(cancellationToken);\n        }\n\n        if (!isLoaded)\n        {\n            return Task.CompletedTask;\n        }\n\n        // Cancel any delayed save tasks\n        try\n        {\n            Interlocked.Exchange(ref delayedSaveCts, null)?.Cancel();\n        }\n        catch (ObjectDisposedException) { }\n\n        return SaveSettingsAsync(cancellationToken);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Services/TrackedDownloadService.cs",
    "content": "﻿using System.Collections.Concurrent;\nusing System.Text;\nusing System.Text.Json;\nusing AsyncAwaitBestPractices;\nusing Microsoft.Extensions.Logging;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\n\nnamespace StabilityMatrix.Core.Services;\n\npublic class TrackedDownloadService : ITrackedDownloadService, IDisposable\n{\n    private readonly ILogger<TrackedDownloadService> logger;\n    private readonly IDownloadService downloadService;\n    private readonly ISettingsManager settingsManager;\n    private readonly IModelIndexService modelIndexService;\n\n    private readonly ConcurrentDictionary<Guid, (TrackedDownload Download, FileStream Stream)> downloads =\n        new();\n    private readonly ConcurrentQueue<TrackedDownload> pendingDownloads = new();\n    private readonly SemaphoreSlim downloadSemaphore;\n\n    public IEnumerable<TrackedDownload> Downloads => downloads.Values.Select(x => x.Download);\n    public IEnumerable<TrackedDownload> PendingDownloads => pendingDownloads;\n\n    /// <inheritdoc />\n    public event EventHandler<TrackedDownload>? DownloadAdded;\n    public event EventHandler<TrackedDownload>? DownloadStarted;\n\n    private int MaxConcurrentDownloads { get; set; }\n\n    private bool IsQueueEnabled => MaxConcurrentDownloads > 0;\n    public int ActiveDownloads =>\n        downloads.Count(kvp => kvp.Value.Download.ProgressState == ProgressState.Working);\n\n    public TrackedDownloadService(\n        ILogger<TrackedDownloadService> logger,\n        IDownloadService downloadService,\n        IModelIndexService modelIndexService,\n        ISettingsManager settingsManager\n    )\n    {\n        this.logger = logger;\n        this.downloadService = downloadService;\n        this.settingsManager = settingsManager;\n        this.modelIndexService = modelIndexService;\n\n        // Index for in-progress downloads when library dir loaded\n        settingsManager.RegisterOnLibraryDirSet(path =>\n        {\n            var downloadsDir = new DirectoryPath(settingsManager.DownloadsDirectory);\n            // Ignore if not exist\n            if (!downloadsDir.Exists)\n                return;\n\n            LoadInProgressDownloads(downloadsDir);\n        });\n\n        MaxConcurrentDownloads = settingsManager.Settings.MaxConcurrentDownloads;\n        downloadSemaphore = new SemaphoreSlim(MaxConcurrentDownloads);\n    }\n\n    private void OnDownloadAdded(TrackedDownload download)\n    {\n        logger.LogInformation(\"Download added: ({Download}, {State})\", download.Id, download.ProgressState);\n        DownloadAdded?.Invoke(this, download);\n    }\n\n    private void OnDownloadStarted(TrackedDownload download)\n    {\n        logger.LogInformation(\"Download started: ({Download}, {State})\", download.Id, download.ProgressState);\n        DownloadStarted?.Invoke(this, download);\n    }\n\n    /// <summary>\n    /// Creates a new tracked download with backed json file and adds it to the dictionary.\n    /// </summary>\n    /// <param name=\"download\"></param>\n    private void AddDownload(TrackedDownload download)\n    {\n        // Set download service\n        download.SetDownloadService(downloadService);\n\n        // Create json file\n        var downloadsDir = new DirectoryPath(settingsManager.DownloadsDirectory);\n        downloadsDir.Create();\n        var jsonFile = downloadsDir.JoinFile($\"{download.Id}.json\");\n        var jsonFileStream = jsonFile.Info.Open(FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read);\n\n        // Serialize to json\n        var json = JsonSerializer.Serialize(download);\n        jsonFileStream.Write(Encoding.UTF8.GetBytes(json));\n        jsonFileStream.Flush();\n\n        // Add to dictionary\n        downloads.TryAdd(download.Id, (download, jsonFileStream));\n\n        // Connect to state changed event to update json file\n        AttachHandlers(download);\n\n        OnDownloadAdded(download);\n    }\n\n    public async Task TryStartDownload(TrackedDownload download)\n    {\n        if (IsQueueEnabled && ActiveDownloads >= MaxConcurrentDownloads)\n        {\n            logger.LogDebug(\"Download {Download} is pending\", download.FileName);\n            pendingDownloads.Enqueue(download);\n            download.SetPending();\n            UpdateJsonForDownload(download);\n            return;\n        }\n\n        if (!IsQueueEnabled || await downloadSemaphore.WaitAsync(0).ConfigureAwait(false))\n        {\n            logger.LogDebug(\"Starting download {Download}\", download.FileName);\n            download.Start();\n            OnDownloadStarted(download);\n        }\n        else\n        {\n            logger.LogDebug(\"Download {Download} is pending\", download.FileName);\n            pendingDownloads.Enqueue(download);\n            download.SetPending();\n            UpdateJsonForDownload(download);\n        }\n    }\n\n    public async Task TryResumeDownload(TrackedDownload download)\n    {\n        if (IsQueueEnabled && ActiveDownloads >= MaxConcurrentDownloads)\n        {\n            logger.LogDebug(\"Download {Download} is pending\", download.FileName);\n            pendingDownloads.Enqueue(download);\n            download.SetPending();\n            UpdateJsonForDownload(download);\n            return;\n        }\n\n        if (!IsQueueEnabled || await downloadSemaphore.WaitAsync(0).ConfigureAwait(false))\n        {\n            logger.LogDebug(\"Resuming download {Download}\", download.FileName);\n            download.Resume();\n            OnDownloadStarted(download);\n        }\n        else\n        {\n            logger.LogDebug(\"Download {Download} is pending\", download.FileName);\n            pendingDownloads.Enqueue(download);\n            download.SetPending();\n            UpdateJsonForDownload(download);\n        }\n    }\n\n    public void UpdateMaxConcurrentDownloads(int newMax)\n    {\n        if (newMax <= 0)\n        {\n            MaxConcurrentDownloads = 0;\n            return;\n        }\n\n        var oldMax = MaxConcurrentDownloads;\n        MaxConcurrentDownloads = newMax;\n\n        if (oldMax == newMax)\n            return;\n\n        logger.LogInformation(\"Updating max concurrent downloads from {OldMax} to {NewMax}\", oldMax, newMax);\n\n        if (newMax > oldMax)\n        {\n            downloadSemaphore.Release(newMax - oldMax);\n            ProcessPendingDownloads().SafeFireAndForget();\n        }\n        // When reducing, we don't need to do anything immediately.\n        // The system will naturally adjust as downloads complete or are paused/resumed.\n\n        AdjustSemaphoreCount();\n    }\n\n    private void AdjustSemaphoreCount()\n    {\n        var currentCount = downloadSemaphore.CurrentCount;\n        var targetCount = MaxConcurrentDownloads - ActiveDownloads;\n\n        if (currentCount < targetCount)\n        {\n            downloadSemaphore.Release(targetCount - currentCount);\n        }\n        else if (currentCount > targetCount)\n        {\n            for (var i = 0; i < currentCount - targetCount; i++)\n            {\n                downloadSemaphore.Wait(0);\n            }\n        }\n    }\n\n    /// <summary>\n    /// Update the json file for the download.\n    /// </summary>\n    private void UpdateJsonForDownload(TrackedDownload download)\n    {\n        // Serialize to json\n        var json = JsonSerializer.Serialize(download);\n        var jsonBytes = Encoding.UTF8.GetBytes(json);\n\n        // Write to file\n        var (_, fs) = downloads[download.Id];\n        fs.Seek(0, SeekOrigin.Begin);\n        fs.Write(jsonBytes);\n        fs.Flush();\n    }\n\n    private void AttachHandlers(TrackedDownload download)\n    {\n        download.ProgressStateChanged += TrackedDownload_OnProgressStateChanged;\n    }\n\n    private async Task ProcessPendingDownloads()\n    {\n        while (pendingDownloads.TryPeek(out var nextDownload))\n        {\n            if (ActiveDownloads >= MaxConcurrentDownloads)\n            {\n                break;\n            }\n\n            if (pendingDownloads.TryDequeue(out nextDownload))\n            {\n                if (nextDownload.DownloadedBytes > 0)\n                {\n                    await TryResumeDownload(nextDownload).ConfigureAwait(false);\n                }\n                else\n                {\n                    await TryStartDownload(nextDownload).ConfigureAwait(false);\n                }\n            }\n            else\n            {\n                break;\n            }\n        }\n    }\n\n    /// <summary>\n    /// Handler when the download's state changes\n    /// </summary>\n    private void TrackedDownload_OnProgressStateChanged(object? sender, ProgressState e)\n    {\n        if (sender is not TrackedDownload download)\n        {\n            return;\n        }\n\n        // Update json file\n        UpdateJsonForDownload(download);\n\n        // If the download is completed, remove it from the dictionary and delete the json file\n        if (e is ProgressState.Success or ProgressState.Failed or ProgressState.Cancelled)\n        {\n            if (downloads.TryRemove(download.Id, out var downloadInfo))\n            {\n                downloadInfo.Item2.Dispose();\n                // Delete json file\n                new DirectoryPath(settingsManager.DownloadsDirectory)\n                    .JoinFile($\"{download.Id}.json\")\n                    .Delete();\n                logger.LogDebug(\"Removed download {Download}\", download.FileName);\n\n                if (IsQueueEnabled)\n                {\n                    try\n                    {\n                        downloadSemaphore.Release();\n                    }\n                    catch (SemaphoreFullException)\n                    {\n                        // Ignore\n                    }\n\n                    ProcessPendingDownloads().SafeFireAndForget();\n                }\n            }\n        }\n        else if (e is ProgressState.Paused && IsQueueEnabled)\n        {\n            downloadSemaphore.Release();\n            ProcessPendingDownloads().SafeFireAndForget();\n        }\n\n        // On successes, run the continuation action\n        if (e == ProgressState.Success)\n        {\n            if (download.ContextAction is not null)\n            {\n                logger.LogDebug(\"Running context action for {Download}\", download.FileName);\n            }\n\n            switch (download.ContextAction)\n            {\n                case CivitPostDownloadContextAction action:\n                    action.Invoke(settingsManager, modelIndexService);\n                    break;\n                case ModelPostDownloadContextAction action:\n                    action.Invoke(modelIndexService);\n                    break;\n            }\n        }\n    }\n\n    private void LoadInProgressDownloads(DirectoryPath downloadsDir)\n    {\n        logger.LogDebug(\"Indexing in-progress downloads at {DownloadsDir}...\", downloadsDir);\n\n        var jsonFiles = downloadsDir.Info.EnumerateFiles(\"*.json\", EnumerationOptionConstants.TopLevelOnly);\n\n        // Add to dictionary, the file name is the guid\n        foreach (var file in jsonFiles)\n        {\n            // Try to get a shared write handle\n            try\n            {\n                var fileStream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.Read);\n\n                // Deserialize json and add to dictionary\n                var download = JsonSerializer.Deserialize<TrackedDownload>(fileStream)!;\n\n                // If the download is marked as working, pause it\n                if (download.ProgressState is ProgressState.Working or ProgressState.Pending)\n                {\n                    download.ProgressState = ProgressState.Paused;\n                }\n                else if (\n                    download.ProgressState != ProgressState.Inactive\n                    && download.ProgressState != ProgressState.Paused\n                    && download.ProgressState != ProgressState.Pending\n                )\n                {\n                    // If the download is not inactive, skip it\n                    logger.LogWarning(\n                        \"Skipping download {Download} with state {State}\",\n                        download.FileName,\n                        download.ProgressState\n                    );\n                    fileStream.Dispose();\n\n                    // Delete json file\n                    logger.LogDebug(\n                        \"Deleting json file for {Download} with unsupported state\",\n                        download.FileName\n                    );\n                    file.Delete();\n                    continue;\n                }\n\n                download.SetDownloadService(downloadService);\n\n                downloads.TryAdd(download.Id, (download, fileStream));\n\n                if (download.ProgressState == ProgressState.Pending)\n                {\n                    pendingDownloads.Enqueue(download);\n                }\n\n                AttachHandlers(download);\n\n                OnDownloadAdded(download);\n\n                logger.LogDebug(\"Loaded in-progress download {Download}\", download.FileName);\n            }\n            catch (Exception e)\n            {\n                logger.LogInformation(e, \"Could not open file {File} for reading\", file.Name);\n            }\n        }\n    }\n\n    public TrackedDownload NewDownload(Uri downloadUrl, FilePath downloadPath)\n    {\n        var download = new TrackedDownload\n        {\n            Id = Guid.NewGuid(),\n            SourceUrl = downloadUrl,\n            DownloadDirectory = downloadPath.Directory!,\n            FileName = downloadPath.Name,\n            TempFileName = NewTempFileName(downloadPath.Directory!),\n        };\n\n        AddDownload(download);\n\n        return download;\n    }\n\n    /// <summary>\n    /// Generate a new temp file name that is unique in the given directory.\n    /// In format of \"Unconfirmed {id}.smdownload\"\n    /// </summary>\n    /// <param name=\"parentDir\"></param>\n    /// <returns></returns>\n    private static string NewTempFileName(DirectoryPath parentDir)\n    {\n        FilePath? tempFile = null;\n\n        for (var i = 0; i < 10; i++)\n        {\n            if (tempFile is { Exists: false })\n            {\n                return tempFile.Name;\n            }\n            var id = Random.Shared.Next(1000000, 9999999);\n            tempFile = parentDir.JoinFile($\"Unconfirmed {id}.smdownload\");\n        }\n\n        throw new Exception(\"Failed to generate a unique temp file name.\");\n    }\n\n    /// <inheritdoc />\n    public void Dispose()\n    {\n        foreach (var (download, fs) in downloads.Values)\n        {\n            if (download.ProgressState == ProgressState.Working)\n            {\n                try\n                {\n                    download.Pause();\n                }\n                catch (Exception e)\n                {\n                    logger.LogWarning(e, \"Failed to pause download {Download}\", download.FileName);\n                }\n            }\n        }\n\n        GC.SuppressFinalize(this);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/StabilityMatrix.Core.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n  <Import Project=\"..\\ConditionalSymbols.props\" />\n  <Import Project=\"..\\Runtimes.Default.props\" />\n    <PropertyGroup>\n        <BuiltInComInteropSupport>true</BuiltInComInteropSupport>\n        <EnableWindowsTargeting>true</EnableWindowsTargeting>\n        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <InternalsVisibleTo Include=\"StabilityMatrix.Tests\" />\n        <InternalsVisibleTo Include=\"StabilityMatrix.UITests\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <ProjectReference Include=\"..\\StabilityMatrix.Native\\StabilityMatrix.Native.csproj\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <PackageReference Include=\"Apizr\" />\n      <PackageReference Include=\"Apizr.Extensions.Microsoft.Caching\" />\n      <PackageReference Include=\"Apizr.Extensions.Microsoft.DependencyInjection\" />\n      <PackageReference Include=\"Apizr.Integrations.Fusillade\" />\n      <PackageReference Include=\"AsyncAwaitBestPractices\" />\n      <PackageReference Include=\"AutoCtor\" />\n      <PackageReference Include=\"Blake3\" />\n      <PackageReference Include=\"CompiledExpressions\" />\n      <PackageReference Include=\"Crc32.NET\" />\n      <PackageReference Include=\"CSharpDiscriminatedUnion\" />\n      <PackageReference Include=\"DeviceId\" />\n      <PackageReference Include=\"DeviceId.Linux\" />\n      <PackageReference Include=\"DeviceId.Mac\" />\n      <PackageReference Include=\"DeviceId.Windows\" />\n      <PackageReference Include=\"DeviceId.Windows.Wmi\" />\n      <PackageReference Include=\"DotNet.Bundle\" />\n      <PackageReference Include=\"DynamicData\" />\n      <PackageReference Include=\"ExifLibNet\" />\n      <PackageReference Include=\"FreneticLLC.FreneticUtilities\" />\n      <PackageReference Include=\"Hardware.Info\" />\n      <PackageReference Include=\"Injectio\" />\n      <PackageReference Include=\"JetBrains.Annotations\" />\n      <PackageReference Include=\"KGySoft.Drawing.Core\" />\n      <PackageReference Include=\"KGySoft.Drawing.SkiaSharp\" />\n      <PackageReference Include=\"LiteDB\" />\n      <PackageReference Include=\"LiteDB.Async\" />\n      <PackageReference Include=\"MetadataExtractor\" />\n      <PackageReference Include=\"Microsoft.Extensions.Caching.Memory\" />\n      <PackageReference Include=\"Microsoft.Extensions.DependencyInjection\" />\n      <PackageReference Include=\"Microsoft.Extensions.Hosting.Abstractions\" />\n      <PackageReference Include=\"Microsoft.Extensions.Options.ConfigurationExtensions\" />\n      <PackageReference Include=\"NLog\" />\n      <PackageReference Include=\"NLog.Extensions.Logging\" />\n      <PackageReference Include=\"NSec.Cryptography\" />\n      <PackageReference Include=\"Octokit\" />\n      <PackageReference Include=\"OneOf\" />\n      <PackageReference Include=\"OneOf.SourceGenerator\" />\n      <PackageReference Include=\"OpenIddict.Client\" />\n      <PackageReference Include=\"OpenIddict.Client.SystemNetHttp\" />\n      <PackageReference Include=\"Polly\" />\n      <PackageReference Include=\"Polly.Contrib.WaitAndRetry\" />\n      <PackageReference Include=\"pythonnet\" />\n      <PackageReference Include=\"Refit\" />\n      <PackageReference Include=\"Refit.HttpClientFactory\" />\n      <PackageReference Include=\"RockLib.Reflection.Optimized\" />\n      <PackageReference Include=\"Salaros.ConfigParser\" />\n      <PackageReference Include=\"Semver\" />\n      <PackageReference Include=\"Sentry.NLog\" />\n      <PackageReference Include=\"SharpCompress\" />\n      <PackageReference Include=\"SkiaSharp\" />\n      <PackageReference Include=\"TextMateSharp.Grammars\" />\n      <PackageReference Include=\"System.Runtime.Serialization.Formatters\" />\n      <PackageReference Include=\"Websocket.Client\" />\n      <PackageReference Include=\"YamlDotNet\" />\n      <PackageReference Include=\"Yoh.Text.Json.NamingPolicies\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <Compile Update=\"Models\\Api\\Lykos\\Analytics\\AnalyticsRequest.cs\">\n        <Generator>MSBuild:GenerateCodeFromAttributes</Generator>\n      </Compile>\n      <Compile Update=\"Models\\Api\\Lykos\\Analytics\\PackageInstallAnalyticsRequest.cs\">\n        <Generator>MSBuild:GenerateCodeFromAttributes</Generator>\n      </Compile>\n    </ItemGroup>\n\n    <ItemGroup>\n      <Folder Include=\"Api\\PromptGen\\Generated\\\" />\n    </ItemGroup>\n</Project>\n"
  },
  {
    "path": "StabilityMatrix.Core/StabilityMatrix.Core.csproj.DotSettings",
    "content": "﻿<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n\t\t\t\t\t\txmlns:s=\"clr-namespace:System;assembly=mscorlib\"\n\t\t\t\t\t\txmlns:wpf=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">\n\t<s:String x:Key=\"/Default/CodeInspection/Daemon/ConfigureAwaitAnalysisMode/@EntryValue\">Library</s:String></wpf:ResourceDictionary>\n"
  },
  {
    "path": "StabilityMatrix.Core/Updater/IUpdateHelper.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Models.Update;\n\nnamespace StabilityMatrix.Core.Updater;\n\npublic interface IUpdateHelper\n{\n    event EventHandler<UpdateStatusChangedEventArgs>? UpdateStatusChanged;\n\n    Task StartCheckingForUpdates();\n\n    Task CheckForUpdate();\n\n    Task DownloadUpdate(UpdateInfo updateInfo, IProgress<ProgressReport> progress);\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Updater/SignatureChecker.cs",
    "content": "﻿using System.Text;\nusing NSec.Cryptography;\n\nnamespace StabilityMatrix.Core.Updater;\n\npublic class SignatureChecker\n{\n    private static readonly SignatureAlgorithm Algorithm = SignatureAlgorithm.Ed25519;\n    private const string UpdatePublicKey = \n        \"-----BEGIN PUBLIC KEY-----\\n\" +\n        \"MCowBQYDK2VwAyEAqYXhKG1b0iOMnAZGBSBdFlFEWpFBIbIPQk0TtyE2SfI=\\n\" +\n        \"-----END PUBLIC KEY-----\\n\";\n\n    private readonly PublicKey publicKey;\n    \n    /// <summary>\n    /// Initializes a new instance of SignatureChecker.\n    /// </summary>\n    /// <param name=\"publicKeyPkix\">Pkix format public key. Defaults to update verification key.</param>\n    public SignatureChecker(string? publicKeyPkix = null)\n    {\n        publicKey = PublicKey.Import(\n            Algorithm, \n            Encoding.ASCII.GetBytes(publicKeyPkix ?? UpdatePublicKey),\n            KeyBlobFormat.PkixPublicKeyText);\n    }\n    \n    /// <summary>\n    /// Verifies the signature of provided data.\n    /// </summary>\n    /// <param name=\"data\">Data to verify</param>\n    /// <param name=\"signature\">Signature in base64 encoding</param>\n    /// <returns>True if verified</returns>\n    public bool Verify(string data, string signature)\n    {\n        var signatureBytes = Convert.FromBase64String(signature);\n        return Algorithm.Verify(publicKey, Encoding.UTF8.GetBytes(data), signatureBytes);\n    }\n    \n    /// <summary>\n    /// Verifies the signature of provided data.\n    /// </summary>\n    /// <param name=\"data\">Data to verify</param>\n    /// <param name=\"signature\">Signature in base64 encoding</param>\n    /// <returns>True if verified</returns>\n    public bool Verify(ReadOnlySpan<byte> data, ReadOnlySpan<byte> signature)\n    {\n        return Algorithm.Verify(publicKey, data, signature);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Updater/UpdateHelper.cs",
    "content": "﻿using System.Text.Json;\nusing System.Web;\nusing Injectio.Attributes;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Options;\nusing StabilityMatrix.Core.Api.LykosAuthApi;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Configs;\nusing StabilityMatrix.Core.Models.FileInterfaces;\nusing StabilityMatrix.Core.Models.Progress;\nusing StabilityMatrix.Core.Models.Update;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Core.Updater;\n\n[RegisterSingleton<IUpdateHelper, UpdateHelper>]\npublic class UpdateHelper : IUpdateHelper\n{\n    private readonly ILogger<UpdateHelper> logger;\n    private readonly IHttpClientFactory httpClientFactory;\n    private readonly IDownloadService downloadService;\n    private readonly ISettingsManager settingsManager;\n    private readonly ILykosAuthApiV2 lykosAuthApi;\n    private readonly DebugOptions debugOptions;\n    private readonly System.Timers.Timer timer = new(TimeSpan.FromMinutes(60));\n\n    private string UpdateManifestUrl =>\n        debugOptions.UpdateManifestUrl ?? \"https://cdn.lykos.ai/update-v3.json\";\n\n    public const string UpdateFolderName = \".StabilityMatrixUpdate\";\n    public static DirectoryPath UpdateFolder => Compat.AppCurrentDir.JoinDir(UpdateFolderName);\n\n    public static IPathObject ExecutablePath =>\n        Compat.IsMacOS\n            ? UpdateFolder.JoinDir(Compat.GetAppName())\n            : UpdateFolder.JoinFile(Compat.GetAppName());\n\n    /// <inheritdoc />\n    public event EventHandler<UpdateStatusChangedEventArgs>? UpdateStatusChanged;\n\n    public UpdateHelper(\n        ILogger<UpdateHelper> logger,\n        IHttpClientFactory httpClientFactory,\n        IDownloadService downloadService,\n        IOptions<DebugOptions> debugOptions,\n        ISettingsManager settingsManager,\n        ILykosAuthApiV2 lykosAuthApi\n    )\n    {\n        this.logger = logger;\n        this.httpClientFactory = httpClientFactory;\n        this.downloadService = downloadService;\n        this.settingsManager = settingsManager;\n        this.lykosAuthApi = lykosAuthApi;\n        this.debugOptions = debugOptions.Value;\n\n        timer.Elapsed += async (_, _) =>\n        {\n            await CheckForUpdate().ConfigureAwait(false);\n        };\n\n        settingsManager.RegisterOnLibraryDirSet(_ =>\n        {\n            timer.Start();\n        });\n    }\n\n    public async Task StartCheckingForUpdates()\n    {\n        timer.Enabled = true;\n        timer.Start();\n        await CheckForUpdate().ConfigureAwait(false);\n    }\n\n    public async Task DownloadUpdate(UpdateInfo updateInfo, IProgress<ProgressReport> progress)\n    {\n        UpdateFolder.Create();\n        UpdateFolder.Info.Attributes |= FileAttributes.Hidden;\n\n        var downloadFile = UpdateFolder.JoinFile(Path.GetFileName(updateInfo.Url.ToString()));\n\n        var extractDir = UpdateFolder.JoinDir(\"extract\");\n\n        try\n        {\n            var url = updateInfo.Url.ToString();\n\n            // check if need authenticated download\n            const string authedPathPrefix = \"/lykos-s1/\";\n            if (\n                updateInfo.Url.Host.Equals(\"cdn.lykos.ai\", StringComparison.OrdinalIgnoreCase)\n                && updateInfo.Url.PathAndQuery.StartsWith(\n                    authedPathPrefix,\n                    StringComparison.OrdinalIgnoreCase\n                )\n            )\n            {\n                logger.LogInformation(\"Handling authenticated update download: {Url}\", updateInfo.Url);\n\n                var path = updateInfo.Url.PathAndQuery.StripStart(authedPathPrefix);\n                path = HttpUtility.UrlDecode(path);\n                url = (\n                    await lykosAuthApi.ApiV2FilesDownload(path).ConfigureAwait(false)\n                ).DownloadUrl.ToString();\n            }\n\n            // Download update\n            await downloadService\n                .DownloadToFileAsync(url, downloadFile, progress: progress, httpClientName: \"UpdateClient\")\n                .ConfigureAwait(false);\n\n            // Unzip if needed\n            if (downloadFile.Extension == \".zip\")\n            {\n                if (extractDir.Exists)\n                {\n                    await extractDir.DeleteAsync(true).ConfigureAwait(false);\n                }\n                extractDir.Create();\n\n                progress.Report(new ProgressReport(-1, isIndeterminate: true, type: ProgressType.Extract));\n\n                await ArchiveHelper.Extract(downloadFile, extractDir).ConfigureAwait(false);\n\n                progress.Report(new ProgressReport(1, isIndeterminate: true, type: ProgressType.Extract));\n\n                // Find binary and move it up to the root\n                var binaryFile = extractDir\n                    .EnumerateFiles(\"*\", EnumerationOptionConstants.AllDirectories)\n                    .First(f => f.Extension.ToLowerInvariant() is \".exe\" or \".appimage\");\n\n                await binaryFile.MoveToAsync((FilePath)ExecutablePath).ConfigureAwait(false);\n            }\n            else if (downloadFile.Extension == \".dmg\")\n            {\n                if (!Compat.IsMacOS)\n                    throw new NotSupportedException(\".dmg is only supported on macOS\");\n\n                if (extractDir.Exists)\n                {\n                    await extractDir.DeleteAsync(true).ConfigureAwait(false);\n                }\n                extractDir.Create();\n\n                // Extract dmg contents\n                await ArchiveHelper.ExtractDmg(downloadFile, extractDir).ConfigureAwait(false);\n\n                // Find app dir and move it up to the root\n                var appBundle = extractDir.EnumerateDirectories(\"*.app\").First();\n\n                await appBundle.MoveToAsync((DirectoryPath)ExecutablePath).ConfigureAwait(false);\n            }\n            // Otherwise just rename\n            else\n            {\n                downloadFile.Rename(ExecutablePath.Name);\n            }\n\n            progress.Report(new ProgressReport(1d));\n        }\n        finally\n        {\n            // Clean up original download\n            await downloadFile.DeleteAsync().ConfigureAwait(false);\n            // Clean up extract dir\n            if (extractDir.Exists)\n            {\n                await extractDir.DeleteAsync(true).ConfigureAwait(false);\n            }\n        }\n    }\n\n    public async Task CheckForUpdate()\n    {\n        try\n        {\n            var httpClient = httpClientFactory.CreateClient(\"UpdateClient\");\n            var response = await httpClient.GetAsync(UpdateManifestUrl).ConfigureAwait(false);\n            if (!response.IsSuccessStatusCode)\n            {\n                logger.LogWarning(\n                    \"Error while checking for update {StatusCode} - {Content}\",\n                    response.StatusCode,\n                    await response.Content.ReadAsStringAsync().ConfigureAwait(false)\n                );\n                return;\n            }\n\n            var updateManifest = await JsonSerializer\n                .DeserializeAsync<UpdateManifest>(\n                    await response.Content.ReadAsStreamAsync().ConfigureAwait(false),\n                    new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }\n                )\n                .ConfigureAwait(false);\n\n            if (updateManifest is null)\n            {\n                logger.LogError(\"UpdateManifest is null\");\n                return;\n            }\n\n            foreach (\n                var channel in Enum.GetValues(typeof(UpdateChannel))\n                    .Cast<UpdateChannel>()\n                    .Where(c =>\n                        c > UpdateChannel.Unknown && c <= settingsManager.Settings.PreferredUpdateChannel\n                    )\n            )\n            {\n                if (\n                    updateManifest.Updates.TryGetValue(channel, out var platforms)\n                    && platforms.GetInfoForCurrentPlatform() is { } update\n                    && ValidateUpdate(update)\n                )\n                {\n                    OnUpdateStatusChanged(\n                        new UpdateStatusChangedEventArgs\n                        {\n                            LatestUpdate = update,\n                            UpdateChannels = updateManifest\n                                .Updates.Select(kv => (kv.Key, kv.Value.GetInfoForCurrentPlatform()))\n                                .Where(kv => kv.Item2 is not null)\n                                .ToDictionary(kv => kv.Item1, kv => kv.Item2)!,\n                        }\n                    );\n                    return;\n                }\n            }\n\n            logger.LogInformation(\"No update available\");\n\n            var args = new UpdateStatusChangedEventArgs\n            {\n                UpdateChannels = updateManifest\n                    .Updates.Select(kv => (kv.Key, kv.Value.GetInfoForCurrentPlatform()))\n                    .Where(kv => kv.Item2 is not null)\n                    .ToDictionary(kv => kv.Item1, kv => kv.Item2)!,\n            };\n            OnUpdateStatusChanged(args);\n        }\n        catch (Exception e)\n        {\n            logger.LogError(e, \"Couldn't check for update\");\n        }\n    }\n\n    private bool ValidateUpdate(UpdateInfo? update)\n    {\n        if (update is null)\n            return false;\n\n        // Verify signature\n        var checker = new SignatureChecker();\n        var signedData = update.GetSignedData();\n\n        if (!checker.Verify(signedData, update.Signature))\n        {\n            logger.LogError(\n                \"UpdateInfo signature {Signature} is invalid, Data = {Data}, UpdateInfo = {Info}\",\n                update.Signature,\n                signedData,\n                update\n            );\n            return false;\n        }\n\n        switch (update.Version.ComparePrecedenceTo(Compat.AppVersion))\n        {\n            case > 0:\n                // Newer version available\n                return true;\n            case 0:\n            {\n                // Same version available, check if we both have commit hash metadata\n                var updateHash = update.Version.Metadata;\n                var appHash = Compat.AppVersion.Metadata;\n\n                // Always assume update if (We don't have hash && Update has hash)\n                if (string.IsNullOrEmpty(appHash) && !string.IsNullOrEmpty(updateHash))\n                {\n                    return true;\n                }\n\n                // Trim both to the lower length, to a minimum of 7 characters\n                var minLength = Math.Min(7, Math.Min(updateHash.Length, appHash.Length));\n                updateHash = updateHash[..minLength];\n                appHash = appHash[..minLength];\n\n                // If different, we can update\n                if (updateHash != appHash)\n                {\n                    return true;\n                }\n\n                break;\n            }\n        }\n\n        return false;\n    }\n\n    private void OnUpdateStatusChanged(UpdateStatusChangedEventArgs args)\n    {\n        UpdateStatusChanged?.Invoke(this, args);\n\n        if (args.LatestUpdate is { } update)\n        {\n            logger.LogInformation(\n                \"Update available {AppVer} -> {UpdateVer}\",\n                Compat.AppVersion,\n                update.Version\n            );\n\n            EventManager.Instance.OnUpdateAvailable(update);\n        }\n    }\n\n    private void NotifyUpdateAvailable(UpdateInfo update)\n    {\n        logger.LogInformation(\"Update available {AppVer} -> {UpdateVer}\", Compat.AppVersion, update.Version);\n        EventManager.Instance.OnUpdateAvailable(update);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Updater/UpdateStatusChangedEventArgs.cs",
    "content": "﻿using StabilityMatrix.Core.Models.Update;\n\nnamespace StabilityMatrix.Core.Updater;\n\npublic class UpdateStatusChangedEventArgs : EventArgs\n{\n    public UpdateInfo? LatestUpdate { get; init; }\n\n    public IReadOnlyDictionary<UpdateChannel, UpdateInfo> UpdateChannels { get; init; } =\n        new Dictionary<UpdateChannel, UpdateInfo>();\n\n    public DateTimeOffset CheckedAt { get; init; } = DateTimeOffset.UtcNow;\n}\n"
  },
  {
    "path": "StabilityMatrix.Core/Validators/RequiresMatchAttribute.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace StabilityMatrix.Core.Validators;\n\n/// <summary>\n/// Validator that requires equality to another property\n/// i.e. Confirm password must match password\n/// </summary>\npublic sealed class RequiresMatchAttribute<T> : ValidationAttribute\n    where T : IEquatable<T>\n{\n    public string PropertyName { get; }\n\n    public RequiresMatchAttribute(string propertyName)\n    {\n        PropertyName = propertyName;\n    }\n\n    public RequiresMatchAttribute(string propertyName, string errorMessage)\n    {\n        PropertyName = propertyName;\n        ErrorMessage = errorMessage;\n    }\n\n    protected override ValidationResult IsValid(object? value, ValidationContext validationContext)\n    {\n        var instance = validationContext.ObjectInstance;\n\n        var otherProperty =\n            instance.GetType().GetProperty(PropertyName)\n            ?? throw new ArgumentException($\"Property {PropertyName} not found\");\n\n        if (otherProperty.PropertyType != typeof(T))\n        {\n            throw new ArgumentException($\"Property {PropertyName} is not of type {typeof(T)}\");\n        }\n\n        var otherValue = otherProperty.GetValue(instance);\n\n        if (otherValue == null && value == null)\n        {\n            return ValidationResult.Success!;\n        }\n\n        if (((IEquatable<T>?)otherValue)!.Equals(value))\n        {\n            return ValidationResult.Success!;\n        }\n\n        return new ValidationResult(\n            $\"{validationContext.DisplayName} does not match {PropertyName}\"\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Native/NativeFileOperations.cs",
    "content": "﻿using System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing JetBrains.Annotations;\nusing StabilityMatrix.Native.Abstractions;\n\nnamespace StabilityMatrix.Native;\n\n[PublicAPI]\npublic static class NativeFileOperations\n{\n    public static INativeRecycleBinProvider? RecycleBin { get; }\n\n    [MemberNotNullWhen(true, nameof(RecycleBin))]\n    public static bool IsRecycleBinAvailable => RecycleBin is not null;\n\n    static NativeFileOperations()\n    {\n#if Windows\n        if (!OperatingSystem.IsWindows())\n        {\n            Debug.Fail(\n                $\"Assembly of {nameof(NativeFileOperations)} was compiled for Windows, \"\n                    + $\"the current OS is '{Environment.OSVersion}'\"\n            );\n            return;\n        }\n\n        RecycleBin = new Windows.NativeRecycleBinProvider();\n#elif OSX\n        if (!OperatingSystem.IsMacOS())\n        {\n            Debug.Fail(\n                $\"Assembly of {nameof(NativeFileOperations)} was compiled for macOS, \"\n                    + $\"the current OS is '{Environment.OSVersion}'\"\n            );\n            return;\n        }\n\n        RecycleBin = new macOS.NativeRecycleBinProvider();\n#endif\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Native/StabilityMatrix.Native.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n    <Import Project=\"..\\Runtimes.Default.props\" />\n\n\t<PropertyGroup Condition=\"'$(RuntimeIdentifier)' == 'win-x64'\">\n\t\t<DefineConstants>Windows</DefineConstants>\n\t\t<IsWindows>true</IsWindows>\n\t</PropertyGroup>\n\t<PropertyGroup Condition=\"'$(RuntimeIdentifier)' == 'osx-x64' Or '$(RuntimeIdentifier)' == 'osx-arm64'\">\n\t\t<DefineConstants>OSX</DefineConstants>\n\t\t<IsOSX>true</IsOSX>\n\t</PropertyGroup>\n\t<PropertyGroup Condition=\"'$(RuntimeIdentifier)' == 'linux-x64'\">\n\t\t<DefineConstants>Linux</DefineConstants>\n\t\t<IsLinux>true</IsLinux>\n\t</PropertyGroup>\n\n\t<PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|AnyCPU'\">\n\t\t<Optimize>false</Optimize>\n\t</PropertyGroup>\n\n\t<ItemGroup>\n\t\t<ProjectReference Include=\"..\\StabilityMatrix.Native.Abstractions\\StabilityMatrix.Native.Abstractions.csproj\" />\n\t</ItemGroup>\n\n\t<ItemGroup Condition=\"'$(RuntimeIdentifier)' == 'win-x64'\">\n\t\t<ProjectReference Include=\"..\\StabilityMatrix.Native.Windows\\StabilityMatrix.Native.Windows.csproj\" />\n\t</ItemGroup>\n\t<ItemGroup Condition=\"'$(RuntimeIdentifier)' == 'osx-x64' Or '$(RuntimeIdentifier)' == 'osx-arm64'\">\n\t\t<ProjectReference Include=\"..\\StabilityMatrix.Native.macOS\\StabilityMatrix.Native.macOS.csproj\" />\n\t</ItemGroup>\n  <ItemGroup>\n      <PackageReference Include=\"JetBrains.Annotations\" PrivateAssets=\"all\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "StabilityMatrix.Native.Abstractions/INativeRecycleBinProvider.cs",
    "content": "﻿namespace StabilityMatrix.Native.Abstractions;\n\npublic interface INativeRecycleBinProvider\n{\n    /// <summary>\n    /// Moves a file to the recycle bin.\n    /// </summary>\n    /// <param name=\"path\">The path of the file to be moved.</param>\n    /// <param name=\"flags\">The flags to be used for the operation.</param>\n    void MoveFileToRecycleBin(string path, NativeFileOperationFlags flags = default);\n\n    /// <summary>\n    /// Asynchronously moves a file to the recycle bin.\n    /// </summary>\n    /// <param name=\"path\">The path of the file to be moved.</param>\n    /// <param name=\"flags\">The flags to be used for the operation.</param>\n    /// <returns>A task representing the asynchronous operation.</returns>\n    Task MoveFileToRecycleBinAsync(string path, NativeFileOperationFlags flags = default);\n\n    /// <summary>\n    /// Moves the specified files to the recycle bin.\n    /// </summary>\n    /// <param name=\"paths\">The paths of the files to be moved.</param>\n    /// <param name=\"flags\">The flags to be used for the operation.</param>\n    void MoveFilesToRecycleBin(IEnumerable<string> paths, NativeFileOperationFlags flags = default);\n\n    /// <summary>\n    /// Asynchronously moves the specified files to the recycle bin.\n    /// </summary>\n    /// <param name=\"paths\">The paths of the files to be moved.</param>\n    /// <param name=\"flags\">The flags to be used for the operation.</param>\n    /// <returns>A task representing the asynchronous operation.</returns>\n    Task MoveFilesToRecycleBinAsync(IEnumerable<string> paths, NativeFileOperationFlags flags = default);\n\n    /// <summary>\n    /// Moves the specified directory to the recycle bin.\n    /// </summary>\n    /// <param name=\"path\">The path of the directory to be moved.</param>\n    /// <param name=\"flags\">The flags to be used for the operation.</param>\n    void MoveDirectoryToRecycleBin(string path, NativeFileOperationFlags flags = default);\n\n    /// <summary>\n    /// Moves a directory to the recycle bin asynchronously.\n    /// </summary>\n    /// <param name=\"path\">The path of the directory to be moved.</param>\n    /// <param name=\"flags\">The flags to be used for the operation.</param>\n    /// <returns>A task representing the asynchronous operation.</returns>\n    Task MoveDirectoryToRecycleBinAsync(string path, NativeFileOperationFlags flags = default);\n\n    /// <summary>\n    /// Moves the specified directories to the recycle bin.\n    /// </summary>\n    /// <param name=\"paths\">The paths of the directories to be moved.</param>\n    /// <param name=\"flags\">The flags to be used for the operation.</param>\n    void MoveDirectoriesToRecycleBin(IEnumerable<string> paths, NativeFileOperationFlags flags = default);\n\n    /// <summary>\n    /// Moves the specified directories to the recycle bin asynchronously.\n    /// </summary>\n    /// <param name=\"paths\">The paths of the directories to be moved.</param>\n    /// <param name=\"flags\">The flags to be used for the operation.</param>\n    /// <returns>A task representing the asynchronous operation.</returns>\n    Task MoveDirectoriesToRecycleBinAsync(\n        IEnumerable<string> paths,\n        NativeFileOperationFlags flags = default\n    );\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.Abstractions/NativeFileOperationFlags.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\n\nnamespace StabilityMatrix.Native.Abstractions;\n\n[Flags]\npublic enum NativeFileOperationFlags : uint\n{\n    /// <summary>\n    /// Do not display a progress dialog.\n    /// </summary>\n    Silent = 1 << 0,\n\n    /// <summary>\n    /// Display a warning if files are being permanently deleted.\n    /// </summary>\n    WarnOnPermanentDelete = 1 << 1,\n\n    /// <summary>\n    /// Do not ask the user to confirm the operation.\n    /// </summary>\n    NoConfirmation = 1 << 2,\n}\n\npublic static class NativeFileOperationFlagsExtensions\n{\n    [SuppressMessage(\"ReSharper\", \"CommentTypo\")]\n    public static void ToWindowsFileOperationFlags(\n        this NativeFileOperationFlags flags,\n        ref uint windowsFileOperationFlags\n    )\n    {\n        if (flags.HasFlag(NativeFileOperationFlags.Silent))\n        {\n            windowsFileOperationFlags |= 0x0004; // FOF_SILENT\n        }\n\n        if (flags.HasFlag(NativeFileOperationFlags.WarnOnPermanentDelete))\n        {\n            windowsFileOperationFlags |= 0x4000; // FOF_WANTNUKEWARNING\n        }\n\n        if (flags.HasFlag(NativeFileOperationFlags.NoConfirmation))\n        {\n            windowsFileOperationFlags |= 0x0010; // FOF_NOCONFIRMATION\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.Abstractions/StabilityMatrix.Native.Abstractions.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n    <ItemGroup>\n      <PackageReference Include=\"JetBrains.Annotations\" PrivateAssets=\"all\" />\n    </ItemGroup>\n</Project>\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/AssemblyInfo.cs",
    "content": "﻿using System.Runtime.Versioning;\n\n[assembly: SupportedOSPlatform(\"windows\")]\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/FileOperations/FileOperationWrapper.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Diagnostics.CodeAnalysis;\nusing JetBrains.Annotations;\nusing StabilityMatrix.Native.Windows.Interop;\n\nnamespace StabilityMatrix.Native.Windows.FileOperations;\n\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\ninternal partial class FileOperationWrapper : IDisposable\n{\n    private bool _disposed;\n    private readonly IFileOperation _fileOperation;\n    private readonly IFileOperationProgressSink? _callbackSink;\n    private readonly uint _sinkCookie;\n\n    [PublicAPI]\n    public FileOperationWrapper()\n        : this(null) { }\n\n    public FileOperationWrapper(IFileOperationProgressSink? callbackSink)\n        : this(callbackSink, IntPtr.Zero) { }\n\n    public FileOperationWrapper(IFileOperationProgressSink? callbackSink, IntPtr ownerHandle)\n    {\n        _callbackSink = callbackSink;\n        _fileOperation =\n            (IFileOperation?)Activator.CreateInstance(FileOperationType)\n            ?? throw new NullReferenceException(\"Failed to create FileOperation instance.\");\n\n        if (_callbackSink != null)\n            _sinkCookie = _fileOperation.Advise(_callbackSink);\n        if (ownerHandle != IntPtr.Zero)\n            _fileOperation.SetOwnerWindow((uint)ownerHandle);\n    }\n\n    public void SetOperationFlags(FileOperationFlags operationFlags)\n    {\n        _fileOperation.SetOperationFlags(operationFlags);\n    }\n\n    [PublicAPI]\n    public void CopyItem(string source, string destination, string newName)\n    {\n        ThrowIfDisposed();\n        using var sourceItem = CreateShellItem(source);\n        using var destinationItem = CreateShellItem(destination);\n        _fileOperation.CopyItem(sourceItem.Item, destinationItem.Item, newName, null);\n    }\n\n    [PublicAPI]\n    public void MoveItem(string source, string destination, string newName)\n    {\n        ThrowIfDisposed();\n        using var sourceItem = CreateShellItem(source);\n        using var destinationItem = CreateShellItem(destination);\n        _fileOperation.MoveItem(sourceItem.Item, destinationItem.Item, newName, null);\n    }\n\n    [PublicAPI]\n    public void RenameItem(string source, string newName)\n    {\n        ThrowIfDisposed();\n        using var sourceItem = CreateShellItem(source);\n        _fileOperation.RenameItem(sourceItem.Item, newName, null);\n    }\n\n    public void DeleteItem(string source)\n    {\n        ThrowIfDisposed();\n        using var sourceItem = CreateShellItem(source);\n        _fileOperation.DeleteItem(sourceItem.Item, null);\n    }\n\n    /*public void DeleteItems(params string[] sources)\n    {\n        ThrowIfDisposed();\n        using var sourceItems = CreateShellItemArray(sources);\n        _fileOperation.DeleteItems(sourceItems.Item);\n    }*/\n\n    public void DeleteItems(string[] sources)\n    {\n        ThrowIfDisposed();\n\n        var pidlArray = new IntPtr[sources.Length];\n\n        try\n        {\n            // Convert paths to PIDLs\n            for (var i = 0; i < sources.Length; i++)\n            {\n                pidlArray[i] = ILCreateFromPathW(sources[i]);\n                if (pidlArray[i] == IntPtr.Zero)\n                    throw new Exception($\"Failed to create PIDL for path: {sources[i]}\");\n            }\n\n            // Create ShellItemArray from PIDLs\n            var shellItemArray = SHCreateShellItemArrayFromIDLists((uint)sources.Length, pidlArray);\n\n            // Use the IFileOperation interface to delete items\n            _fileOperation.DeleteItems(shellItemArray);\n        }\n        finally\n        {\n            // Free PIDLs\n            foreach (var pidl in pidlArray)\n            {\n                if (pidl != IntPtr.Zero)\n                {\n                    Marshal.FreeCoTaskMem(pidl);\n                }\n            }\n        }\n    }\n\n    [PublicAPI]\n    public void NewItem(string folderName, string name, FileAttributes attrs)\n    {\n        ThrowIfDisposed();\n        using var folderItem = CreateShellItem(folderName);\n        _fileOperation.NewItem(folderItem.Item, attrs, name, string.Empty, _callbackSink);\n    }\n\n    public void PerformOperations()\n    {\n        ThrowIfDisposed();\n        _fileOperation.PerformOperations();\n    }\n\n    private void ThrowIfDisposed()\n    {\n        if (_disposed)\n        {\n            throw new ObjectDisposedException(GetType().Name);\n        }\n    }\n\n    public void Dispose()\n    {\n        if (!_disposed)\n        {\n            _disposed = true;\n\n            if (_callbackSink != null)\n            {\n                _fileOperation.Unadvise(_sinkCookie);\n            }\n\n            Marshal.FinalReleaseComObject(_fileOperation);\n        }\n    }\n\n    private static ComReleaser<IShellItem> CreateShellItem(string path)\n    {\n        // Normalize path slashes\n        path = path.Replace('/', '\\\\');\n\n        return new ComReleaser<IShellItem>(\n            (IShellItem)SHCreateItemFromParsingName(path, IntPtr.Zero, ref _shellItemGuid)\n        );\n    }\n\n    private static ComReleaser<IShellItemArray> CreateShellItemArray(params string[] paths)\n    {\n        var pidls = new IntPtr[paths.Length];\n\n        try\n        {\n            for (var i = 0; i < paths.Length; i++)\n            {\n                if (SHParseDisplayName(paths[i], IntPtr.Zero, out var pidl, 0, out _) != 0)\n                {\n                    throw new Win32Exception(Marshal.GetLastWin32Error());\n                }\n\n                pidls[i] = pidl;\n            }\n\n            return new ComReleaser<IShellItemArray>(\n                SHCreateShellItemArrayFromIDLists((uint)pidls.Length, pidls)\n            );\n        }\n        finally\n        {\n            foreach (var pidl in pidls)\n            {\n                Marshal.FreeCoTaskMem(pidl);\n            }\n        }\n    }\n\n    [LibraryImport(\"shell32.dll\", SetLastError = true)]\n    private static partial int SHParseDisplayName(\n        [MarshalAs(UnmanagedType.LPWStr)] string pszName,\n        IntPtr pbc, // IBindCtx\n        out IntPtr ppidl,\n        uint sfgaoIn,\n        out uint psfgaoOut\n    );\n\n    [LibraryImport(\"shell32.dll\", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]\n    private static partial IntPtr ILCreateFromPathW(string pszPath);\n\n    [DllImport(\"shell32.dll\", SetLastError = true, CharSet = CharSet.Unicode, PreserveSig = false)]\n    [return: MarshalAs(UnmanagedType.Interface)]\n    private static extern object SHCreateItemFromParsingName(\n        [MarshalAs(UnmanagedType.LPWStr)] string pszPath,\n        IntPtr pbc, // IBindCtx\n        ref Guid riid\n    );\n\n    [DllImport(\"shell32.dll\", SetLastError = true, CharSet = CharSet.Unicode, PreserveSig = false)]\n    [return: MarshalAs(UnmanagedType.Interface)]\n    private static extern IShellItemArray SHCreateShellItemArrayFromIDLists(\n        uint cidl,\n        [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStruct)] IntPtr[] rgpidl\n    );\n\n    private static readonly Guid ClsidFileOperation = new(\"3ad05575-8857-4850-9277-11b85bdb8e09\");\n    private static readonly Type FileOperationType =\n        Type.GetTypeFromCLSID(ClsidFileOperation)\n        ?? throw new NullReferenceException(\"Failed to get FileOperation type from CLSID\");\n    private static Guid _shellItemGuid = typeof(IShellItem).GUID;\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/GlobalUsings.cs",
    "content": "﻿// Global using directives\n\nglobal using System.Runtime.InteropServices;\nglobal using System.Runtime.InteropServices.Marshalling;\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/Interop/ComReleaser.cs",
    "content": "﻿namespace StabilityMatrix.Native.Windows.Interop\n{\n    internal sealed class ComReleaser<T> : IDisposable\n        where T : class\n    {\n        public T? Item { get; private set; }\n\n        public ComReleaser(T obj)\n        {\n            ArgumentNullException.ThrowIfNull(obj);\n            if (!Marshal.IsComObject(obj))\n                throw new ArgumentOutOfRangeException(nameof(obj));\n            Item = obj;\n        }\n\n        public void Dispose()\n        {\n            if (Item != null)\n            {\n                Marshal.FinalReleaseComObject(Item);\n                Item = null;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/Interop/FileOperationFlags.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\n\nnamespace StabilityMatrix.Native.Windows.Interop;\n\n[Flags]\n[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n[SuppressMessage(\"ReSharper\", \"IdentifierTypo\")]\ninternal enum FileOperationFlags : uint\n{\n    FOF_MULTIDESTFILES = 0x0001,\n    FOF_CONFIRMMOUSE = 0x0002,\n    FOF_WANTMAPPINGHANDLE = 0x0020, // Fill in SHFILEOPSTRUCT.hNameMappings\n    FOF_FILESONLY = 0x0080, // on *.*, do only files\n    FOF_NOCONFIRMMKDIR = 0x0200, // don't confirm making any needed dirs\n    FOF_NOCOPYSECURITYATTRIBS = 0x0800, // dont copy NT file Security Attributes\n    FOF_NORECURSION = 0x1000, // don't recurse into directories.\n    FOF_NO_CONNECTED_ELEMENTS = 0x2000, // don't operate on connected file elements.\n    FOF_NORECURSEREPARSE = 0x8000, // treat reparse points as objects, not containers\n\n    /// <summary>\n    /// Do not show a dialog during the process\n    /// </summary>\n    FOF_SILENT = 0x0004,\n\n    FOF_RENAMEONCOLLISION = 0x0008,\n\n    /// <summary>\n    /// Do not ask the user to confirm selection\n    /// </summary>\n    FOF_NOCONFIRMATION = 0x0010,\n\n    /// <summary>\n    /// Delete the file to the recycle bin.  (Required flag to send a file to the bin\n    /// </summary>\n    FOF_ALLOWUNDO = 0x0040,\n\n    /// <summary>\n    /// Do not show the names of the files or folders that are being recycled.\n    /// </summary>\n    FOF_SIMPLEPROGRESS = 0x0100,\n\n    /// <summary>\n    /// Surpress errors, if any occur during the process.\n    /// </summary>\n    FOF_NOERRORUI = 0x0400,\n\n    /// <summary>\n    /// Warn if files are too big to fit in the recycle bin and will need\n    /// to be deleted completely.\n    /// </summary>\n    FOF_WANTNUKEWARNING = 0x4000,\n\n    FOFX_ADDUNDORECORD = 0x20000000,\n\n    FOFX_NOSKIPJUNCTIONS = 0x00010000,\n\n    FOFX_PREFERHARDLINK = 0x00020000,\n\n    FOFX_SHOWELEVATIONPROMPT = 0x00040000,\n\n    FOFX_EARLYFAILURE = 0x00100000,\n\n    FOFX_PRESERVEFILEEXTENSIONS = 0x00200000,\n\n    FOFX_KEEPNEWERFILE = 0x00400000,\n\n    FOFX_NOCOPYHOOKS = 0x00800000,\n\n    FOFX_NOMINIMIZEBOX = 0x01000000,\n\n    FOFX_MOVEACLSACROSSVOLUMES = 0x02000000,\n\n    FOFX_DONTDISPLAYSOURCEPATH = 0x04000000,\n\n    FOFX_DONTDISPLAYDESTPATH = 0x08000000,\n\n    FOFX_RECYCLEONDELETE = 0x00080000,\n\n    FOFX_REQUIREELEVATION = 0x10000000,\n\n    FOFX_COPYASDOWNLOAD = 0x40000000,\n\n    FOFX_DONTDISPLAYLOCATIONS = 0x80000000,\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/Interop/FileOperationProgressSinkTcs.cs",
    "content": "﻿namespace StabilityMatrix.Native.Windows.Interop;\n\n[GeneratedComClass]\n[Guid(\"04b0f1a7-9490-44bc-96e1-4296a31252e2\")]\npublic partial class FileOperationProgressSinkTcs : TaskCompletionSource<uint>, IFileOperationProgressSink\n{\n    private readonly IProgress<(uint WorkTotal, uint WorkSoFar)>? progress;\n\n    public FileOperationProgressSinkTcs() { }\n\n    public FileOperationProgressSinkTcs(IProgress<(uint, uint)> progress)\n    {\n        this.progress = progress;\n    }\n\n    /// <inheritdoc />\n    public virtual void StartOperations() { }\n\n    /// <inheritdoc />\n    public virtual void FinishOperations(uint hrResult)\n    {\n        SetResult(hrResult);\n    }\n\n    /// <inheritdoc />\n    public virtual void PreRenameItem(uint dwFlags, IShellItem psiItem, string pszNewName) { }\n\n    /// <inheritdoc />\n    public virtual void PostRenameItem(\n        uint dwFlags,\n        IShellItem psiItem,\n        string pszNewName,\n        uint hrRename,\n        IShellItem psiNewlyCreated\n    ) { }\n\n    /// <inheritdoc />\n    public virtual void PreMoveItem(\n        uint dwFlags,\n        IShellItem psiItem,\n        IShellItem psiDestinationFolder,\n        string pszNewName\n    ) { }\n\n    /// <inheritdoc />\n    public virtual void PostMoveItem(\n        uint dwFlags,\n        IShellItem psiItem,\n        IShellItem psiDestinationFolder,\n        string pszNewName,\n        uint hrMove,\n        IShellItem psiNewlyCreated\n    ) { }\n\n    /// <inheritdoc />\n    public virtual void PreCopyItem(\n        uint dwFlags,\n        IShellItem psiItem,\n        IShellItem psiDestinationFolder,\n        string pszNewName\n    ) { }\n\n    /// <inheritdoc />\n    public virtual void PostCopyItem(\n        uint dwFlags,\n        IShellItem psiItem,\n        IShellItem psiDestinationFolder,\n        string pszNewName,\n        uint hrCopy,\n        IShellItem psiNewlyCreated\n    ) { }\n\n    /// <inheritdoc />\n    public virtual void PreDeleteItem(uint dwFlags, IShellItem psiItem) { }\n\n    /// <inheritdoc />\n    public virtual void PostDeleteItem(\n        uint dwFlags,\n        IShellItem psiItem,\n        uint hrDelete,\n        IShellItem psiNewlyCreated\n    ) { }\n\n    /// <inheritdoc />\n    public virtual void PreNewItem(uint dwFlags, IShellItem psiDestinationFolder, string pszNewName) { }\n\n    /// <inheritdoc />\n    public virtual void PostNewItem(\n        uint dwFlags,\n        IShellItem psiDestinationFolder,\n        string pszNewName,\n        string pszTemplateName,\n        uint dwFileAttributes,\n        uint hrNew,\n        IShellItem psiNewItem\n    ) { }\n\n    /// <inheritdoc />\n    public virtual void UpdateProgress(uint iWorkTotal, uint iWorkSoFar)\n    {\n        progress?.Report((iWorkTotal, iWorkSoFar));\n    }\n\n    /// <inheritdoc />\n    public virtual void ResetTimer() { }\n\n    /// <inheritdoc />\n    public virtual void PauseTimer() { }\n\n    /// <inheritdoc />\n    public virtual void ResumeTimer() { }\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/Interop/IFileOperation.cs",
    "content": "﻿namespace StabilityMatrix.Native.Windows.Interop;\n\n[GeneratedComInterface]\n[Guid(\"947aab5f-0a5c-4c13-b4d6-4bf7836fc9f8\")]\n[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]\ninternal partial interface IFileOperation\n{\n    uint Advise(IFileOperationProgressSink pfops);\n    void Unadvise(uint dwCookie);\n    void SetOperationFlags(FileOperationFlags dwOperationFlags);\n    void SetProgressMessage([MarshalAs(UnmanagedType.LPWStr)] string pszMessage);\n    void SetProgressDialog([MarshalAs(UnmanagedType.Interface)] object popd);\n    void SetProperties([MarshalAs(UnmanagedType.Interface)] object pproparray);\n    void SetOwnerWindow(uint hwndParent);\n    void ApplyPropertiesToItem(IShellItem psiItem);\n    void ApplyPropertiesToItems([MarshalAs(UnmanagedType.Interface)] object punkItems);\n    void RenameItem(\n        IShellItem psiItem,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszNewName,\n        IFileOperationProgressSink? pfopsItem\n    );\n    void RenameItems(\n        [MarshalAs(UnmanagedType.Interface)] object pUnkItems,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszNewName\n    );\n    void MoveItem(\n        IShellItem psiItem,\n        IShellItem psiDestinationFolder,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszNewName,\n        IFileOperationProgressSink? pfopsItem\n    );\n    void MoveItems([MarshalAs(UnmanagedType.Interface)] object punkItems, IShellItem psiDestinationFolder);\n    void CopyItem(\n        IShellItem psiItem,\n        IShellItem psiDestinationFolder,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszCopyName,\n        IFileOperationProgressSink? pfopsItem\n    );\n    void CopyItems([MarshalAs(UnmanagedType.Interface)] object punkItems, IShellItem psiDestinationFolder);\n    void DeleteItem(IShellItem psiItem, IFileOperationProgressSink? pfopsItem);\n    void DeleteItems([MarshalAs(UnmanagedType.Interface)] object punkItems);\n    uint NewItem(\n        IShellItem psiDestinationFolder,\n        FileAttributes dwFileAttributes,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszName,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszTemplateName,\n        IFileOperationProgressSink? pfopsItem\n    );\n    void PerformOperations();\n\n    [return: MarshalAs(UnmanagedType.Bool)]\n    bool GetAnyOperationsAborted();\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/Interop/IFileOperationProgressSink.cs",
    "content": "﻿namespace StabilityMatrix.Native.Windows.Interop;\n\n[GeneratedComInterface]\n[Guid(\"04b0f1a7-9490-44bc-96e1-4296a31252e2\")]\n[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]\npublic partial interface IFileOperationProgressSink\n{\n    void StartOperations();\n    void FinishOperations(uint hrResult);\n\n    void PreRenameItem(uint dwFlags, IShellItem psiItem, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName);\n    void PostRenameItem(\n        uint dwFlags,\n        IShellItem psiItem,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszNewName,\n        uint hrRename,\n        IShellItem psiNewlyCreated\n    );\n\n    void PreMoveItem(\n        uint dwFlags,\n        IShellItem psiItem,\n        IShellItem psiDestinationFolder,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszNewName\n    );\n    void PostMoveItem(\n        uint dwFlags,\n        IShellItem psiItem,\n        IShellItem psiDestinationFolder,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszNewName,\n        uint hrMove,\n        IShellItem psiNewlyCreated\n    );\n\n    void PreCopyItem(\n        uint dwFlags,\n        IShellItem psiItem,\n        IShellItem psiDestinationFolder,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszNewName\n    );\n    void PostCopyItem(\n        uint dwFlags,\n        IShellItem psiItem,\n        IShellItem psiDestinationFolder,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszNewName,\n        uint hrCopy,\n        IShellItem psiNewlyCreated\n    );\n\n    void PreDeleteItem(uint dwFlags, IShellItem psiItem);\n    void PostDeleteItem(uint dwFlags, IShellItem psiItem, uint hrDelete, IShellItem psiNewlyCreated);\n\n    void PreNewItem(\n        uint dwFlags,\n        IShellItem psiDestinationFolder,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszNewName\n    );\n    void PostNewItem(\n        uint dwFlags,\n        IShellItem psiDestinationFolder,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszNewName,\n        [MarshalAs(UnmanagedType.LPWStr)] string pszTemplateName,\n        uint dwFileAttributes,\n        uint hrNew,\n        IShellItem psiNewItem\n    );\n\n    void UpdateProgress(uint iWorkTotal, uint iWorkSoFar);\n\n    void ResetTimer();\n    void PauseTimer();\n    void ResumeTimer();\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/Interop/IShellItem.cs",
    "content": "﻿namespace StabilityMatrix.Native.Windows.Interop;\n\n[GeneratedComInterface]\n[Guid(\"43826d1e-e718-42ee-bc55-a1e261c37bfe\")]\n[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]\npublic partial interface IShellItem\n{\n    [return: MarshalAs(UnmanagedType.Interface)]\n    object BindToHandler(\n        IntPtr pbc, // IBindCTX\n        ref Guid bhid,\n        ref Guid riid\n    );\n\n    IShellItem GetParent();\n\n    [return: MarshalAs(UnmanagedType.LPWStr)]\n    string GetDisplayName(SIGDN sigdnName);\n\n    uint GetAttributes(uint sfgaoMask);\n\n    int Compare(IShellItem psi, uint hint);\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/Interop/IShellItemArray.cs",
    "content": "﻿using System.Runtime.CompilerServices;\n\nnamespace StabilityMatrix.Native.Windows.Interop;\n\n[GeneratedComInterface]\n[Guid(\"b63ea76d-1f85-456f-a19c-48159efa858b\")]\n[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]\npublic partial interface IShellItemArray\n{\n    // uint BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, out object ppvOut);\n\n    /*[return: MarshalAs(UnmanagedType.Interface)]\n    IShellItem GetItemAt(uint dwIndex);\n\n    [return: MarshalAs(UnmanagedType.U4)]\n    uint GetCount();*/\n\n    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]\n    void BindToHandler(\n        [MarshalAs(UnmanagedType.Interface)] IntPtr pbc,\n        ref Guid rbhid,\n        ref Guid riid,\n        out IntPtr ppvOut\n    );\n\n    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]\n    void GetPropertyStore(int flags, ref Guid riid, out IntPtr ppv);\n\n    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]\n    int GetCount();\n\n    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]\n    IShellItem GetItemAt(int dwIndex);\n\n    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]\n    void EnumItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenumShellItems);\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/Interop/SIGDN.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\n\nnamespace StabilityMatrix.Native.Windows.Interop\n{\n    [SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n    [SuppressMessage(\"ReSharper\", \"IdentifierTypo\")]\n    public enum SIGDN : uint\n    {\n        SIGDN_NORMALDISPLAY = 0x00000000,\n        SIGDN_PARENTRELATIVEPARSING = 0x80018001,\n        SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8001c001,\n        SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000,\n        SIGDN_PARENTRELATIVEEDITING = 0x80031001,\n        SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000,\n        SIGDN_FILESYSPATH = 0x80058000,\n        SIGDN_URL = 0x80068000\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/NativeRecycleBinProvider.cs",
    "content": "﻿using JetBrains.Annotations;\nusing StabilityMatrix.Native.Abstractions;\nusing StabilityMatrix.Native.Windows.FileOperations;\nusing StabilityMatrix.Native.Windows.Interop;\n\nnamespace StabilityMatrix.Native.Windows;\n\n[PublicAPI]\npublic class NativeRecycleBinProvider : INativeRecycleBinProvider\n{\n    /// <inheritdoc />\n    public void MoveFileToRecycleBin(string path, NativeFileOperationFlags flags = default)\n    {\n        using var fo = new FileOperationWrapper();\n\n        var fileOperationFlags = default(uint);\n        flags.ToWindowsFileOperationFlags(ref fileOperationFlags);\n\n        fo.SetOperationFlags(\n            (FileOperationFlags)fileOperationFlags | FileOperationFlags.FOFX_RECYCLEONDELETE\n        );\n        fo.DeleteItem(path);\n        fo.PerformOperations();\n    }\n\n    /// <inheritdoc />\n    public Task MoveFileToRecycleBinAsync(string path, NativeFileOperationFlags flags = default)\n    {\n        return Task.Run(() => MoveFileToRecycleBin(path, flags));\n    }\n\n    /// <inheritdoc />\n    public void MoveFilesToRecycleBin(IEnumerable<string> paths, NativeFileOperationFlags flags = default)\n    {\n        using var fo = new FileOperationWrapper();\n\n        var fileOperationFlags = default(uint);\n        flags.ToWindowsFileOperationFlags(ref fileOperationFlags);\n\n        fo.SetOperationFlags(\n            (FileOperationFlags)fileOperationFlags | FileOperationFlags.FOFX_RECYCLEONDELETE\n        );\n        fo.DeleteItems(paths.ToArray());\n        fo.PerformOperations();\n    }\n\n    /// <inheritdoc />\n    public Task MoveFilesToRecycleBinAsync(\n        IEnumerable<string> paths,\n        NativeFileOperationFlags flags = default\n    )\n    {\n        return Task.Run(() => MoveFilesToRecycleBin(paths, flags));\n    }\n\n    /// <inheritdoc />\n    public void MoveDirectoryToRecycleBin(string path, NativeFileOperationFlags flags = default)\n    {\n        using var fo = new FileOperationWrapper();\n\n        var fileOperationFlags = default(uint);\n        flags.ToWindowsFileOperationFlags(ref fileOperationFlags);\n\n        fo.SetOperationFlags(\n            (FileOperationFlags)fileOperationFlags | FileOperationFlags.FOFX_RECYCLEONDELETE\n        );\n        fo.DeleteItem(path);\n        fo.PerformOperations();\n    }\n\n    /// <inheritdoc />\n    public Task MoveDirectoryToRecycleBinAsync(string path, NativeFileOperationFlags flags = default)\n    {\n        return Task.Run(() => MoveDirectoryToRecycleBin(path, flags));\n    }\n\n    /// <inheritdoc />\n    public void MoveDirectoriesToRecycleBin(\n        IEnumerable<string> paths,\n        NativeFileOperationFlags flags = default\n    )\n    {\n        using var fo = new FileOperationWrapper();\n\n        var fileOperationFlags = default(uint);\n        flags.ToWindowsFileOperationFlags(ref fileOperationFlags);\n\n        fo.SetOperationFlags(\n            (FileOperationFlags)fileOperationFlags | FileOperationFlags.FOFX_RECYCLEONDELETE\n        );\n        fo.DeleteItems(paths.ToArray());\n        fo.PerformOperations();\n    }\n\n    /// <inheritdoc />\n    public Task MoveDirectoriesToRecycleBinAsync(\n        IEnumerable<string> paths,\n        NativeFileOperationFlags flags = default\n    )\n    {\n        return Task.Run(() => MoveDirectoriesToRecycleBin(paths, flags));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.Windows/StabilityMatrix.Native.Windows.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <RuntimeIdentifiers>win-x64</RuntimeIdentifiers>\n        <BuiltInComInteropSupport>true</BuiltInComInteropSupport>\n        <EnableWindowsTargeting>true</EnableWindowsTargeting>\n        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <InternalsVisibleTo Include=\"StabilityMatrix.Tests\" />\n        <InternalsVisibleTo Include=\"StabilityMatrix.UITests\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <ProjectReference Include=\"..\\StabilityMatrix.Native.Abstractions\\StabilityMatrix.Native.Abstractions.csproj\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <PackageReference Include=\"JetBrains.Annotations\" PrivateAssets=\"all\" />\n    </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "StabilityMatrix.Native.macOS/AssemblyInfo.cs",
    "content": "﻿using System.Runtime.Versioning;\n\n[assembly: SupportedOSPlatform(\"macos\")]\n[assembly: SupportedOSPlatform(\"osx\")]\n"
  },
  {
    "path": "StabilityMatrix.Native.macOS/NativeRecycleBinProvider.cs",
    "content": "﻿using System.Diagnostics;\nusing JetBrains.Annotations;\nusing StabilityMatrix.Native.Abstractions;\n\nnamespace StabilityMatrix.Native.macOS;\n\n[PublicAPI]\npublic class NativeRecycleBinProvider : INativeRecycleBinProvider\n{\n    /// <inheritdoc />\n    public void MoveFileToRecycleBin(string path, NativeFileOperationFlags flags = default)\n    {\n        MoveFileToRecycleBinAsync(path, flags).GetAwaiter().GetResult();\n    }\n\n    /// <inheritdoc />\n    public async Task MoveFileToRecycleBinAsync(string path, NativeFileOperationFlags flags = default)\n    {\n        await RunAppleScriptAsync($\"tell application \\\\\\\"Finder\\\\\\\" to delete POSIX file \\\\\\\"{path}\\\\\\\"\");\n    }\n\n    /// <inheritdoc />\n    public void MoveFilesToRecycleBin(IEnumerable<string> paths, NativeFileOperationFlags flags = default)\n    {\n        MoveFilesToRecycleBinAsync(paths, flags).GetAwaiter().GetResult();\n    }\n\n    /// <inheritdoc />\n    public async Task MoveFilesToRecycleBinAsync(\n        IEnumerable<string> paths,\n        NativeFileOperationFlags flags = default\n    )\n    {\n        var pathsArrayString = string.Join(\", \", paths.Select(p => $\"POSIX file \\\\\\\"{p}\\\\\\\"\"));\n\n        await RunAppleScriptAsync($\"tell application \\\\\\\"Finder\\\\\\\" to delete {{{pathsArrayString}}}\");\n    }\n\n    /// <inheritdoc />\n    public void MoveDirectoryToRecycleBin(string path, NativeFileOperationFlags flags = default)\n    {\n        MoveDirectoryToRecycleBinAsync(path, flags).GetAwaiter().GetResult();\n    }\n\n    /// <inheritdoc />\n    public async Task MoveDirectoryToRecycleBinAsync(string path, NativeFileOperationFlags flags = default)\n    {\n        await RunAppleScriptAsync(\n            $\"tell application \\\\\\\"Finder\\\\\\\" to delete folder POSIX file \\\\\\\"{path}\\\\\\\"\"\n        );\n    }\n\n    /// <inheritdoc />\n    public void MoveDirectoriesToRecycleBin(\n        IEnumerable<string> paths,\n        NativeFileOperationFlags flags = default\n    )\n    {\n        MoveDirectoriesToRecycleBinAsync(paths, flags).GetAwaiter().GetResult();\n    }\n\n    /// <inheritdoc />\n    public async Task MoveDirectoriesToRecycleBinAsync(\n        IEnumerable<string> paths,\n        NativeFileOperationFlags flags = default\n    )\n    {\n        var pathsArrayString = string.Join(\", \", paths.Select(p => $\"folder POSIX file \\\\\\\"{p}\\\\\\\"\"));\n\n        await RunAppleScriptAsync($\"tell application \\\\\\\"Finder\\\\\\\" to delete {{{pathsArrayString}}}\");\n    }\n\n    /// <summary>\n    /// Runs an AppleScript script.\n    /// </summary>\n    private static async Task RunAppleScriptAsync(\n        string script,\n        CancellationToken cancellationToken = default\n    )\n    {\n        using var process = new Process();\n\n        process.StartInfo = new ProcessStartInfo\n        {\n            FileName = \"/usr/bin/osascript\",\n            Arguments = $\"-e \\\"{script}\\\"\",\n            RedirectStandardOutput = true,\n            RedirectStandardError = true,\n            UseShellExecute = false,\n            CreateNoWindow = true\n        };\n\n        process.Start();\n\n        await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);\n\n        if (process.ExitCode != 0)\n        {\n            var stdOut = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false);\n            var stdErr = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);\n            throw new InvalidOperationException(\n                $\"The AppleScript script failed with exit code {process.ExitCode}: (StdOut = {stdOut}, StdErr = {stdErr})\"\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Native.macOS/StabilityMatrix.Native.macOS.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <RuntimeIdentifiers>osx-x64;osx-arm64</RuntimeIdentifiers>\n    </PropertyGroup>\n\n    <ItemGroup>\n      <ProjectReference Include=\"..\\StabilityMatrix.Native.Abstractions\\StabilityMatrix.Native.Abstractions.csproj\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <PackageReference Include=\"JetBrains.Annotations\" PrivateAssets=\"all\" />\n    </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "StabilityMatrix.Tests/Avalonia/CheckpointFileViewModelTests.cs",
    "content": "﻿using Microsoft.Extensions.Logging;\nusing NSubstitute;\nusing StabilityMatrix.Avalonia.Services;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\nusing StabilityMatrix.Avalonia.ViewModels.CheckpointManager;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Models.Settings;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Tests.Avalonia;\n\n[TestClass]\npublic class CheckpointFileViewModelTests\n{\n    [TestMethod]\n    public void HasStandardUpdate_IsFalse_WhenUpdateIsEarlyAccessOnly()\n    {\n        var vm = CreateViewModel(CreateCheckpointFile(hasUpdate: true, hasEarlyAccessUpdateOnly: true));\n\n        Assert.IsTrue(vm.HasEarlyAccessUpdateOnly);\n        Assert.IsFalse(vm.HasStandardUpdate);\n    }\n\n    [TestMethod]\n    public void CheckpointFile_Setter_RaisesDerivedUpdatePropertyNotifications()\n    {\n        var vm = CreateViewModel(CreateCheckpointFile(hasUpdate: true, hasEarlyAccessUpdateOnly: true));\n        var changed = new List<string>();\n\n        vm.PropertyChanged += (_, e) =>\n        {\n            if (!string.IsNullOrWhiteSpace(e.PropertyName))\n            {\n                changed.Add(e.PropertyName!);\n            }\n        };\n\n        vm.CheckpointFile = CreateCheckpointFile(hasUpdate: true, hasEarlyAccessUpdateOnly: false);\n\n        Assert.IsFalse(vm.HasEarlyAccessUpdateOnly);\n        Assert.IsTrue(vm.HasStandardUpdate);\n        CollectionAssert.Contains(changed, nameof(CheckpointFileViewModel.HasEarlyAccessUpdateOnly));\n        CollectionAssert.Contains(changed, nameof(CheckpointFileViewModel.HasStandardUpdate));\n    }\n\n    private static CheckpointFileViewModel CreateViewModel(LocalModelFile checkpointFile)\n    {\n        var settingsManager = Substitute.For<ISettingsManager>();\n        settingsManager.Settings.Returns(new Settings { ShowNsfwInCheckpointsPage = true });\n        settingsManager.IsLibraryDirSet.Returns(false);\n\n        return new CheckpointFileViewModel(\n            settingsManager,\n            Substitute.For<IModelIndexService>(),\n            Substitute.For<INotificationService>(),\n            Substitute.For<IDownloadService>(),\n            Substitute.For<IServiceManager<ViewModelBase>>(),\n            Substitute.For<ILogger>(),\n            checkpointFile\n        );\n    }\n\n    private static LocalModelFile CreateCheckpointFile(bool hasUpdate, bool hasEarlyAccessUpdateOnly)\n    {\n        return new LocalModelFile\n        {\n            RelativePath = \"StableDiffusion/test-vm.safetensors\",\n            SharedFolderType = SharedFolderType.StableDiffusion,\n            HasUpdate = hasUpdate,\n            HasEarlyAccessUpdateOnly = hasEarlyAccessUpdateOnly,\n            ConnectedModelInfo = new ConnectedModelInfo\n            {\n                ModelId = 77,\n                VersionId = 700,\n                Source = ConnectedModelSource.Civitai,\n                ModelName = \"VM Test Model\",\n                ModelDescription = string.Empty,\n                VersionName = \"v700\",\n                Tags = [],\n                Hashes = new CivitFileHashes(),\n            },\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Avalonia/Converters/NullableDefaultNumericConverterTests.cs",
    "content": "﻿using System.Globalization;\nusing StabilityMatrix.Avalonia.Converters;\n\nnamespace StabilityMatrix.Tests.Avalonia.Converters;\n\n[TestClass]\npublic class NullableDefaultNumericConverterTests\n{\n    [TestMethod]\n    public void Convert_IntToDecimal_ValueReturnsNullable()\n    {\n        const int value = 123;\n\n        var converter = NullableDefaultNumericConverters.IntToDecimal;\n\n        var result = converter.Convert(value, typeof(decimal?), null, CultureInfo.InvariantCulture);\n\n        Assert.AreEqual((decimal?)123, result);\n    }\n\n    [TestMethod]\n    public void ConvertBack_IntToDecimal_NullableReturnsDefault()\n    {\n        decimal? value = null;\n\n        var converter = NullableDefaultNumericConverters.IntToDecimal;\n\n        var result = converter.ConvertBack(value, typeof(int), null, CultureInfo.InvariantCulture);\n\n        Assert.AreEqual(0, result);\n    }\n\n    [TestMethod]\n    public void ConvertBack_IntToDouble_NanReturnsDefault()\n    {\n        const double value = double.NaN;\n\n        var converter = new NullableDefaultNumericConverter<int, double>();\n\n        var result = converter.ConvertBack(value, typeof(int), null, CultureInfo.InvariantCulture);\n\n        Assert.AreEqual(0, result);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Avalonia/DesignDataTests.cs",
    "content": "﻿using System.Reflection;\nusing StabilityMatrix.Avalonia.DesignData;\n\nnamespace StabilityMatrix.Tests.Avalonia;\n\n[TestClass]\npublic class DesignDataTests\n{\n    [ClassInitialize]\n    public static void ClassInitialize(TestContext context)\n    {\n        SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());\n        DesignData.Initialize();\n    }\n    \n    // Return all properties\n    public static IEnumerable<object[]> DesignDataProperties => \n        typeof(DesignData).GetProperties()\n            .Select(p => new object[] { p });\n    \n    [TestMethod]\n    [DynamicData(nameof(DesignDataProperties))]\n    public void Property_ShouldBeNotNull(PropertyInfo property)\n    {\n        var value = property.GetValue(null);\n        Assert.IsNotNull(value);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Avalonia/FileNameFormatProviderTests.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\nusing StabilityMatrix.Avalonia.Models.Inference;\n\nnamespace StabilityMatrix.Tests.Avalonia;\n\n[TestClass]\npublic class FileNameFormatProviderTests\n{\n    [TestMethod]\n    public void TestFileNameFormatProviderValidate_Valid_ShouldNotThrow()\n    {\n        var provider = new FileNameFormatProvider();\n\n        var result = provider.Validate(\"{date}_{time}-{model_name}-{seed}\");\n        Assert.AreEqual(ValidationResult.Success, result);\n    }\n\n    [TestMethod]\n    public void TestFileNameFormatProviderValidate_Invalid_ShouldThrow()\n    {\n        var provider = new FileNameFormatProvider();\n\n        var result = provider.Validate(\"{date}_{time}-{model_name}-{seed}-{invalid}\");\n        Assert.AreNotEqual(ValidationResult.Success, result);\n\n        Assert.AreEqual(\"Unknown variable 'invalid'\", result.ErrorMessage);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Avalonia/FileNameFormatTests.cs",
    "content": "﻿using StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Inference;\n\nnamespace StabilityMatrix.Tests.Avalonia;\n\n[TestClass]\npublic class FileNameFormatTests\n{\n    [TestMethod]\n    public void TestFileNameFormatParse()\n    {\n        var provider = new FileNameFormatProvider\n        {\n            GenerationParameters = new GenerationParameters { Seed = 123 },\n            ProjectName = \"uwu\",\n            ProjectType = InferenceProjectType.TextToImage,\n        };\n\n        var format = FileNameFormat.Parse(\"{project_type} - {project_name} ({seed})\", provider);\n\n        Assert.AreEqual(\"TextToImage - uwu (123)\", format.GetFileName());\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Avalonia/LoadableViewModelBaseTests.cs",
    "content": "﻿using System.Text.Json;\nusing System.Text.Json.Serialization;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NSubstitute;\nusing StabilityMatrix.Avalonia.Models;\nusing StabilityMatrix.Avalonia.ViewModels.Base;\n\n#pragma warning disable CS0657 // Not a valid attribute location for this declaration\n\nnamespace StabilityMatrix.Tests.Avalonia;\n\n// Example subclass\npublic class TestLoadableViewModel : LoadableViewModelBase\n{\n    [JsonInclude]\n    public string? Included { get; set; }\n\n    public int Id { get; set; }\n\n    [JsonIgnore]\n    public int Ignored { get; set; }\n}\n\npublic class TestLoadableViewModelReadOnly : LoadableViewModelBase\n{\n    public int ReadOnly { get; }\n\n    public TestLoadableViewModelReadOnly(int readOnly)\n    {\n        ReadOnly = readOnly;\n    }\n}\n\npublic class TestLoadableViewModelReadOnlyLoadable : LoadableViewModelBase\n{\n    public TestLoadableViewModel ReadOnlyLoadable { get; } = new();\n}\n\npublic partial class TestLoadableViewModelObservable : LoadableViewModelBase\n{\n    [ObservableProperty]\n    [property: JsonIgnore]\n    private string? title;\n\n    [ObservableProperty]\n    private int id;\n\n    [RelayCommand]\n    private void TestCommand()\n    {\n        throw new NotImplementedException();\n    }\n}\n\npublic class TestLoadableViewModelNestedInterface : LoadableViewModelBase\n{\n    public IJsonLoadableState? NestedState { get; set; }\n}\n\npublic class TestLoadableViewModelNested : LoadableViewModelBase\n{\n    public TestLoadableViewModel? NestedState { get; set; }\n}\n\n[TestClass]\npublic class LoadableViewModelBaseTests\n{\n    [TestMethod]\n    public void TestSaveStateToJsonObject_JsonIgnoreAttribute()\n    {\n        var vm = new TestLoadableViewModel\n        {\n            Included = \"abc\",\n            Id = 123,\n            Ignored = 456,\n        };\n\n        var state = vm.SaveStateToJsonObject();\n\n        // [JsonInclude] and not marked property should be serialized.\n        // Ignored property should be ignored.\n        Assert.AreEqual(2, state.Count);\n        Assert.AreEqual(\"abc\", state[\"Included\"].Deserialize<string>());\n        Assert.AreEqual(123, state[\"Id\"].Deserialize<int>());\n    }\n\n    [TestMethod]\n    public void TestSaveStateToJsonObject_Observable()\n    {\n        // Mvvm ObservableProperty should be serialized.\n        var vm = new TestLoadableViewModelObservable { Title = \"abc\", Id = 123, };\n        var state = vm.SaveStateToJsonObject();\n\n        // Title should be ignored since it has [JsonIgnore]\n        // Command should be ignored from excluded type rules\n        // Id should be serialized\n\n        Assert.AreEqual(1, state.Count);\n        Assert.AreEqual(123, state[\"Id\"].Deserialize<int>());\n    }\n\n    [TestMethod]\n    public void TestSaveStateToJsonObject_IJsonLoadableState()\n    {\n        // Properties of type IJsonLoadableState should be serialized by calling their\n        // SaveStateToJsonObject method.\n\n        // Make a mock IJsonLoadableState\n        var mockState = Substitute.For<IJsonLoadableState>();\n\n        var vm = new TestLoadableViewModelNestedInterface { NestedState = mockState };\n\n        // Serialize\n        var state = vm.SaveStateToJsonObject();\n\n        // Check results\n        Assert.AreEqual(1, state.Count);\n\n        // Check that SaveStateToJsonObject was called\n        mockState.Received().SaveStateToJsonObject();\n    }\n\n    [TestMethod]\n    public void TestLoadStateFromJsonObject()\n    {\n        // Simple round trip save / load\n        var vm = new TestLoadableViewModel\n        {\n            Included = \"abc\",\n            Id = 123,\n            Ignored = 456,\n        };\n\n        var state = vm.SaveStateToJsonObject();\n\n        // Create a new instance and load the state\n        var vm2 = new TestLoadableViewModel();\n        vm2.LoadStateFromJsonObject(state);\n\n        // Check [JsonInclude] and not marked property was loaded\n        Assert.AreEqual(\"abc\", vm2.Included);\n        Assert.AreEqual(123, vm2.Id);\n        // Check ignored property was not loaded\n        Assert.AreEqual(0, vm2.Ignored);\n    }\n\n    [TestMethod]\n    public void TestLoadStateFromJsonObject_Nested_DefaultCtor()\n    {\n        // Round trip save / load with nested IJsonLoadableState property\n        var nested = new TestLoadableViewModel\n        {\n            Included = \"abc\",\n            Id = 123,\n            Ignored = 456,\n        };\n\n        var vm = new TestLoadableViewModelNested { NestedState = nested };\n\n        var state = vm.SaveStateToJsonObject();\n\n        // Create a new instance with null NestedState, rely on default ctor\n        var vm2 = new TestLoadableViewModelNested();\n        vm2.LoadStateFromJsonObject(state);\n\n        // Check nested state was loaded\n        Assert.IsNotNull(vm2.NestedState);\n\n        var loadedNested = (TestLoadableViewModel)vm2.NestedState;\n        Assert.AreEqual(\"abc\", loadedNested.Included);\n        Assert.AreEqual(123, loadedNested.Id);\n        Assert.AreEqual(0, loadedNested.Ignored);\n    }\n\n    [TestMethod]\n    public void TestLoadStateFromJsonObject_Nested_Existing()\n    {\n        // Round trip save / load with nested IJsonLoadableState property\n        var nested = new TestLoadableViewModel\n        {\n            Included = \"abc\",\n            Id = 123,\n            Ignored = 456,\n        };\n\n        var vm = new TestLoadableViewModelNestedInterface { NestedState = nested };\n\n        var state = vm.SaveStateToJsonObject();\n\n        // Create a new instance with existing NestedState\n        var vm2 = new TestLoadableViewModelNestedInterface\n        {\n            NestedState = new TestLoadableViewModel()\n        };\n        vm2.LoadStateFromJsonObject(state);\n\n        // Check nested state was loaded\n        Assert.IsNotNull(vm2.NestedState);\n\n        var loadedNested = (TestLoadableViewModel)vm2.NestedState;\n        Assert.AreEqual(\"abc\", loadedNested.Included);\n        Assert.AreEqual(123, loadedNested.Id);\n        Assert.AreEqual(0, loadedNested.Ignored);\n    }\n\n    [TestMethod]\n    public void TestLoadStateFromJsonObject_ReadOnly()\n    {\n        var vm = new TestLoadableViewModelReadOnly(456);\n\n        var state = vm.SaveStateToJsonObject();\n\n        // Check no properties were serialized\n        Assert.AreEqual(0, state.Count);\n\n        // Create a new instance and load the state\n        var vm2 = new TestLoadableViewModelReadOnly(123);\n        vm2.LoadStateFromJsonObject(state);\n\n        // Read only property should have been ignored\n        Assert.AreEqual(123, vm2.ReadOnly);\n    }\n\n    [TestMethod]\n    public void TestLoadStateFromJsonObject_ReadOnlyLoadable()\n    {\n        var vm = new TestLoadableViewModelReadOnlyLoadable\n        {\n            ReadOnlyLoadable = { Included = \"abc-123\" }\n        };\n\n        var state = vm.SaveStateToJsonObject();\n\n        // Check readonly loadable property was serialized\n        Assert.AreEqual(1, state.Count);\n        Assert.AreEqual(\n            \"abc-123\",\n            state[\"ReadOnlyLoadable\"].Deserialize<TestLoadableViewModel>()!.Included\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Avalonia/PromptTests.cs",
    "content": "﻿using System.Globalization;\nusing System.Reflection;\nusing NSubstitute;\nusing StabilityMatrix.Avalonia.Extensions;\nusing StabilityMatrix.Avalonia.Models.Inference;\nusing StabilityMatrix.Avalonia.Models.TagCompletion;\nusing StabilityMatrix.Core.Models.Tokens;\nusing TextMateSharp.Grammars;\nusing TextMateSharp.Registry;\n\nnamespace StabilityMatrix.Tests.Avalonia;\n\n[TestClass]\npublic class PromptTests\n{\n    private ITokenizerProvider tokenizerProvider = null!;\n\n    [TestInitialize]\n    public void TestInitialize()\n    {\n        tokenizerProvider = Substitute.For<ITokenizerProvider>();\n\n        var promptSyntaxFile = Assembly\n            .GetExecutingAssembly()\n            .GetManifestResourceStream(\"StabilityMatrix.Tests.ImagePrompt.tmLanguage.json\")!;\n\n        var registry = new Registry(new RegistryOptions(ThemeName.DarkPlus));\n        var grammar = registry.LoadGrammarFromStream(promptSyntaxFile);\n\n        tokenizerProvider.TokenizeLine(Arg.Any<string>()).Returns(x => grammar.TokenizeLine(x.Arg<string>()));\n    }\n\n    [TestMethod]\n    public void TestPromptProcessedText()\n    {\n        var prompt = Prompt.FromRawText(\"test\", tokenizerProvider);\n\n        prompt.Process();\n\n        Assert.AreEqual(\"test\", prompt.ProcessedText);\n    }\n\n    [TestMethod]\n    public void TestPromptWeightParsing()\n    {\n        var prompt = Prompt.FromRawText(\"<lora:my_model:1.5>\", tokenizerProvider);\n\n        prompt.Process();\n\n        // Output should have no loras\n        Assert.AreEqual(\"\", prompt.ProcessedText);\n\n        var network = prompt.ExtraNetworks[0];\n\n        Assert.AreEqual(PromptExtraNetworkType.Lora, network.Type);\n        Assert.AreEqual(\"my_model\", network.Name);\n        Assert.AreEqual(1.5f, network.ModelWeight);\n    }\n\n    /// <summary>\n    /// Tests that we can parse decimal numbers with different cultures\n    /// </summary>\n    [TestMethod]\n    public void TestPromptWeightParsing_DecimalSeparatorCultures_ShouldParse()\n    {\n        var prompt = Prompt.FromRawText(\"<lora:my_model:1.5>\", tokenizerProvider);\n\n        // Cultures like de-DE use commas as decimal separators, check that we can parse those too\n        ExecuteWithCulture(() => prompt.Process(), CultureInfo.GetCultureInfo(\"de-DE\"));\n\n        // Output should have no loras\n        Assert.AreEqual(\"\", prompt.ProcessedText);\n\n        var network = prompt.ExtraNetworks![0];\n\n        Assert.AreEqual(PromptExtraNetworkType.Lora, network.Type);\n        Assert.AreEqual(\"my_model\", network.Name);\n        Assert.AreEqual(1.5f, network.ModelWeight);\n    }\n\n    private static T? ExecuteWithCulture<T>(Func<T> func, CultureInfo culture)\n    {\n        var result = default(T);\n\n        var thread = new Thread(() =>\n        {\n            result = func();\n        })\n        {\n            CurrentCulture = culture\n        };\n\n        thread.Start();\n        thread.Join();\n\n        return result;\n    }\n\n    private static void ExecuteWithCulture(Action func, CultureInfo culture)\n    {\n        var thread = new Thread(() =>\n        {\n            func();\n        })\n        {\n            CurrentCulture = culture\n        };\n\n        thread.Start();\n        thread.Join();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Avalonia/UpdateViewModelTests.cs",
    "content": "﻿using Semver;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Models.Update;\n\nnamespace StabilityMatrix.Tests.Avalonia;\n\n[TestClass]\npublic class UpdateViewModelTests\n{\n    [TestMethod]\n    public void FormatChangelogTest()\n    {\n        // Arrange\n        const string markdown = \"\"\"\n                                # Changelog\n\n                                All notable changes to Stability Matrix will be documented in this file.\n\n                                The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\n                                and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).\n\n                                ## v2.4.6\n                                ### Added\n                                - Stuff\n                                ### Changed\n                                - Things\n\n                                ## v2.4.5\n                                ### Fixed\n                                - Fixed bug\n\n                                ## v2.4.4\n                                ### Changed\n                                - Changed stuff\n                                \"\"\";\n\n        // Act\n        var result = UpdateViewModel.FormatChangelog(markdown, SemVersion.Parse(\"2.4.5\"));\n        var resultPre = UpdateViewModel.FormatChangelog(\n            markdown,\n            SemVersion.Parse(\"2.4.5-pre.1+1a7b4e4\")\n        );\n\n        // Assert\n        const string expected = \"\"\"\n                                ## v2.4.6\n                                ### Added\n                                - Stuff\n                                ### Changed\n                                - Things\n                                \"\"\";\n\n        Assert.AreEqual(expected, result);\n\n        // Pre-release should include the current release\n        const string expectedPre = \"\"\"\n                                   ## v2.4.6\n                                   ### Added\n                                   - Stuff\n                                   ### Changed\n                                   - Things\n\n                                   ## v2.4.5\n                                   ### Fixed\n                                   - Fixed bug\n                                   \"\"\";\n        Assert.AreEqual(expectedPre, resultPre);\n    }\n\n    [TestMethod]\n    public void FormatChangelogWithChannelTest()\n    {\n        // Arrange\n        const string markdown = \"\"\"\n                                # Changelog\n\n                                All notable changes to Stability Matrix will be documented in this file.\n\n                                The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\n                                and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).\n\n                                ## v2.4.6\n                                ### Added\n                                - Stuff\n                                ### Changed\n                                - Things\n\n                                ## v2.4.6-pre.1\n                                ### Fixed\n                                - Fixed bug\n\n                                ## v2.4.6-dev.1\n                                ### Fixed\n                                - Fixed bug\n                                \n                                ## v2.4.5\n                                ### Changed\n                                - Changed stuff\n                                \"\"\";\n\n        // Act\n        var result = UpdateViewModel.FormatChangelog(\n            markdown,\n            SemVersion.Parse(\"2.4.0\"),\n            UpdateChannel.Preview\n        );\n\n        // Assert\n        const string expected = \"\"\"\n                                ## v2.4.6\n                                ### Added\n                                - Stuff\n                                ### Changed\n                                - Things\n                                \n                                ## v2.4.6-pre.1\n                                ### Fixed\n                                - Fixed bug\n                                \n                                ## v2.4.5\n                                ### Changed\n                                - Changed stuff\n                                \"\"\";\n\n        // Should include pre but not dev\n        Assert.AreEqual(expected, result);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Core/AnsiParserTests.cs",
    "content": "﻿using StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Tests.Core;\n\n[TestClass]\npublic class AnsiParserTests\n{\n    [DataTestMethod]\n    [DataRow(\"\\u001b[0m\", \"\\u001b[0m\")]\n    [DataRow(\"\\u001b[A\", \"\\u001b[A\")]\n    [DataRow(\"\\u001b[A\\r\\n\", \"\\u001b[A\")]\n    public void TestAnsiRegex(string source, string expectedMatch)\n    {\n        var pattern = AnsiParser.AnsiEscapeSequenceRegex();\n        var match = pattern.Match(source);\n        Assert.IsTrue(match.Success);\n        Assert.AreEqual(expectedMatch, match.Value);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Core/AsyncStreamReaderTests.cs",
    "content": "﻿using System.Text;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Tests.Core;\n\n/// <summary>\n/// Tests AsyncStreamReader and ApcMessage parsing\n/// </summary>\n[TestClass]\npublic class AsyncStreamReaderTests\n{\n    \n    [DataTestMethod]\n    // Test newlines handling for \\r\\n, \\n\n    [DataRow(\"a\\r\\nb\\nc\", \"a\\r\\n\", \"b\\n\", \"c\", null)]\n    // Carriage returns \\r should be sent as is\n    [DataRow(\"a\\rb\\rc\", \"a\", \"\\rb\", \"\\rc\", null)]\n    [DataRow(\"a1\\ra2\\nb1\\rb2\", \"a1\", \"\\ra2\\n\", \"b1\", \"\\rb2\", null)]\n    // Ansi escapes should be seperated\n    [DataRow(\"\\x1b[A\\x1b[A\", \"\\x1b[A\", \"\\x1b[A\", null)]\n    // Mixed Ansi and newlines\n    [DataRow(\"a \\x1b[A\\r\\n\\r xyz\", \"a \", \"\\x1b[A\", \"\\r\\n\", \"\\r xyz\", null)]\n    public async Task TestRead(string source, params string?[] expected)\n    {\n        var results = new List<string?>();\n        \n        var callback = new Action<string?>(s =>\n        {\n            results.Add(s);\n        });\n        \n        using var stream = new MemoryStream(Encoding.UTF8.GetBytes(source));\n        using (var reader = new AsyncStreamReader(stream, callback, Encoding.UTF8))\n        {\n            // Begin read line and wait until finish\n            reader.BeginReadLine();\n            // Wait for maximum 1 second\n            await reader.EOF.WaitAsync(new CancellationTokenSource(1000).Token);\n        }\n\n        // Check expected output matches\n        Assert.IsTrue(expected.SequenceEqual(results.ToArray()), \n            \"Results [{0}] do not match expected [{1}]\", \n            string.Join(\", \", results.Select(s => s?.ToRepr() ?? \"<null>\")), \n            string.Join(\", \", expected.Select(s => s?.ToRepr() ?? \"<null>\")));\n    }\n    \n    [TestMethod]\n    public async Task TestCarriageReturnHandling()\n    {\n        var expected = new[] {\"dog\\r\\n\", \"cat\", \"\\r123\", \"\\r456\", null};\n        \n        var results = new List<string?>();\n        \n        var callback = new Action<string?>(s =>\n        {\n            results.Add(s);\n        });\n        \n        // The previous buffer should be sent when \\r is encountered\n        const string source = \"dog\\r\\ncat\\r123\\r456\";\n        \n        // Make the reader\n        using var stream = new MemoryStream(Encoding.UTF8.GetBytes(source));\n        using (var reader = new AsyncStreamReader(stream, callback, Encoding.UTF8))\n        {\n            // Begin read line and wait until finish\n            reader.BeginReadLine();\n            // Wait for maximum 1 second\n            await reader.EOF.WaitAsync(new CancellationTokenSource(1000).Token);\n        }\n        \n        // Check if all expected strings were read\n        Assert.IsTrue(expected.SequenceEqual(results.ToArray()), \n            \"Results [{0}] do not match expected [{1}]\", \n            string.Join(\", \", results.Select(s => s?.ToRepr() ?? \"<null>\")), \n            string.Join(\", \", expected.Select(s => s?.ToRepr() ?? \"<null>\")));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Core/DefaultUnknownEnumConverterTests.cs",
    "content": "﻿using System.Runtime.Serialization;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing StabilityMatrix.Core.Converters.Json;\n\nnamespace StabilityMatrix.Tests.Core;\n\n[TestClass]\npublic class DefaultUnknownEnumConverterTests\n{\n    [TestMethod]\n    [ExpectedException(typeof(JsonException))]\n    public void TestDeserialize_NormalEnum_ShouldError()\n    {\n        const string json = \"\\\"SomeUnknownValue\\\"\";\n\n        JsonSerializer.Deserialize<NormalEnum>(json);\n    }\n\n    [TestMethod]\n    public void TestDeserialize_UnknownEnum_ShouldConvert()\n    {\n        const string json = \"\\\"SomeUnknownValue\\\"\";\n\n        var result = JsonSerializer.Deserialize<UnknownEnum>(json);\n\n        Assert.AreEqual(UnknownEnum.Unknown, result);\n    }\n\n    [TestMethod]\n    public void TestDeserialize_DefaultEnum_ShouldConvert()\n    {\n        const string json = \"\\\"SomeUnknownValue\\\"\";\n\n        var result = JsonSerializer.Deserialize<DefaultEnum>(json);\n\n        Assert.AreEqual(DefaultEnum.CustomDefault, result);\n    }\n\n    [TestMethod]\n    public void TestSerialize_UnknownEnum_ShouldConvert()\n    {\n        const string expected = \"\\\"Unknown\\\"\";\n\n        var result = JsonSerializer.Serialize(UnknownEnum.Unknown);\n\n        Assert.AreEqual(expected, result);\n    }\n\n    [TestMethod]\n    public void TestDeserialize_UnknownEnum_ShouldUseEnumMemberValue()\n    {\n        const string json = \"\\\"Value 2\\\"\";\n\n        var result = JsonSerializer.Deserialize<UnknownEnum>(json);\n\n        Assert.AreEqual(UnknownEnum.Value2, result);\n    }\n\n    [TestMethod]\n    public void TestSerialize_DefaultEnum_ShouldConvert()\n    {\n        const string expected = \"\\\"CustomDefault\\\"\";\n\n        var result = JsonSerializer.Serialize(DefaultEnum.CustomDefault);\n\n        Assert.AreEqual(expected, result);\n    }\n\n    [TestMethod]\n    public void TestSerialize_UnknownEnum_ShouldUseEnumMemberValue()\n    {\n        const string json = \"\\\"Value 2\\\"\";\n\n        var result = JsonSerializer.Deserialize<UnknownEnum>(json);\n\n        Assert.AreEqual(UnknownEnum.Value2, result);\n    }\n\n    [TestMethod]\n    public void TestSerialize_ComplexObject_ShouldUseEnumMemberValue()\n    {\n        const string expected = \"{\\\"Key\\\":\\\"Value 2\\\"}\";\n\n        var result = JsonSerializer.Serialize(new { Key = UnknownEnum.Value2 });\n\n        Assert.AreEqual(expected, result);\n    }\n\n    private enum NormalEnum\n    {\n        Unknown,\n        Value1,\n        Value2\n    }\n\n    [JsonConverter(typeof(DefaultUnknownEnumConverter<UnknownEnum>))]\n    private enum UnknownEnum\n    {\n        Unknown,\n        Value1,\n\n        [EnumMember(Value = \"Value 2\")]\n        Value2\n    }\n\n    [JsonConverter(typeof(DefaultUnknownEnumConverter<DefaultEnum>))]\n    private enum DefaultEnum\n    {\n        CustomDefault,\n        Value1,\n        Value2\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Core/FileSystemPathTests.cs",
    "content": "﻿using System.Runtime.Versioning;\nusing StabilityMatrix.Core.Models.FileInterfaces;\n\nnamespace StabilityMatrix.Tests.Core;\n\n[TestClass]\npublic class FileSystemPathTests\n{\n    [SupportedOSPlatform(\"windows\")]\n    [DataTestMethod]\n    [DataRow(\"M:\\\\Path\", \"M:\\\\Path\")]\n    [DataRow(\"root/abc\", \"root/abc\")]\n    [DataRow(\"root\\\\abc\", \"root\\\\abc\")]\n    public void TestFilePathEqualsWin(string left, string right)\n    {\n        // Arrange\n        var leftPath = new FilePath(left);\n        var rightPath = new FilePath(right);\n\n        // Act\n        var resultEquals = leftPath.Equals(rightPath);\n        var resultOperator = leftPath == rightPath;\n        var resultNotOperator = leftPath != rightPath;\n\n        // Assert\n        Assert.IsTrue(resultEquals);\n        Assert.IsTrue(resultOperator);\n        Assert.IsFalse(resultNotOperator);\n    }\n\n    [DataTestMethod]\n    [DataRow(\"M:/Path\", \"M:/Path\")]\n    [DataRow(\"root/abc\", \"root/abc\")]\n    [DataRow(\"root/abc\", \"root/abc\")]\n    public void TestFilePathEquals(string left, string right)\n    {\n        // Arrange\n        var leftPath = new FilePath(left);\n        var rightPath = new FilePath(right);\n\n        // Act\n        var resultEquals = leftPath.Equals(rightPath);\n        var resultOperator = leftPath == rightPath;\n        var resultNotOperator = leftPath != rightPath;\n\n        // Assert\n        Assert.IsTrue(resultEquals);\n        Assert.IsTrue(resultOperator);\n        Assert.IsFalse(resultNotOperator);\n    }\n\n    [DataTestMethod]\n    [DataRow(\"M:/Path\", \"M:/Path2\")]\n    [DataRow(\"root/abc\", \"root/abc2\")]\n    public void TestFilePathNotEquals(string left, string right)\n    {\n        // Arrange\n        var leftPath = new FilePath(left);\n        var rightPath = new FilePath(right);\n\n        // Act\n        var resultEquals = leftPath.Equals(rightPath);\n        var resultOperator = leftPath == rightPath;\n        var resultNotOperator = leftPath != rightPath;\n\n        // Assert\n        Assert.IsFalse(resultEquals);\n        Assert.IsFalse(resultOperator);\n        Assert.IsTrue(resultNotOperator);\n    }\n\n    [DataTestMethod]\n    [DataRow(\"root/abc\", \"root/abc\")]\n    [DataRow(\"root/abc\", \"root/abc/\")]\n    public void TestDirectoryPathEquals(string left, string right)\n    {\n        // Arrange\n        var leftPath = new DirectoryPath(left);\n        var rightPath = new DirectoryPath(right);\n\n        // Act\n        var resultEquals = leftPath.Equals(rightPath);\n        var resultOperator = leftPath == rightPath;\n        var resultNotOperator = leftPath != rightPath;\n\n        // Assert\n        Assert.IsTrue(resultEquals);\n        Assert.IsTrue(resultOperator);\n        Assert.IsFalse(resultNotOperator);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Core/GlobalEncryptedSerializerTests.cs",
    "content": "﻿using System.Security;\nusing System.Security.Cryptography;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api.Lykos;\n#pragma warning disable CS0618 // Type or member is obsolete\n\nnamespace StabilityMatrix.Tests.Core;\n\n[TestClass]\npublic class GlobalEncryptedSerializerTests\n{\n    [TestMethod]\n    public void Serialize_ShouldDeserializeToSameObject()\n    {\n        // Arrange\n        var secrets = new Secrets { LykosAccount = new LykosAccountV1Tokens(\"123\", \"456\"), };\n\n        // Act\n        var serialized = GlobalEncryptedSerializer.Serialize(secrets);\n        var deserialized = GlobalEncryptedSerializer.Deserialize<Secrets>(serialized);\n\n        // Assert\n        Assert.AreEqual(secrets, deserialized);\n    }\n\n    [TestMethod]\n    public void SerializeV1_ShouldDeserializeToSameObject()\n    {\n        // Arrange\n        var secrets = new Secrets { LykosAccount = new LykosAccountV1Tokens(\"123\", \"456\"), };\n\n        // Act\n        var serialized = GlobalEncryptedSerializer.Serialize(secrets, GlobalEncryptedSerializer.KeyInfoV1);\n        var deserialized = GlobalEncryptedSerializer.Deserialize<Secrets>(serialized);\n\n        // Assert\n        Assert.AreEqual(secrets, deserialized);\n    }\n\n    [TestMethod]\n    public void SerializeV2_ShouldDeserializeToSameObject()\n    {\n        // Arrange\n        var secrets = new Secrets { LykosAccount = new LykosAccountV1Tokens(\"123\", \"456\"), };\n\n        // Act\n        var serialized = GlobalEncryptedSerializer.Serialize(secrets, GlobalEncryptedSerializer.KeyInfoV2);\n        var deserialized = GlobalEncryptedSerializer.Deserialize<Secrets>(serialized);\n\n        // Assert\n        Assert.AreEqual(secrets, deserialized);\n    }\n\n    [TestMethod]\n    public void SerializeWithNonDefaultKeyInfo_ShouldDeserializeToSameObject()\n    {\n        // Arrange\n        var secrets = new Secrets { LykosAccount = new LykosAccountV1Tokens(\"123\", \"456\"), };\n\n        // Act\n        var serialized = GlobalEncryptedSerializer.Serialize(\n            secrets,\n            GlobalEncryptedSerializer.KeyInfoV2 with\n            {\n                Iterations = GlobalEncryptedSerializer.KeyInfoV2.Iterations + 10,\n            }\n        );\n        var deserialized = GlobalEncryptedSerializer.Deserialize<Secrets>(serialized);\n\n        // Assert\n        Assert.AreEqual(secrets, deserialized);\n    }\n\n    [TestMethod]\n    public void EncryptAndDecryptBytesWithKeyInfoV2_ShouldReturnSameBytes()\n    {\n        // Arrange\n        var data = \"hello\"u8.ToArray();\n        var keyInfo = GlobalEncryptedSerializer.KeyInfoV2;\n        var password = GetSecureString(\"password\");\n\n        // Act\n        var (encrypted, salt) = GlobalEncryptedSerializer.EncryptBytes(data, password, keyInfo);\n        var decrypted = GlobalEncryptedSerializer.DecryptBytes(encrypted, salt, password, keyInfo);\n\n        // Assert\n        CollectionAssert.AreEqual(data, decrypted);\n    }\n\n    [TestMethod]\n    public void EncryptAndDecryptBytesWithKeyInfoV2_DifferentPassword_ShouldFail()\n    {\n        // Arrange\n        var data = \"hello\"u8.ToArray();\n        var keyInfo = GlobalEncryptedSerializer.KeyInfoV2;\n        var encryptPassword = GetSecureString(\"password\");\n        var decryptPassword = GetSecureString(\"a_different_password\");\n\n        // Act\n        var (encrypted, salt) = GlobalEncryptedSerializer.EncryptBytes(data, encryptPassword, keyInfo);\n\n        // Assert\n        Assert.ThrowsException<CryptographicException>(\n            () => GlobalEncryptedSerializer.DecryptBytes(encrypted, salt, decryptPassword, keyInfo)\n        );\n    }\n\n    private static SecureString GetSecureString(string value)\n    {\n        var secureString = new SecureString();\n        foreach (var c in value)\n        {\n            secureString.AppendChar(c);\n        }\n        return secureString;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Core/ModelIndexServiceTests.cs",
    "content": "﻿using System.Reflection;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Tests.Core;\n\n[TestClass]\npublic class ModelIndexServiceTests\n{\n    [TestMethod]\n    public void GetHasEarlyAccessUpdateOnly_ReturnsTrue_WhenAllNewerVersionsAreEarlyAccess()\n    {\n        var model = CreateLocalModel(installedVersionId: 100, hasUpdate: true);\n        var remoteModel = CreateRemoteModel(\n            CreateVersion(id: 300, isEarlyAccess: true),\n            CreateVersion(id: 200, isEarlyAccess: true),\n            CreateVersion(id: 100, isEarlyAccess: false)\n        );\n\n        var result = InvokeGetHasEarlyAccessUpdateOnly(model, remoteModel);\n\n        Assert.IsTrue(result);\n    }\n\n    [TestMethod]\n    public void GetHasEarlyAccessUpdateOnly_ReturnsFalse_WhenAnyNewerVersionIsPublic()\n    {\n        var model = CreateLocalModel(installedVersionId: 100, hasUpdate: true);\n        var remoteModel = CreateRemoteModel(\n            CreateVersion(id: 300, isEarlyAccess: true),\n            CreateVersion(id: 200, isEarlyAccess: false),\n            CreateVersion(id: 100, isEarlyAccess: false)\n        );\n\n        var result = InvokeGetHasEarlyAccessUpdateOnly(model, remoteModel);\n\n        Assert.IsFalse(result);\n    }\n\n    [TestMethod]\n    public void GetHasEarlyAccessUpdateOnly_ReturnsFalse_WhenInstalledVersionIsLatest()\n    {\n        var model = CreateLocalModel(installedVersionId: 100, hasUpdate: true);\n        var remoteModel = CreateRemoteModel(\n            CreateVersion(id: 100, isEarlyAccess: false),\n            CreateVersion(id: 90, isEarlyAccess: true)\n        );\n\n        var result = InvokeGetHasEarlyAccessUpdateOnly(model, remoteModel);\n\n        Assert.IsFalse(result);\n    }\n\n    [TestMethod]\n    public void GetHasEarlyAccessUpdateOnly_ReturnsFalse_WhenModelHasNoUpdate()\n    {\n        var model = CreateLocalModel(installedVersionId: 100, hasUpdate: false);\n        var remoteModel = CreateRemoteModel(\n            CreateVersion(id: 300, isEarlyAccess: true),\n            CreateVersion(id: 200, isEarlyAccess: true),\n            CreateVersion(id: 100, isEarlyAccess: false)\n        );\n\n        var result = InvokeGetHasEarlyAccessUpdateOnly(model, remoteModel);\n\n        Assert.IsFalse(result);\n    }\n\n    [TestMethod]\n    public void GetHasEarlyAccessUpdateOnly_ReturnsFalse_WhenInstalledVersionIsNotInRemoteList()\n    {\n        var model = CreateLocalModel(installedVersionId: 100, hasUpdate: true);\n        var remoteModel = CreateRemoteModel(\n            CreateVersion(id: 300, isEarlyAccess: true),\n            CreateVersion(id: 200, isEarlyAccess: true),\n            CreateVersion(id: 150, isEarlyAccess: false)\n        );\n\n        var result = InvokeGetHasEarlyAccessUpdateOnly(model, remoteModel);\n\n        Assert.IsFalse(result);\n    }\n\n    private static bool InvokeGetHasEarlyAccessUpdateOnly(LocalModelFile model, CivitModel? remoteModel)\n    {\n        var method = typeof(ModelIndexService).GetMethod(\n            \"GetHasEarlyAccessUpdateOnly\",\n            BindingFlags.NonPublic | BindingFlags.Static\n        );\n\n        Assert.IsNotNull(method);\n\n        var result = method.Invoke(null, [model, remoteModel]);\n\n        Assert.IsNotNull(result);\n\n        return (bool)result;\n    }\n\n    private static LocalModelFile CreateLocalModel(int installedVersionId, bool hasUpdate)\n    {\n        return new LocalModelFile\n        {\n            RelativePath = \"StableDiffusion/test-model.safetensors\",\n            SharedFolderType = SharedFolderType.StableDiffusion,\n            HasUpdate = hasUpdate,\n            ConnectedModelInfo = new ConnectedModelInfo\n            {\n                ModelId = 123,\n                VersionId = installedVersionId,\n                Source = ConnectedModelSource.Civitai,\n                ModelName = \"Test Model\",\n                ModelDescription = string.Empty,\n                VersionName = $\"v{installedVersionId}\",\n                Tags = [],\n                Hashes = new CivitFileHashes(),\n            },\n        };\n    }\n\n    private static CivitModel CreateRemoteModel(params CivitModelVersion[] versions)\n    {\n        return new CivitModel\n        {\n            Id = 123,\n            Name = \"Test Model\",\n            Description = string.Empty,\n            Type = CivitModelType.Unknown,\n            Tags = [],\n            Stats = new CivitModelStats(),\n            ModelVersions = versions.ToList(),\n        };\n    }\n\n    private static CivitModelVersion CreateVersion(int id, bool isEarlyAccess)\n    {\n        return new CivitModelVersion\n        {\n            Id = id,\n            Name = $\"v{id}\",\n            Description = string.Empty,\n            DownloadUrl = string.Empty,\n            TrainedWords = [],\n            Availability = isEarlyAccess ? \"EarlyAccess\" : \"Public\",\n            Stats = new CivitModelStats(),\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Core/PipInstallArgsTests.cs",
    "content": "﻿using StabilityMatrix.Core.Processes;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Tests.Core;\n\n[TestClass]\npublic class PipInstallArgsTests\n{\n    [TestMethod]\n    public void TestGetTorch()\n    {\n        // Arrange\n        const string version = \"==2.1.0\";\n\n        // Act\n        var args = new PipInstallArgs().WithTorch(version).ToProcessArgs().ToString();\n\n        // Assert\n        Assert.AreEqual(\"torch==2.1.0\", args);\n    }\n\n    [TestMethod]\n    public void TestGetTorchWithExtraIndex()\n    {\n        // Arrange\n        const string version = \">=2.0.0\";\n        const string index = \"cu118\";\n\n        // Act\n        var args = new PipInstallArgs()\n            .WithTorch(version)\n            .WithTorchVision()\n            .WithTorchExtraIndex(index)\n            .ToProcessArgs()\n            .ToString();\n\n        // Assert\n        Assert.AreEqual(\n            \"torch>=2.0.0 torchvision --extra-index-url https://download.pytorch.org/whl/cu118\",\n            args\n        );\n    }\n\n    [TestMethod]\n    public void TestGetTorchWithMoreStuff()\n    {\n        // Act\n        var args = new PipInstallArgs()\n            .AddArg(\"--pre\")\n            .WithTorch(\"~=2.0.0\")\n            .WithTorchVision()\n            .WithTorchExtraIndex(\"nightly/cpu\")\n            .ToString();\n\n        // Assert\n        Assert.AreEqual(\n            \"--pre torch~=2.0.0 torchvision --extra-index-url https://download.pytorch.org/whl/nightly/cpu\",\n            args\n        );\n    }\n\n    [TestMethod]\n    public void TestParsedFromRequirementsTxt()\n    {\n        // Arrange\n        const string requirements = \"\"\"\n                                    torch~=2.0.0\n                                    torchvision # comment\n                                    --extra-index-url https://example.org\n                                    \"\"\";\n\n        // Act\n        var args = new PipInstallArgs().WithParsedFromRequirementsTxt(requirements);\n\n        // Assert\n        CollectionAssert.AreEqual(\n            new[] { \"torch~=2.0.0\", \"torchvision\", \"--extra-index-url https://example.org\" },\n            args.ToProcessArgs().Select(arg => arg.GetQuotedValue()).ToArray()\n        );\n\n        Assert.AreEqual(\"torch~=2.0.0 torchvision --extra-index-url https://example.org\", args.ToString());\n    }\n\n    [TestMethod]\n    public void TestWithUserOverrides()\n    {\n        // Arrange\n        var args = new PipInstallArgs()\n            .AddArg(\"numpy\")\n            .WithTorch(\"==1.0.0\")\n            .WithExtraIndex(\"https://download.pytorch.org/whl/cu121\");\n\n        var overrides = new List<PipPackageSpecifierOverride>\n        {\n            new()\n            {\n                Name = \"torch\",\n                Constraint = \">=\",\n                Version = \"2.0.0\",\n                Action = PipPackageSpecifierOverrideAction.Update\n            },\n            new()\n            {\n                Name = \"--extra-index-url https://download.pytorch.org/whl/nightly/cpu\",\n                Action = PipPackageSpecifierOverrideAction.Update\n            }\n        };\n\n        // Act\n        var resultArgs = args.WithUserOverrides(overrides);\n\n        // Assert\n        Assert.AreEqual(\n            \"numpy torch==1.0.0 --extra-index-url https://download.pytorch.org/whl/cu121\",\n            args.ToString()\n        );\n\n        Assert.AreEqual(\n            \"numpy torch>=2.0.0 --extra-index-url https://download.pytorch.org/whl/nightly/cpu\",\n            resultArgs.ToString()\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Core/PipShowResultsTests.cs",
    "content": "using System.IO;\nusing System.Threading.Tasks;\nusing Microsoft.VisualStudio.TestTools.UnitTesting;\nusing StabilityMatrix.Core.Python;\n\nnamespace StabilityMatrix.Tests.Core;\n\n[TestClass]\npublic class PipShowResultsTests\n{\n    [TestMethod]\n    public void TestSinglePackage()\n    {\n        var input = \"\"\"\n            Name: package-a\n            Version: 1.0.0\n            Summary: A test package\n            Home-page: https://example.com\n            Author: John Doe\n            Author-email: john.doe@example.com\n            License: MIT\n            Location: /path/to/package\n            Requires:\n            Required-by:\n            \"\"\";\n\n        var result = PipShowResult.Parse(input);\n\n        Assert.IsNotNull(result);\n        Assert.AreEqual(\"package-a\", result.Name);\n        Assert.AreEqual(\"1.0.0\", result.Version);\n        Assert.AreEqual(\"A test package\", result.Summary);\n    }\n\n    [TestMethod]\n    public void TestMultiplePackages()\n    {\n        var input = \"\"\"\n            Name: package-a\n            Version: 1.0.0\n            Summary: A test package\n            Home-page: https://example.com\n            Author: John Doe\n            Author-email: john.doe@example.com\n            License: MIT\n            Location: /path/to/package\n            Requires:\n            Required-by:\n            ---\n            Name: package-b\n            Version: 2.0.0\n            Summary: Another test package\n            Home-page: https://example.com\n            Author: Jane Doe\n            Author-email: jane.doe@example.com\n            License: Apache-2.0\n            Location: /path/to/another/package\n            Requires: package-a\n            Required-by:\n            \"\"\";\n\n        var result = PipShowResult.Parse(input);\n\n        Assert.IsNotNull(result);\n        Assert.AreEqual(\"package-a\", result.Name);\n        Assert.AreEqual(\"1.0.0\", result.Version);\n        Assert.AreNotEqual(\"package-b\", result.Name);\n    }\n\n    [TestMethod]\n    public void TestMalformedPackage()\n    {\n        var input = \"\"\"\n            Name: package-a\n            Version: 1.0.0\n            Summary A test package\n            Home-page: https://example.com\n            Author: John Doe\n            Author-email: john.doe@example.com\n            License: MIT\n            Location: /path/to/package\n            Requires:\n            Required-by:\n            \"\"\";\n\n        var result = PipShowResult.Parse(input);\n\n        Assert.IsNotNull(result);\n        Assert.AreEqual(\"package-a\", result.Name);\n        Assert.AreEqual(\"1.0.0\", result.Version);\n        Assert.IsNull(result.Summary);\n    }\n\n    [TestMethod]\n    public void TestMultiLineLicense()\n    {\n        var input = \"\"\"\n            Name: package-a\n            Version: 1.0.0\n            Summary: A test package\n            Home-page: https://example.com\n            Author: John Doe\n            Author-email: john.doe@example.com\n            License: The MIT License (MIT)\n\n                     Copyright (c) 2015 John Doe\n\n                     Permission is hereby granted, free of charge, to any person obtaining a copy\n                     of this software and associated documentation files (the \"Software\"), to deal\n                     in the Software without restriction, including without limitation the rights\n                     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n                     copies of the Software, and to permit persons to whom the Software is\n                     furnished to do so, subject to the following conditions:\n\n                     The above copyright notice and this permission notice shall be included in all\n                     copies or substantial portions of the Software.\n\n                     THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n                     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n                     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n                     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n                     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n                     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n                     SOFTWARE.\n            Location: /path/to/package\n            Requires:\n            Required-by:\n            \"\"\";\n\n        var result = PipShowResult.Parse(input);\n\n        Assert.IsNotNull(result);\n        Assert.AreEqual(\"package-a\", result.Name);\n        Assert.AreEqual(\"1.0.0\", result.Version);\n        Assert.IsTrue(result.License?.StartsWith(\"License: The MIT License (MIT)\"));\n    }\n\n    /// <summary>\n    /// This test simulates the input that caused the crash reported in Sentry issue b125504f.\n    /// The old implementation of PipShowResult.Parse used ToDictionary, which would throw an\n    /// ArgumentException if the input contained multiple packages, as the \"Name\" key would be\n    /// duplicated. The new implementation uses a foreach loop and TryAdd to prevent this crash.\n    /// </summary>\n    [TestMethod]\n    public void TestDuplicatePackageNameInOutput()\n    {\n        var input = \"\"\"\n            Name: package-a\n            Version: 1.0.0\n            Summary: A test package\n            Home-page: https://example.com\n            Author: John Doe\n            Author-email: john.doe@example.com\n            License: MIT\n            Location: /path/to/package\n            Requires:\n            Required-by:\n            ---\n            Name: package-a\n            Version: 1.0.0\n            Summary: A test package\n            Home-page: https://example.com\n            Author: John Doe\n            Author-email: john.doe@example.com\n            License: MIT\n            Location: /path/to/package\n            Requires:\n            Required-by:\n            \"\"\";\n\n        var result = PipShowResult.Parse(input);\n\n        Assert.IsNotNull(result);\n        Assert.AreEqual(\"package-a\", result.Name);\n        Assert.AreEqual(\"1.0.0\", result.Version);\n    }\n\n    [TestMethod]\n    public void TestEmptyInputThrowsFormatException()\n    {\n        var input = \"\";\n        Assert.ThrowsException<FormatException>(() => PipShowResult.Parse(input));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Core/ServiceProviderExtensionsTests.cs",
    "content": "﻿using Microsoft.Extensions.DependencyInjection;\nusing NSubstitute;\nusing StabilityMatrix.Core.Extensions;\n\nnamespace StabilityMatrix.Tests.Core;\n\n[TestClass]\npublic class ServiceProviderExtensionsTests\n{\n    public abstract class TestDisposable : IDisposable\n    {\n        public abstract void Dispose();\n    }\n\n    public abstract class TestAsyncDisposable : IAsyncDisposable\n    {\n        public abstract ValueTask DisposeAsync();\n    }\n\n    [TestMethod]\n    public void GetDisposables_ReturnsEmptyList_WhenNoDisposables()\n    {\n        // Arrange\n        var services = new ServiceCollection();\n        var serviceProvider = services.BuildServiceProvider();\n\n        // Act\n        var disposables = serviceProvider.GetDisposables();\n\n        // Assert\n        Assert.AreEqual(0, disposables.Count);\n    }\n\n    [TestMethod]\n    public void GetDisposables_ReturnsEmptyList_WhenNoMaterializedDisposables()\n    {\n        // Arrange\n        var services = new ServiceCollection();\n        services.AddSingleton(_ => Substitute.For<TestDisposable>());\n        services.AddSingleton(_ => Substitute.For<TestAsyncDisposable>());\n        var serviceProvider = services.BuildServiceProvider();\n\n        // Act\n        var disposables = serviceProvider.GetDisposables();\n\n        // Assert\n        Assert.AreEqual(0, disposables.Count);\n    }\n\n    [TestMethod]\n    public void GetDisposables_ReturnsDisposables_WhenMaterializedDisposables()\n    {\n        // Arrange\n        var services = new ServiceCollection();\n        services.AddSingleton(_ => Substitute.For<TestDisposable>());\n        services.AddSingleton(_ => Substitute.For<TestAsyncDisposable>());\n        var serviceProvider = services.BuildServiceProvider();\n\n        // Act\n        var testDisposable = serviceProvider.GetRequiredService<TestDisposable>();\n        var testAsyncDisposable = serviceProvider.GetRequiredService<TestAsyncDisposable>();\n        var disposables = serviceProvider.GetDisposables();\n\n        // Assert\n        Assert.AreEqual(2, disposables.Count);\n        CollectionAssert.Contains(disposables, testDisposable);\n        CollectionAssert.Contains(disposables, testAsyncDisposable);\n    }\n\n    [TestMethod]\n    public void GetDisposables_ReturnsMutableListReference()\n    {\n        // Arrange\n        var services = new ServiceCollection();\n        services.AddSingleton(_ => Substitute.For<TestDisposable>());\n        var serviceProvider = services.BuildServiceProvider();\n\n        // Act\n        // Clearing the list should result in TestDisposable not being disposed by the ServiceProvider\n        var testDisposable = serviceProvider.GetRequiredService<TestDisposable>();\n        var disposables = serviceProvider.GetDisposables();\n        disposables.Clear();\n        serviceProvider.Dispose();\n\n        // Assert\n        testDisposable.DidNotReceive().Dispose();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Helper/EventManagerTests.cs",
    "content": "using System.Diagnostics.CodeAnalysis;\nusing StabilityMatrix.Core.Helper;\n\nnamespace StabilityMatrix.Tests.Helper;\n\n[TestClass]\npublic class EventManagerTests\n{\n    private EventManager eventManager = null!;\n        \n    [TestInitialize]\n    public void TestInitialize()\n    {\n        eventManager = EventManager.Instance;\n    }\n    \n    [TestMethod]\n    public void GlobalProgressChanged_ShouldBeInvoked()\n    {\n        // Arrange\n        var progress = 0;\n        eventManager.GlobalProgressChanged += (sender, args) => progress = args;\n        \n        // Act\n        eventManager.OnGlobalProgressChanged(100);\n        \n        // Assert\n        Assert.AreEqual(100, progress);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Helper/ImageProcessorTests.cs",
    "content": "﻿using StabilityMatrix.Avalonia.Helpers;\n\nnamespace StabilityMatrix.Tests.Helper;\n\n[TestClass]\npublic class ImageProcessorTests\n{\n    [DataTestMethod]\n    [DataRow(0, 1, 1)]\n    [DataRow(1, 1, 1)]\n    [DataRow(4, 2, 2)]\n    [DataRow(8, 2, 4)]\n    [DataRow(12, 3, 4)]\n    [DataRow(20, 4, 5)]\n    public void TestGetGridDimensionsFromImageCount(int count, int expectedRow, int expectedCols)\n    {\n        var result = ImageProcessor.GetGridDimensionsFromImageCount(count);\n        Assert.AreEqual(expectedRow, result.rows);\n        Assert.AreEqual(expectedCols, result.columns);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Helper/PackageFactoryTests.cs",
    "content": "﻿using StabilityMatrix.Core.Helper.Factory;\nusing StabilityMatrix.Core.Models.Packages;\n\nnamespace StabilityMatrix.Tests.Helper;\n\n[TestClass]\npublic class PackageFactoryTests\n{\n    private PackageFactory packageFactory = null!;\n    private IEnumerable<BasePackage> fakeBasePackages = null!;\n\n    [TestInitialize]\n    public void Setup()\n    {\n        fakeBasePackages = new List<BasePackage>\n        {\n            new DankDiffusion(null!, null!, null!, null!, null!, null!),\n        };\n        packageFactory = new PackageFactory(\n            fakeBasePackages,\n            null!,\n            null!,\n            null!,\n            null!,\n            null!,\n            null!,\n            null!\n        );\n    }\n\n    [TestMethod]\n    public void GetAllAvailablePackages_ReturnsAllPackages()\n    {\n        var result = packageFactory.GetAllAvailablePackages();\n        Assert.AreEqual(1, result.Count());\n    }\n\n    [TestMethod]\n    public void FindPackageByName_ReturnsPackage()\n    {\n        var result = packageFactory.FindPackageByName(\"dank-diffusion\");\n        Assert.IsNotNull(result);\n    }\n\n    [TestMethod]\n    public void FindPackageByName_ReturnsNull()\n    {\n        var result = packageFactory.FindPackageByName(\"not-a-package\");\n        Assert.IsNull(result);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Models/GenerationParametersTests.cs",
    "content": "﻿using StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Tests.Models;\n\n[TestClass]\npublic class GenerationParametersTests\n{\n    [TestMethod]\n    public void TestParse()\n    {\n        const string data = \"\"\"\n                            test123\n                            Negative prompt: test, easy negative\n                            Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 3589107295, Size: 1024x1028, Model hash: 9aa0c3e54d, Model: nightvisionXL_v0770_BakedVAE, VAE hash: 235745af8d, VAE: sdxl_vae.safetensors, Style Selector Enabled: True, Style Selector Randomize: False, Style Selector Style: base, Version: 1.6.0\n                            \"\"\";\n\n        Assert.IsTrue(GenerationParameters.TryParse(data, out var result));\n\n        Assert.AreEqual(\"test123\", result.PositivePrompt);\n        Assert.AreEqual(\"test, easy negative\", result.NegativePrompt);\n        Assert.AreEqual(20, result.Steps);\n        Assert.AreEqual(\"Euler a\", result.Sampler);\n        Assert.AreEqual(7, result.CfgScale);\n        Assert.AreEqual(3589107295, result.Seed);\n        Assert.AreEqual(1024, result.Width);\n        Assert.AreEqual(1028, result.Height);\n        Assert.AreEqual(\"9aa0c3e54d\", result.ModelHash);\n        Assert.AreEqual(\"nightvisionXL_v0770_BakedVAE\", result.ModelName);\n    }\n\n    [TestMethod]\n    public void TestParse_NoNegative()\n    {\n        const string data = \"\"\"\n                                test123\n                                Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 3589107295, Size: 1024x1028, Model hash: 9aa0c3e54d, Model: nightvisionXL_v0770_BakedVAE, VAE hash: 235745af8d, VAE: sdxl_vae.safetensors, Style Selector Enabled: True, Style Selector Randomize: False, Style Selector Style: base, Version: 1.6.0\n                                \"\"\";\n\n        Assert.IsTrue(GenerationParameters.TryParse(data, out var result));\n\n        Assert.AreEqual(\"test123\", result.PositivePrompt);\n        Assert.IsNull(result.NegativePrompt);\n        Assert.AreEqual(20, result.Steps);\n        Assert.AreEqual(\"Euler a\", result.Sampler);\n        Assert.AreEqual(7, result.CfgScale);\n        Assert.AreEqual(3589107295, result.Seed);\n        Assert.AreEqual(1024, result.Width);\n        Assert.AreEqual(1028, result.Height);\n        Assert.AreEqual(\"9aa0c3e54d\", result.ModelHash);\n        Assert.AreEqual(\"nightvisionXL_v0770_BakedVAE\", result.ModelName);\n    }\n\n    [TestMethod]\n    // basic data\n    [DataRow(\n        \"\"\"Steps: 30, Sampler: DPM++ 2M Karras, CFG scale: 7, Seed: 2216407431, Size: 640x896, Model hash: eb2h052f91, Model: anime_v1\"\"\",\n        7,\n        \"30\",\n        \"DPM++ 2M Karras\",\n        \"7\",\n        \"2216407431\",\n        \"640x896\",\n        \"eb2h052f91\",\n        \"anime_v1\",\n        new string[] { \"Steps\", \"Sampler\", \"CFG scale\", \"Seed\", \"Size\", \"Model hash\", \"Model\" }\n    )]\n    // duplicated keys\n    [DataRow(\n        \"\"\"Steps: 30, Sampler: DPM++ 2M Karras, CFG scale: 7, Seed: 2216407431, Size: 640x896, Model hash: eb2h052f91, Model: anime_v1, Steps: 40, Sampler: Whatever, CFG scale: 1, Seed: 1234567890, Size: 1024x1024, Model hash: 1234567890, Model: anime_v2\"\"\",\n        7,\n        \"30\",\n        \"DPM++ 2M Karras\",\n        \"7\",\n        \"2216407431\",\n        \"640x896\",\n        \"eb2h052f91\",\n        \"anime_v1\",\n        new string[] { \"Steps\", \"Sampler\", \"CFG scale\", \"Seed\", \"Size\", \"Model hash\", \"Model\" }\n    )]\n    public void TestParseLineFields(\n        string line,\n        int totalFields,\n        string? expectedSteps,\n        string? expectedSampler,\n        string? expectedCfgScale,\n        string? expectedSeed,\n        string? expectedSize,\n        string? expectedModelHash,\n        string? expectedModel,\n        string[] expectedKeys\n    )\n    {\n        var fields = GenerationParameters.ParseLine(line);\n\n        Assert.AreEqual(totalFields, fields.Count);\n        Assert.AreEqual(expectedSteps, fields[\"Steps\"]);\n        Assert.AreEqual(expectedSampler, fields[\"Sampler\"]);\n        Assert.AreEqual(expectedCfgScale, fields[\"CFG scale\"]);\n        Assert.AreEqual(expectedSeed, fields[\"Seed\"]);\n        Assert.AreEqual(expectedSize, fields[\"Size\"]);\n        Assert.AreEqual(expectedModelHash, fields[\"Model hash\"]);\n        Assert.AreEqual(expectedModel, fields[\"Model\"]);\n        CollectionAssert.AreEqual(expectedKeys, fields.Keys);\n    }\n\n    [TestMethod]\n    // empty line\n    [DataRow(\"\", new string[] { })]\n    [DataRow(\"  \", new string[] { })]\n    // basic data\n    [DataRow(\n        \"Steps: 30, Sampler: DPM++ 2M Karras, CFG scale: 7, Seed: 2216407431, Size: 640x896, Model hash: eb2h052f91, Model: anime_v1\",\n        new string[] { \"Steps\", \"Sampler\", \"CFG scale\", \"Seed\", \"Size\", \"Model hash\", \"Model\" }\n    )]\n    // no spaces\n    [DataRow(\n        \"Steps:30,Sampler:DPM++2MKarras,CFGscale:7,Seed:2216407431,Size:640x896,Modelhash:eb2h052f91,Model:anime_v1\",\n        new string[] { \"Steps\", \"Sampler\", \"CFGscale\", \"Seed\", \"Size\", \"Modelhash\", \"Model\" }\n    )]\n    // extra commas\n    [DataRow(\n        \"Steps: 30, Sampler: DPM++ 2M Karras, CFG scale: 7, Seed: 2216407431, Size: 640x896,,,,,, Model hash: eb2h052f91, Model: anime_v1,,,,,,,\",\n        new string[] { \"Steps\", \"Sampler\", \"CFG scale\", \"Seed\", \"Size\", \"Model hash\", \"Model\" }\n    )]\n    // quoted string\n    [DataRow(\n        \"\"\"Name: \"John, Doe\", Json: {\"key:with:colon\": \"value, with, comma\"}, It still: should work\"\"\",\n        new string[] { \"Name\", \"Json\", \"It still\" }\n    )]\n    // extra ending brackets\n    [DataRow(\n        \"\"\"Name: \"John, Doe\", Json: {\"key:with:colon\": \"value, with, comma\"}}}}}}}})))>>, It still: should work\"\"\",\n        new string[] { \"Name\", \"Json\", \"It still\" }\n    )]\n    // civitai\n    [DataRow(\n        \"\"\"Steps: 8, Sampler: Euler, CFG scale: 1, Seed: 12346789098, Size: 832x1216, Clip skip: 2, Created Date: 2024-12-22T01:01:01.0222111Z, Civitai resources: [{\"type\":\"checkpoint\",\"modelVersionId\":123456,\"modelName\":\"Some model name here [Pony XL] which hopefully doesnt contains half pair of quotes and brackets\",\"modelVersionName\":\"v2.0\"},{\"type\":\"lycoris\",\"weight\":0.7,\"modelVersionId\":11111111,\"modelName\":\"some style\",\"modelVersionName\":\"v1.0 pony\"},{\"type\":\"lora\",\"weight\":1,\"modelVersionId\":222222222,\"modelName\":\"another name\",\"modelVersionName\":\"v1.0\"},{\"type\":\"lora\",\"modelVersionId\":3333333,\"modelName\":\"name for 33333333333\",\"modelVersionName\":\"version name here\"}], Civitai metadata: {\"remixOfId\":11111100000}\"\"\",\n        new string[]\n        {\n            \"Steps\",\n            \"Sampler\",\n            \"CFG scale\",\n            \"Seed\",\n            \"Size\",\n            \"Clip skip\",\n            \"Created Date\",\n            \"Civitai resources\",\n            \"Civitai metadata\"\n        }\n    )]\n    // github.com/nkchocoai/ComfyUI-SaveImageWithMetaData\n    [DataRow(\n        \"\"\"Steps: 20, Sampler: DPM++ SDE Karras, CFG scale: 6.0, Seed: 1111111111111, Clip skip: 2, Size: 1024x1024, Model: the_main_model.safetensors, Model hash: ababababab, Lora_0 Model name: name_of_the_first_lora.safetensors, Lora_0 Model hash: ababababab, Lora_0 Strength model: -1.1, Lora_0 Strength clip: -1.1, Lora_1 Model name: name_of_the_second_lora.safetensors, Lora_1 Model hash: ababababab, Lora_1 Strength model: 1, Lora_1 Strength clip: 1, Lora_2 Model name: name_of_the_third_lora.safetensors, Lora_2 Model hash: ababababab, Lora_2 Strength model: 0.9, Lora_2 Strength clip: 0.9, Hashes: {\"model\": \"ababababab\", \"lora:name_of_the_first_lora\": \"ababababab\", \"lora:name_of_the_second_lora\": \"ababababab\", \"lora:name_of_the_third_lora\": \"ababababab\"}\"\"\",\n        new string[]\n        {\n            \"Steps\",\n            \"Sampler\",\n            \"CFG scale\",\n            \"Seed\",\n            \"Clip skip\",\n            \"Size\",\n            \"Model\",\n            \"Model hash\",\n            \"Lora_0 Model name\",\n            \"Lora_0 Model hash\",\n            \"Lora_0 Strength model\",\n            \"Lora_0 Strength clip\",\n            \"Lora_1 Model name\",\n            \"Lora_1 Model hash\",\n            \"Lora_1 Strength model\",\n            \"Lora_1 Strength clip\",\n            \"Lora_2 Model name\",\n            \"Lora_2 Model hash\",\n            \"Lora_2 Strength model\",\n            \"Lora_2 Strength clip\",\n            \"Hashes\"\n        }\n    )]\n    // asymmetrical bracket\n    [DataRow(\n        \"\"\"Steps: 20, Missing closing bracket: {\"name\": \"Someone did not close [this bracket\"}, But: the parser, should: still return, the: fields before it\"\"\",\n        new string[] { \"Steps\", \"Missing closing bracket\" }\n    )]\n    public void TestParseLineEdgeCases(string line, string[] expectedKeys)\n    {\n        var fields = GenerationParameters.ParseLine(line);\n\n        Assert.AreEqual(expectedKeys.Length, fields.Count);\n        CollectionAssert.AreEqual(expectedKeys, fields.Keys);\n    }\n\n    [TestMethod]\n    public void TestParseLine()\n    {\n        var fields = GenerationParameters.ParseLine(\n            \"\"\"Steps: 8, Sampler: Euler, CFG scale: 1, Seed: 12346789098, Size: 832x1216, Clip skip: 2, \"\"\"\n                + \"\"\"Created Date: 2024-12-22T01:01:01.0222111Z, Civitai resources: [{\"type\":\"checkpoint\",\"modelVersionId\":123456,\"modelName\":\"Some model name here [Pony XL] which hopefully doesnt contains half pair of quotes and brackets\",\"modelVersionName\":\"v2.0\"},{\"type\":\"lycoris\",\"weight\":0.7,\"modelVersionId\":11111111,\"modelName\":\"some style\",\"modelVersionName\":\"v1.0 pony\"},{\"type\":\"lora\",\"weight\":1,\"modelVersionId\":222222222,\"modelName\":\"another name\",\"modelVersionName\":\"v1.0\"},{\"type\":\"lora\",\"modelVersionId\":3333333,\"modelName\":\"name for 33333333333\",\"modelVersionName\":\"version name here\"}], Civitai metadata: {\"remixOfId\":11111100000},\"\"\"\n                + \"\"\"Hashes: {\"model\": \"1234455678\", \"lora:aaaaaaa\": \"1234455678\", \"lora:bbbbbb\": \"1234455678\", \"lora:cccccccc\": \"1234455678\"}\"\"\"\n        );\n\n        Assert.AreEqual(10, fields.Count);\n        Assert.AreEqual(\"8\", fields[\"Steps\"]);\n        Assert.AreEqual(\"Euler\", fields[\"Sampler\"]);\n        Assert.AreEqual(\"1\", fields[\"CFG scale\"]);\n        Assert.AreEqual(\"12346789098\", fields[\"Seed\"]);\n        Assert.AreEqual(\"832x1216\", fields[\"Size\"]);\n        Assert.AreEqual(\"2\", fields[\"Clip skip\"]);\n        Assert.AreEqual(\"2024-12-22T01:01:01.0222111Z\", fields[\"Created Date\"]);\n        Assert.AreEqual(\n            \"\"\"[{\"type\":\"checkpoint\",\"modelVersionId\":123456,\"modelName\":\"Some model name here [Pony XL] which hopefully doesnt contains half pair of quotes and brackets\",\"modelVersionName\":\"v2.0\"},{\"type\":\"lycoris\",\"weight\":0.7,\"modelVersionId\":11111111,\"modelName\":\"some style\",\"modelVersionName\":\"v1.0 pony\"},{\"type\":\"lora\",\"weight\":1,\"modelVersionId\":222222222,\"modelName\":\"another name\",\"modelVersionName\":\"v1.0\"},{\"type\":\"lora\",\"modelVersionId\":3333333,\"modelName\":\"name for 33333333333\",\"modelVersionName\":\"version name here\"}]\"\"\",\n            fields[\"Civitai resources\"]\n        );\n        Assert.AreEqual(\"\"\"{\"remixOfId\":11111100000}\"\"\", fields[\"Civitai metadata\"]);\n        Assert.AreEqual(\n            \"\"\"{\"model\": \"1234455678\", \"lora:aaaaaaa\": \"1234455678\", \"lora:bbbbbb\": \"1234455678\", \"lora:cccccccc\": \"1234455678\"}\"\"\",\n            fields[\"Hashes\"]\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Models/InstalledPackageTests.cs",
    "content": "﻿using System.Runtime.InteropServices;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Tests.Models;\n\n[TestClass]\npublic class InstalledPackageTests\n{\n    [DataTestMethod]\n    [DataRow(\"C:\\\\User\\\\AppData\\\\StabilityMatrix\", \"C:\\\\User\\\\Other\", null)]\n    [DataRow(\"C:\\\\Data\", \"D:\\\\Data\\\\abc\", null)]\n    [DataRow(\"C:\\\\Data\", \"C:\\\\Data\\\\abc\", \"abc\")]\n    [DataRow(\"C:\\\\User\\\\AppData\\\\StabilityMatrix\", \"C:\\\\User\\\\AppData\\\\StabilityMatrix\\\\Packages\\\\abc\", \"Packages\\\\abc\")]\n    public void TestGetSubPath(string relativeTo, string path, string? expected)\n    {\n        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))\n        {\n            relativeTo = relativeTo.Replace(\"C:\\\\\", $\"{Path.DirectorySeparatorChar}\")\n                .Replace('\\\\', Path.DirectorySeparatorChar);\n            path = path.Replace(\"C:\\\\\", $\"{Path.DirectorySeparatorChar}\")\n                .Replace('\\\\', Path.DirectorySeparatorChar);\n            expected = expected?.Replace(\"C:\\\\\", $\"{Path.DirectorySeparatorChar}\")\n                .Replace('\\\\', Path.DirectorySeparatorChar);\n        }\n        \n        var result = InstalledPackage.GetSubPath(relativeTo, path);\n        Assert.AreEqual(expected, result);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Models/LocalModelFileTests.cs",
    "content": "﻿using StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Api;\nusing StabilityMatrix.Core.Models.Database;\n\nnamespace StabilityMatrix.Tests.Models;\n\n[TestClass]\npublic class LocalModelFileTests\n{\n    [TestMethod]\n    public void Equals_ReturnsFalse_WhenEarlyAccessOnlyFlagDiffers()\n    {\n        var standardUpdateModel = CreateLocalModelFile(hasEarlyAccessUpdateOnly: false);\n        var earlyAccessOnlyModel = standardUpdateModel with { HasEarlyAccessUpdateOnly = true };\n\n        Assert.IsFalse(standardUpdateModel.Equals(earlyAccessOnlyModel));\n        Assert.IsFalse(\n            LocalModelFile.RelativePathConnectedModelInfoComparer.Equals(\n                standardUpdateModel,\n                earlyAccessOnlyModel\n            )\n        );\n    }\n\n    [TestMethod]\n    public void RelativePathConnectedModelInfoComparer_TreatsEarlyAccessFlagAsDistinct()\n    {\n        var standardUpdateModel = CreateLocalModelFile(hasEarlyAccessUpdateOnly: false);\n        var earlyAccessOnlyModel = standardUpdateModel with { HasEarlyAccessUpdateOnly = true };\n\n        var set = new HashSet<LocalModelFile>(LocalModelFile.RelativePathConnectedModelInfoComparer)\n        {\n            standardUpdateModel,\n            earlyAccessOnlyModel,\n        };\n\n        Assert.AreEqual(2, set.Count);\n    }\n\n    private static LocalModelFile CreateLocalModelFile(bool hasEarlyAccessUpdateOnly)\n    {\n        return new LocalModelFile\n        {\n            RelativePath = \"StableDiffusion/model-a.safetensors\",\n            SharedFolderType = SharedFolderType.StableDiffusion,\n            HasUpdate = true,\n            HasEarlyAccessUpdateOnly = hasEarlyAccessUpdateOnly,\n            ConnectedModelInfo = new ConnectedModelInfo\n            {\n                ModelId = 123,\n                VersionId = 101,\n                Source = ConnectedModelSource.Civitai,\n                ModelName = \"Model A\",\n                ModelDescription = string.Empty,\n                VersionName = \"v101\",\n                Tags = [],\n                Hashes = new CivitFileHashes(),\n            },\n        };\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Models/Packages/PackageHelper.cs",
    "content": "﻿using Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.DependencyInjection.Extensions;\nusing NSubstitute;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Helper.Cache;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Python;\nusing StabilityMatrix.Core.Services;\n\nnamespace StabilityMatrix.Tests.Models.Packages;\n\npublic static class PackageHelper\n{\n    /// <summary>\n    /// Get all BasePackage implementations in the assembly.\n    /// </summary>\n    public static IEnumerable<BasePackage> GetPackages()\n    {\n        var services = new ServiceCollection();\n        services\n            .AddSingleton(Substitute.For<IGithubApiCache>())\n            .AddSingleton(Substitute.For<ISettingsManager>())\n            .AddSingleton(Substitute.For<IDownloadService>())\n            .AddSingleton(Substitute.For<IPyRunner>())\n            .AddSingleton(Substitute.For<IPyInstallationManager>())\n            .AddSingleton(Substitute.For<IPrerequisiteHelper>())\n            .AddSingleton(Substitute.For<IPipWheelService>());\n\n        var assembly = typeof(BasePackage).Assembly;\n        var packageTypes = assembly\n            .GetTypes()\n            .Where(t => t.IsSubclassOf(typeof(BasePackage)) && !t.IsAbstract)\n            .Where(t => t != typeof(DankDiffusion) && t != typeof(UnknownPackage))\n            .ToList();\n\n        // Register all package types\n        services.TryAddEnumerable(\n            packageTypes.Select(t => ServiceDescriptor.Singleton(typeof(BasePackage), t))\n        );\n\n        var serviceProvider = services.BuildServiceProvider();\n        return serviceProvider.GetServices<BasePackage>();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Models/Packages/PackageLinkTests.cs",
    "content": "﻿using System.Net.Http.Headers;\nusing Polly;\nusing Polly.Contrib.WaitAndRetry;\nusing Polly.Retry;\nusing StabilityMatrix.Core.Models.Packages;\n\nnamespace StabilityMatrix.Tests.Models.Packages;\n\n/// <summary>\n/// Tests that URL links on Packages should be valid. Requires internet connection.\n/// </summary>\n[TestClass]\n[TestCategory(\"Http\")]\npublic sealed class PackageLinkTests\n{\n    private static HttpClient HttpClient { get; } =\n        new() { DefaultRequestHeaders = { { \"User-Agent\", \"StabilityMatrix/2.0\" } } };\n\n    private static IEnumerable<object[]> PackagesData =>\n        PackageHelper.GetPackages().Where(x => x is not ComfyZluda).Select(p => new object[] { p });\n\n    private static readonly AsyncRetryPolicy<HttpResponseMessage> RetryPolicy = Policy<HttpResponseMessage>\n        .HandleResult(response => response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)\n        .WaitAndRetryAsync(\n            Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromMilliseconds(200), 3),\n            onRetry: (outcome, timespan, retryAttempt, context) =>\n            {\n                // Log retry attempt if needed\n                Console.WriteLine($\"Retry attempt {retryAttempt}, waiting {timespan.TotalSeconds} seconds\");\n            }\n        );\n\n    [TestMethod]\n    [DynamicData(nameof(PackagesData))]\n    public async Task TestPreviewImageUri(BasePackage package)\n    {\n        var imageUri = package.PreviewImageUri;\n\n        // If is GitHub Uri, use jsdelivr instead due to rate limiting\n        imageUri = GitHubToJsDelivr(imageUri);\n\n        // Test http head is successful with retry policy\n        var response = await RetryPolicy.ExecuteAsync(async () =>\n            await HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, imageUri))\n        );\n\n        Assert.IsTrue(\n            response.IsSuccessStatusCode,\n            \"Failed to get PreviewImageUri at {0}: {1}\",\n            imageUri,\n            response\n        );\n    }\n\n    [TestMethod]\n    [DynamicData(nameof(PackagesData))]\n    public async Task TestLicenseUrl(BasePackage package)\n    {\n        if (string.IsNullOrEmpty(package.LicenseUrl))\n        {\n            Assert.Inconclusive($\"No LicenseUrl for package {package.GetType().Name} '{package.Name}'\");\n        }\n\n        var licenseUri = new Uri(package.LicenseUrl);\n\n        // If is GitHub Uri, use jsdelivr instead due to rate limiting\n        licenseUri = GitHubToJsDelivr(licenseUri);\n\n        // Test http head is successful with retry policy\n        var response = await RetryPolicy.ExecuteAsync(async () =>\n            await HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, licenseUri))\n        );\n\n        Assert.IsTrue(\n            response.IsSuccessStatusCode,\n            \"Failed to get LicenseUrl at {0}: {1}\",\n            licenseUri,\n            response\n        );\n    }\n\n    private static Uri GitHubToJsDelivr(Uri uri)\n    {\n        // Like https://github.com/user/Repo/blob/main/LICENSE\n        // becomes: https://cdn.jsdelivr.net/gh/user/Repo@main/LICENSE\n        if (uri.Host.Equals(\"github.com\", StringComparison.OrdinalIgnoreCase))\n        {\n            var segments = uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);\n            if (segments is [var user, var repo, \"blob\", var branch, ..])\n            {\n                var path = string.Join(\"/\", segments.Skip(4));\n                return new Uri($\"https://cdn.jsdelivr.net/gh/{user}/{repo}@{branch}/{path}\");\n            }\n        }\n\n        return uri;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Models/Packages/SharedFolderConfigHelperTests.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Text;\nusing System.Text.Json.Nodes;\nusing FreneticUtilities.FreneticDataSyntax;\nusing StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\nusing StabilityMatrix.Core.Models.Packages;\nusing StabilityMatrix.Core.Models.Packages.Config;\nusing YamlDotNet.RepresentationModel;\n\nnamespace StabilityMatrix.Tests.Models.Packages;\n\n[TestClass]\npublic class SharedFoldersConfigHelperTests\n{\n    // Define mock paths used across tests\n    private const string MockPackageRoot = @\"C:\\SM\\Packages\\TestPackage\"; // Use OS-specific or normalized\n    private const string MockSharedModelsRoot = @\"C:\\SM\\Models\";\n\n    // Helper to run the target method and return the resulting stream content as string\n    private async Task<string> RunHelperAndGetOutput(\n        SharedFolderLayout layout,\n        string packageRoot,\n        string sharedModelsRoot,\n        bool useSharedMode // True for SharedAsync, False for DefaultAsync\n    )\n    {\n        using var stream = new MemoryStream();\n\n        if (useSharedMode)\n        {\n            await SharedFoldersConfigHelper.UpdateConfigFileForSharedAsync(\n                layout,\n                packageRoot,\n                sharedModelsRoot,\n                stream\n            );\n        }\n        else\n        {\n            await SharedFoldersConfigHelper.UpdateConfigFileForDefaultAsync(layout, packageRoot, stream);\n        }\n\n        stream.Position = 0; // Rewind stream to read the output\n        using var reader = new StreamReader(stream, Encoding.UTF8);\n        return await reader.ReadToEndAsync();\n    }\n\n    // Helper to normalize paths in expected strings for cross-platform compatibility\n    private string NormalizeExpectedPath(string path) => path.Replace('/', Path.DirectorySeparatorChar);\n\n    // --- JSON Tests ---\n\n    [TestMethod]\n    public async Task Json_UpdateForShared_WritesCorrectPaths()\n    {\n        // Arrange\n        var layout = new SharedFolderLayout\n        {\n            RelativeConfigPath = \"config.json\",\n            ConfigFileType = ConfigFileType.Json,\n            ConfigSharingOptions = ConfigSharingOptions.Default, // Use default options\n            Rules = ImmutableList.Create(\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.StableDiffusion],\n                    ConfigDocumentPaths = [\"ckpt_dir\"]\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS],\n                    ConfigDocumentPaths = [\"lora_dirs\"]\n                } // Test multiple sources -> array\n            )\n        };\n\n        // Act\n        var outputJson = await RunHelperAndGetOutput(\n            layout,\n            MockPackageRoot,\n            MockSharedModelsRoot,\n            useSharedMode: true\n        );\n        var jsonNode = JsonNode.Parse(outputJson);\n\n        // Assert\n        Assert.IsNotNull(jsonNode);\n        var expectedCkptPath = Path.Combine(MockSharedModelsRoot, \"StableDiffusion\").Replace('\\\\', '/'); // JSON usually uses /\n        var expectedLoraPath = Path.Combine(MockSharedModelsRoot, \"Lora\").Replace('\\\\', '/');\n        var expectedLycoPath = Path.Combine(MockSharedModelsRoot, \"LyCORIS\").Replace('\\\\', '/');\n\n        Assert.AreEqual(expectedCkptPath, jsonNode[\"ckpt_dir\"]?.GetValue<string>());\n\n        var loraDirs = jsonNode[\"lora_dirs\"] as JsonArray;\n        Assert.IsNotNull(loraDirs);\n        Assert.AreEqual(2, loraDirs.Count);\n        Assert.IsTrue(loraDirs.Any(n => n != null && n.GetValue<string>() == expectedLoraPath));\n        Assert.IsTrue(loraDirs.Any(n => n != null && n.GetValue<string>() == expectedLycoPath));\n    }\n\n    [TestMethod]\n    public async Task Json_UpdateForDefault_WritesCorrectPaths()\n    {\n        // Arrange\n        var layout = new SharedFolderLayout\n        {\n            RelativeConfigPath = \"config.json\",\n            ConfigFileType = ConfigFileType.Json,\n            ConfigSharingOptions = ConfigSharingOptions.Default,\n            Rules = ImmutableList.Create(\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.StableDiffusion],\n                    TargetRelativePaths = [\"models/checkpoints\"],\n                    ConfigDocumentPaths = [\"ckpt_dir\"]\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Lora],\n                    TargetRelativePaths = [\"models/loras\"],\n                    ConfigDocumentPaths = [\"lora_dirs\"]\n                } // Assume single default path\n            )\n        };\n        var expectedCkptPath = Path.Combine(MockPackageRoot, \"models\", \"checkpoints\").Replace('\\\\', '/');\n        var expectedLoraPath = Path.Combine(MockPackageRoot, \"models\", \"loras\").Replace('\\\\', '/');\n\n        // Act\n        var outputJson = await RunHelperAndGetOutput(\n            layout,\n            MockPackageRoot,\n            MockSharedModelsRoot,\n            useSharedMode: false\n        ); // Default Mode\n        var jsonNode = JsonNode.Parse(outputJson);\n\n        // Assert\n        Assert.IsNotNull(jsonNode);\n        Assert.AreEqual(expectedCkptPath, jsonNode[\"ckpt_dir\"]?.GetValue<string>());\n        // Since default writes single target path, expect string, not array\n        Assert.AreEqual(expectedLoraPath, jsonNode[\"lora_dirs\"]?.GetValue<string>());\n    }\n\n    [TestMethod]\n    public async Task Json_NestedPaths_UpdateForShared_WritesCorrectly()\n    {\n        // Arrange\n        var layout = new SharedFolderLayout\n        {\n            RelativeConfigPath = \"config.json\",\n            ConfigFileType = ConfigFileType.Json,\n            ConfigSharingOptions = ConfigSharingOptions.Default,\n            Rules = ImmutableList.Create(\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.VAE],\n                    ConfigDocumentPaths = [\"paths.models.vae\"]\n                }\n            )\n        };\n        var expectedVaePath = Path.Combine(MockSharedModelsRoot, \"VAE\").Replace('\\\\', '/');\n\n        // Act\n        var outputJson = await RunHelperAndGetOutput(\n            layout,\n            MockPackageRoot,\n            MockSharedModelsRoot,\n            useSharedMode: true\n        );\n        var jsonNode = JsonNode.Parse(outputJson);\n\n        // Assert\n        Assert.IsNotNull(jsonNode);\n        Assert.AreEqual(expectedVaePath, jsonNode?[\"paths\"]?[\"models\"]?[\"vae\"]?.GetValue<string>());\n    }\n\n    // --- YAML Tests ---\n\n    [TestMethod]\n    public async Task Yaml_UpdateForShared_WritesCorrectPathsWithRootKey()\n    {\n        // Arrange\n        var layout = new SharedFolderLayout\n        {\n            RelativeConfigPath = \"extra_paths.yaml\",\n            ConfigFileType = ConfigFileType.Yaml,\n            ConfigSharingOptions = ConfigSharingOptions.Default with { RootKey = \"stability_matrix\" }, // Set RootKey\n            Rules = ImmutableList.Create(\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.VAE],\n                    ConfigDocumentPaths = [\"vae\"]\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS],\n                    ConfigDocumentPaths = [\"loras\"]\n                }\n            )\n        };\n        var expectedVaePath = Path.Combine(MockSharedModelsRoot, \"VAE\").Replace('\\\\', '/');\n        var expectedLoraPath = Path.Combine(MockSharedModelsRoot, \"Lora\").Replace('\\\\', '/');\n        var expectedLycoPath = Path.Combine(MockSharedModelsRoot, \"LyCORIS\").Replace('\\\\', '/');\n\n        // Act\n        var outputYaml = await RunHelperAndGetOutput(\n            layout,\n            MockPackageRoot,\n            MockSharedModelsRoot,\n            useSharedMode: true\n        );\n\n        // Assert using YamlDotNet.RepresentationModel\n        var yamlStream = new YamlStream();\n        yamlStream.Load(new StringReader(outputYaml));\n        var rootMapping = yamlStream.Documents[0].RootNode as YamlMappingNode;\n        Assert.IsNotNull(rootMapping);\n\n        var smNode = rootMapping.Children[new YamlScalarNode(\"stability_matrix\")] as YamlMappingNode;\n        Assert.IsNotNull(smNode);\n\n        // Scalars\n        var vaeNode = smNode.Children[new YamlScalarNode(\"vae\")] as YamlScalarNode;\n        Assert.IsNotNull(vaeNode);\n        Assert.AreEqual(expectedVaePath, vaeNode.Value);\n\n        var lorasNode = smNode.Children[new YamlScalarNode(\"loras\")] as YamlScalarNode;\n        Assert.IsNotNull(lorasNode);\n        // Split into sequences\n        var loras = lorasNode.Value?.SplitLines() ?? [];\n        CollectionAssert.Contains(loras, expectedLoraPath);\n        CollectionAssert.Contains(loras, expectedLycoPath);\n\n        // Sequence support\n        /*var vaeNode = smNode.Children[new YamlScalarNode(\"vae\")] as YamlSequenceNode;\n        Assert.IsNotNull(vaeNode);\n        Assert.AreEqual(1, vaeNode.Children.Count);\n        Assert.AreEqual(expectedVaePath, (vaeNode.Children[0] as YamlScalarNode)?.Value);\n\n        var lorasNode = smNode.Children[new YamlScalarNode(\"loras\")] as YamlSequenceNode;\n        Assert.IsNotNull(lorasNode);\n        Assert.AreEqual(2, lorasNode.Children.Count);\n        Assert.IsTrue(lorasNode.Children.Any(n => n is YamlScalarNode ns && ns.Value == expectedLoraPath));\n        Assert.IsTrue(lorasNode.Children.Any(n => n is YamlScalarNode ns && ns.Value == expectedLycoPath));*/\n    }\n\n    [TestMethod]\n    public async Task Yaml_UpdateForDefault_RelativePaths()\n    {\n        // Arrange\n        var initialYamlContent = \"\"\"\n                                 # Existing content\n                                 some_other_key: value\n                                 stability_matrix:\n                                   vae:\n                                   - C:\\SM\\Models/VAE\n                                   loras:\n                                   - C:\\SM\\Models/Lora\n                                   - C:\\SM\\Models/LyCORIS\n                                 another_key: 123\n                                 \"\"\";\n        var layout = new SharedFolderLayout\n        {\n            RelativeConfigPath = \"extra_paths.yaml\",\n            ConfigFileType = ConfigFileType.Yaml,\n            ConfigSharingOptions = ConfigSharingOptions.Default with\n            {\n                RootKey = \"stability_matrix\",\n                ConfigDefaultType = ConfigDefaultType.TargetRelativePaths // Configure relative paths\n            },\n            Rules = ImmutableList.Create( // Define rules so helper knows which keys to clear under RootKey\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.VAE],\n                    TargetRelativePaths = [\"models/vae\"],\n                    ConfigDocumentPaths = [\"vae\"]\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS],\n                    TargetRelativePaths = [\"models/loras\"],\n                    ConfigDocumentPaths = [\"loras\"]\n                }\n            )\n        };\n\n        // Act - Write initial content, then run Default Mode\n        using var stream = new MemoryStream();\n        await using (var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true))\n        {\n            await writer.WriteAsync(initialYamlContent);\n        }\n        stream.Position = 0; // Reset for the helper\n\n        await SharedFoldersConfigHelper.UpdateConfigFileForDefaultAsync(layout, MockPackageRoot, stream); // Use overload that reads layout options\n        stream.Position = 0;\n        using var reader = new StreamReader(stream);\n        var outputYaml = await reader.ReadToEndAsync();\n\n        // Assert\n        var yamlStream = new YamlStream();\n        yamlStream.Load(new StringReader(outputYaml));\n        var rootMapping = yamlStream.Documents[0].RootNode as YamlMappingNode;\n        Assert.IsNotNull(rootMapping);\n\n        // Check that stability_matrix key is not gone (or empty)\n        Assert.IsTrue(\n            rootMapping.Children.ContainsKey(new YamlScalarNode(\"stability_matrix\")),\n            \"stability_matrix key should exist.\"\n        );\n        // Check that other keys remain\n        Assert.IsTrue(rootMapping.Children.ContainsKey(new YamlScalarNode(\"some_other_key\")));\n        Assert.IsTrue(rootMapping.Children.ContainsKey(new YamlScalarNode(\"another_key\")));\n    }\n\n    [TestMethod]\n    public async Task Yaml_UpdateForDefault_RemovesSmRootKey()\n    {\n        // Arrange\n        var initialYamlContent = \"\"\"\n                                 # Existing content\n                                 some_other_key: value\n                                 stability_matrix:\n                                   vae:\n                                   - C:\\SM\\Models/VAE\n                                   loras:\n                                   - C:\\SM\\Models/Lora\n                                   - C:\\SM\\Models/LyCORIS\n                                 another_key: 123\n                                 \"\"\";\n        var layout = new SharedFolderLayout\n        {\n            RelativeConfigPath = \"extra_paths.yaml\",\n            ConfigFileType = ConfigFileType.Yaml,\n            ConfigSharingOptions = ConfigSharingOptions.Default with\n            {\n                RootKey = \"stability_matrix\",\n                ConfigDefaultType = ConfigDefaultType.ClearRoot // Configure clearing of RootKey\n            },\n            Rules = ImmutableList.Create( // Define rules so helper knows which keys to clear under RootKey\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.VAE],\n                    TargetRelativePaths = [\"models/vae\"],\n                    ConfigDocumentPaths = [\"vae\"]\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.Lora, SharedFolderType.LyCORIS],\n                    TargetRelativePaths = [\"models/loras\"],\n                    ConfigDocumentPaths = [\"loras\"]\n                }\n            )\n        };\n\n        // Act - Write initial content, then run Default Mode\n        using var stream = new MemoryStream();\n        await using (var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true))\n        {\n            await writer.WriteAsync(initialYamlContent);\n        }\n        stream.Position = 0; // Reset for the helper\n\n        await SharedFoldersConfigHelper.UpdateConfigFileForDefaultAsync(layout, MockPackageRoot, stream); // Use overload that reads layout options\n        stream.Position = 0;\n        using var reader = new StreamReader(stream);\n        var outputYaml = await reader.ReadToEndAsync();\n\n        // Assert\n        var yamlStream = new YamlStream();\n        yamlStream.Load(new StringReader(outputYaml));\n        var rootMapping = yamlStream.Documents[0].RootNode as YamlMappingNode;\n        Assert.IsNotNull(rootMapping);\n\n        // Check that stability_matrix key is gone (or empty)\n        Assert.IsFalse(\n            rootMapping.Children.ContainsKey(new YamlScalarNode(\"stability_matrix\")),\n            \"stability_matrix key should be removed.\"\n        );\n        // Check that other keys remain\n        Assert.IsTrue(rootMapping.Children.ContainsKey(new YamlScalarNode(\"some_other_key\")));\n        Assert.IsTrue(rootMapping.Children.ContainsKey(new YamlScalarNode(\"another_key\")));\n    }\n\n    // --- FDS Tests ---\n\n    [TestMethod]\n    public async Task Fds_UpdateForShared_WritesCorrectPathsWithRoot()\n    {\n        // Arrange\n        var layout = new SharedFolderLayout\n        {\n            RelativeConfigPath = Path.Combine(\"Data\", \"Settings.fds\"),\n            ConfigFileType = ConfigFileType.Fds,\n            ConfigSharingOptions = ConfigSharingOptions.Default, // RootKey not used by FDS strategy directly\n            Rules = ImmutableList.Create(\n                new SharedFolderLayoutRule { ConfigDocumentPaths = [\"ModelRoot\"], IsRoot = true },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.StableDiffusion],\n                    ConfigDocumentPaths = [\"SDModelFolder\"]\n                }\n            )\n        };\n        var expectedModelRoot = MockSharedModelsRoot.Replace('/', Path.DirectorySeparatorChar);\n        var expectedSdModelFolder = Path.Combine(MockSharedModelsRoot, \"StableDiffusion\")\n            .Replace('/', Path.DirectorySeparatorChar);\n\n        // Act\n        var outputFds = await RunHelperAndGetOutput(\n            layout,\n            MockPackageRoot,\n            MockSharedModelsRoot,\n            useSharedMode: true\n        );\n        var fdsSection = new FDSSection(outputFds);\n\n        // Assert\n        Assert.IsNotNull(fdsSection);\n        var pathsSection = fdsSection.GetSection(\"Paths\");\n        Assert.IsNotNull(pathsSection);\n        Assert.AreEqual(expectedModelRoot, pathsSection.GetString(\"ModelRoot\"));\n        Assert.AreEqual(expectedSdModelFolder, pathsSection.GetString(\"SDModelFolder\"));\n    }\n\n    [TestMethod]\n    public async Task Fds_UpdateForDefault_WritesCorrectPaths()\n    {\n        // Arrange\n        var layout = new SharedFolderLayout\n        {\n            RelativeConfigPath = Path.Combine(\"Data\", \"Settings.fds\"),\n            ConfigFileType = ConfigFileType.Fds,\n            ConfigSharingOptions = ConfigSharingOptions.Default,\n            Rules = ImmutableList.Create(\n                // Root rule should result in ModelRoot being *removed* in Default mode\n                new SharedFolderLayoutRule { ConfigDocumentPaths = [\"ModelRoot\"], IsRoot = true },\n                // Regular rule should write the default path\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.StableDiffusion],\n                    TargetRelativePaths = [\"Models/Stable-Diffusion\"],\n                    ConfigDocumentPaths = [\"SDModelFolder\"]\n                }\n            )\n        };\n        var expectedSdModelFolder = Path.Combine(MockPackageRoot, \"Models\", \"Stable-Diffusion\")\n            .Replace('/', Path.DirectorySeparatorChar);\n\n        // Act\n        var outputFds = await RunHelperAndGetOutput(\n            layout,\n            MockPackageRoot,\n            MockSharedModelsRoot,\n            useSharedMode: false\n        ); // Default Mode\n        var fdsSection = new FDSSection(outputFds);\n\n        // Assert\n        Assert.IsNotNull(fdsSection);\n        var pathsSection = fdsSection.GetSection(\"Paths\"); // May or may not exist depending on if SDModelFolder was only key\n        if (pathsSection != null)\n        {\n            Assert.IsNull(\n                pathsSection.GetString(\"ModelRoot\"),\n                \"ModelRoot should be removed in Default mode.\"\n            ); // Check ModelRoot is gone\n            Assert.AreEqual(expectedSdModelFolder, pathsSection.GetString(\"SDModelFolder\"));\n        }\n        else\n        {\n            // If only ModelRoot was defined, Paths section itself might be removed, which is also ok\n            Assert.IsNull(\n                fdsSection.GetSection(\"Paths\"),\n                \"Paths section should be removed if only ModelRoot existed.\"\n            );\n        }\n    }\n\n    [TestMethod]\n    public async Task Json_SplitRule_UpdateForShared_WritesCorrectArray()\n    {\n        // Arrange: Simulate SDFX IP-Adapter rules\n        var layout = new SharedFolderLayout\n        {\n            RelativeConfigPath = \"config.json\",\n            ConfigFileType = ConfigFileType.Json,\n            ConfigSharingOptions = ConfigSharingOptions.Default with { AlwaysWriteArray = true }, // Force array\n            Rules = ImmutableList.Create(\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.IpAdapter],\n                    ConfigDocumentPaths = [\"paths.models.ipadapter\"]\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.IpAdapters15],\n                    ConfigDocumentPaths = [\"paths.models.ipadapter\"]\n                },\n                new SharedFolderLayoutRule\n                {\n                    SourceTypes = [SharedFolderType.IpAdaptersXl],\n                    ConfigDocumentPaths = [\"paths.models.ipadapter\"]\n                }\n            )\n        };\n        var expectedIpBasePath = Path.Combine(MockSharedModelsRoot, \"IpAdapter\").Replace('\\\\', '/');\n        var expectedIp15Path = Path.Combine(MockSharedModelsRoot, \"IpAdapters15\").Replace('\\\\', '/'); // SM SourceTypes map like this\n        var expectedIpXlPath = Path.Combine(MockSharedModelsRoot, \"IpAdaptersXl\").Replace('\\\\', '/');\n\n        // Act\n        var outputJson = await RunHelperAndGetOutput(\n            layout,\n            MockPackageRoot,\n            MockSharedModelsRoot,\n            useSharedMode: true\n        );\n        var jsonNode = JsonNode.Parse(outputJson);\n\n        // Assert\n        Assert.IsNotNull(jsonNode);\n        var ipAdapterNode = jsonNode?[\"paths\"]?[\"models\"]?[\"ipadapter\"];\n        Assert.IsInstanceOfType(ipAdapterNode, typeof(JsonArray));\n\n        var ipAdapterArray = ipAdapterNode as JsonArray;\n        Assert.AreEqual(3, ipAdapterArray?.Count);\n        Assert.IsTrue(ipAdapterArray.Any(n => n?.GetValue<string>() == expectedIpBasePath));\n        Assert.IsTrue(ipAdapterArray.Any(n => n?.GetValue<string>() == expectedIp15Path));\n        Assert.IsTrue(ipAdapterArray.Any(n => n?.GetValue<string>() == expectedIpXlPath));\n    }\n\n    // Add more tests:\n    // - Starting with an existing config file and modifying it.\n    // - Testing specific ConfigSharingOptions (AlwaysWriteArray for JSON, different RootKey for YAML).\n    // - Testing removal of keys when rules are removed from the layout.\n    // - Edge cases like empty layouts or layouts with no matching rules.\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Models/ProcessArgsTests.cs",
    "content": "﻿using System.Collections.Immutable;\nusing NSubstitute;\nusing StabilityMatrix.Core.Processes;\n\nnamespace StabilityMatrix.Tests.Models;\n\n[TestClass]\npublic class ProcessArgsTests\n{\n    [DataTestMethod]\n    [DataRow(\"pip\", new[] { \"pip\" })]\n    [DataRow(\"pip install torch\", new[] { \"pip\", \"install\", \"torch\" })]\n    [DataRow(\"pip install -r \\\"file spaces/here\\\"\", new[] { \"pip\", \"install\", \"-r\", \"file spaces/here\" })]\n    [DataRow(\"pip install -r \\\"file spaces\\\\here\\\"\", new[] { \"pip\", \"install\", \"-r\", \"file spaces\\\\here\" })]\n    public void TestStringToArray(string input, string[] expected)\n    {\n        // Implicit (string -> ProcessArgs)\n        ProcessArgs args = input;\n\n        var result = args.ToArgumentArray().Select(arg => arg.Value).ToArray();\n\n        // Assert\n        CollectionAssert.AreEqual(expected, result);\n    }\n\n    [DataTestMethod]\n    [DataRow(new[] { \"pip\" }, \"pip\")]\n    [DataRow(new[] { \"pip\", \"install\", \"torch\" }, \"pip install torch\")]\n    [DataRow(new[] { \"pip\", \"install\", \"-r\", \"file spaces/here\" }, \"pip install -r \\\"file spaces/here\\\"\")]\n    [DataRow(new[] { \"pip\", \"install\", \"-r\", \"file spaces\\\\here\" }, \"pip install -r \\\"file spaces\\\\here\\\"\")]\n    public void TestArrayToString(string[] input, string expected)\n    {\n        ProcessArgs args = input;\n        string result = args;\n        Assert.AreEqual(expected, result);\n    }\n\n    [TestMethod]\n    public void TestIsQuoted()\n    {\n        // Arrange\n        var args = new ProcessArgsBuilder(\n            \"-test\",\n            // This should be quoted (has space)\n            \"--arg 123\",\n            // Should not be quoted in result\n            Argument.Quoted(\"--arg 123\")\n        ).ToProcessArgs();\n\n        // Act\n        var argString = args.ToString();\n\n        // Assert\n        Assert.AreEqual(argString, \"-test \\\"--arg 123\\\" --arg 123\");\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Models/SafetensorMetadataTests.cs",
    "content": "using System.Buffers.Binary;\nusing System.Text;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Tests.Models;\n\n[TestClass]\npublic class SafetensorMetadataTests\n{\n    [TestMethod]\n    public async Task TestParseStreamAsync()\n    {\n        const string SOURCE_JSON = \"\"\"\n{\n\"anything\":[1,2,3,4,\"\",{ \"a\": 1, \"b\": 2, \"c\": 3 }],\n\"__metadata__\":{\"ss_network_module\":\"some network module\",\"modelspec.architecture\":\"some architecture\",\n    \"ss_tag_frequency\":\"{\\\"aaa\\\":{\\\"tag1\\\":59,\\\"tag2\\\":2},\\\"bbb\\\":{\\\"tag1\\\":4,\\\"tag3\\\":1}}\" },\n\"someotherdata\":{ \"a\": 1, \"b\": 2, \"c\": 3 }\n}\n\"\"\";\n\n        var stream = new MemoryStream();\n        Span<byte> buffer = stackalloc byte[8];\n        BinaryPrimitives.WriteUInt64LittleEndian(buffer, (ulong)SOURCE_JSON.Length);\n        stream.Write(buffer);\n        stream.Write(Encoding.UTF8.GetBytes(SOURCE_JSON));\n        stream.Position = 0;\n\n        var metadata = await SafetensorMetadata.ParseAsync(stream);\n\n        // Assert.AreEqual(\"some network module\", metadata.NetworkModule);\n        // Assert.AreEqual(\"some architecture\", metadata.ModelSpecArchitecture);\n\n        Assert.IsNotNull(metadata);\n        Assert.IsNotNull(metadata.TagFrequency);\n        CollectionAssert.AreEqual(\n            new List<SafetensorMetadata.Tag> { new(\"tag1\", 63), new(\"tag2\", 2), new(\"tag3\", 1) },\n            metadata.TagFrequency\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Models/SharedFoldersTests.cs",
    "content": "﻿using StabilityMatrix.Core.Extensions;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models;\n\nnamespace StabilityMatrix.Tests.Models;\n\n[TestClass]\npublic class SharedFoldersTests\n{\n    private string tempFolder = string.Empty;\n    private string TempModelsFolder => Path.Combine(tempFolder, \"models\");\n    private string TempPackageFolder => Path.Combine(tempFolder, \"package\");\n\n    private readonly Dictionary<SharedFolderType, string> sampleDefinitions =\n        new()\n        {\n            [SharedFolderType.StableDiffusion] = \"models/Stable-diffusion\",\n            [SharedFolderType.ESRGAN] = \"models/ESRGAN\",\n            [SharedFolderType.Embeddings] = \"embeddings\",\n        };\n\n    [TestInitialize]\n    public void Initialize()\n    {\n        tempFolder = Path.GetTempFileName();\n        File.Delete(tempFolder);\n        Directory.CreateDirectory(tempFolder);\n    }\n\n    [TestCleanup]\n    public void Cleanup()\n    {\n        if (string.IsNullOrEmpty(tempFolder))\n            return;\n        TempFiles.DeleteDirectory(tempFolder);\n    }\n\n    private void CreateSampleJunctions()\n    {\n        var definitions = new Dictionary<SharedFolderType, IReadOnlyList<string>>\n        {\n            [SharedFolderType.StableDiffusion] = new[] { \"models/Stable-diffusion\" },\n            [SharedFolderType.ESRGAN] = new[] { \"models/ESRGAN\" },\n            [SharedFolderType.Embeddings] = new[] { \"embeddings\" },\n        };\n        SharedFolders\n            .UpdateLinksForPackage(definitions, TempModelsFolder, TempPackageFolder)\n            .GetAwaiter()\n            .GetResult();\n    }\n\n    [TestMethod]\n    public void SetupLinks_CreatesJunctions()\n    {\n        CreateSampleJunctions();\n\n        // Check model folders\n        foreach (var (folderType, relativePath) in sampleDefinitions)\n        {\n            var packagePath = Path.Combine(TempPackageFolder, relativePath);\n            var modelFolder = Path.Combine(TempModelsFolder, folderType.GetStringValue());\n            // Should exist and be a junction\n            Assert.IsTrue(Directory.Exists(packagePath), $\"Package folder {packagePath} does not exist.\");\n            var info = new DirectoryInfo(packagePath);\n            Assert.IsTrue(\n                info.Attributes.HasFlag(FileAttributes.ReparsePoint),\n                $\"Package folder {packagePath} is not a junction.\"\n            );\n            // Check junction target should be in models folder\n            Assert.AreEqual(\n                modelFolder,\n                info.LinkTarget,\n                $\"Package folder {packagePath} does not point to {modelFolder}.\"\n            );\n        }\n    }\n\n    [TestMethod]\n    public void SetupLinks_CanDeleteJunctions()\n    {\n        CreateSampleJunctions();\n\n        var modelFolder = Path.Combine(\n            tempFolder,\n            \"models\",\n            SharedFolderType.StableDiffusion.GetStringValue()\n        );\n        var packagePath = Path.Combine(\n            tempFolder,\n            \"package\",\n            sampleDefinitions[SharedFolderType.StableDiffusion]\n        );\n\n        // Write a file to a model folder\n        File.Create(Path.Combine(modelFolder, \"AFile\")).Close();\n        Assert.IsTrue(\n            File.Exists(Path.Combine(modelFolder, \"AFile\")),\n            $\"File should exist in {modelFolder}.\"\n        );\n        // Should exist in the package folder\n        Assert.IsTrue(\n            File.Exists(Path.Combine(packagePath, \"AFile\")),\n            $\"File should exist in {packagePath}.\"\n        );\n\n        // Now delete the junction\n        Directory.Delete(packagePath, false);\n        Assert.IsFalse(Directory.Exists(packagePath), $\"Package folder {packagePath} should not exist.\");\n\n        // The file should still exist in the model folder\n        Assert.IsTrue(\n            File.Exists(Path.Combine(modelFolder, \"AFile\")),\n            $\"File should exist in {modelFolder}.\"\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Native/NativeRecycleBinProviderTests.cs",
    "content": "﻿using System.Runtime.InteropServices;\nusing StabilityMatrix.Native;\nusing StabilityMatrix.Native.Abstractions;\n\nnamespace StabilityMatrix.Tests.Native;\n\n[TestClass]\npublic class NativeRecycleBinProviderTests\n{\n    private string tempFolder = string.Empty;\n\n    [TestInitialize]\n    public void Initialize()\n    {\n        if (\n            !(\n                RuntimeInformation.IsOSPlatform(OSPlatform.Windows)\n                || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)\n            )\n        )\n        {\n            Assert.IsFalse(NativeFileOperations.IsRecycleBinAvailable);\n            Assert.IsNull(NativeFileOperations.RecycleBin);\n            Assert.Inconclusive(\"Recycle bin is only available on Windows and macOS.\");\n            return;\n        }\n\n        Assert.IsTrue(NativeFileOperations.IsRecycleBinAvailable);\n        Assert.IsNotNull(NativeFileOperations.RecycleBin);\n\n        tempFolder = Path.GetTempFileName();\n        File.Delete(tempFolder);\n        Directory.CreateDirectory(tempFolder);\n    }\n\n    [TestCleanup]\n    public void Cleanup()\n    {\n        if (string.IsNullOrEmpty(tempFolder))\n            return;\n        TempFiles.DeleteDirectory(tempFolder);\n    }\n\n    [TestMethod]\n    public void RecycleFile()\n    {\n        var targetFile = Path.Combine(tempFolder, $\"{nameof(RecycleFile)}_{Guid.NewGuid().ToString()}\");\n        File.Create(targetFile).Close();\n\n        Assert.IsTrue(File.Exists(targetFile));\n\n        NativeFileOperations.RecycleBin!.MoveFileToRecycleBin(\n            targetFile,\n            NativeFileOperationFlags.Silent | NativeFileOperationFlags.NoConfirmation\n        );\n\n        Assert.IsFalse(File.Exists(targetFile));\n    }\n\n    [TestMethod]\n    public void RecycleFiles()\n    {\n        var targetFiles = Enumerable\n            .Range(0, 8)\n            .Select(i => Path.Combine(tempFolder, $\"{nameof(RecycleFiles)}_{i}_{Guid.NewGuid().ToString()}\"))\n            .ToArray();\n\n        foreach (var targetFile in targetFiles)\n        {\n            File.Create(targetFile).Close();\n            Assert.IsTrue(File.Exists(targetFile));\n        }\n\n        NativeFileOperations.RecycleBin!.MoveFilesToRecycleBin(\n            targetFiles,\n            NativeFileOperationFlags.Silent | NativeFileOperationFlags.NoConfirmation\n        );\n\n        foreach (var targetFile in targetFiles)\n        {\n            Assert.IsFalse(File.Exists(targetFile));\n        }\n    }\n\n    [TestMethod]\n    public void RecycleDirectory()\n    {\n        var targetDirectory = Path.Combine(\n            tempFolder,\n            $\"{nameof(RecycleDirectory)}_{Guid.NewGuid().ToString()}\"\n        );\n        Directory.CreateDirectory(targetDirectory);\n\n        Assert.IsTrue(Directory.Exists(targetDirectory));\n\n        NativeFileOperations.RecycleBin!.MoveDirectoryToRecycleBin(\n            targetDirectory,\n            NativeFileOperationFlags.Silent | NativeFileOperationFlags.NoConfirmation\n        );\n\n        Assert.IsFalse(Directory.Exists(targetDirectory));\n    }\n\n    [TestMethod]\n    public void RecycleDirectories()\n    {\n        var targetDirectories = Enumerable\n            .Range(0, 2)\n            .Select(\n                i => Path.Combine(tempFolder, $\"{nameof(RecycleDirectories)}_{i}_{Guid.NewGuid().ToString()}\")\n            )\n            .ToArray();\n\n        foreach (var targetDirectory in targetDirectories)\n        {\n            Directory.CreateDirectory(targetDirectory);\n            Assert.IsTrue(Directory.Exists(targetDirectory));\n        }\n\n        NativeFileOperations.RecycleBin!.MoveDirectoriesToRecycleBin(\n            targetDirectories,\n            NativeFileOperationFlags.Silent | NativeFileOperationFlags.NoConfirmation\n        );\n\n        foreach (var targetDirectory in targetDirectories)\n        {\n            Assert.IsFalse(Directory.Exists(targetDirectory));\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/ReparsePoints/JunctionTests.cs",
    "content": "﻿using System.Runtime.InteropServices;\nusing System.Runtime.Versioning;\nusing StabilityMatrix.Core.ReparsePoints;\n\nnamespace StabilityMatrix.Tests.ReparsePoints;\n\nusing System.IO;\n\n[TestClass]\n[SupportedOSPlatform(\"windows\")]\npublic class JunctionTest\n{\n    private string tempFolder = string.Empty;\n\n    [TestInitialize]\n    public void Initialize()\n    {\n        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))\n        {\n            Assert.Inconclusive(\"Test cannot be run on anything but Windows currently.\");\n            return;\n        }\n\n        tempFolder = Path.GetTempFileName();\n        File.Delete(tempFolder);\n        Directory.CreateDirectory(tempFolder);\n    }\n\n    [TestCleanup]\n    public void Cleanup()\n    {\n        if (string.IsNullOrEmpty(tempFolder))\n            return;\n        TempFiles.DeleteDirectory(tempFolder);\n    }\n\n    [TestMethod]\n    public void Exists_NoSuchFile()\n    {\n        Assert.IsFalse(Junction.Exists(Path.Combine(tempFolder, \"$$$NoSuchFolder$$$\")));\n    }\n\n    [TestMethod]\n    public void Exists_IsADirectory()\n    {\n        File.Create(Path.Combine(tempFolder, \"AFile\")).Close();\n\n        Assert.IsFalse(Junction.Exists(Path.Combine(tempFolder, \"AFile\")));\n    }\n\n    [TestMethod]\n    public void Create_VerifyExists_GetTarget_Delete()\n    {\n        var targetFolder = Path.Combine(tempFolder, \"ADirectory\");\n        var junctionPoint = Path.Combine(tempFolder, \"SymLink\");\n\n        Directory.CreateDirectory(targetFolder);\n        File.Create(Path.Combine(targetFolder, \"AFile\")).Close();\n\n        // Verify behavior before junction point created.\n        Assert.IsFalse(\n            File.Exists(Path.Combine(junctionPoint, \"AFile\")),\n            \"File should not be located until junction point created.\"\n        );\n\n        Assert.IsFalse(Junction.Exists(junctionPoint), \"Junction point not created yet.\");\n\n        // Create junction point and confirm its properties.\n        Junction.Create(\n            junctionPoint,\n            targetFolder,\n            false /*don't overwrite*/\n        );\n\n        Assert.IsTrue(Junction.Exists(junctionPoint), \"Junction point exists now.\");\n\n        Assert.AreEqual(targetFolder, Junction.GetTarget(junctionPoint));\n\n        Assert.IsTrue(\n            File.Exists(Path.Combine(junctionPoint, \"AFile\")),\n            \"File should be accessible via the junction point.\"\n        );\n\n        // Delete junction point.\n        Junction.Delete(junctionPoint);\n\n        Assert.IsFalse(Junction.Exists(junctionPoint), \"Junction point should not exist now.\");\n\n        Assert.IsFalse(\n            File.Exists(Path.Combine(junctionPoint, \"AFile\")),\n            \"File should not be located after junction point deleted.\"\n        );\n\n        Assert.IsFalse(Directory.Exists(junctionPoint), \"Ensure directory was deleted too.\");\n\n        // Cleanup\n        File.Delete(Path.Combine(targetFolder, \"AFile\"));\n    }\n\n    [TestMethod]\n    [ExpectedException(\n        typeof(IOException),\n        \"Directory already exists and overwrite parameter is false.\"\n    )]\n    public void Create_ThrowsIfOverwriteNotSpecifiedAndDirectoryExists()\n    {\n        var targetFolder = Path.Combine(tempFolder, \"ADirectory\");\n        var junctionPoint = Path.Combine(tempFolder, \"SymLink\");\n\n        Directory.CreateDirectory(junctionPoint);\n\n        Junction.Create(junctionPoint, targetFolder, false);\n    }\n\n    [TestMethod]\n    public void Create_OverwritesIfSpecifiedAndDirectoryExists()\n    {\n        var targetFolder = Path.Combine(tempFolder, \"ADirectory\");\n        var junctionPoint = Path.Combine(tempFolder, \"SymLink\");\n\n        Directory.CreateDirectory(junctionPoint);\n        Directory.CreateDirectory(targetFolder);\n\n        Junction.Create(junctionPoint, targetFolder, true);\n\n        Assert.AreEqual(targetFolder, Junction.GetTarget(junctionPoint));\n    }\n\n    [TestMethod]\n    [ExpectedException(typeof(IOException), \"Target path does not exist or is not a directory.\")]\n    public void Create_ThrowsIfTargetDirectoryDoesNotExist()\n    {\n        var targetFolder = Path.Combine(tempFolder, \"ADirectory\");\n        var junctionPoint = Path.Combine(tempFolder, \"SymLink\");\n\n        Junction.Create(junctionPoint, targetFolder, false);\n    }\n\n    [TestMethod]\n    [ExpectedException(typeof(IOException), \"Unable to open reparse point.\")]\n    public void GetTarget_NonExistentJunctionPoint()\n    {\n        Junction.GetTarget(Path.Combine(tempFolder, \"SymLink\"));\n    }\n\n    [TestMethod]\n    [ExpectedException(typeof(IOException), \"Path is not a junction point.\")]\n    public void GetTarget_CalledOnADirectoryThatIsNotAJunctionPoint()\n    {\n        Junction.GetTarget(tempFolder);\n    }\n\n    [TestMethod]\n    [ExpectedException(typeof(IOException), \"Path is not a junction point.\")]\n    public void GetTarget_CalledOnAFile()\n    {\n        File.Create(Path.Combine(tempFolder, \"AFile\")).Close();\n\n        Junction.GetTarget(Path.Combine(tempFolder, \"AFile\"));\n    }\n\n    [TestMethod]\n    public void Delete_NonExistentJunctionPoint()\n    {\n        // Should do nothing.\n        Junction.Delete(Path.Combine(tempFolder, \"SymLink\"));\n    }\n\n    [TestMethod]\n    [ExpectedException(typeof(IOException), \"Unable to delete junction point.\")]\n    public void Delete_CalledOnADirectoryThatIsNotAJunctionPoint()\n    {\n        Junction.Delete(tempFolder);\n    }\n\n    [TestMethod]\n    [ExpectedException(typeof(IOException), \"Path is not a junction point.\")]\n    public void Delete_CalledOnAFile()\n    {\n        File.Create(Path.Combine(tempFolder, \"AFile\")).Close();\n\n        Junction.Delete(Path.Combine(tempFolder, \"AFile\"));\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/StabilityMatrix.Tests.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n    <Import Project=\"..\\Runtimes.Default.props\" />\n    \n    <PropertyGroup>\n        <EnableWindowsTargeting>true</EnableWindowsTargeting>\n        <IsPackable>false</IsPackable>\n        <IsTestProject>true</IsTestProject>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <PackageReference Include=\"DotNet.Bundle\" />\n        <PackageReference Include=\"DotNext\" />\n        <PackageReference Include=\"Microsoft.Extensions.Configuration.Json\" />\n        <PackageReference Include=\"Microsoft.Extensions.Http.Polly\" />\n        <PackageReference Include=\"Microsoft.NET.Test.Sdk\" />\n        <PackageReference Include=\"MSTest.TestAdapter\" />\n        <PackageReference Include=\"MSTest.TestFramework\" />\n        <PackageReference Include=\"coverlet.collector\">\n          <PrivateAssets>all</PrivateAssets>\n          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n        </PackageReference>\n        <PackageReference Include=\"NSubstitute\" />\n        <PackageReference Include=\"Polly\" />\n        <PackageReference Include=\"Polly.Contrib.WaitAndRetry\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <ProjectReference Include=\"..\\StabilityMatrix.Avalonia\\StabilityMatrix.Avalonia.csproj\" />\n      <ProjectReference Include=\"..\\StabilityMatrix.Core\\StabilityMatrix.Core.csproj\" />\n      <ProjectReference Include=\"..\\StabilityMatrix.Native\\StabilityMatrix.Native.csproj\" />\n\n    </ItemGroup>\n\n    <ItemGroup>\n        <EmbeddedResource Include=\"..\\StabilityMatrix.Avalonia\\Assets\\ImagePrompt.tmLanguage.json\" />\n        <EmbeddedResource Include=\"..\\StabilityMatrix.Avalonia\\Assets\\ThemeMatrixDark.json\" />\n    </ItemGroup>\n\n\n</Project>\n"
  },
  {
    "path": "StabilityMatrix.Tests/TempFiles.cs",
    "content": "﻿namespace StabilityMatrix.Tests;\n\npublic static class TempFiles\n{\n    // Deletes directory while handling junction folders\n    public static void DeleteDirectory(string directory)\n    {\n        // Enumerate to delete any directory links\n        foreach (var item in Directory.EnumerateDirectories(directory))\n        {\n            var info = new DirectoryInfo(item);\n            if (info.Exists && info.Attributes.HasFlag(FileAttributes.ReparsePoint))\n            {\n                info.Delete();\n            }\n            else\n            {\n                DeleteDirectory(item);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.Tests/Usings.cs",
    "content": "global using Microsoft.VisualStudio.TestTools.UnitTesting;"
  },
  {
    "path": "StabilityMatrix.UITests/Attributes/TestPriorityAttribute.cs",
    "content": "﻿namespace StabilityMatrix.UITests.Attributes;\n\n[AttributeUsage(AttributeTargets.Method)]\npublic class TestPriorityAttribute : Attribute\n{\n    public int Priority { get; private set; }\n\n    public TestPriorityAttribute(int priority)\n    {\n        Priority = priority;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.UITests/Extensions/VisualExtensions.cs",
    "content": "﻿using Avalonia.Controls;\n\nnamespace StabilityMatrix.UITests.Extensions;\n\npublic static class VisualExtensions\n{\n    public static Rect GetRelativeBounds(this Visual visual, TopLevel topLevel)\n    {\n        var origin =\n            visual.TranslatePoint(new Point(0, 0), topLevel)\n            ?? throw new NullReferenceException(\"Origin is null\");\n\n        var bounds = new Rect(origin, visual.Bounds.Size);\n\n        return bounds;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.UITests/Extensions/WindowExtensions.cs",
    "content": "﻿using Avalonia.Controls;\nusing Avalonia.Threading;\nusing Avalonia.VisualTree;\n\nnamespace StabilityMatrix.UITests.Extensions;\n\n/// <summary>\n/// Window extensions for UI tests\n/// </summary>\npublic static class WindowExtensions\n{\n    public static void ClickTarget(this TopLevel topLevel, Control target)\n    {\n        // Check target is part of the visual tree\n        var targetVisualRoot = target.GetVisualRoot();\n        if (targetVisualRoot is not TopLevel)\n        {\n            throw new ArgumentException(\"Target is not part of the visual tree\");\n        }\n        if (targetVisualRoot.Equals(topLevel))\n        {\n            throw new ArgumentException(\"Target is not part of the same visual tree as the top level\");\n        }\n\n        var point =\n            target.TranslatePoint(new Point(target.Bounds.Width / 2, target.Bounds.Height / 2), topLevel)\n            ?? throw new NullReferenceException(\"Point is null\");\n\n        topLevel.MouseMove(point);\n        topLevel.MouseDown(point, MouseButton.Left);\n        topLevel.MouseUp(point, MouseButton.Left);\n\n        // Return mouse to outside of window\n        topLevel.MouseMove(new Point(-50, -50));\n    }\n\n    public static async Task ClickTargetAsync(this TopLevel topLevel, Control target)\n    {\n        // Check target is part of the visual tree\n        var targetVisualRoot = target.GetVisualRoot();\n        if (targetVisualRoot is not TopLevel)\n        {\n            throw new ArgumentException(\"Target is not part of the visual tree\");\n        }\n        if (!targetVisualRoot.Equals(topLevel))\n        {\n            throw new ArgumentException(\"Target is not part of the same visual tree as the top level\");\n        }\n\n        var point =\n            target.TranslatePoint(new Point(target.Bounds.Width / 2, target.Bounds.Height / 2), topLevel)\n            ?? throw new NullReferenceException(\"Point is null\");\n\n        topLevel.MouseMove(point);\n        topLevel.MouseDown(point, MouseButton.Left);\n        topLevel.MouseUp(point, MouseButton.Left);\n\n        await Task.Delay(40);\n\n        // Return mouse to outside of window\n        topLevel.MouseMove(new Point(-50, -50));\n\n        await Task.Delay(300);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.UITests/MainWindowTests.cs",
    "content": "using Avalonia.Controls;\nusing Avalonia.VisualTree;\nusing FluentAvalonia.UI.Controls;\nusing Microsoft.Extensions.DependencyInjection;\nusing StabilityMatrix.Avalonia.ViewModels;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.UITests.Extensions;\n\nnamespace StabilityMatrix.UITests;\n\n[Collection(\"TempDir\")]\n[TestCaseOrderer(\"StabilityMatrix.UITests.PriorityOrderer\", \"StabilityMatrix.UITests\")]\npublic class MainWindowTests : TestBase\n{\n    [AvaloniaFact, TestPriority(1)]\n    public async Task MainWindow_ShouldOpen()\n    {\n        var (window, _) = GetMainWindow();\n\n        window.Show();\n        await Task.Delay(300);\n\n        await DoInitialSetup();\n\n        await Task.Delay(1000);\n        await Verify(window, Settings);\n    }\n\n    [AvaloniaFact, TestPriority(2)]\n    public async Task MainWindowViewModel_ShouldOk()\n    {\n        var viewModel = Services.GetRequiredService<MainWindowViewModel>();\n        await Verify(viewModel, Settings);\n    }\n\n    [AvaloniaFact, TestPriority(3)]\n    public async Task NavigateToModelBrowser_ShouldWork()\n    {\n        var (window, viewModel) = GetMainWindow();\n        await DoInitialSetup();\n\n        var y = window\n            .FindDescendantOfType<NavigationView>()!\n            .GetVisualDescendants()\n            .OfType<NavigationViewItem>()\n            .FirstOrDefault(i => i.Content?.ToString() == \"Model Browser\")!;\n\n        await window.ClickTargetAsync(y);\n\n        var frame = window.FindControl<Frame>(\"FrameView\");\n        Assert.IsType<CheckpointBrowserPage>(frame!.Content);\n\n        await Task.Delay(1000);\n        SaveScreenshot(window);\n        await Verify(window, Settings);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.UITests/ModelBrowser/CivitAiBrowserTests.cs",
    "content": "﻿namespace StabilityMatrix.UITests.ModelBrowser;\n\n[Collection(\"TempDir\")]\npublic class CivitAiBrowserTests : TestBase;\n"
  },
  {
    "path": "StabilityMatrix.UITests/ModuleInit.cs",
    "content": "﻿using System.Runtime.CompilerServices;\n\n[assembly: CollectionBehavior(DisableTestParallelization = true)]\n\nnamespace StabilityMatrix.UITests;\n\npublic static class ModuleInit\n{\n    [ModuleInitializer]\n    public static void Init() => VerifyAvalonia.Initialize();\n\n    [ModuleInitializer]\n    public static void InitOther() => VerifierSettings.InitializePlugins();\n\n    [ModuleInitializer]\n    public static void ConfigureVerify()\n    {\n        VerifyPhash.RegisterComparer(\"png\");\n\n        DerivePathInfo(\n            (sourceFile, projectDirectory, type, method) =>\n                new PathInfo(\n                    directory: Path.Combine(projectDirectory, \"Snapshots\"),\n                    typeName: type.Name,\n                    methodName: method.Name\n                )\n        );\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.UITests/PriorityOrderer.cs",
    "content": "﻿using StabilityMatrix.UITests.Attributes;\nusing Xunit.Abstractions;\nusing Xunit.Sdk;\n\nnamespace StabilityMatrix.UITests;\n\npublic class PriorityOrderer : ITestCaseOrderer\n{\n    public IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases)\n        where TTestCase : ITestCase\n    {\n        var sortedMethods = new SortedDictionary<int, List<TTestCase>>();\n\n        foreach (var testCase in testCases)\n        {\n            var priority = 0;\n\n            foreach (\n                var attr in testCase.TestMethod.Method.GetCustomAttributes(\n                    typeof(TestPriorityAttribute).AssemblyQualifiedName\n                )\n            )\n            {\n                priority = attr.GetNamedArgument<int>(\"Priority\");\n            }\n\n            GetOrCreate(sortedMethods, priority).Add(testCase);\n        }\n\n        foreach (var list in sortedMethods.Keys.Select(priority => sortedMethods[priority]))\n        {\n            list.Sort(\n                (x, y) =>\n                    StringComparer.OrdinalIgnoreCase.Compare(\n                        x.TestMethod.Method.Name,\n                        y.TestMethod.Method.Name\n                    )\n            );\n            foreach (var testCase in list)\n            {\n                yield return testCase;\n            }\n        }\n    }\n\n    private static TValue GetOrCreate<TKey, TValue>(IDictionary<TKey, TValue> dictionary, TKey key)\n        where TValue : new()\n    {\n        if (dictionary.TryGetValue(key, out var result))\n            return result;\n\n        result = new TValue();\n        dictionary[key] = result;\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.UITests/Snapshots/MainWindowTests.MainWindowViewModel_ShouldOk.verified.txt",
    "content": "﻿{\n  Greeting: Welcome to Avalonia!,\n  ProgressManagerViewModel: {\n    Title: Download Manager,\n    IconSource: {\n      Type: SymbolIconSource\n    },\n    IsOpen: false,\n    CanNavigateNext: false,\n    CanNavigatePrevious: false,\n    RemoveFromParentListCommand: ViewModelBase.RemoveFromParentList(),\n    HasErrors: false\n  },\n  UpdateViewModel: {\n    Title: ,\n    IsUpdateAvailable: true,\n    UpdateInfo: {\n      Version: {\n        Major: 2,\n        Minor: 999,\n        Prerelease: ,\n        IsPrerelease: false,\n        IsRelease: true,\n        Metadata: \n      },\n      ReleaseDate: DateTimeOffset_1,\n      Channel: Stable,\n      Type: Normal,\n      Url: https://example.org,\n      Changelog: https://example.org,\n      HashBlake3: 46e11a5216c55d4c9d3c54385f62f3e1022537ae191615237f05e06d6f8690d0,\n      Signature: IX5/CCXWJQG0oGkYWVnuF34gTqF/dJSrDrUd6fuNMYnncL39G3HSvkXrjvJvR18MA2rQNB5z13h3/qBSf9c7DA==\n    },\n    IsProgressIndeterminate: false,\n    ShowProgressBar: false,\n    NewVersionText: v2.999.0,\n    InstallUpdateCommand: UpdateViewModel.InstallUpdate(),\n    RemoveFromParentListCommand: ViewModelBase.RemoveFromParentList(),\n    HasErrors: false\n  },\n  RemoveFromParentListCommand: ViewModelBase.RemoveFromParentList(),\n  HasErrors: false\n}"
  },
  {
    "path": "StabilityMatrix.UITests/Snapshots/MainWindowTests.MainWindow_ShouldOpen.verified.txt",
    "content": "﻿{\n  Type: MainWindow,\n  Title: Stability Matrix,\n  Icon: {},\n  TransparencyLevelHint: [\n    {},\n    {},\n    {}\n  ],\n  TransparencyBackgroundFallback: Transparent,\n  Content: {\n    Type: Grid,\n    Children: [\n      {\n        Type: Grid,\n        Background: Transparent,\n        Height: 32.0,\n        Name: TitleBarHost,\n        Children: [\n          {\n            Type: Image,\n            Source: {\n              Dpi: {\n                X: 96.0,\n                Y: 96.0,\n                Length: 135.7645019878171,\n                SquaredLength: 18432.0\n              },\n              Size: {\n                AspectRatio: 1.0,\n                Width: 256.0,\n                Height: 256.0\n              },\n              PixelSize: {\n                AspectRatio: 1.0,\n                Width: 256,\n                Height: 256\n              },\n              Format: {\n                BitsPerPixel: 32\n              }\n            },\n            IsHitTestVisible: false,\n            Width: 18.0,\n            Height: 18.0,\n            Margin: 12,4,12,4,\n            IsVisible: true,\n            Name: WindowIcon\n          },\n          {\n            Type: TextBlock,\n            FontSize: 12.0,\n            Text: Stability Matrix,\n            IsHitTestVisible: false,\n            VerticalAlignment: Center,\n            IsVisible: true\n          },\n          {\n            Type: Border,\n            Padding: 6\n          }\n        ]\n      },\n      {\n        Type: NavigationView,\n        Content: {\n          Type: Frame,\n          Content: {\n            Type: LaunchPageView,\n            Content: {\n              Type: Grid,\n              Children: [\n                {\n                  Type: Grid,\n                  Margin: 0,8,0,8,\n                  Children: [\n                    {\n                      Type: Grid,\n                      Margin: 16,8,0,0,\n                      Children: [\n                        {\n                          Type: Grid,\n                          Column: 0,\n                          Row: 0,\n                          Name: LaunchButtonGrid,\n                          Children: [\n                            {\n                              Type: Button,\n                              Command: LaunchPageViewModel.LaunchAsync(string command),\n                              Content: Launch,\n                              Width: 95.0,\n                              HorizontalAlignment: Left,\n                              VerticalAlignment: Stretch,\n                              IsVisible: false\n                            },\n                            {\n                              Type: SplitButton,\n                              Command: LaunchPageViewModel.LaunchAsync(string command),\n                              Flyout: {\n                                Type: FAMenuFlyout\n                              },\n                              Content: Launch,\n                              Width: 104.0,\n                              HorizontalAlignment: Left,\n                              VerticalAlignment: Stretch,\n                              IsVisible: false\n                            }\n                          ]\n                        },\n                        {\n                          Type: TeachingTip,\n                          Name: TeachingTip1\n                        },\n                        {\n                          Type: Grid,\n                          Column: 0,\n                          Row: 0,\n                          IsVisible: false,\n                          Name: StopButtonGrid,\n                          Children: [\n                            {\n                              Type: Button,\n                              Command: {},\n                              Content: Stop,\n                              Width: 95.0,\n                              HorizontalAlignment: Left,\n                              VerticalAlignment: Stretch,\n                              IsVisible: false\n                            },\n                            {\n                              Type: Button,\n                              Command: {},\n                              Content: Stop,\n                              Width: 104.0,\n                              HorizontalAlignment: Left,\n                              VerticalAlignment: Stretch,\n                              IsVisible: false\n                            }\n                          ]\n                        },\n                        {\n                          Type: Button,\n                          Command: LaunchPageViewModel.Config(),\n                          Content: {\n                            Type: SymbolIcon\n                          },\n                          FontSize: 16.0,\n                          Width: 48.0,\n                          Margin: 8,0,0,0,\n                          HorizontalAlignment: Left,\n                          VerticalAlignment: Stretch\n                        }\n                      ]\n                    },\n                    {\n                      Type: ComboBox,\n                      SelectedIndex: -1,\n                      Selection: {\n                        SingleSelect: true,\n                        SelectedIndex: -1,\n                        AnchorIndex: -1\n                      },\n                      SelectionMode: Single,\n                      ItemTemplate: {\n                        DataType: InstalledPackage,\n                        Content: {\n                          Type: Func<IServiceProvider, object>,\n                          Target: XamlIlRuntimeHelpers.<>c__DisplayClass1_0<Control>,\n                          Method: System.Object DeferredTransformationFactoryV2(System.IServiceProvider)\n                        }\n                      },\n                      IsEnabled: true,\n                      Margin: 8,8,0,0,\n                      HorizontalAlignment: Stretch,\n                      VerticalAlignment: Top,\n                      Name: SelectPackageComboBox\n                    },\n                    {\n                      Type: ToggleButton,\n                      IsChecked: true,\n                      Content: {\n                        Type: Icon,\n                        Template: {\n                          Content: {\n                            Type: Func<IServiceProvider, object>,\n                            Target: XamlIlRuntimeHelpers.<>c__DisplayClass1_0<Control>,\n                            Method: System.Object DeferredTransformationFactoryV2(System.IServiceProvider)\n                          }\n                        },\n                        RenderTransform: {\n                          Type: TransformGroup,\n                          Children: [\n                            {\n                              Type: RotateTransform\n                            }\n                          ]\n                        }\n                      },\n                      FontSize: 16.0,\n                      Width: 48.0,\n                      Margin: 8,8,0,0,\n                      HorizontalAlignment: Left,\n                      VerticalAlignment: Stretch\n                    },\n                    {\n                      Type: ToggleButton,\n                      IsChecked: false,\n                      Content: {\n                        Type: SymbolIcon\n                      },\n                      FontSize: 16.0,\n                      Width: 48.0,\n                      Margin: 8,8,16,0,\n                      HorizontalAlignment: Left,\n                      VerticalAlignment: Stretch\n                    }\n                  ]\n                },\n                {\n                  Type: TextEditor,\n                  FontFamily: Cascadia Code,\n                  Margin: 8,8,16,10,\n                  DataContext: {\n                    IsUpdatesRunning: false,\n                    WriteCursorLockTimeout: 00:00:00.1000000,\n                    Document: {\n                      _undoStack: {\n                        IsOriginalFile: true,\n                        AcceptChanges: true,\n                        CanUndo: false,\n                        CanRedo: false,\n                        SizeLimit: 2147483647\n                      },\n                      Text: ,\n                      Version: {},\n                      IsInUpdate: false,\n                      Lines: [\n                        {\n                          IsDeleted: false,\n                          LineNumber: 1\n                        }\n                      ],\n                      LineTrackers: [\n                        {}\n                      ],\n                      UndoStack: {\n                        IsOriginalFile: true,\n                        AcceptChanges: true,\n                        CanUndo: false,\n                        CanRedo: false,\n                        SizeLimit: 2147483647\n                      },\n                      LineCount: 1\n                    }\n                  },\n                  Name: Console\n                },\n                {\n                  Type: Grid,\n                  Row: 1,\n                  Children: [\n                    {\n                      Type: StackPanel,\n                      Spacing: 4.0,\n                      Margin: 8,\n                      Children: [\n                        {\n                          Type: InfoBar,\n                          Margin: 0\n                        },\n                        {\n                          Type: InfoBar,\n                          Margin: 0\n                        }\n                      ]\n                    }\n                  ]\n                },\n                {\n                  Type: Button,\n                  Command: {},\n                  Content: Open Web UI,\n                  FontSize: 12.0,\n                  Margin: 24,0,24,8,\n                  HorizontalAlignment: Stretch,\n                  IsVisible: false\n                }\n              ]\n            },\n            DataContext: {\n              Title: Launch,\n              IconSource: {\n                Type: SymbolIconSource\n              },\n              Console: {\n                IsUpdatesRunning: false,\n                WriteCursorLockTimeout: 00:00:00.1000000,\n                Document: {\n                  _undoStack: {\n                    IsOriginalFile: true,\n                    AcceptChanges: true,\n                    CanUndo: false,\n                    CanRedo: false,\n                    SizeLimit: 2147483647\n                  },\n                  Text: ,\n                  Version: {},\n                  IsInUpdate: false,\n                  Lines: [\n                    {\n                      IsDeleted: false,\n                      LineNumber: 1\n                    }\n                  ],\n                  LineTrackers: [\n                    {}\n                  ],\n                  UndoStack: {\n                    IsOriginalFile: true,\n                    AcceptChanges: true,\n                    CanUndo: false,\n                    CanRedo: false,\n                    SizeLimit: 2147483647\n                  },\n                  LineCount: 1\n                }\n              },\n              LaunchButtonVisibility: false,\n              StopButtonVisibility: false,\n              IsLaunchTeachingTipsOpen: false,\n              ShowWebUiButton: false,\n              AutoScrollToEnd: true,\n              ShowManualInputPrompt: false,\n              ShowConfirmInputPrompt: false,\n              LaunchCommand: LaunchPageViewModel.LaunchAsync(string command),\n              ConfigCommand: LaunchPageViewModel.Config(),\n              SendConfirmInputCommand: LaunchPageViewModel.SendConfirmInput(bool value),\n              SendManualInputCommand: LaunchPageViewModel.SendManualInput(string input),\n              CanNavigateNext: false,\n              CanNavigatePrevious: false,\n              RemoveFromParentListCommand: ViewModelBase.RemoveFromParentList(),\n              HasErrors: false\n            }\n          },\n          Name: FrameView\n        },\n        Name: NavigationView\n      },\n      {\n        Type: TeachingTip,\n        Name: UpdateAvailableTeachingTip\n      }\n    ]\n  },\n  Background: #ff101010,\n  FontFamily: Segoe UI Variable Text,\n  Width: 1400.0,\n  Height: 900.0,\n  IsVisible: true,\n  DataContext: {\n    Greeting: Welcome to Avalonia!,\n    ProgressManagerViewModel: {\n      Title: Download Manager,\n      IconSource: {\n        Type: SymbolIconSource\n      },\n      IsOpen: false,\n      CanNavigateNext: false,\n      CanNavigatePrevious: false,\n      RemoveFromParentListCommand: ViewModelBase.RemoveFromParentList(),\n      HasErrors: false\n    },\n    UpdateViewModel: {\n      Title: ,\n      IsUpdateAvailable: true,\n      UpdateInfo: {\n        Version: {\n          Major: 2,\n          Minor: 999,\n          Prerelease: ,\n          IsPrerelease: false,\n          IsRelease: true,\n          Metadata: \n        },\n        ReleaseDate: DateTimeOffset_1,\n        Channel: Stable,\n        Type: Normal,\n        Url: https://example.org,\n        Changelog: https://example.org,\n        HashBlake3: 46e11a5216c55d4c9d3c54385f62f3e1022537ae191615237f05e06d6f8690d0,\n        Signature: IX5/CCXWJQG0oGkYWVnuF34gTqF/dJSrDrUd6fuNMYnncL39G3HSvkXrjvJvR18MA2rQNB5z13h3/qBSf9c7DA==\n      },\n      IsProgressIndeterminate: false,\n      ShowProgressBar: false,\n      NewVersionText: v2.999.0,\n      InstallUpdateCommand: UpdateViewModel.InstallUpdate(),\n      RemoveFromParentListCommand: ViewModelBase.RemoveFromParentList(),\n      HasErrors: false\n    },\n    SelectedCategory: {\n      Title: Launch,\n      IconSource: {\n        Type: SymbolIconSource\n      },\n      Console: {\n        IsUpdatesRunning: false,\n        WriteCursorLockTimeout: 00:00:00.1000000,\n        Document: {\n          _undoStack: {\n            IsOriginalFile: true,\n            AcceptChanges: true,\n            CanUndo: false,\n            CanRedo: false,\n            SizeLimit: 2147483647\n          },\n          Text: ,\n          Version: {},\n          IsInUpdate: false,\n          Lines: [\n            {\n              IsDeleted: false,\n              LineNumber: 1\n            }\n          ],\n          LineTrackers: [\n            {}\n          ],\n          UndoStack: {\n            IsOriginalFile: true,\n            AcceptChanges: true,\n            CanUndo: false,\n            CanRedo: false,\n            SizeLimit: 2147483647\n          },\n          LineCount: 1\n        }\n      },\n      LaunchButtonVisibility: false,\n      StopButtonVisibility: false,\n      IsLaunchTeachingTipsOpen: false,\n      ShowWebUiButton: false,\n      AutoScrollToEnd: true,\n      ShowManualInputPrompt: false,\n      ShowConfirmInputPrompt: false,\n      LaunchCommand: LaunchPageViewModel.LaunchAsync(string command),\n      ConfigCommand: LaunchPageViewModel.Config(),\n      SendConfirmInputCommand: LaunchPageViewModel.SendConfirmInput(bool value),\n      SendManualInputCommand: LaunchPageViewModel.SendManualInput(string input),\n      CanNavigateNext: false,\n      CanNavigatePrevious: false,\n      RemoveFromParentListCommand: ViewModelBase.RemoveFromParentList(),\n      HasErrors: false\n    },\n    RemoveFromParentListCommand: ViewModelBase.RemoveFromParentList(),\n    HasErrors: false\n  }\n}"
  },
  {
    "path": "StabilityMatrix.UITests/StabilityMatrix.UITests.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n    <Import Project=\"..\\Runtimes.Default.props\" />\n    \n    <PropertyGroup>\n        <IsPackable>false</IsPackable>\n        <IsTestProject>true</IsTestProject>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <PackageReference Include=\"Avalonia.Headless.XUnit\" />\n        <PackageReference Include=\"DotNet.Bundle\" />\n        <PackageReference Include=\"Microsoft.NET.Test.Sdk\" />\n        <PackageReference Include=\"NSubstitute\" />\n        <PackageReference Include=\"System.Drawing.Common\" />\n        <PackageReference Include=\"Verify\" />\n        <PackageReference Include=\"Verify.CommunityToolkit.Mvvm\" />\n        <PackageReference Include=\"Verify.Avalonia\" />\n        <PackageReference Include=\"Verify.Phash\" />\n        <PackageReference Include=\"Verify.Xunit\" />\n        <PackageReference Include=\"xunit\" />\n        <PackageReference Include=\"xunit.runner.visualstudio\">\n            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n            <PrivateAssets>all</PrivateAssets>\n        </PackageReference>\n        <PackageReference Include=\"coverlet.collector\">\n            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n            <PrivateAssets>all</PrivateAssets>\n        </PackageReference>\n    </ItemGroup>\n\n    <ItemGroup>\n      <ProjectReference Include=\"..\\StabilityMatrix.Avalonia\\StabilityMatrix.Avalonia.csproj\" />\n      <ProjectReference Include=\"..\\StabilityMatrix.Core\\StabilityMatrix.Core.csproj\" />\n    </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "StabilityMatrix.UITests/TempDirFixture.cs",
    "content": "﻿using System.Runtime.CompilerServices;\n\nnamespace StabilityMatrix.UITests;\n\npublic class TempDirFixture : IDisposable\n{\n    public static string ModuleTempDir { get; set; }\n\n    static TempDirFixture()\n    {\n        var tempDir = Path.Combine(Path.GetTempPath(), \"StabilityMatrixTest\");\n\n        if (Directory.Exists(tempDir))\n        {\n            Directory.Delete(tempDir, true);\n        }\n        Directory.CreateDirectory(tempDir);\n\n        ModuleTempDir = tempDir;\n\n        // ReSharper disable once LocalizableElement\n        Console.WriteLine($\"Using temp dir: {ModuleTempDir}\");\n    }\n\n    /// <inheritdoc />\n    public void Dispose()\n    {\n        if (Directory.Exists(ModuleTempDir))\n        {\n            // ReSharper disable once LocalizableElement\n            Console.WriteLine($\"Deleting temp dir: {ModuleTempDir}\");\n            Directory.Delete(ModuleTempDir, true);\n        }\n\n        GC.SuppressFinalize(this);\n    }\n}\n\n[CollectionDefinition(\"TempDir\")]\npublic class TempDirCollection : ICollectionFixture<TempDirFixture> { }\n"
  },
  {
    "path": "StabilityMatrix.UITests/TestAppBuilder.cs",
    "content": "﻿using Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\nusing NSubstitute;\nusing NSubstitute.Extensions;\nusing Semver;\nusing StabilityMatrix.Avalonia;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Core.Helper;\nusing StabilityMatrix.Core.Models.Update;\nusing StabilityMatrix.Core.Services;\nusing StabilityMatrix.Core.Updater;\nusing StabilityMatrix.UITests;\n\n[assembly: AvaloniaTestApplication(typeof(TestAppBuilder))]\n\nnamespace StabilityMatrix.UITests;\n\npublic static class TestAppBuilder\n{\n    public static AppBuilder BuildAvaloniaApp()\n    {\n        ConfigureGlobals();\n\n        Program.SetupAvaloniaApp();\n\n        App.BeforeBuildServiceProvider += (_, x) => ConfigureAppServices(x);\n\n        return AppBuilder\n            .Configure<App>()\n            .UseSkia()\n            .UseHeadless(new AvaloniaHeadlessPlatformOptions { UseHeadlessDrawing = false });\n    }\n\n    private static void ConfigureGlobals()\n    {\n        var tempDir = TempDirFixture.ModuleTempDir;\n        var globalSettings = Path.Combine(tempDir, \"AppDataHome\");\n\n        Compat.SetAppDataHome(globalSettings);\n    }\n\n    private static void ConfigureAppServices(IServiceCollection serviceCollection)\n    {\n        // ISettingsManager\n        var settingsManager = Substitute.ForPartsOf<SettingsManager>();\n        serviceCollection.AddSingleton<ISettingsManager>(settingsManager);\n\n        // IUpdateHelper\n        var mockUpdateInfo = new UpdateInfo()\n        {\n            Version = SemVersion.Parse(\"2.999.0\"),\n            ReleaseDate = DateTimeOffset.UnixEpoch,\n            Channel = UpdateChannel.Stable,\n            Type = UpdateType.Normal,\n            Url = new Uri(\"https://example.org\"),\n            Changelog = new Uri(\"https://example.org\"),\n            HashBlake3 = \"46e11a5216c55d4c9d3c54385f62f3e1022537ae191615237f05e06d6f8690d0\",\n            Signature =\n                \"IX5/CCXWJQG0oGkYWVnuF34gTqF/dJSrDrUd6fuNMYnncL39G3HSvkXrjvJvR18MA2rQNB5z13h3/qBSf9c7DA==\"\n        };\n\n        var updateHelper = Substitute.For<IUpdateHelper>();\n        updateHelper\n            .Configure()\n            .StartCheckingForUpdates()\n            .Returns(Task.CompletedTask)\n            .AndDoes(_ => EventManager.Instance.OnUpdateAvailable(mockUpdateInfo));\n\n        serviceCollection.AddSingleton(updateHelper);\n\n        // UpdateViewModel\n        var updateViewModel = Substitute.ForPartsOf<UpdateViewModel>(\n            Substitute.For<ILogger<UpdateViewModel>>(),\n            settingsManager,\n            null,\n            updateHelper\n        );\n        updateViewModel.Configure().GetReleaseNotes(\"\").Returns(\"Test\");\n\n        serviceCollection.AddSingleton(updateViewModel);\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.UITests/TestBase.cs",
    "content": "﻿using Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Media.Imaging;\nusing Avalonia.Threading;\nusing Avalonia.VisualTree;\nusing FluentAvalonia.UI.Controls;\nusing FluentAvalonia.UI.Windowing;\nusing Microsoft.Extensions.DependencyInjection;\nusing StabilityMatrix.Avalonia;\nusing StabilityMatrix.Avalonia.Controls;\nusing StabilityMatrix.Avalonia.Languages;\nusing StabilityMatrix.Avalonia.ViewModels;\nusing StabilityMatrix.Avalonia.ViewModels.Dialogs;\nusing StabilityMatrix.Avalonia.Views;\nusing StabilityMatrix.Avalonia.Views.Dialogs;\nusing StabilityMatrix.UITests.Extensions;\n\nnamespace StabilityMatrix.UITests;\n\npublic class TestBase\n{\n    internal static IServiceProvider Services => App.Services;\n\n    internal static (AppWindow, MainWindowViewModel)? currentMainWindow;\n\n    internal virtual VerifySettings Settings\n    {\n        get\n        {\n            var settings = new VerifySettings();\n            settings.IgnoreMembers<MainWindowViewModel>(\n                vm => vm.Pages,\n                vm => vm.FooterPages,\n                vm => vm.CurrentPage\n            );\n            settings.IgnoreMember<UpdateViewModel>(vm => vm.CurrentVersionText);\n            settings.DisableDiff();\n            return settings;\n        }\n    }\n\n    internal static (AppWindow, MainWindowViewModel) GetMainWindow()\n    {\n        if (currentMainWindow is not null)\n        {\n            return currentMainWindow.Value;\n        }\n\n        var window = Services.GetRequiredService<MainWindow>();\n        var viewModel = Services.GetRequiredService<MainWindowViewModel>();\n        window.DataContext = viewModel;\n\n        window.Width = 1400;\n        window.Height = 900;\n\n        App.VisualRoot = window;\n\n        currentMainWindow = (window, viewModel);\n        return currentMainWindow.Value;\n    }\n\n    internal static async Task DoInitialSetup()\n    {\n        var (window, _) = GetMainWindow();\n\n        if (!window.IsVisible)\n        {\n            window.Show();\n            await Task.Delay(300);\n            Dispatcher.UIThread.RunJobs();\n        }\n\n        try\n        {\n            var dialog = await WaitHelper.WaitForNotNullAsync(() => GetWindowDialog(window));\n\n            if (dialog.Content is SelectDataDirectoryDialog selectDataDirectoryDialog)\n            {\n                // Click continue button\n                var continueButton = selectDataDirectoryDialog\n                    .GetVisualDescendants()\n                    .OfType<Button>()\n                    .First(b => b.Content as string == \"Continue\");\n\n                await window.ClickTargetAsync(continueButton);\n                await Task.Delay(300);\n                Dispatcher.UIThread.RunJobs();\n\n                // Find the one click install dialog\n                var oneClickDialog = await WaitHelper.WaitForConditionAsync(\n                    () => GetWindowDialog(window),\n                    d => d?.Content is OneClickInstallDialog\n                );\n                Assert.NotNull(oneClickDialog);\n\n                var skipButton = oneClickDialog\n                    .GetVisualDescendants()\n                    .OfType<Button>()\n                    .First(b => b.Content as string == Resources.Label_SkipSetup);\n\n                await window.ClickTargetAsync(skipButton);\n                await Task.Delay(300);\n                Dispatcher.UIThread.RunJobs();\n            }\n            else if (dialog.Content is OneClickInstallDialog)\n            {\n                var skipButton = dialog\n                    .GetVisualDescendants()\n                    .OfType<Button>()\n                    .First(b => b.Content as string == Resources.Label_SkipSetup);\n\n                await window.ClickTargetAsync(skipButton);\n                await Task.Delay(300);\n                Dispatcher.UIThread.RunJobs();\n            }\n        }\n        catch (TimeoutException)\n        {\n            // ignored\n        }\n    }\n\n    internal static async Task CloseUpdateDialog()\n    {\n        var (window, _) = GetMainWindow();\n\n        var updateTip = window.FindControl<TeachingTip>(\"UpdateAvailableTeachingTip\")!;\n        await window.ClickTargetAsync(updateTip);\n\n        await Task.Delay(300);\n        Dispatcher.UIThread.RunJobs();\n    }\n\n    internal static BetterContentDialog? GetWindowDialog(Visual window)\n    {\n        return window\n            .FindDescendantOfType<VisualLayerManager>()\n            ?.FindDescendantOfType<OverlayLayer>()\n            ?.FindDescendantOfType<DialogHost>()\n            ?.FindDescendantOfType<LayoutTransformControl>()\n            ?.FindDescendantOfType<VisualLayerManager>()\n            ?.FindDescendantOfType<BetterContentDialog>();\n    }\n\n    internal static IEnumerable<BetterContentDialog> EnumerateWindowDialogs(Visual window)\n    {\n        return window\n                .FindDescendantOfType<VisualLayerManager>()\n                ?.FindDescendantOfType<OverlayLayer>()\n                ?.FindDescendantOfType<DialogHost>()\n                ?.FindDescendantOfType<LayoutTransformControl>()\n                ?.FindDescendantOfType<VisualLayerManager>()\n                ?.GetVisualDescendants()\n                .OfType<BetterContentDialog>() ?? Enumerable.Empty<BetterContentDialog>();\n    }\n\n    internal async Task<(BetterContentDialog, T)> WaitForDialog<T>(Visual window)\n        where T : Control\n    {\n        var dialogs = await WaitHelper.WaitForConditionAsync(\n            () => EnumerateWindowDialogs(window).ToList(),\n            list => list.Any(dialog => dialog.Content is T)\n        );\n\n        if (dialogs.Count == 0)\n        {\n            throw new InvalidOperationException(\"No dialogs found\");\n        }\n\n        var contentDialog = dialogs.First(dialog => dialog.Content is T);\n\n        return (contentDialog, contentDialog.Content as T)!;\n    }\n\n    internal void SaveScreenshot(Visual visual)\n    {\n        var rect = new Rect(visual!.Bounds.Size);\n\n        var pixelSize = new PixelSize((int)rect.Width, (int)rect.Height);\n        var dpiVector = new Vector(96, 96);\n        using var bitmap = new RenderTargetBitmap(pixelSize, dpiVector);\n\n        using var fs = File.Open(\n            $\"C:\\\\StabilityMatrix\\\\StabilityMatrix-avalonia\\\\StabilityMatrix.UITests\\\\\"\n                + Guid.NewGuid().ToString()\n                + \".png\",\n            FileMode.OpenOrCreate\n        );\n        bitmap.Render(visual);\n        bitmap.Save(fs);\n        fs.Flush();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.UITests/Usings.cs",
    "content": "global using Xunit;\nglobal using Avalonia;\nglobal using Avalonia.Headless;\nglobal using Avalonia.Headless.XUnit;\nglobal using Avalonia.Input;\nglobal using StabilityMatrix.UITests.Attributes;\n"
  },
  {
    "path": "StabilityMatrix.UITests/VerifyConfig.cs",
    "content": "﻿using AsyncAwaitBestPractices;\n\nnamespace StabilityMatrix.UITests;\n\ninternal static class VerifyConfig\n{\n    public static VerifySettings Default { get; }\n\n    static VerifyConfig()\n    {\n        Default = new VerifySettings();\n        Default.IgnoreMembersWithType<WeakEventManager>();\n        Default.DisableDiff();\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.UITests/WaitHelper.cs",
    "content": "﻿namespace StabilityMatrix.UITests;\n\npublic static class WaitHelper\n{\n    public static async Task<T> WaitForConditionAsync<T>(\n        Func<T> getter,\n        Func<T, bool> condition,\n        int delayMs = 50,\n        int maxAttempts = 20,\n        int initialDelayMs = 100\n    )\n    {\n        await Task.Delay(initialDelayMs);\n\n        for (var i = 0; i < maxAttempts; i++)\n        {\n            await Task.Delay(delayMs);\n\n            var result = getter();\n\n            if (condition(result))\n            {\n                return result;\n            }\n        }\n\n        throw new TimeoutException(\"Waited too long for a condition to be met\");\n    }\n\n    public static async Task<T> WaitForNotNullAsync<T>(\n        Func<T?> getter,\n        int delayMs = 50,\n        int maxAttempts = 20,\n        int initialDelayMs = 100\n    )\n    {\n        await Task.Delay(initialDelayMs);\n\n        for (var i = 0; i < maxAttempts; i++)\n        {\n            await Task.Delay(delayMs);\n\n            if (getter() is { } result)\n            {\n                return result;\n            }\n        }\n\n        throw new TimeoutException(\"Waited too long for a non-null value\");\n    }\n}\n"
  },
  {
    "path": "StabilityMatrix.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.6.33717.318\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"StabilityMatrix\", \"StabilityMatrix\\StabilityMatrix.csproj\", \"{7CA2E862-B121-495D-8CCC-2D6EF56A3312}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"StabilityMatrix.Tests\", \"StabilityMatrix.Tests\\StabilityMatrix.Tests.csproj\", \"{63EF4330-CCFF-4677-B14C-1A700CD81FDA}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"StabilityMatrix.Core\", \"StabilityMatrix.Core\\StabilityMatrix.Core.csproj\", \"{E2E0B158-26D2-48EB-84E3-C5559D9AE84C}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"StabilityMatrix.Avalonia\", \"StabilityMatrix.Avalonia\\StabilityMatrix.Avalonia.csproj\", \"{3F9F96FA-12E7-4B82-BFE5-1FAD88F3ACB2}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"StabilityMatrix.Avalonia.Diagnostics\", \"StabilityMatrix.Avalonia.Diagnostics\\StabilityMatrix.Avalonia.Diagnostics.csproj\", \"{6D088B89-12D4-4EA0-BA6B-305C7D10C084}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"StabilityMatrix.UITests\", \"StabilityMatrix.UITests\\StabilityMatrix.UITests.csproj\", \"{8C7EDDD1-7FC1-4A15-B379-910A8DA7BCA6}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Avalonia.Gif\", \"Avalonia.Gif\\Avalonia.Gif.csproj\", \"{72A73F1E-024B-4A25-AD34-626198D9527F}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"StabilityMatrix.Native\", \"StabilityMatrix.Native\\StabilityMatrix.Native.csproj\", \"{254FC709-B602-4EF8-8714-FF5E47E14E50}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"StabilityMatrix.Native.Windows\", \"StabilityMatrix.Native.Windows\\StabilityMatrix.Native.Windows.csproj\", \"{27B4D892-B507-4A01-B25F-67C9499E61B8}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"StabilityMatrix.Native.Abstractions\", \"StabilityMatrix.Native.Abstractions\\StabilityMatrix.Native.Abstractions.csproj\", \"{C521F1CF-7A5E-4016-AAD0-427FEDC53FB8}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"StabilityMatrix.Native.macOS\", \"StabilityMatrix.Native.macOS\\StabilityMatrix.Native.macOS.csproj\", \"{473AE646-17E4-4247-9271-858D257DFFE1}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"analyzers\", \"analyzers\", \"{87BDF27C-A933-4FE5-A734-57AB61BFF15A}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"StabilityMatrix.Analyzers\", \"analyzers\\StabilityMatrix.Analyzers\\StabilityMatrix.Analyzers.csproj\", \"{BDE3B28C-F4AD-4A00-AA91-A1DA706AE4E4}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"StabilityMatrix.Analyzers.CodeFixes\", \"analyzers\\StabilityMatrix.Analyzers.CodeFixes\\StabilityMatrix.Analyzers.CodeFixes.csproj\", \"{32B22C7D-BF47-4D77-A9D7-FFD863650FCB}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{7CA2E862-B121-495D-8CCC-2D6EF56A3312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{7CA2E862-B121-495D-8CCC-2D6EF56A3312}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{63EF4330-CCFF-4677-B14C-1A700CD81FDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{63EF4330-CCFF-4677-B14C-1A700CD81FDA}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{63EF4330-CCFF-4677-B14C-1A700CD81FDA}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{63EF4330-CCFF-4677-B14C-1A700CD81FDA}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{E2E0B158-26D2-48EB-84E3-C5559D9AE84C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{E2E0B158-26D2-48EB-84E3-C5559D9AE84C}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{E2E0B158-26D2-48EB-84E3-C5559D9AE84C}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{E2E0B158-26D2-48EB-84E3-C5559D9AE84C}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{3F9F96FA-12E7-4B82-BFE5-1FAD88F3ACB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{3F9F96FA-12E7-4B82-BFE5-1FAD88F3ACB2}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{3F9F96FA-12E7-4B82-BFE5-1FAD88F3ACB2}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{3F9F96FA-12E7-4B82-BFE5-1FAD88F3ACB2}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{6D088B89-12D4-4EA0-BA6B-305C7D10C084}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{6D088B89-12D4-4EA0-BA6B-305C7D10C084}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{6D088B89-12D4-4EA0-BA6B-305C7D10C084}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{6D088B89-12D4-4EA0-BA6B-305C7D10C084}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{8C7EDDD1-7FC1-4A15-B379-910A8DA7BCA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{8C7EDDD1-7FC1-4A15-B379-910A8DA7BCA6}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{8C7EDDD1-7FC1-4A15-B379-910A8DA7BCA6}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{8C7EDDD1-7FC1-4A15-B379-910A8DA7BCA6}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{72A73F1E-024B-4A25-AD34-626198D9527F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{72A73F1E-024B-4A25-AD34-626198D9527F}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{72A73F1E-024B-4A25-AD34-626198D9527F}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{72A73F1E-024B-4A25-AD34-626198D9527F}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{254FC709-B602-4EF8-8714-FF5E47E14E50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{254FC709-B602-4EF8-8714-FF5E47E14E50}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{254FC709-B602-4EF8-8714-FF5E47E14E50}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{254FC709-B602-4EF8-8714-FF5E47E14E50}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{27B4D892-B507-4A01-B25F-67C9499E61B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{27B4D892-B507-4A01-B25F-67C9499E61B8}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{27B4D892-B507-4A01-B25F-67C9499E61B8}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{27B4D892-B507-4A01-B25F-67C9499E61B8}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{C521F1CF-7A5E-4016-AAD0-427FEDC53FB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{C521F1CF-7A5E-4016-AAD0-427FEDC53FB8}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{C521F1CF-7A5E-4016-AAD0-427FEDC53FB8}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{C521F1CF-7A5E-4016-AAD0-427FEDC53FB8}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{473AE646-17E4-4247-9271-858D257DFFE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{473AE646-17E4-4247-9271-858D257DFFE1}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{473AE646-17E4-4247-9271-858D257DFFE1}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{473AE646-17E4-4247-9271-858D257DFFE1}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{BDE3B28C-F4AD-4A00-AA91-A1DA706AE4E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{BDE3B28C-F4AD-4A00-AA91-A1DA706AE4E4}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{BDE3B28C-F4AD-4A00-AA91-A1DA706AE4E4}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{BDE3B28C-F4AD-4A00-AA91-A1DA706AE4E4}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{32B22C7D-BF47-4D77-A9D7-FFD863650FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{32B22C7D-BF47-4D77-A9D7-FFD863650FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{32B22C7D-BF47-4D77-A9D7-FFD863650FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{32B22C7D-BF47-4D77-A9D7-FFD863650FCB}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {97DDAF21-661E-4E36-ABC3-BF2052415919}\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{BDE3B28C-F4AD-4A00-AA91-A1DA706AE4E4} = {87BDF27C-A933-4FE5-A734-57AB61BFF15A}\n\t\t{32B22C7D-BF47-4D77-A9D7-FFD863650FCB} = {87BDF27C-A933-4FE5-A734-57AB61BFF15A}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "StabilityMatrix.sln.DotSettings",
    "content": "﻿<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namespace:System;assembly=mscorlib\" xmlns:ss=\"urn:shemas-jetbrains-com:settings-storage-xaml\" xmlns:wpf=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue\">AI</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DDIM/@EntryIndexedValue\">DDIM</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EOF/@EntryIndexedValue\">EOF</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ESRGAN/@EntryIndexedValue\">ESRGAN</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LMS/@EntryIndexedValue\">LMS</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LRU/@EntryIndexedValue\">LRU</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PC/@EntryIndexedValue\">PC</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue\">&lt;Policy Inspect=\"True\" Prefix=\"\" Suffix=\"\" Style=\"aaBb\"&gt;&lt;ExtraRule Prefix=\"_\" Suffix=\"\" Style=\"aaBb\" /&gt;&lt;/Policy&gt;</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue\">&lt;Policy&gt;&lt;Descriptor Staticness=\"Instance\" AccessRightKinds=\"Private\" Description=\"Instance fields (private)\"&gt;&lt;ElementKinds&gt;&lt;Kind Name=\"FIELD\" /&gt;&lt;Kind Name=\"READONLY_FIELD\" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect=\"True\" Prefix=\"\" Suffix=\"\" Style=\"aaBb\"&gt;&lt;ExtraRule Prefix=\"_\" Suffix=\"\" Style=\"aaBb\" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>\n\t<s:Boolean x:Key=\"/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/@KeyIndexDefined\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=1A64B45C2359DD448CF8A25D4A7469F9/@KeyIndexDefined\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=1A64B45C2359DD448CF8A25D4A7469F9/EntryName/@EntryValue\">Class</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=1A64B45C2359DD448CF8A25D4A7469F9/Position/@EntryValue\">10</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=2CBD6971A7955044AD2624B84FB49E38/Position/@EntryValue\">9</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=567DCF4B487C244A9F6BB46E4E9F3B84/Position/@EntryValue\">6</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=7F2A1BE8D0078241A9AE7802038BAD3C/Position/@EntryValue\">7</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=C4795E57DDEC1C4F97BBC8C7173EBBCA/Position/@EntryValue\">8</s:Int64>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/@KeyIndexDefined\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Applicability/=File/@EntryIndexedValue\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/CustomProperties/=Category/@EntryIndexedValue\">Primary</s:String>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/CustomProperties/=Extension/@EntryIndexedValue\">cs</s:String>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/CustomProperties/=FileName/@EntryIndexedValue\">ViewModel</s:String>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/CustomProperties/=ValidateFileName/@EntryIndexedValue\">True</s:String>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Description/@EntryValue\">Class (View Model)</s:String>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=CLASS/@KeyIndexDefined\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=CLASS/Expression/@EntryValue\">getAlphaNumericFileNameWithoutExtension()</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=CLASS/InitialRange/@EntryValue\">-1</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=CLASS/Order/@EntryValue\">1</s:Int64>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=HEADER/@KeyIndexDefined\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=HEADER/Expression/@EntryValue\">fileheader()</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=HEADER/Order/@EntryValue\">0</s:Int64>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=NAMESPACE/@KeyIndexDefined\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=NAMESPACE/Expression/@EntryValue\">fileDefaultNamespace()</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=NAMESPACE/InitialRange/@EntryValue\">-1</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=NAMESPACE/Order/@EntryValue\">2</s:Int64>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=VIEWCLASS/@KeyIndexDefined\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=VIEWCLASS/Expression/@EntryValue\">completeSmart()</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Field/=VIEWCLASS/Order/@EntryValue\">3</s:Int64>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Image/@EntryValue\">CSharp</s:String>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Reformat/@EntryValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Scope/=E8F0594528C33E45BBFEC6CFE851095D/@KeyIndexDefined\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Scope/=E8F0594528C33E45BBFEC6CFE851095D/Type/@EntryValue\">InCSharpProjectFile</s:String>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/ShortenQualifiedReferences/@EntryValue\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/Text/@EntryValue\">using CommunityToolkit.Mvvm.ComponentModel;\nusing StabilityMatrix.Core.Attributes;\n\n$HEADER$namespace $NAMESPACE$;\n\n[View(typeof($VIEWCLASS$))]\npublic partial class $CLASS$ : ViewModelBase \n{\n    [ObservableProperty] private string title = \"Hello\";$END$\n}</s:String>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=1A64B45C2359DD448CF8A25D4A7469F9/UITag/@EntryValue\">Class/Interface</s:String>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/@KeyIndexDefined\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Applicability/=File/@EntryIndexedValue\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/CustomProperties/=Extension/@EntryIndexedValue\">axaml</s:String>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/CustomProperties/=FileName/@EntryIndexedValue\">UserControlBase</s:String>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/CustomProperties/=ValidateFileName/@EntryIndexedValue\">True</s:String>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Description/@EntryValue\">SM User Control Base</s:String>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Image/@EntryValue\">Xaml</s:String>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Reformat/@EntryValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/ShortenQualifiedReferences/@EntryValue\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Text/@EntryValue\">&lt;controls:UserControlBase xmlns=\"https://github.com/avaloniaui\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:vm=\"clr-namespace:StabilityMatrix.Avalonia.ViewModels\"\n             xmlns:ui=\"using:FluentAvalonia.UI.Controls\"\n             xmlns:mocks=\"clr-namespace:StabilityMatrix.Avalonia.DesignData\"\n             xmlns:controls=\"clr-namespace:StabilityMatrix.Avalonia.Controls\"\n             xmlns:system=\"clr-namespace:System;assembly=System.Runtime\"\n             d:DataContext=\"{x:Static mocks:DesignData.$CLASS$ViewModel}\"\n             x:DataType=\"vm:$CLASS$ViewModel\"\n             mc:Ignorable=\"d\" d:DesignWidth=\"800\" d:DesignHeight=\"450\"\n             x:Class=\"$NAMESPACE$.$CLASS$\"&gt;\n  Welcome to Avalonia!$END$\n&lt;/controls:UserControlBase&gt;\n</s:String>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=CLASS/@KeyIndexDefined\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=CLASS/Expression/@EntryValue\">getAlphaNumericFileNameWithoutExtension()</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=CLASS/InitialRange/@EntryValue\">-1</s:Int64>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=CLASS/InitialSection/@EntryValue\">348A597E5D7F8E41BA425BC567BAB17D</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=CLASS/Order/@EntryValue\">1</s:Int64>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=HEADER/@KeyIndexDefined\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=HEADER/Expression/@EntryValue\">fileheader()</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=HEADER/InitialRange/@EntryValue\">-1</s:Int64>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=HEADER/InitialSection/@EntryValue\">348A597E5D7F8E41BA425BC567BAB17D</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=HEADER/Order/@EntryValue\">0</s:Int64>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=NAMESPACE/@KeyIndexDefined\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=NAMESPACE/Expression/@EntryValue\">fileDefaultNamespace()</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=NAMESPACE/InitialRange/@EntryValue\">-1</s:Int64>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=NAMESPACE/InitialSection/@EntryValue\">348A597E5D7F8E41BA425BC567BAB17D</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Field/=NAMESPACE/Order/@EntryValue\">2</s:Int64>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Scope/=1F4DBD605BD33C409AE7328D8C5965C3/@KeyIndexDefined\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Scope/=1F4DBD605BD33C409AE7328D8C5965C3/Type/@EntryValue\">InAnyXamlProject</s:String>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Section/=348A597E5D7F8E41BA425BC567BAB17D/@KeyIndexDefined\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Section/=348A597E5D7F8E41BA425BC567BAB17D/LocationSelectorConfig/@EntryValue\">&lt;RelativeConfig File=\"$FULLNAME$.cs\" /&gt;</s:String>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Section/=348A597E5D7F8E41BA425BC567BAB17D/LocationSelectorName/@EntryValue\">manual</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Section/=348A597E5D7F8E41BA425BC567BAB17D/Order/@EntryValue\">0</s:Int64>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/Template/=40269FE32811444D85F8EF8700116732/Section/=348A597E5D7F8E41BA425BC567BAB17D/SectionPlain/Text/@EntryValue\">$HEADER$using Avalonia;&#xD;\nusing Avalonia.Controls;&#xD;\nusing Avalonia.Markup.Xaml;&#xD;\n&#xD;\nnamespace $NAMESPACE$&#xD;\n{&#xD;\n    public partial class $CLASS$ : UserControl&#xD;\n    {&#xD;\n        public $CLASS$()&#xD;\n        {&#xD;\n            InitializeComponent();&#xD;\n        }&#xD;\n&#xD;\n        private void InitializeComponent()&#xD;\n        {&#xD;\n            AvaloniaXamlLoader.Load(this);&#xD;\n        }&#xD;\n    }&#xD;\n}&#xD;\n</s:String>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/@KeyIndexDefined\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=40269FE32811444D85F8EF8700116732/@KeyIndexDefined\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=40269FE32811444D85F8EF8700116732/EntryName/@EntryValue\">Avalonia User Control</s:String>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=40269FE32811444D85F8EF8700116732/Position/@EntryValue\">13</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=0576D37125E06E44BDF356E5F1394D13/Position/@EntryValue\">5</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=1413FE2BDD2D35429C91F3F1C6A11BCF/Position/@EntryValue\">3</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=A359E40D698747F595ECF797B64FD476/Position/@EntryValue\">8</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=AA00FE2BDD2D35429C91F3F1C6A11BCF/Position/@EntryValue\">7</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=AA11FE2BDD2D35429C91F3F1C6A11BCF/Position/@EntryValue\">9</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=AA22FE2BDD2D35429C91F3F1C6A11BCF/Position/@EntryValue\">10</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=AA33FE2BDD2D35429C91F3F1C6A11BCF/Position/@EntryValue\">11</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=CD61AADE5CC302459B8F69842EEE17F6/Position/@EntryValue\">1</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=DCE2B537F04E224599B099ED5C694FBB/Position/@EntryValue\">4</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=E86FFC4DDF68794DBEA79D17669D7153/Position/@EntryValue\">12</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=EF1D5DFC66A9604AA88DEC8E06B78488/Position/@EntryValue\">2</s:Int64>\n\t<s:Int64 x:Key=\"/Default/PatternsAndTemplates/LiveTemplates/QuickList/=2FBEA98F8AB8294B87A62E4CCD2CC3E3/Entry/=F5A94B826E116646921666E505F10390/Position/@EntryValue\">6</s:Int64>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Noto/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Projektanker/@EntryIndexedValue\">True</s:Boolean></wpf:ResourceDictionary>\n"
  },
  {
    "path": "Tools/ConvertAttributes.csx",
    "content": "﻿// Run with: `dotnet script ./Tools/ConvertAttributes.csx`\n#r \"nuget: System.Text.RegularExpressions, 4.3.1\"\n#r \"nuget: Ignore, 0.2.1\"\n\nusing System.IO;\nusing System.Text.RegularExpressions;\nusing Ignore;\n\n// Options:\n// Only checks files if --write is not provided\nvar checkOnly = !Args.Contains(\"--write\");\n// \n    \nvar projectDirectory = Directory.GetCurrentDirectory();\nvar gitIgnoreFile = Path.Combine(projectDirectory, \".gitignore\");\n\nvar ignore = new Ignore.Ignore();\nif (File.Exists(gitIgnoreFile))\n{\n    var gitIgnoreContent = File.ReadAllText(gitIgnoreFile);\n    ignore.Add(gitIgnoreContent);\n}\n\nvar csFiles = new List<string>();\nforeach (var file in Directory.GetFiles(projectDirectory, \"*.cs\", SearchOption.AllDirectories))\n{\n    if (ignore.IsIgnored(file) || file.EndsWith(\".Designer.cs\") || file.EndsWith(\".g.cs\") || file.EndsWith(\".generated.cs\"))\n    {\n        continue;\n    }\n    var parts = file.Split(Path.DirectorySeparatorChar);\n    if (parts.Contains(\"bin\") || parts.Contains(\"obj\"))\n    {\n        continue;\n    }\n    csFiles.Add(file);\n}\n\n// var attributePattern = new Regex(@\"\\[(?<AttributeName>\\w+)(?:\\((typeof\\((?<TypeName>\\w+)\\))?\\))?\\]\");\nvar attributePattern = new Regex(@\"\\[(?<AttributeName>\\w+)(?:\\((typeof\\((?<TypeName>\\w+)\\))?\\))?\");\n\nvar totalFiles = 0;\n\nforeach (var file in csFiles)\n{\n    var content = File.ReadAllText(file);\n    var updatedContent = content;\n    \n    var attributeMatches = attributePattern.Matches(content);\n    \n    // Get Singleton and Transient attributes\n    var singletonMatch = attributeMatches.FirstOrDefault(m => m.Groups[\"AttributeName\"].Value == \"Singleton\");\n    var transientMatch = attributeMatches.FirstOrDefault(m => m.Groups[\"AttributeName\"].Value == \"Transient\");\n\n    // Get type name from file name\n    var targetTypeName = Path.GetFileNameWithoutExtension(file);\n    if (targetTypeName.Contains('.'))\n    {\n        targetTypeName = targetTypeName.Split('.').First();\n    }\n\n    // Replace SingletonAttribute with RegisterSingletonAttribute\n    if (singletonMatch != null)\n    {\n        var interfaceTypeName = singletonMatch.Groups[\"TypeName\"].Value;\n        var isMulti = interfaceTypeName == \"BasePackage\";\n        \n        var replacement = string.IsNullOrWhiteSpace(interfaceTypeName)\n            ? $\"[RegisterSingleton<{targetTypeName}>\"\n            : $\"[RegisterSingleton<{interfaceTypeName}, {targetTypeName}>\";\n        \n        if (isMulti)\n        {\n            replacement += \"(Duplicate = DuplicateStrategy.Append)\";\n        }\n        \n        // replacement += \"]\";\n        \n        // Replace\n        Console.WriteLine($\"Replacing {singletonMatch.Value} with {replacement}\");\n        updatedContent = updatedContent.Remove(singletonMatch.Index, singletonMatch.Length);\n        updatedContent = updatedContent.Insert(singletonMatch.Index, replacement);\n    }\n    else if (transientMatch != null)\n    {\n        var interfaceTypeName = transientMatch.Groups[\"TypeName\"].Value;\n        var isMulti = interfaceTypeName == \"BasePackage\";\n                \n        var replacement = string.IsNullOrWhiteSpace(interfaceTypeName)\n            ? $\"[RegisterTransient<{targetTypeName}>\"\n            : $\"[RegisterTransient<{interfaceTypeName}, {targetTypeName}>\";\n        \n        if (isMulti)\n        {\n            replacement += \"(Duplicate = DuplicateStrategy.Append)\";\n        }\n        \n        // replacement += \"]\";\n        \n        // Replace\n        Console.WriteLine($\"Replacing {transientMatch.Value} with {replacement}\");\n        updatedContent = updatedContent.Remove(transientMatch.Index, transientMatch.Length);\n        updatedContent = updatedContent.Insert(transientMatch.Index, replacement);\n    }\n\n    if (content == updatedContent)\n    {\n        // No changes\n        continue;\n    }\n    \n    // If file not containing `[ManagedService]` / `[..., ManagedService] replace using\n    if (!updatedContent.Contains(\"ManagedService]\"))\n    {\n        updatedContent = Regex.Replace(updatedContent, @\"using StabilityMatrix\\.Core\\.Attributes;\", \"using Injectio.Attributes;\");\n    }\n    else\n    {\n        // Otherwise just add after\n        var originalUsing = Regex.Match(updatedContent, @\"using StabilityMatrix\\.Core\\.Attributes;\");\n        updatedContent = updatedContent.Insert(originalUsing.Index + originalUsing.Length, $\"{Environment.NewLine}using Injectio.Attributes;\");\n    }\n    \n    if (checkOnly)\n    {\n        Console.WriteLine($\"Would modify: {file}\");\n        // Console.WriteLine(\"<--\");\n        // Console.WriteLine(updatedContent);\n        // Console.WriteLine(\"-->\");\n    }\n    else\n    {\n        // On Windows, save with UTF-8 BOM\n        if (Environment.OSVersion.Platform == PlatformID.Win32NT)\n        {\n            File.WriteAllText(file, updatedContent, new UTF8Encoding(true));\n        }\n        else\n        {\n            File.WriteAllText(file, updatedContent);\n        }\n        \n        Console.WriteLine($\"Updated file: {file}\");\n    }\n    totalFiles++;\n}\n\nif (checkOnly)\n{\n    Console.WriteLine($\"Check complete. {totalFiles} files would be modified.\");\n}\nelse\n{\n    Console.WriteLine($\"Update complete. {totalFiles} files modified.\");\n}\n"
  },
  {
    "path": "Tools/add_resx_strings.py",
    "content": "# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\n#     \"pyperclip\",\n#     \"rich\",\n#     \"typer\",\n# ]\n# ///\n\"\"\"\nScript to add new localization strings to .resx files from JSON data\nprovided either via a file or the system clipboard.\n\nUses standard library xml.etree.ElementTree with a CommentedTreeBuilder\nfor parsing (to check existing keys while preserving comment info).\nUses Regex and text manipulation to insert new entries, aiming for\nminimal changes to the original file formatting.\n\"\"\"\n\nimport json\nimport os\nimport re\nimport xml.etree.ElementTree as ET\nfrom pathlib import Path\nfrom typing import Dict, Tuple, Optional, List\n\nimport typer\nimport pyperclip  # For clipboard access\nfrom rich.console import Console\nfrom rich.table import Table\nfrom rich.panel import Panel\n\n# Initialize Typer app and Rich console\napp = typer.Typer(help=\"Adds new localization strings to .resx files from JSON using text insertion.\")\nconsole = Console()\n\n# --- Custom Tree Builder to handle comments ---\n# This allows parsing comments, primarily so the parser doesn't fail\n# on them and we can still build a tree to check existing keys.\nclass CommentedTreeBuilder(ET.TreeBuilder):\n    def comment(self, data):\n        # We don't strictly need to store comments in the tree for this script's logic,\n        # but this prevents the parser from erroring out.\n        # You could store them if needed using ET.Comment element type.\n        pass # Simply ignore comments during tree building for key checking\n\n# --- Helper Functions ---\n\ndef get_indentation(line: str) -> str:\n    \"\"\"Extracts leading whitespace (indentation) from a line.\"\"\"\n    match = re.match(r\"^(\\s*)\", line)\n    return match.group(1) if match else \"\"\n\ndef escape_xml_value(value: str) -> str:\n    \"\"\"Escapes special XML characters in the string value.\"\"\"\n    value = value.replace(\"&\", \"&amp;\") # Must be first\n    value = value.replace(\"<\", \"&lt;\")\n    value = value.replace(\">\", \"&gt;\")\n    value = value.replace(\"'\", \"&apos;\")\n    value = value.replace(\"\\\"\", \"&quot;\")\n    return value\n\n# --- Core Logic ---\n\ndef add_strings_to_resx(\n    resx_file_path: Path, \n    strings_to_add: Dict[str, str],\n    add_comment: str | None = None,\n) -> Tuple[int, int, bool]:\n    \"\"\"\n    Parses a .resx file minimally to check keys, then uses text manipulation\n    and regex to insert new <data> elements, preserving original formatting.\n\n    Args:\n        resx_file_path: Path to the .resx file.\n        strings_to_add: Dictionary of {key: value} strings to add.\n        add_comment: Optional comment to add to the new data elements.\n\n    Returns:\n        A tuple containing (added_count, skipped_count, success_boolean).\n    \"\"\"\n    if not resx_file_path.exists():\n        console.print(f\"  [[yellow]Skipped[/]]: File not found.\")\n        return 0, 0, False\n\n    added_count = 0\n    skipped_count = 0\n    changes_made = False\n\n    try:\n        # Read the original file content\n        original_content = resx_file_path.read_text(encoding=\"utf-8\")\n\n        # 1. Parse minimally to find existing keys (ignore comments in tree)\n        parser = ET.XMLParser(target=CommentedTreeBuilder())\n        try:\n            # Feed the content to the parser\n            parser.feed(original_content)\n            root = parser.close() # Get the root element\n            existing_keys = {data.get(\"name\") for data in root.findall(\"./data\")}\n        except ET.ParseError as e:\n            # If parsing fails even with CommentedTreeBuilder, report and exit for this file\n            console.print(\n                f\"  [[bold red]XML Parse Error[/]]: Failed basic structure check for {resx_file_path.name}: {e}\"\n            )\n            return 0, 0, False # Treat as failure\n\n        # 2. Filter strings to add only new keys\n        new_strings = {\n            key: value for key, value in strings_to_add.items() if key not in existing_keys\n        }\n        skipped_count = len(strings_to_add) - len(new_strings)\n\n        if not new_strings:\n            console.print(\n                f\"  [[bold blue]No Changes[/]]: No new keys to add. Skipped [yellow]{skipped_count}[/] keys.\"\n            )\n            return 0, skipped_count, True # No changes needed, but operation was successful\n\n        # 3. Prepare new XML snippets as strings\n        new_elements_xml: List[str] = []\n        base_indentation = \"  \" # Default/fallback indentation\n        last_data_line_indent = None\n\n        # Try to find indentation from existing data elements\n        lines = original_content.splitlines()\n        for line in reversed(lines):\n             stripped_line = line.strip()\n             if stripped_line.startswith(\"<data name=\"):\n                 last_data_line_indent = get_indentation(line)\n                 break\n             elif stripped_line.startswith(\"<resheader name=\"): # Check resheader as fallback\n                 last_data_line_indent = get_indentation(line)\n                 break\n\n        if last_data_line_indent is not None:\n            base_indentation = last_data_line_indent\n        else:\n             # If no data/resheader found, look for root indent + 2 spaces? Or keep default.\n             for line in lines:\n                  if \"<root>\" in line:\n                       base_indentation = get_indentation(line) + \"  \"\n                       break\n             console.print(f\"  [dim]Could not detect indentation from <data> tags, using default '{base_indentation}'.[/dim]\")\n\n\n        for key, value in new_strings.items():\n            escaped_value = escape_xml_value(value)\n            # Manually format the XML string to match typical .resx style\n            xml_snippet = (\n                f'{base_indentation}<data name=\"{key}\" xml:space=\"preserve\">\\n'\n                f'{base_indentation}  <value>{escaped_value}</value>\\n'\n            )\n            \n            if add_comment:\n                xml_snippet += f'{base_indentation}  <comment>{add_comment}</comment>\\n'\n                \n            xml_snippet += f'{base_indentation}</data>'\n            \n            new_elements_xml.append(xml_snippet)\n            added_count += 1\n            changes_made = True\n\n        # 4. Find insertion point using Regex\n        insertion_pos = -1\n        # Regex to find the end of the last </data> tag\n        # We search backwards from the end of the file for efficiency\n        last_data_match = None\n        for match in re.finditer(r\"</data>\\s*$\", original_content, re.MULTILINE):\n             last_data_match = match\n\n        if last_data_match:\n            insertion_pos = last_data_match.end()\n            # Ensure insertion happens *after* the newline following the tag\n            if insertion_pos < len(original_content) and original_content[insertion_pos] == '\\n':\n                 insertion_pos += 1\n            elif insertion_pos < len(original_content) and original_content[insertion_pos:insertion_pos+2] == '\\r\\n':\n                 insertion_pos += 2 # Handle Windows line endings\n            else:\n                 # If </data> is the very last thing, add a newline before inserting\n                 new_elements_xml.insert(0, \"\") # Add newline before first new element\n\n            console.print(f\"  [dim]Found last </data> tag. Inserting new elements after line {original_content[:insertion_pos].count(chr(10)) + 1}.[/dim]\")\n\n        else:\n            # Fallback: Insert before the closing </root> tag\n            root_close_match = re.search(r\"</root>\\s*$\", original_content, re.MULTILINE)\n            if root_close_match:\n                insertion_pos = root_close_match.start()\n                # Add a newline if the line before </root> isn't empty\n                prev_char_index = insertion_pos - 1\n                while prev_char_index >= 0 and original_content[prev_char_index] in (' ', '\\t', '\\r', '\\n'):\n                    prev_char_index -= 1\n                if prev_char_index >= 0 and original_content[prev_char_index] != '\\n':\n                     new_elements_xml.insert(0, \"\") # Add newline before first new element\n\n                console.print(f\"  [dim]No </data> tags found. Inserting new elements before </root> (line {original_content[:insertion_pos].count(chr(10)) + 1}).[/dim]\")\n            else:\n                # Very unlikely fallback: append to the end (might break XML structure)\n                console.print(\"  [[bold red]Error[/]]: Could not find last </data> or </root> tag. Cannot determine insertion point.\")\n                return added_count, skipped_count, False\n\n        # 5. Construct the new file content\n        new_content = (\n            original_content[:insertion_pos]\n            + \"\\n\".join(new_elements_xml) + (\"\\n\" if insertion_pos != len(original_content) and not original_content[insertion_pos:].startswith((\"\\n\", \"\\r\")) else \"\") # Add newline separator if needed\n            + original_content[insertion_pos:]\n        )\n\n        # 6. Write the modified content back to the file\n        try:\n            resx_file_path.write_text(new_content, encoding=\"utf-8\")\n            console.print(\n                f\"  [[bold green]Success[/]]: Added [cyan]{added_count}[/] keys via text insertion. Skipped [yellow]{skipped_count}[/] keys. File updated.\"\n            )\n            return added_count, skipped_count, True\n        except IOError as e:\n            console.print(\n                f\"  [[bold red]IO Error[/]]: Failed writing updated content to {resx_file_path.name}: {e}\"\n            )\n            return added_count, skipped_count, False\n\n\n    except ET.ParseError as e: # Catch errors during the initial key check parse\n        console.print(\n            f\"  [[bold red]XML Error[/]]: Failed to parse {resx_file_path.name} for key checking: {e}\"\n        )\n        return added_count, skipped_count, False\n    except IOError as e:\n        console.print(\n            f\"  [[bold red]IO Error[/]]: Failed reading/writing {resx_file_path.name}: {e}\"\n        )\n        return added_count, skipped_count, False\n    except Exception as e:\n        console.print(\n            f\"  [[bold red]Unexpected Error[/]] processing {resx_file_path.name}: {e}\"\n        )\n        import traceback\n        traceback.print_exc()\n        return added_count, skipped_count, False\n\n\n@app.command()\ndef main(\n    languages_dir: Path = typer.Argument(\n        \"./StabilityMatrix.Avalonia/Languages\",\n        help=\"Path to the directory containing the .resx files (e.g., ./Languages)\",\n        exists=True,\n        file_okay=False,\n        dir_okay=True,\n        readable=True,\n        resolve_path=True,\n    ),\n    json_input_file: Optional[Path] = typer.Option(\n        None,\n        \"--json-file\",\n        \"-f\",\n        help=\"Path to the JSON file containing the strings to add.\",\n        exists=True,\n        file_okay=True,\n        dir_okay=False,\n        readable=True,\n        resolve_path=True,\n    ),\n    from_clipboard: bool = typer.Option(\n        False,\n        \"--clipboard\",\n        \"-c\",\n        help=\"Read JSON data directly from the system clipboard instead of a file.\",\n    ),\n    extra_non_fuzzy_languages: List[str] = typer.Option(\n        [],\n        \"--non-fuzzy\",\n        \"-nf\",\n        help=\"List of additional languages that should not have 'Fuzzy' comments added.\",\n        show_default=False,\n    ),\n):\n    \"\"\"\n    Adds new localization strings to .RESX files from JSON data (file or clipboard).\n    \"\"\"\n    all_strings: Optional[ResxData] = None\n\n    # --- Input Validation and Loading ---\n    if from_clipboard and json_input_file:\n        console.print(\"[bold red]Error:[/bold red] Cannot use --json-file and --clipboard simultaneously.\")\n        raise typer.Exit(code=1)\n    if from_clipboard:\n        console.print(\"Attempting to read JSON from clipboard...\")\n        try:\n            clipboard_content = pyperclip.paste()\n            if not clipboard_content:\n                console.print(\"[bold red]Error:[/bold red] Clipboard is empty.\")\n                raise typer.Exit(code=1)\n            all_strings = json.loads(clipboard_content)\n            console.print(\"[green]Successfully parsed JSON from clipboard.[/green]\")\n        except pyperclip.PyperclipException as e:\n            console.print(f\"[bold red]Clipboard Error:[/bold red] Could not access clipboard: {e}\")\n            raise typer.Exit(code=1)\n        except json.JSONDecodeError as e:\n            console.print(f\"[bold red]JSON Error:[/bold red] Failed to decode JSON from clipboard: {e}\")\n            raise typer.Exit(code=1)\n        except Exception as e:\n            console.print(f\"[bold red]Error:[/bold red] An unexpected error occurred reading from clipboard: {e}\")\n            raise typer.Exit(code=1)\n    elif json_input_file:\n        console.print(f\"Reading JSON from file: [blue]{json_input_file}[/blue]\")\n        try:\n            with open(json_input_file, \"r\", encoding=\"utf-8\") as f:\n                all_strings = json.load(f)\n            console.print(\"[green]Successfully parsed JSON from file.[/green]\")\n        except json.JSONDecodeError as e:\n            console.print(f\"[bold red]JSON Error:[/bold red] Failed to decode JSON file {json_input_file}: {e}\")\n            raise typer.Exit(code=1)\n        except IOError as e:\n            console.print(f\"[bold red]IO Error:[/bold red] Failed reading JSON file {json_input_file}: {e}\")\n            raise typer.Exit(code=1)\n        except Exception as e:\n            console.print(f\"[bold red]Error:[/bold red] An unexpected error occurred reading JSON file: {e}\")\n            raise typer.Exit(code=1)\n    else:\n        console.print(\"[bold red]Error:[/bold red] Please provide either --json-file or --clipboard.\")\n        raise typer.Exit(code=1)\n    if not all_strings:\n        console.print(\"[bold red]Error:[/bold red] No JSON data loaded.\")\n        raise typer.Exit(code=1)\n\n    # --- Processing Logic ---\n    console.print(f\"\\nStarting localization update in: [blue]{languages_dir}[/blue]\")\n    results = []\n    any_changes_made_across_files = False\n    for lang_code, strings_for_lang in all_strings.items():\n        lang_code_lower = lang_code.strip().lower() if isinstance(lang_code, str) else \"\"\n        if lang_code_lower in (\"base\", \"en\", \"\"):\n             resx_filename = \"Resources.resx\"\n        else:\n             resx_filename = f\"Resources.{lang_code.strip()}.resx\"\n        resx_file_path = languages_dir / resx_filename\n        console.print(f\"\\nProcessing language: [magenta]{lang_code}[/] ({resx_file_path.name})\")\n        if not isinstance(strings_for_lang, dict):\n            console.print(f\"  [[bold red]Data Error[/]]: Expected a dictionary of strings for language '{lang_code}', but got {type(strings_for_lang)}. Skipping.\")\n            results.append((resx_file_path.name, \"[bold red]Invalid Data Format[/]\", 0, 0))\n            continue\n            \n        # For non-base languages, add a 'Fuzzy' comment\n        non_fuzzy_langs = [\"base\", \"en\", \"\"]\n        if extra_non_fuzzy_languages:\n            non_fuzzy_langs.extend([lang.strip().lower() for lang in extra_non_fuzzy_languages])\n        add_comment = \"Fuzzy\" if lang_code_lower not in non_fuzzy_langs else None\n            \n        added, skipped, success = add_strings_to_resx(resx_file_path, strings_for_lang, add_comment=add_comment)\n        if success and added > 0:\n            any_changes_made_across_files = True\n        status = \"[bold green]Success[/]\"\n        if not success:\n             if not resx_file_path.exists():\n                 status = \"[yellow]Not Found[/]\"\n             else:\n                 status = \"[bold red]Failed[/]\"\n        elif added == 0:\n             status = \"[bold blue]No Changes[/]\"\n        results.append((resx_file_path.name, status, added, skipped))\n\n    # --- Summary Report ---\n    console.print(\"\\n[bold]Localization Update Summary:[/bold]\")\n    summary_table = Table(show_header=True, header_style=\"bold magenta\")\n    summary_table.add_column(\"Language File\", style=\"dim cyan\", width=35)\n    summary_table.add_column(\"Status\")\n    summary_table.add_column(\"Keys Added\", justify=\"right\")\n    summary_table.add_column(\"Keys Skipped\", justify=\"right\")\n    total_added = 0\n    total_skipped = 0\n    total_failed_or_missing = 0\n    for filename, status, added, skipped in results:\n        summary_table.add_row(filename, status, str(added), str(skipped))\n        total_added += added\n        total_skipped += skipped\n        if \"Failed\" in status or \"Not Found\" in status or \"Invalid Data\" in status:\n            total_failed_or_missing += 1\n    console.print(summary_table)\n    console.print(f\"\\nTotal Keys Added: [bold green]{total_added}[/]\")\n    console.print(f\"Total Keys Skipped (Already Exist): [bold yellow]{total_skipped}[/]\")\n    if total_failed_or_missing > 0:\n        console.print(f\"Files Failed, Not Found, or Invalid Data: [bold red]{total_failed_or_missing}[/]\")\n    if not any_changes_made_across_files and total_failed_or_missing == 0:\n         console.print(\"\\n[bold blue]No changes were made to any files.[/bold blue]\")\n    else:\n         console.print(\"\\n[bold green]Update complete![/bold green]\")\n\n\nif __name__ == \"__main__\":\n    app()\n"
  },
  {
    "path": "analyzers/StabilityMatrix.Analyzers/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace StabilityMatrix.Analyzers {\n    using System;\n    \n    \n    /// <summary>\n    ///   A strongly-typed resource class, for looking up localized strings, etc.\n    /// </summary>\n    // This class was auto-generated by the StronglyTypedResourceBuilder\n    // class via a tool like ResGen or Visual Studio.\n    // To add or remove a member, edit your .ResX file then rerun ResGen\n    // with the /str option, or rebuild your VS project.\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"4.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    internal class Resources {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal Resources() {\n        }\n        \n        /// <summary>\n        ///   Returns the cached ResourceManager instance used by this class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"StabilityMatrix.Analyzers.Resources\", typeof(Resources).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   Overrides the current thread's CurrentUICulture property for all\n        ///   resource lookups using this strongly typed resource class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to ViewModels inheriting from ViewModelBase rely on lifecycle methods (OnLoaded/OnUnloaded) called by UserControlBase or TemplatedControlBase. Ensure the control specified in the [View] attribute inherits from one of these..\n        /// </summary>\n        internal static string SM0001_Description {\n            get {\n                return ResourceManager.GetString(\"SM0001_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Control &apos;{0}&apos; must inherit from &apos;{2}&apos; or &apos;{3}&apos; because its associated ViewModel &apos;{1}&apos; inherits from ViewModelBase.\n        /// </summary>\n        internal static string SM0001_MessageFormat {\n            get {\n                return ResourceManager.GetString(\"SM0001_MessageFormat\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Control associated with ViewModelBase must inherit UserControlBase or TemplatedControlBase.\n        /// </summary>\n        internal static string SM0001_Title {\n            get {\n                return ResourceManager.GetString(\"SM0001_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Concrete ViewModels inheriting from ViewModelBase should specify their associated UI control via the [View(typeof(YourControl))] attribute..\n        /// </summary>\n        internal static string SM0002_Description {\n            get {\n                return ResourceManager.GetString(\"SM0002_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to ViewModel &apos;{0}&apos; inherits from ViewModelBase but does not have a [View(...)] attribute to specify its corresponding control.\n        /// </summary>\n        internal static string SM0002_MessageFormat {\n            get {\n                return ResourceManager.GetString(\"SM0002_MessageFormat\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to ViewModel inheriting ViewModelBase should have a [View] attribute.\n        /// </summary>\n        internal static string SM0002_Title {\n            get {\n                return ResourceManager.GetString(\"SM0002_Title\", resourceCulture);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "analyzers/StabilityMatrix.Analyzers/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<root>\n    <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n        <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n            \n        </xsd:element>\n    </xsd:schema>\n    <resheader name=\"resmimetype\">\n        <value>text/microsoft-resx</value>\n    </resheader>\n    <resheader name=\"version\">\n        <value>1.3</value>\n    </resheader>\n    <resheader name=\"reader\">\n        <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n    </resheader>\n    <resheader name=\"writer\">\n        <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n    </resheader>\n    <data name=\"SM0001_Title\" xml:space=\"preserve\">\n        <value>Control associated with ViewModelBase must inherit UserControlBase or TemplatedControlBase</value>\n    </data>\n    <data name=\"SM0001_MessageFormat\" xml:space=\"preserve\">\n        <value>Control '{0}' must inherit from '{2}' or '{3}' because its associated ViewModel '{1}' inherits from ViewModelBase</value>\n    </data>\n    <data name=\"SM0001_Description\" xml:space=\"preserve\">\n        <value>ViewModels inheriting from ViewModelBase rely on lifecycle methods (OnLoaded/OnUnloaded) called by UserControlBase or TemplatedControlBase. Ensure the control specified in the [View] attribute inherits from one of these.</value>\n    </data>\n    <data name=\"SM0002_Title\" xml:space=\"preserve\">\n        <value>ViewModel inheriting ViewModelBase should have a [View] attribute</value>\n    </data>\n    <data name=\"SM0002_MessageFormat\" xml:space=\"preserve\">\n        <value>ViewModel '{0}' inherits from ViewModelBase but does not have a [View(...)] attribute to specify its corresponding control</value>\n    </data>\n    <data name=\"SM0002_Description\" xml:space=\"preserve\">\n        <value>Concrete ViewModels inheriting from ViewModelBase should specify their associated UI control via the [View(typeof(YourControl))] attribute.</value>\n    </data>\n</root>\n"
  },
  {
    "path": "analyzers/StabilityMatrix.Analyzers/StabilityMatrix.Analyzers.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>netstandard2.0</TargetFramework>\n        <ImplicitUsings>enable</ImplicitUsings>\n        <Nullable>enable</Nullable>\n        <LangVersion>latest</LangVersion>\n        <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>\n\n        <!-- Override defaults from Directory.Build.props -->\n        <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>\n        <CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>\n        \n        <!-- NuGet Package Information -->\n        <IsPackable>false</IsPackable>\n        <Title>StabilityMatrix Analyzers</Title>\n        <Description>Roslyn Analyzers for Stability Matrix project conventions.</Description>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <!-- Core Roslyn Dependencies -->\n        <PackageReference Include=\"Microsoft.CodeAnalysis.CSharp\" Version=\"4.9.2\" PrivateAssets=\"all\" />\n        <PackageReference Include=\"Microsoft.CodeAnalysis.Analyzers\" Version=\"3.3.4\" PrivateAssets=\"all\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <!-- For Resx -->\n        <Compile Update=\"Resources.Designer.cs\">\n            <DesignTime>True</DesignTime>\n            <AutoGen>True</AutoGen>\n            <DependentUpon>Resources.resx</DependentUpon>\n        </Compile>\n        <EmbeddedResource Update=\"Resources.resx\">\n            <Generator>ResXFileCodeGenerator</Generator>\n            <LastGenOutput>Resources.Designer.cs</LastGenOutput>\n        </EmbeddedResource>\n        <EmbeddedResource Update=\"Resources.resx\">\n          <Generator>ResXFileCodeGenerator</Generator>\n          <LastGenOutput>Resources.Designer.cs</LastGenOutput>\n        </EmbeddedResource>\n        <Compile Update=\"Resources.Designer.cs\">\n          <DesignTime>True</DesignTime>\n          <AutoGen>True</AutoGen>\n          <DependentUpon>Resources.resx</DependentUpon>\n        </Compile>\n    </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "analyzers/StabilityMatrix.Analyzers/ViewModelControlConventionAnalyzer.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Linq;\nusing Microsoft.CodeAnalysis;\nusing Microsoft.CodeAnalysis.CSharp;\nusing Microsoft.CodeAnalysis.CSharp.Syntax;\nusing Microsoft.CodeAnalysis.Diagnostics;\n\nnamespace StabilityMatrix.Analyzers;\n\n[DiagnosticAnalyzer(LanguageNames.CSharp)]\npublic class ViewModelControlConventionAnalyzer : DiagnosticAnalyzer\n{\n    // --- Diagnostic Rule 1: Control must inherit UserControlBase/TemplatedControlBase ---\n    private const string Rule1_Id = \"SM0001\";\n    private static readonly LocalizableString Rule1_Title = new LocalizableResourceString(\n        nameof(Resources.SM0001_Title),\n        Resources.ResourceManager,\n        typeof(Resources)\n    );\n    private static readonly LocalizableString Rule1_MessageFormat = new LocalizableResourceString(\n        nameof(Resources.SM0001_MessageFormat),\n        Resources.ResourceManager,\n        typeof(Resources)\n    );\n    private static readonly LocalizableString Rule1_Description = new LocalizableResourceString(\n        nameof(Resources.SM0001_Description),\n        Resources.ResourceManager,\n        typeof(Resources)\n    );\n    private const string Category = \"Naming\"; // Or \"Design\", \"Usage\", etc.\n\n    private static readonly DiagnosticDescriptor Rule1_ControlMustInheritBase = new DiagnosticDescriptor(\n        Rule1_Id,\n        Rule1_Title,\n        Rule1_MessageFormat,\n        Category,\n        DiagnosticSeverity.Error, // This is an error because it breaks functionality\n        isEnabledByDefault: true,\n        description: Rule1_Description\n    );\n\n    // --- Diagnostic Rule 2: Missing View Attribute ---\n    private const string Rule2_Id = \"SM0002\";\n    private static readonly LocalizableString Rule2_Title = new LocalizableResourceString(\n        nameof(Resources.SM0002_Title),\n        Resources.ResourceManager,\n        typeof(Resources)\n    );\n    private static readonly LocalizableString Rule2_MessageFormat = new LocalizableResourceString(\n        nameof(Resources.SM0002_MessageFormat),\n        Resources.ResourceManager,\n        typeof(Resources)\n    );\n    private static readonly LocalizableString Rule2_Description = new LocalizableResourceString(\n        nameof(Resources.SM0002_Description),\n        Resources.ResourceManager,\n        typeof(Resources)\n    );\n\n    private static readonly DiagnosticDescriptor Rule2_MissingViewAttribute = new DiagnosticDescriptor(\n        Rule2_Id,\n        Rule2_Title,\n        Rule2_MessageFormat,\n        Category,\n        DiagnosticSeverity.Warning, // This is a warning as it's a convention\n        isEnabledByDefault: true,\n        description: Rule2_Description\n    );\n\n    // --- Analyzer Implementation ---\n\n    // Base DiagnosticAnalyzer requires this property\n    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>\n        ImmutableArray.Create(Rule1_ControlMustInheritBase, Rule2_MissingViewAttribute);\n\n    // This is the entry point for DiagnosticAnalyzer\n    public override void Initialize(AnalysisContext context)\n    {\n        // Configure analysis\n        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); // Don't analyze generated code\n        context.EnableConcurrentExecution(); // Allow running concurrently\n\n        // --- Register the actions ---\n        // We'll use RegisterSymbolAction as it's often simpler for type-based checks\n        // than the full incremental pipeline, unless performance becomes an issue.\n        context.RegisterSymbolAction(AnalyzeNamedTypeSymbol, SymbolKind.NamedType);\n    }\n\n    private static void AnalyzeNamedTypeSymbol(SymbolAnalysisContext context)\n    {\n        var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;\n\n        var viewModelBaseSymbol = context.Compilation.GetTypeByMetadataName(\n            \"StabilityMatrix.Avalonia.ViewModels.Base.ViewModelBase\"\n        );\n        var userControlBaseSymbol = context.Compilation.GetTypeByMetadataName(\n            \"StabilityMatrix.Avalonia.Controls.UserControlBase\"\n        );\n        var templatedControlBaseSymbol = context.Compilation.GetTypeByMetadataName(\n            \"StabilityMatrix.Avalonia.Controls.TemplatedControlBase\"\n        );\n        var appWindowBaseSymbol = context.Compilation.GetTypeByMetadataName(\n            \"StabilityMatrix.Avalonia.Controls.AppWindowBase\"\n        );\n        var viewAttributeSymbol = context.Compilation.GetTypeByMetadataName(\n            \"StabilityMatrix.Core.Attributes.ViewAttribute\"\n        );\n\n        if (viewModelBaseSymbol == null || userControlBaseSymbol == null || viewAttributeSymbol == null)\n        {\n            return;\n        }\n\n        var inheritsViewModelBase = DoesInheritFrom(namedTypeSymbol, viewModelBaseSymbol);\n\n        if (!inheritsViewModelBase)\n        {\n            return;\n        }\n\n        // Rule 1 Check (remains the same)\n        var directViewAttribute = namedTypeSymbol\n            .GetAttributes()\n            .FirstOrDefault(ad =>\n                SymbolEqualityComparer.Default.Equals(ad.AttributeClass, viewAttributeSymbol)\n            );\n\n        if (directViewAttribute != null)\n        {\n            if (\n                directViewAttribute.ConstructorArguments.Length > 0\n                && directViewAttribute.ConstructorArguments[0].Kind == TypedConstantKind.Type\n                && directViewAttribute.ConstructorArguments[0].Value is INamedTypeSymbol controlTypeSymbol\n            )\n            {\n                var inheritsUserControlBase = DoesInheritFrom(controlTypeSymbol, userControlBaseSymbol);\n                var inheritsTemplatedControlBase =\n                    templatedControlBaseSymbol != null\n                    && DoesInheritFrom(controlTypeSymbol, templatedControlBaseSymbol);\n                var inheritsAppWindowBase =\n                    appWindowBaseSymbol != null && DoesInheritFrom(controlTypeSymbol, appWindowBaseSymbol);\n\n                if (!inheritsUserControlBase && !inheritsTemplatedControlBase && !inheritsAppWindowBase)\n                {\n                    var location = directViewAttribute.ApplicationSyntaxReference?.GetSyntax(\n                        context.CancellationToken\n                    )\n                        is AttributeSyntax { ArgumentList.Arguments.Count: > 0 } attrSyntax\n                        ? attrSyntax.ArgumentList.Arguments[0].Expression.GetLocation()\n                        : namedTypeSymbol.Locations.FirstOrDefault() ?? Location.None;\n\n                    // --- Report diagnostic on the ControlType's location ---\n                    /*var controlLocation = controlTypeSymbol.Locations.FirstOrDefault();\n                    if (\n                        controlLocation == null\n                        || controlLocation == Location.None\n                        || controlLocation.SourceTree == null\n                    )\n                    {\n                        // Fallback to the attribute location if control source isn't found\n                        controlLocation = location;\n                    }*/\n\n                    var diagnostic = Diagnostic.Create(\n                        Rule1_ControlMustInheritBase,\n                        location,\n                        controlTypeSymbol.Name,\n                        namedTypeSymbol.Name,\n                        userControlBaseSymbol.Name,\n                        templatedControlBaseSymbol?.Name ?? \"TemplatedControlBase\"\n                    );\n                    context.ReportDiagnostic(diagnostic);\n                }\n            }\n        }\n        // Rule 2 Check: ViewModel inherits ViewModelBase, is NOT abstract, but LACKS [View]\n        // *AND* no base class in the ViewModelBase hierarchy has a [View] attribute.\n        else // directViewAttribute is null\n        {\n            if (!namedTypeSymbol.IsAbstract)\n            {\n                // Check if any base class (that also inherits ViewModelBase) has the ViewAttribute\n                var viewAttributeFoundInHierarchy = false;\n                var currentAncestor = namedTypeSymbol.BaseType;\n                while (currentAncestor != null && DoesInheritFrom(currentAncestor, viewModelBaseSymbol))\n                {\n                    if (\n                        currentAncestor\n                            .GetAttributes()\n                            .Any(ad =>\n                                SymbolEqualityComparer.Default.Equals(ad.AttributeClass, viewAttributeSymbol)\n                            )\n                    )\n                    {\n                        viewAttributeFoundInHierarchy = true;\n                        break;\n                    }\n                    // Stop if we hit ViewModelBase itself without finding the attribute,\n                    // or if we go beyond where ViewModelBase is relevant.\n                    if (SymbolEqualityComparer.Default.Equals(currentAncestor, viewModelBaseSymbol))\n                        break;\n                    currentAncestor = currentAncestor.BaseType;\n                }\n\n                if (!viewAttributeFoundInHierarchy)\n                {\n                    var diagnostic = Diagnostic.Create(\n                        Rule2_MissingViewAttribute,\n                        namedTypeSymbol.Locations.FirstOrDefault() ?? Location.None,\n                        namedTypeSymbol.Name\n                    );\n                    context.ReportDiagnostic(diagnostic);\n                }\n            }\n        }\n    }\n\n    /// <summary>\n    /// Helper method to check if a type inherits from a specific base type.\n    /// (Includes checking if the type itself is the base type)\n    /// </summary>\n    private static bool DoesInheritFrom(INamedTypeSymbol? type, INamedTypeSymbol? baseType)\n    {\n        if (type == null || baseType == null)\n            return false;\n\n        if (SymbolEqualityComparer.Default.Equals(type, baseType))\n            return true; // Check if type itself is the baseType\n\n        var currentBase = type.BaseType;\n        while (currentBase != null)\n        {\n            if (SymbolEqualityComparer.Default.Equals(currentBase, baseType))\n            {\n                return true;\n            }\n            // Important: Check for object type to prevent infinite loop for interfaces or misconfigured hierarchies\n            if (currentBase.SpecialType == SpecialType.System_Object)\n            {\n                break;\n            }\n            currentBase = currentBase.BaseType;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "analyzers/StabilityMatrix.Analyzers.CodeFixes/ControlMustInheritBaseFixProvider.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Composition;\nusing System.Runtime.Serialization;\nusing Microsoft.CodeAnalysis;\nusing Microsoft.CodeAnalysis.CodeActions;\nusing Microsoft.CodeAnalysis.CodeFixes;\nusing Microsoft.CodeAnalysis.CSharp;\nusing Microsoft.CodeAnalysis.CSharp.Syntax;\nusing Microsoft.CodeAnalysis.Editing;\nusing Microsoft.CodeAnalysis.Options;\nusing Microsoft.CodeAnalysis.Simplification;\nusing Formatter = Microsoft.CodeAnalysis.Formatting.Formatter;\n\nnamespace StabilityMatrix.Analyzers.CodeFixes;\n\n[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ControlMustInheritBaseFixProvider)), Shared]\npublic class ControlMustInheritBaseFixProvider : CodeFixProvider\n{\n    public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(\"SM0001\");\n\n    public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;\n\n    public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)\n    {\n        var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);\n        if (root == null)\n            return;\n\n        var diagnostic = context.Diagnostics.First();\n        var diagnosticSpan = diagnostic.Location.SourceSpan;\n\n        if (\n            root.FindNode(diagnosticSpan, getInnermostNodeForTie: true)\n            is not TypeOfExpressionSyntax typeOfExpression\n        )\n            return;\n\n        var semanticModel = await context\n            .Document.GetSemanticModelAsync(context.CancellationToken)\n            .ConfigureAwait(false);\n        if (semanticModel == null)\n            return;\n\n        var controlTypeSymbol =\n            semanticModel.GetSymbolInfo(typeOfExpression.Type, context.CancellationToken).Symbol\n            as INamedTypeSymbol;\n        if (controlTypeSymbol == null || controlTypeSymbol.DeclaringSyntaxReferences.IsDefaultOrEmpty)\n            return;\n\n        // --- CRITICAL CHANGE: Get the Document for the Control Type ---\n        var controlSyntaxRef = controlTypeSymbol.DeclaringSyntaxReferences.FirstOrDefault();\n        if (controlSyntaxRef == null)\n            return;\n\n        var controlDocument = context.Document.Project.Solution.GetDocument(controlSyntaxRef.SyntaxTree);\n        if (controlDocument == null)\n            return;\n        // --- END CRITICAL CHANGE ---\n\n        var userControlSymbol = semanticModel.Compilation.GetTypeByMetadataName(\n            \"Avalonia.Controls.UserControl\"\n        );\n        var templatedControlSymbol = semanticModel.Compilation.GetTypeByMetadataName(\n            \"Avalonia.Controls.TemplatedControl\"\n        );\n\n        var suggestedBaseTypeName = \"UserControlBase\";\n        var suggestedBaseTypeFullName = \"StabilityMatrix.Avalonia.Controls.UserControlBase\";\n\n        if (templatedControlSymbol != null && DoesInheritFrom(controlTypeSymbol, templatedControlSymbol))\n        {\n            suggestedBaseTypeName = \"TemplatedControlBase\";\n            suggestedBaseTypeFullName = \"StabilityMatrix.Avalonia.Controls.TemplatedControlBase\";\n        }\n\n        var title = $\"Make '{controlTypeSymbol.Name}' inherit from {suggestedBaseTypeName}\";\n        context.RegisterCodeFix(\n            CodeAction.Create(\n                title: title,\n                // Pass the controlDocument to the fix method\n                createChangedSolution: c =>\n                    MakeControlInheritBaseAsync(\n                        controlDocument,\n                        controlTypeSymbol,\n                        suggestedBaseTypeFullName,\n                        c\n                    ),\n                equivalenceKey: title\n            ),\n            diagnostic\n        );\n    }\n\n    // MakeControlInheritBaseAsync now takes a Document parameter which is the control's document\n    private async Task<Solution> MakeControlInheritBaseAsync(\n        Document controlDocument,\n        INamedTypeSymbol controlTypeSymbol,\n        string suggestedBaseTypeFullName,\n        CancellationToken cancellationToken\n    )\n    {\n        var editor = await DocumentEditor\n            .CreateAsync(controlDocument, cancellationToken)\n            .ConfigureAwait(false);\n        var semanticModel = await controlDocument\n            .GetSemanticModelAsync(cancellationToken)\n            .ConfigureAwait(false);\n        if (semanticModel == null)\n            return controlDocument.Project.Solution;\n\n        if (\n            controlTypeSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken)\n            is not ClassDeclarationSyntax controlDeclarationSyntaxFromSymbol\n        )\n            return controlDocument.Project.Solution;\n\n        var classNodeToModify = editor\n            .OriginalRoot.DescendantNodesAndSelf()\n            .OfType<ClassDeclarationSyntax>()\n            .FirstOrDefault(c =>\n                c.Span == controlDeclarationSyntaxFromSymbol.Span\n                && c.IsEquivalentTo(controlDeclarationSyntaxFromSymbol)\n            );\n\n        if (classNodeToModify == null)\n            return controlDocument.Project.Solution;\n\n        var suggestedBaseSymbol = semanticModel.Compilation.GetTypeByMetadataName(suggestedBaseTypeFullName);\n        if (suggestedBaseSymbol == null)\n            return controlDocument.Project.Solution;\n\n        // If it already inherits the target, no change needed.\n        if (DoesInheritFrom(controlTypeSymbol, suggestedBaseSymbol))\n        {\n            return controlDocument.Project.Solution;\n        }\n\n        var finalClassNode = classNodeToModify; // Start with the original\n        var baseListModified = false;\n\n        if (classNodeToModify.BaseList != null && classNodeToModify.BaseList.Types.Any())\n        {\n            var existingBaseList = classNodeToModify.BaseList;\n            var newTypes = new List<BaseTypeSyntax>();\n            var replacedExisting = false;\n\n            var typeToReplaceSimpleName = \"\";\n            var replacementSimpleName = \"\";\n\n            if (suggestedBaseTypeFullName.EndsWith(\"UserControlBase\"))\n            {\n                typeToReplaceSimpleName = \"UserControl\";\n                replacementSimpleName = \"UserControlBase\";\n            }\n            else if (suggestedBaseTypeFullName.EndsWith(\"TemplatedControlBase\"))\n            {\n                typeToReplaceSimpleName = \"TemplatedControl\";\n                replacementSimpleName = \"TemplatedControlBase\";\n            }\n\n            if (!string.IsNullOrEmpty(typeToReplaceSimpleName))\n            {\n                foreach (var baseTypeSyntax in existingBaseList.Types)\n                {\n                    // Check if the current baseTypeSyntax's Type is a SimpleNameSyntax matching typeToReplaceSimpleName\n                    if (\n                        baseTypeSyntax.Type is SimpleNameSyntax simpleName\n                        && simpleName.Identifier.ValueText == typeToReplaceSimpleName\n                    )\n                    {\n                        // Replace it\n                        var replacementIdentifier = SyntaxFactory\n                            .IdentifierName(replacementSimpleName)\n                            .WithLeadingTrivia(simpleName.GetLeadingTrivia()) // Preserve trivia\n                            .WithTrailingTrivia(simpleName.GetTrailingTrivia());\n                        newTypes.Add(SyntaxFactory.SimpleBaseType(replacementIdentifier));\n                        replacedExisting = true;\n                        baseListModified = true;\n                    }\n                    // Check if it's a QualifiedNameSyntax ending with the typeToReplaceSimpleName\n                    else if (\n                        baseTypeSyntax.Type is QualifiedNameSyntax { Right: { } rightName }\n                        && rightName.Identifier.ValueText == typeToReplaceSimpleName\n                    )\n                    {\n                        // More complex: replace Namespace.UserControl with Namespace.UserControlBase\n                        // Or, ideally, just use the minimally qualified name here too.\n                        // For simplicity, let's assume we aim for simple names in base list and handle with usings.\n                        var replacementIdentifier = SyntaxFactory\n                            .IdentifierName(replacementSimpleName)\n                            .WithLeadingTrivia(baseTypeSyntax.Type.GetLeadingTrivia())\n                            .WithTrailingTrivia(baseTypeSyntax.Type.GetTrailingTrivia());\n                        newTypes.Add(SyntaxFactory.SimpleBaseType(replacementIdentifier));\n                        replacedExisting = true;\n                        baseListModified = true;\n                    }\n                    else\n                    {\n                        newTypes.Add(baseTypeSyntax); // Keep other bases\n                    }\n                }\n            }\n\n            if (replacedExisting)\n            {\n                finalClassNode = classNodeToModify.WithBaseList(\n                    existingBaseList.WithTypes(SyntaxFactory.SeparatedList(newTypes))\n                );\n            }\n            else // Didn't replace, so add the suggested base type (if not already present)\n            {\n                var minimallyQualifiedSuggestedName = suggestedBaseSymbol.ToDisplayString(\n                    SymbolDisplayFormat.MinimallyQualifiedFormat\n                );\n                var alreadyPresent = existingBaseList.Types.Any(bt =>\n                    bt.Type.ToString() == minimallyQualifiedSuggestedName\n                );\n                if (!alreadyPresent)\n                {\n                    var newSimpleBaseType = SyntaxFactory.SimpleBaseType(\n                        SyntaxFactory.ParseTypeName(minimallyQualifiedSuggestedName)\n                    );\n                    finalClassNode = classNodeToModify.WithBaseList(\n                        existingBaseList.AddTypes(newSimpleBaseType)\n                    );\n                    baseListModified = true;\n                }\n            }\n        }\n        else // No base list, add a new one\n        {\n            var newSimpleBaseType = SyntaxFactory.SimpleBaseType(\n                SyntaxFactory.ParseTypeName(\n                    suggestedBaseSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)\n                )\n            );\n            finalClassNode = classNodeToModify.WithBaseList(\n                SyntaxFactory.BaseList(\n                    SyntaxFactory.SingletonSeparatedList<BaseTypeSyntax>(newSimpleBaseType)\n                )\n            );\n            baseListModified = true;\n        }\n\n        if (baseListModified)\n        {\n            // Add Formatter.Annotation to help with curly brace placement\n            editor.ReplaceNode(\n                classNodeToModify,\n                finalClassNode.WithAdditionalAnnotations(Formatter.Annotation)\n            );\n        }\n\n        // --- Add Using Directive ---\n        if (\n            suggestedBaseSymbol.ContainingNamespace != null\n            && !IsGlobalNamespace(suggestedBaseSymbol.ContainingNamespace)\n        )\n        {\n            // await editor.AddUsingDirectiveIfNotPresentAsync(suggestedBaseSymbol.ContainingNamespace, cancellationToken);\n        }\n\n        var finalDocument = editor.GetChangedDocument();\n\n        // Simplifier should ensure minimally qualified names are used where possible\n        var simplifiedDocument = await Simplifier\n            .ReduceAsync(\n                finalDocument,\n                await controlDocument.GetOptionsAsync(cancellationToken),\n                cancellationToken\n            )\n            .ConfigureAwait(false);\n\n        return simplifiedDocument.Project.Solution;\n    }\n\n    private bool IsGlobalNamespace(INamespaceSymbol? namespaceSymbol)\n    {\n        return namespaceSymbol == null || namespaceSymbol.IsGlobalNamespace;\n    }\n\n    private bool HasUsingDirective(CompilationUnitSyntax root, string namespaceName)\n    {\n        return root.Usings.Any(u => u.Name?.ToString() == namespaceName);\n    }\n\n    private static bool DoesInheritFrom(INamedTypeSymbol? type, INamedTypeSymbol? baseType)\n    {\n        // ... (implementation remains the same)\n        if (type == null || baseType == null)\n            return false;\n        if (SymbolEqualityComparer.Default.Equals(type, baseType))\n            return true;\n        var currentBase = type.BaseType;\n        while (currentBase != null)\n        {\n            if (SymbolEqualityComparer.Default.Equals(currentBase, baseType))\n                return true;\n            if (currentBase.SpecialType == SpecialType.System_Object)\n                break;\n            currentBase = currentBase.BaseType;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "analyzers/StabilityMatrix.Analyzers.CodeFixes/DocumentEditorExtensions.cs",
    "content": "﻿using Microsoft.CodeAnalysis;\nusing Microsoft.CodeAnalysis.CSharp;\nusing Microsoft.CodeAnalysis.CSharp.Syntax;\nusing Microsoft.CodeAnalysis.Editing;\nusing Microsoft.CodeAnalysis.Formatting;\nusing Microsoft.CodeAnalysis.Simplification;\n\nnamespace StabilityMatrix.Analyzers.CodeFixes;\n\npublic static class DocumentEditorExtensions\n{\n    public static Task AddUsingDirectiveIfNotPresentAsync(\n        this DocumentEditor editor,\n        string namespaceName,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (string.IsNullOrEmpty(namespaceName))\n        {\n            return Task.CompletedTask;\n        }\n\n        // Get the current root from the editor. This reflects all previous changes.\n        var currentRoot = editor.GetChangedRoot();\n        if (currentRoot is not CompilationUnitSyntax compilationUnit)\n        {\n            // This might happen if the document isn't C# or some other issue.\n            // Consider logging or handling this case.\n            return Task.CompletedTask;\n        }\n\n        // Check if the using directive already exists.\n        var alreadyHasUsing = compilationUnit.Usings.Any(u => u.Name?.ToString() == namespaceName);\n\n        if (alreadyHasUsing)\n        {\n            return Task.CompletedTask;\n        }\n\n        // Create the new using directive.\n        var usingDirective = SyntaxFactory\n            .UsingDirective(SyntaxFactory.ParseName(namespaceName))\n            .WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation)\n            .WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed); // Ensure it gets a new line\n\n        // Create a new CompilationUnit with the added using.\n        var newCompilationUnit = compilationUnit.AddUsings(usingDirective);\n\n        // It's crucial that 'compilationUnit' (the node being replaced) is the *exact* node\n        // that the editor currently considers the root of its tracked changes.\n        // 'GetChangedRoot()' should provide this.\n        editor.ReplaceNode(compilationUnit, newCompilationUnit);\n        return Task.CompletedTask;\n    }\n\n    // Optional: A version that takes an INamespaceSymbol\n    public static async Task AddUsingDirectiveIfNotPresentAsync(\n        this DocumentEditor editor,\n        INamespaceSymbol? namespaceSymbol,\n        CancellationToken cancellationToken = default\n    )\n    {\n        if (namespaceSymbol == null || IsGlobalNamespace(namespaceSymbol))\n        {\n            return;\n        }\n        await editor.AddUsingDirectiveIfNotPresentAsync(namespaceSymbol.ToDisplayString(), cancellationToken);\n    }\n\n    private static bool IsGlobalNamespace(INamespaceSymbol? namespaceSymbol)\n    {\n        return namespaceSymbol == null || namespaceSymbol.IsGlobalNamespace;\n    }\n}\n"
  },
  {
    "path": "analyzers/StabilityMatrix.Analyzers.CodeFixes/MissingViewAttributeFixProvider.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Composition;\nusing Microsoft.CodeAnalysis;\nusing Microsoft.CodeAnalysis.CodeActions;\nusing Microsoft.CodeAnalysis.CodeFixes;\nusing Microsoft.CodeAnalysis.CSharp;\nusing Microsoft.CodeAnalysis.CSharp.Syntax;\nusing Microsoft.CodeAnalysis.Editing;\n\nnamespace StabilityMatrix.Analyzers.CodeFixes;\n\n[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MissingViewAttributeFixProvider)), Shared]\npublic class MissingViewAttributeFixProvider : CodeFixProvider\n{\n    public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(\"SM0002\");\n\n    public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;\n\n    public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)\n    {\n        var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);\n        if (root == null)\n            return;\n\n        var diagnostic = context.Diagnostics.First();\n        var diagnosticSpan = diagnostic.Location.SourceSpan;\n\n        var viewModelClassDeclaration = root.FindToken(diagnosticSpan.Start)\n            .Parent?.AncestorsAndSelf()\n            .OfType<ClassDeclarationSyntax>()\n            .FirstOrDefault();\n        if (viewModelClassDeclaration == null)\n            return;\n\n        var semanticModel = await context\n            .Document.GetSemanticModelAsync(context.CancellationToken)\n            .ConfigureAwait(false);\n        if (semanticModel == null)\n            return;\n\n        var viewModelSymbol = semanticModel.GetDeclaredSymbol(\n            viewModelClassDeclaration,\n            context.CancellationToken\n        );\n        if (viewModelSymbol == null)\n            return;\n\n        // Suggestion 1: Add [View(typeof(PlaceholderView))]\n        var titleAddAttribute = \"Add missing [View(...)] attribute\";\n        context.RegisterCodeFix(\n            CodeAction.Create(\n                title: titleAddAttribute,\n                createChangedDocument: c =>\n                    AddViewAttributeAsync(\n                        context.Document,\n                        viewModelClassDeclaration,\n                        \"YourControlTypeHere\",\n                        c\n                    ),\n                equivalenceKey: titleAddAttribute + \"_placeholder\"\n            ),\n            diagnostic\n        );\n\n        // Suggestion 2: Find potential matching views based on naming convention\n        var potentialViews = await FindPotentialViewsAsync(\n            context.Document.Project.Solution,\n            viewModelSymbol,\n            context.CancellationToken\n        );\n        foreach (var potentialView in potentialViews)\n        {\n            var titleAddSpecificAttribute = $\"Add [View(typeof({potentialView.Name}))]\";\n            context.RegisterCodeFix(\n                CodeAction.Create(\n                    title: titleAddSpecificAttribute,\n                    createChangedDocument: c =>\n                        AddViewAttributeAsync(\n                            context.Document,\n                            viewModelClassDeclaration,\n                            potentialView.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),\n                            c\n                        ),\n                    equivalenceKey: titleAddSpecificAttribute + \"_\" + potentialView.Name\n                ),\n                diagnostic\n            );\n        }\n\n        // Suggestion 3: Change base class (if applicable and makes sense)\n        var observableObjectSymbol = semanticModel.Compilation.GetTypeByMetadataName(\n            \"CommunityToolkit.Mvvm.ComponentModel.ObservableObject\"\n        );\n        if (\n            observableObjectSymbol != null\n            && viewModelSymbol.BaseType != null\n            && !SymbolEqualityComparer.Default.Equals(viewModelSymbol.BaseType, observableObjectSymbol)\n        )\n        {\n            // This is a more complex change and might not always be desired.\n            // Consider if the ViewModel truly doesn't need the ViewModelBase lifecycle.\n            var titleChangeBase =\n                $\"Change base class to ObservableObject (if lifecycle methods are not needed)\";\n            context.RegisterCodeFix(\n                CodeAction.Create(\n                    title: titleChangeBase,\n                    createChangedDocument: c =>\n                        ChangeBaseClassAsync(\n                            context.Document,\n                            viewModelClassDeclaration,\n                            observableObjectSymbol,\n                            c\n                        ),\n                    equivalenceKey: titleChangeBase\n                ),\n                diagnostic\n            );\n        }\n    }\n\n    private async Task<Document> AddViewAttributeAsync(\n        Document document,\n        ClassDeclarationSyntax classDecl,\n        string controlTypeName,\n        CancellationToken cancellationToken\n    )\n    {\n        var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);\n\n        var attributeArgument = SyntaxFactory.AttributeArgument(\n            SyntaxFactory.TypeOfExpression(SyntaxFactory.ParseTypeName(controlTypeName))\n        );\n        var attribute = SyntaxFactory\n            .Attribute(SyntaxFactory.IdentifierName(\"View\"))\n            .WithArgumentList(\n                SyntaxFactory.AttributeArgumentList(SyntaxFactory.SingletonSeparatedList(attributeArgument))\n            );\n\n        editor.AddAttribute(classDecl, attribute);\n\n        // Add using for ViewAttribute if necessary\n        var viewAttributeNamespace = \"StabilityMatrix.Core.Attributes\"; // Adjust if different\n        if (!HasUsingDirective(editor.OriginalRoot, viewAttributeNamespace))\n        {\n            // editor.AddUsingDirective(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(viewAttributeNamespace)));\n        }\n        // Potentially add using for controlTypeName's namespace if it's fully qualified\n\n        return editor.GetChangedDocument();\n    }\n\n    private async Task<Document> ChangeBaseClassAsync(\n        Document document,\n        ClassDeclarationSyntax classDecl,\n        INamedTypeSymbol newBaseTypeSymbol,\n        CancellationToken cancellationToken\n    )\n    {\n        var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);\n        var newBaseTypeName = SyntaxFactory.ParseTypeName(\n            newBaseTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)\n        );\n\n        if (classDecl.BaseList == null || !classDecl.BaseList.Types.Any())\n        {\n            editor.ReplaceNode(\n                classDecl,\n                classDecl.WithBaseList(\n                    SyntaxFactory.BaseList(\n                        SyntaxFactory.SingletonSeparatedList<BaseTypeSyntax>(\n                            SyntaxFactory.SimpleBaseType(newBaseTypeName)\n                        )\n                    )\n                )\n            );\n        }\n        else\n        {\n            // Replace the first base type (assuming ViewModelBase or its derivative was first)\n            var existingBaseList = classDecl.BaseList;\n            var types = existingBaseList.Types.ToList();\n            if (types.Any())\n            {\n                types[0] = SyntaxFactory.SimpleBaseType(newBaseTypeName); // Replace first base type\n                editor.ReplaceNode(\n                    existingBaseList,\n                    existingBaseList.WithTypes(SyntaxFactory.SeparatedList(types))\n                );\n            }\n        }\n\n        // Add using for newBaseTypeSymbol's namespace if necessary\n        var newBaseNamespace = newBaseTypeSymbol.ContainingNamespace.ToDisplayString();\n        if (!HasUsingDirective(editor.OriginalRoot, newBaseNamespace))\n        {\n            // editor.AddUsingDirective(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(newBaseNamespace)));\n        }\n\n        return editor.GetChangedDocument();\n    }\n\n    private async Task<IEnumerable<INamedTypeSymbol>> FindPotentialViewsAsync(\n        Solution solution,\n        INamedTypeSymbol viewModelSymbol,\n        CancellationToken cancellationToken\n    )\n    {\n        var potentialViews = new List<INamedTypeSymbol>();\n        var viewModelName = viewModelSymbol.Name;\n\n        if (!viewModelName.EndsWith(\"ViewModel\"))\n            return potentialViews;\n\n        var baseName = viewModelName.Substring(0, viewModelName.Length - \"ViewModel\".Length);\n        var possibleViewNames = new[]\n        {\n            baseName,\n            baseName + \"Page\",\n            baseName + \"View\",\n            baseName + \"Dialog\",\n            baseName + \"Window\",\n            baseName + \"Control\",\n            baseName + \"Card\", // From your example ExtraNetworkCard\n        };\n\n        // This can be slow on large solutions. Consider optimizing or scoping search.\n        foreach (var project in solution.Projects)\n        {\n            if (cancellationToken.IsCancellationRequested)\n                break;\n            var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);\n            if (compilation == null)\n                continue;\n\n            foreach (var viewName in possibleViewNames)\n            {\n                // A more robust search would iterate all INamedTypeSymbols in the compilation\n                // and check their names and if they are suitable (e.g., derive from Control).\n                // GetTypeByMetadataName is for fully qualified names.\n                // We'll do a simpler name check for now.\n                var symbols = compilation\n                    .GlobalNamespace.GetAllTypes(cancellationToken)\n                    .Where(s =>\n                        s.Name == viewName && s.CanBeReferencedByName && s.TypeKind == TypeKind.Class\n                    );\n\n                potentialViews.AddRange(symbols);\n            }\n        }\n        return potentialViews.Distinct(SymbolEqualityComparer.Default).OfType<INamedTypeSymbol>(); // Ensure distinct\n    }\n\n    private bool HasUsingDirective(SyntaxNode root, string namespaceName)\n    {\n        return root.DescendantNodes()\n            .OfType<UsingDirectiveSyntax>()\n            .Any(u => u.Name?.ToString() == namespaceName);\n    }\n}\n\n// Helper extension for GetAllTypes\ninternal static class NamespaceSymbolExtensions\n{\n    internal static IEnumerable<INamedTypeSymbol> GetAllTypes(\n        this INamespaceSymbol namespaceSymbol,\n        CancellationToken cancellationToken\n    )\n    {\n        var types = new List<INamedTypeSymbol>();\n        var queue = new Queue<INamespaceOrTypeSymbol>();\n        queue.Enqueue(namespaceSymbol);\n\n        while (queue.Count > 0)\n        {\n            if (cancellationToken.IsCancellationRequested)\n                break;\n            var current = queue.Dequeue();\n            foreach (var member in current.GetMembers())\n            {\n                if (member is INamespaceSymbol ns)\n                {\n                    queue.Enqueue(ns);\n                }\n                else if (member is INamedTypeSymbol ts)\n                {\n                    types.Add(ts);\n                    // Also enqueue nested types if any\n                    foreach (var nestedType in ts.GetTypeMembers())\n                    {\n                        queue.Enqueue(nestedType);\n                    }\n                }\n            }\n        }\n        return types;\n    }\n}\n"
  },
  {
    "path": "analyzers/StabilityMatrix.Analyzers.CodeFixes/StabilityMatrix.Analyzers.CodeFixes.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>netstandard2.0</TargetFramework>\n        <ImplicitUsings>enable</ImplicitUsings>\n        <Nullable>enable</Nullable>\n        <LangVersion>latest</LangVersion>\n\n        <!-- Override defaults from Directory.Build.props -->\n        <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>\n        <CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>\n        \n        <IsPackable>false</IsPackable>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <PackageReference Include=\"Microsoft.CodeAnalysis.CSharp.Workspaces\" Version=\"4.13.0\" PrivateAssets=\"all\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <!-- This project references the Analyzers project -->\n        <ProjectReference Include=\"..\\StabilityMatrix.Analyzers\\StabilityMatrix.Analyzers.csproj\" />\n    </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "global.json",
    "content": "{\n  \"sdk\": {\n    \"version\": \"9.0.0\",\n    \"rollForward\": \"latestMajor\",\n    \"allowPrerelease\": true\n  }\n}"
  }
]